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 }