github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/resolver/cluster_specifier_plugin_test.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package resolver
    20  
    21  import (
    22  	"context"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/hxx258456/ccgo/grpc/balancer"
    27  	"github.com/hxx258456/ccgo/grpc/internal"
    28  	iresolver "github.com/hxx258456/ccgo/grpc/internal/resolver"
    29  	"github.com/hxx258456/ccgo/grpc/resolver"
    30  	"github.com/hxx258456/ccgo/grpc/serviceconfig"
    31  	"github.com/hxx258456/ccgo/grpc/xds/internal/balancer/clustermanager"
    32  	"github.com/hxx258456/ccgo/grpc/xds/internal/clusterspecifier"
    33  	"github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource"
    34  )
    35  
    36  func init() {
    37  	balancer.Register(cspB{})
    38  }
    39  
    40  type cspB struct{}
    41  
    42  func (cspB) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
    43  	return nil
    44  }
    45  
    46  func (cspB) Name() string {
    47  	return "csp_experimental"
    48  }
    49  
    50  type cspConfig struct {
    51  	ArbitraryField string `json:"arbitrary_field"`
    52  }
    53  
    54  // TestXDSResolverClusterSpecifierPlugin tests that cluster specifier plugins
    55  // produce the correct service config, and that the config selector routes to a
    56  // cluster specifier plugin supported by this service config (i.e. prefixed with
    57  // a cluster specifier plugin prefix).
    58  func (s) TestXDSResolverClusterSpecifierPlugin(t *testing.T) {
    59  	xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target})
    60  	defer xdsR.Close()
    61  	defer cancel()
    62  
    63  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
    64  	defer cancel()
    65  	waitForWatchListener(ctx, t, xdsC, targetStr)
    66  	xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil)
    67  
    68  	waitForWatchRouteConfig(ctx, t, xdsC, routeStr)
    69  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
    70  		VirtualHosts: []*xdsresource.VirtualHost{
    71  			{
    72  				Domains: []string{targetStr},
    73  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspA"}},
    74  			},
    75  		},
    76  		// Top level csp config here - the value of cspA should get directly
    77  		// placed as a child policy of xds cluster manager.
    78  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspA": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anything"}}}},
    79  	}, nil)
    80  
    81  	gotState, err := tcc.stateCh.Receive(ctx)
    82  	if err != nil {
    83  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
    84  	}
    85  	rState := gotState.(resolver.State)
    86  	if err := rState.ServiceConfig.Err; err != nil {
    87  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
    88  	}
    89  	wantJSON := `{"loadBalancingConfig":[{
    90      "xds_cluster_manager_experimental":{
    91        "children":{
    92          "cluster_specifier_plugin:cspA":{
    93            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anything"}}]
    94          }
    95        }
    96      }}]}`
    97  
    98  	wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON)
    99  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) {
   100  		t.Errorf("ClientConn.UpdateState received different service config")
   101  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   102  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed.Config))
   103  	}
   104  
   105  	cs := iresolver.GetConfigSelector(rState)
   106  	if cs == nil {
   107  		t.Fatal("received nil config selector")
   108  	}
   109  
   110  	res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()})
   111  	if err != nil {
   112  		t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err)
   113  	}
   114  
   115  	cluster := clustermanager.GetPickedClusterForTesting(res.Context)
   116  	clusterWant := clusterSpecifierPluginPrefix + "cspA"
   117  	if cluster != clusterWant {
   118  		t.Fatalf("cluster: %+v, want: %+v", cluster, clusterWant)
   119  	}
   120  }
   121  
   122  // TestXDSResolverClusterSpecifierPluginConfigUpdate tests that cluster
   123  // specifier plugins produce the correct service config, and that on an update
   124  // to the CSP Configuration, the new config is accounted for in the output
   125  // service config.
   126  func (s) TestXDSResolverClusterSpecifierPluginConfigUpdate(t *testing.T) {
   127  	xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target})
   128  	defer xdsR.Close()
   129  	defer cancel()
   130  
   131  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   132  	defer cancel()
   133  	waitForWatchListener(ctx, t, xdsC, targetStr)
   134  	xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil)
   135  
   136  	waitForWatchRouteConfig(ctx, t, xdsC, routeStr)
   137  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   138  		VirtualHosts: []*xdsresource.VirtualHost{
   139  			{
   140  				Domains: []string{targetStr},
   141  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspA"}},
   142  			},
   143  		},
   144  		// Top level csp config here - the value of cspA should get directly
   145  		// placed as a child policy of xds cluster manager.
   146  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspA": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anything"}}}},
   147  	}, nil)
   148  
   149  	gotState, err := tcc.stateCh.Receive(ctx)
   150  	if err != nil {
   151  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
   152  	}
   153  	rState := gotState.(resolver.State)
   154  	if err := rState.ServiceConfig.Err; err != nil {
   155  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
   156  	}
   157  	wantJSON := `{"loadBalancingConfig":[{
   158      "xds_cluster_manager_experimental":{
   159        "children":{
   160          "cluster_specifier_plugin:cspA":{
   161            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anything"}}]
   162          }
   163        }
   164      }}]}`
   165  
   166  	wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON)
   167  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) {
   168  		t.Errorf("ClientConn.UpdateState received different service config")
   169  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   170  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed.Config))
   171  	}
   172  
   173  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   174  		VirtualHosts: []*xdsresource.VirtualHost{
   175  			{
   176  				Domains: []string{targetStr},
   177  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspA"}},
   178  			},
   179  		},
   180  		// Top level csp config here - the value of cspA should get directly
   181  		// placed as a child policy of xds cluster manager.
   182  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspA": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "changed"}}}},
   183  	}, nil)
   184  
   185  	gotState, err = tcc.stateCh.Receive(ctx)
   186  	if err != nil {
   187  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
   188  	}
   189  	rState = gotState.(resolver.State)
   190  	if err := rState.ServiceConfig.Err; err != nil {
   191  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
   192  	}
   193  	wantJSON = `{"loadBalancingConfig":[{
   194      "xds_cluster_manager_experimental":{
   195        "children":{
   196          "cluster_specifier_plugin:cspA":{
   197            "childPolicy":[{"csp_experimental":{"arbitrary_field":"changed"}}]
   198          }
   199        }
   200      }}]}`
   201  
   202  	wantSCParsed = internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON)
   203  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) {
   204  		t.Errorf("ClientConn.UpdateState received different service config")
   205  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   206  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed.Config))
   207  	}
   208  }
   209  
   210  // TestXDSResolverDelayedOnCommittedCSP tests that cluster specifier plugins and
   211  // their corresponding configurations remain in service config if RPCs are in
   212  // flight.
   213  func (s) TestXDSResolverDelayedOnCommittedCSP(t *testing.T) {
   214  	xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target})
   215  	defer xdsR.Close()
   216  	defer cancel()
   217  
   218  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   219  	defer cancel()
   220  	waitForWatchListener(ctx, t, xdsC, targetStr)
   221  	xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil)
   222  	waitForWatchRouteConfig(ctx, t, xdsC, routeStr)
   223  
   224  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   225  		VirtualHosts: []*xdsresource.VirtualHost{
   226  			{
   227  				Domains: []string{targetStr},
   228  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspA"}},
   229  			},
   230  		},
   231  		// Top level csp config here - the value of cspA should get directly
   232  		// placed as a child policy of xds cluster manager.
   233  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspA": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anythingA"}}}},
   234  	}, nil)
   235  
   236  	gotState, err := tcc.stateCh.Receive(ctx)
   237  	if err != nil {
   238  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
   239  	}
   240  	rState := gotState.(resolver.State)
   241  	if err := rState.ServiceConfig.Err; err != nil {
   242  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
   243  	}
   244  	wantJSON := `{"loadBalancingConfig":[{
   245      "xds_cluster_manager_experimental":{
   246        "children":{
   247          "cluster_specifier_plugin:cspA":{
   248            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anythingA"}}]
   249          }
   250        }
   251      }}]}`
   252  
   253  	wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON)
   254  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) {
   255  		t.Errorf("ClientConn.UpdateState received different service config")
   256  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   257  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed.Config))
   258  	}
   259  
   260  	cs := iresolver.GetConfigSelector(rState)
   261  	if cs == nil {
   262  		t.Fatal("received nil config selector")
   263  	}
   264  
   265  	res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()})
   266  	if err != nil {
   267  		t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err)
   268  	}
   269  
   270  	cluster := clustermanager.GetPickedClusterForTesting(res.Context)
   271  	clusterWant := clusterSpecifierPluginPrefix + "cspA"
   272  	if cluster != clusterWant {
   273  		t.Fatalf("cluster: %+v, want: %+v", cluster, clusterWant)
   274  	}
   275  	// delay res.OnCommitted()
   276  
   277  	// Perform TWO updates to ensure the old config selector does not hold a reference to cspA
   278  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   279  		VirtualHosts: []*xdsresource.VirtualHost{
   280  			{
   281  				Domains: []string{targetStr},
   282  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspB"}},
   283  			},
   284  		},
   285  		// Top level csp config here - the value of cspB should get directly
   286  		// placed as a child policy of xds cluster manager.
   287  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspB": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anythingB"}}}},
   288  	}, nil)
   289  	tcc.stateCh.Receive(ctx) // Ignore the first update.
   290  
   291  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   292  		VirtualHosts: []*xdsresource.VirtualHost{
   293  			{
   294  				Domains: []string{targetStr},
   295  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspB"}},
   296  			},
   297  		},
   298  		// Top level csp config here - the value of cspB should get directly
   299  		// placed as a child policy of xds cluster manager.
   300  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspB": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anythingB"}}}},
   301  	}, nil)
   302  
   303  	gotState, err = tcc.stateCh.Receive(ctx)
   304  	if err != nil {
   305  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
   306  	}
   307  	rState = gotState.(resolver.State)
   308  	if err := rState.ServiceConfig.Err; err != nil {
   309  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
   310  	}
   311  	wantJSON2 := `{"loadBalancingConfig":[{
   312      "xds_cluster_manager_experimental":{
   313        "children":{
   314          "cluster_specifier_plugin:cspA":{
   315            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anythingA"}}]
   316          },
   317          "cluster_specifier_plugin:cspB":{
   318            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anythingB"}}]
   319          }
   320        }
   321      }}]}`
   322  
   323  	wantSCParsed2 := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON2)
   324  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed2.Config) {
   325  		t.Errorf("ClientConn.UpdateState received different service config")
   326  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   327  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed2.Config))
   328  	}
   329  
   330  	// Invoke OnCommitted; should lead to a service config update that deletes
   331  	// cspA.
   332  	res.OnCommitted()
   333  
   334  	xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{
   335  		VirtualHosts: []*xdsresource.VirtualHost{
   336  			{
   337  				Domains: []string{targetStr},
   338  				Routes:  []*xdsresource.Route{{Prefix: newStringP(""), ClusterSpecifierPlugin: "cspB"}},
   339  			},
   340  		},
   341  		// Top level csp config here - the value of cspB should get directly
   342  		// placed as a child policy of xds cluster manager.
   343  		ClusterSpecifierPlugins: map[string]clusterspecifier.BalancerConfig{"cspB": []map[string]interface{}{{"csp_experimental": cspConfig{ArbitraryField: "anythingB"}}}},
   344  	}, nil)
   345  	gotState, err = tcc.stateCh.Receive(ctx)
   346  	if err != nil {
   347  		t.Fatalf("Error waiting for UpdateState to be called: %v", err)
   348  	}
   349  	rState = gotState.(resolver.State)
   350  	if err := rState.ServiceConfig.Err; err != nil {
   351  		t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err)
   352  	}
   353  	wantJSON3 := `{"loadBalancingConfig":[{
   354      "xds_cluster_manager_experimental":{
   355        "children":{
   356          "cluster_specifier_plugin:cspB":{
   357            "childPolicy":[{"csp_experimental":{"arbitrary_field":"anythingB"}}]
   358          }
   359        }
   360      }}]}`
   361  
   362  	wantSCParsed3 := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON3)
   363  	if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed3.Config) {
   364  		t.Errorf("ClientConn.UpdateState received different service config")
   365  		t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config))
   366  		t.Fatal("want: ", cmp.Diff(nil, wantSCParsed3.Config))
   367  	}
   368  }