github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/controller/v2_testutils_test.go (about) 1 /* 2 * 3 * Copyright 2019 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 controller 20 21 import ( 22 "context" 23 "testing" 24 "time" 25 26 "github.com/golang/protobuf/proto" 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 grpc "github.com/hxx258456/ccgo/grpc" 30 "github.com/hxx258456/ccgo/grpc/credentials/insecure" 31 "github.com/hxx258456/ccgo/grpc/internal/grpclog" 32 "github.com/hxx258456/ccgo/grpc/internal/grpctest" 33 "github.com/hxx258456/ccgo/grpc/internal/testutils" 34 "github.com/hxx258456/ccgo/grpc/xds/internal/testutils/fakeserver" 35 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/bootstrap" 36 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/pubsub" 37 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource" 38 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource/version" 39 "google.golang.org/protobuf/testing/protocmp" 40 41 anypb "github.com/golang/protobuf/ptypes/any" 42 structpb "github.com/golang/protobuf/ptypes/struct" 43 xdspb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2" 44 basepb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2/core" 45 routepb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2/route" 46 httppb "github.com/hxx258456/ccgo/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" 47 listenerpb "github.com/hxx258456/ccgo/go-control-plane/envoy/config/listener/v2" 48 ) 49 50 type s struct { 51 grpctest.Tester 52 } 53 54 func Test(t *testing.T) { 55 grpctest.RunSubTests(t, s{}) 56 } 57 58 const ( 59 goodLDSTarget1 = "lds.target.good:1111" 60 goodLDSTarget2 = "lds.target.good:2222" 61 goodRouteName1 = "GoodRouteConfig1" 62 goodRouteName2 = "GoodRouteConfig2" 63 goodEDSName = "GoodClusterAssignment1" 64 uninterestingDomain = "uninteresting.domain" 65 goodClusterName1 = "GoodClusterName1" 66 goodClusterName2 = "GoodClusterName2" 67 uninterestingClusterName = "UninterestingClusterName" 68 httpConnManagerURL = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager" 69 ) 70 71 var ( 72 goodNodeProto = &basepb.Node{ 73 Id: "ENVOY_NODE_ID", 74 Metadata: &structpb.Struct{ 75 Fields: map[string]*structpb.Value{ 76 "TRAFFICDIRECTOR_GRPC_HOSTNAME": { 77 Kind: &structpb.Value_StringValue{StringValue: "trafficdirector"}, 78 }, 79 }, 80 }, 81 } 82 goodLDSRequest = &xdspb.DiscoveryRequest{ 83 Node: goodNodeProto, 84 TypeUrl: version.V2ListenerURL, 85 ResourceNames: []string{goodLDSTarget1}, 86 } 87 goodRDSRequest = &xdspb.DiscoveryRequest{ 88 Node: goodNodeProto, 89 TypeUrl: version.V2RouteConfigURL, 90 ResourceNames: []string{goodRouteName1}, 91 } 92 goodCDSRequest = &xdspb.DiscoveryRequest{ 93 Node: goodNodeProto, 94 TypeUrl: version.V2ClusterURL, 95 ResourceNames: []string{goodClusterName1}, 96 } 97 goodEDSRequest = &xdspb.DiscoveryRequest{ 98 Node: goodNodeProto, 99 TypeUrl: version.V2EndpointsURL, 100 ResourceNames: []string{goodEDSName}, 101 } 102 goodHTTPConnManager1 = &httppb.HttpConnectionManager{ 103 RouteSpecifier: &httppb.HttpConnectionManager_Rds{ 104 Rds: &httppb.Rds{ 105 ConfigSource: &basepb.ConfigSource{ 106 ConfigSourceSpecifier: &basepb.ConfigSource_Ads{Ads: &basepb.AggregatedConfigSource{}}, 107 }, 108 RouteConfigName: goodRouteName1, 109 }, 110 }, 111 } 112 marshaledConnMgr1 = testutils.MarshalAny(goodHTTPConnManager1) 113 goodListener1 = &xdspb.Listener{ 114 Name: goodLDSTarget1, 115 ApiListener: &listenerpb.ApiListener{ 116 ApiListener: marshaledConnMgr1, 117 }, 118 } 119 marshaledListener1 = testutils.MarshalAny(goodListener1) 120 goodListener2 = &xdspb.Listener{ 121 Name: goodLDSTarget2, 122 ApiListener: &listenerpb.ApiListener{ 123 ApiListener: marshaledConnMgr1, 124 }, 125 } 126 marshaledListener2 = testutils.MarshalAny(goodListener2) 127 noAPIListener = &xdspb.Listener{Name: goodLDSTarget1} 128 marshaledNoAPIListener = testutils.MarshalAny(noAPIListener) 129 badAPIListener2 = &xdspb.Listener{ 130 Name: goodLDSTarget2, 131 ApiListener: &listenerpb.ApiListener{ 132 ApiListener: &anypb.Any{ 133 TypeUrl: httpConnManagerURL, 134 Value: []byte{1, 2, 3, 4}, 135 }, 136 }, 137 } 138 badlyMarshaledAPIListener2, _ = proto.Marshal(badAPIListener2) 139 goodLDSResponse1 = &xdspb.DiscoveryResponse{ 140 Resources: []*anypb.Any{ 141 marshaledListener1, 142 }, 143 TypeUrl: version.V2ListenerURL, 144 } 145 goodLDSResponse2 = &xdspb.DiscoveryResponse{ 146 Resources: []*anypb.Any{ 147 marshaledListener2, 148 }, 149 TypeUrl: version.V2ListenerURL, 150 } 151 emptyLDSResponse = &xdspb.DiscoveryResponse{TypeUrl: version.V2ListenerURL} 152 badlyMarshaledLDSResponse = &xdspb.DiscoveryResponse{ 153 Resources: []*anypb.Any{ 154 { 155 TypeUrl: version.V2ListenerURL, 156 Value: []byte{1, 2, 3, 4}, 157 }, 158 }, 159 TypeUrl: version.V2ListenerURL, 160 } 161 badResourceTypeInLDSResponse = &xdspb.DiscoveryResponse{ 162 Resources: []*anypb.Any{marshaledConnMgr1}, 163 TypeUrl: version.V2ListenerURL, 164 } 165 ldsResponseWithMultipleResources = &xdspb.DiscoveryResponse{ 166 Resources: []*anypb.Any{ 167 marshaledListener2, 168 marshaledListener1, 169 }, 170 TypeUrl: version.V2ListenerURL, 171 } 172 noAPIListenerLDSResponse = &xdspb.DiscoveryResponse{ 173 Resources: []*anypb.Any{marshaledNoAPIListener}, 174 TypeUrl: version.V2ListenerURL, 175 } 176 goodBadUglyLDSResponse = &xdspb.DiscoveryResponse{ 177 Resources: []*anypb.Any{ 178 marshaledListener2, 179 marshaledListener1, 180 { 181 TypeUrl: version.V2ListenerURL, 182 Value: badlyMarshaledAPIListener2, 183 }, 184 }, 185 TypeUrl: version.V2ListenerURL, 186 } 187 badlyMarshaledRDSResponse = &xdspb.DiscoveryResponse{ 188 Resources: []*anypb.Any{ 189 { 190 TypeUrl: version.V2RouteConfigURL, 191 Value: []byte{1, 2, 3, 4}, 192 }, 193 }, 194 TypeUrl: version.V2RouteConfigURL, 195 } 196 badResourceTypeInRDSResponse = &xdspb.DiscoveryResponse{ 197 Resources: []*anypb.Any{marshaledConnMgr1}, 198 TypeUrl: version.V2RouteConfigURL, 199 } 200 noVirtualHostsRouteConfig = &xdspb.RouteConfiguration{ 201 Name: goodRouteName1, 202 } 203 marshaledNoVirtualHostsRouteConfig = testutils.MarshalAny(noVirtualHostsRouteConfig) 204 noVirtualHostsInRDSResponse = &xdspb.DiscoveryResponse{ 205 Resources: []*anypb.Any{ 206 marshaledNoVirtualHostsRouteConfig, 207 }, 208 TypeUrl: version.V2RouteConfigURL, 209 } 210 goodRouteConfig1 = &xdspb.RouteConfiguration{ 211 Name: goodRouteName1, 212 VirtualHosts: []*routepb.VirtualHost{ 213 { 214 Domains: []string{uninterestingDomain}, 215 Routes: []*routepb.Route{ 216 { 217 Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: ""}}, 218 Action: &routepb.Route_Route{ 219 Route: &routepb.RouteAction{ 220 ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: uninterestingClusterName}, 221 }, 222 }, 223 }, 224 }, 225 }, 226 { 227 Domains: []string{goodLDSTarget1}, 228 Routes: []*routepb.Route{ 229 { 230 Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: ""}}, 231 Action: &routepb.Route_Route{ 232 Route: &routepb.RouteAction{ 233 ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: goodClusterName1}, 234 }, 235 }, 236 }, 237 }, 238 }, 239 }, 240 } 241 marshaledGoodRouteConfig1 = testutils.MarshalAny(goodRouteConfig1) 242 goodRouteConfig2 = &xdspb.RouteConfiguration{ 243 Name: goodRouteName2, 244 VirtualHosts: []*routepb.VirtualHost{ 245 { 246 Domains: []string{uninterestingDomain}, 247 Routes: []*routepb.Route{ 248 { 249 Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: ""}}, 250 Action: &routepb.Route_Route{ 251 Route: &routepb.RouteAction{ 252 ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: uninterestingClusterName}, 253 }, 254 }, 255 }, 256 }, 257 }, 258 { 259 Domains: []string{goodLDSTarget1}, 260 Routes: []*routepb.Route{ 261 { 262 Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: ""}}, 263 Action: &routepb.Route_Route{ 264 Route: &routepb.RouteAction{ 265 ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: goodClusterName2}, 266 }, 267 }, 268 }, 269 }, 270 }, 271 }, 272 } 273 marshaledGoodRouteConfig2 = testutils.MarshalAny(goodRouteConfig2) 274 goodRDSResponse1 = &xdspb.DiscoveryResponse{ 275 Resources: []*anypb.Any{ 276 marshaledGoodRouteConfig1, 277 }, 278 TypeUrl: version.V2RouteConfigURL, 279 } 280 goodRDSResponse2 = &xdspb.DiscoveryResponse{ 281 Resources: []*anypb.Any{ 282 marshaledGoodRouteConfig2, 283 }, 284 TypeUrl: version.V2RouteConfigURL, 285 } 286 ) 287 288 type watchHandleTestcase struct { 289 rType xdsresource.ResourceType 290 resourceName string 291 292 responseToHandle *xdspb.DiscoveryResponse 293 wantHandleErr bool 294 wantUpdate interface{} 295 wantUpdateMD xdsresource.UpdateMetadata 296 wantUpdateErr bool 297 } 298 299 type testUpdateReceiver struct { 300 f func(rType xdsresource.ResourceType, d map[string]interface{}, md xdsresource.UpdateMetadata) 301 } 302 303 func (t *testUpdateReceiver) NewListeners(d map[string]xdsresource.ListenerUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 304 dd := make(map[string]interface{}) 305 for k, v := range d { 306 dd[k] = v 307 } 308 t.newUpdate(xdsresource.ListenerResource, dd, metadata) 309 } 310 311 func (t *testUpdateReceiver) NewRouteConfigs(d map[string]xdsresource.RouteConfigUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 312 dd := make(map[string]interface{}) 313 for k, v := range d { 314 dd[k] = v 315 } 316 t.newUpdate(xdsresource.RouteConfigResource, dd, metadata) 317 } 318 319 func (t *testUpdateReceiver) NewClusters(d map[string]xdsresource.ClusterUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 320 dd := make(map[string]interface{}) 321 for k, v := range d { 322 dd[k] = v 323 } 324 t.newUpdate(xdsresource.ClusterResource, dd, metadata) 325 } 326 327 func (t *testUpdateReceiver) NewEndpoints(d map[string]xdsresource.EndpointsUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 328 dd := make(map[string]interface{}) 329 for k, v := range d { 330 dd[k] = v 331 } 332 t.newUpdate(xdsresource.EndpointsResource, dd, metadata) 333 } 334 335 func (t *testUpdateReceiver) NewConnectionError(error) {} 336 337 func (t *testUpdateReceiver) newUpdate(rType xdsresource.ResourceType, d map[string]interface{}, metadata xdsresource.UpdateMetadata) { 338 t.f(rType, d, metadata) 339 } 340 341 // testWatchHandle is called to test response handling for each xDS. 342 // 343 // It starts the xDS watch as configured in test, waits for the fake xds server 344 // to receive the request (so watch callback is installed), and calls 345 // handleXDSResp with responseToHandle (if it's set). It then compares the 346 // update received by watch callback with the expected results. 347 func testWatchHandle(t *testing.T, test *watchHandleTestcase) { 348 t.Helper() 349 350 fakeServer, cleanup := startServer(t) 351 defer cleanup() 352 353 type updateErr struct { 354 u interface{} 355 md xdsresource.UpdateMetadata 356 err error 357 } 358 gotUpdateCh := testutils.NewChannel() 359 360 v2c, err := newTestController(&testUpdateReceiver{ 361 f: func(rType xdsresource.ResourceType, d map[string]interface{}, md xdsresource.UpdateMetadata) { 362 if rType == test.rType { 363 switch test.rType { 364 case xdsresource.ListenerResource: 365 dd := make(map[string]xdsresource.ListenerUpdateErrTuple) 366 for n, u := range d { 367 dd[n] = u.(xdsresource.ListenerUpdateErrTuple) 368 } 369 gotUpdateCh.Send(updateErr{dd, md, nil}) 370 case xdsresource.RouteConfigResource: 371 dd := make(map[string]xdsresource.RouteConfigUpdateErrTuple) 372 for n, u := range d { 373 dd[n] = u.(xdsresource.RouteConfigUpdateErrTuple) 374 } 375 gotUpdateCh.Send(updateErr{dd, md, nil}) 376 case xdsresource.ClusterResource: 377 dd := make(map[string]xdsresource.ClusterUpdateErrTuple) 378 for n, u := range d { 379 dd[n] = u.(xdsresource.ClusterUpdateErrTuple) 380 } 381 gotUpdateCh.Send(updateErr{dd, md, nil}) 382 case xdsresource.EndpointsResource: 383 dd := make(map[string]xdsresource.EndpointsUpdateErrTuple) 384 for n, u := range d { 385 dd[n] = u.(xdsresource.EndpointsUpdateErrTuple) 386 } 387 gotUpdateCh.Send(updateErr{dd, md, nil}) 388 } 389 } 390 }, 391 }, fakeServer.Address, goodNodeProto, func(int) time.Duration { return 0 }, nil) 392 if err != nil { 393 t.Fatal(err) 394 } 395 defer v2c.Close() 396 397 // Register the watcher, this will also trigger the v2Client to send the xDS 398 // request. 399 v2c.AddWatch(test.rType, test.resourceName) 400 401 // Wait till the request makes it to the fakeServer. This ensures that 402 // the watch request has been processed by the v2Client. 403 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 404 defer cancel() 405 if _, err := fakeServer.XDSRequestChan.Receive(ctx); err != nil { 406 t.Fatalf("Timeout waiting for an xDS request: %v", err) 407 } 408 409 // Directly push the response through a call to handleXDSResp. This bypasses 410 // the fakeServer, so it's only testing the handle logic. Client response 411 // processing is covered elsewhere. 412 // 413 // Also note that this won't trigger ACK, so there's no need to clear the 414 // request channel afterwards. 415 if _, _, _, err := v2c.handleResponse(test.responseToHandle); (err != nil) != test.wantHandleErr { 416 t.Fatalf("v2c.handleRDSResponse() returned err: %v, wantErr: %v", err, test.wantHandleErr) 417 } 418 419 wantUpdate := test.wantUpdate 420 cmpOpts := cmp.Options{ 421 cmpopts.EquateEmpty(), protocmp.Transform(), 422 cmpopts.IgnoreFields(xdsresource.UpdateMetadata{}, "Timestamp"), 423 cmpopts.IgnoreFields(xdsresource.UpdateErrorMetadata{}, "Timestamp"), 424 cmp.FilterValues(func(x, y error) bool { return true }, cmpopts.EquateErrors()), 425 } 426 uErr, err := gotUpdateCh.Receive(ctx) 427 if err == context.DeadlineExceeded { 428 t.Fatal("Timeout expecting xDS update") 429 } 430 gotUpdate := uErr.(updateErr).u 431 if diff := cmp.Diff(gotUpdate, wantUpdate, cmpOpts); diff != "" { 432 t.Fatalf("got update : %+v, want %+v, diff: %s", gotUpdate, wantUpdate, diff) 433 } 434 gotUpdateMD := uErr.(updateErr).md 435 if diff := cmp.Diff(gotUpdateMD, test.wantUpdateMD, cmpOpts); diff != "" { 436 t.Fatalf("got update : %+v, want %+v, diff: %s", gotUpdateMD, test.wantUpdateMD, diff) 437 } 438 gotUpdateErr := uErr.(updateErr).err 439 if (gotUpdateErr != nil) != test.wantUpdateErr { 440 t.Fatalf("got xDS update error {%v}, wantErr: %v", gotUpdateErr, test.wantUpdateErr) 441 } 442 } 443 444 // startServer starts a fake XDS server and also returns a ClientConn 445 // connected to it. 446 func startServer(t *testing.T) (*fakeserver.Server, func()) { 447 t.Helper() 448 fs, sCleanup, err := fakeserver.StartServer() 449 if err != nil { 450 t.Fatalf("Failed to start fake xDS server: %v", err) 451 } 452 return fs, sCleanup 453 } 454 455 func newTestController(p pubsub.UpdateHandler, controlPlanAddr string, n *basepb.Node, b func(int) time.Duration, l *grpclog.PrefixLogger) (*Controller, error) { 456 c, err := New(&bootstrap.ServerConfig{ 457 ServerURI: controlPlanAddr, 458 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 459 TransportAPI: version.TransportV2, 460 NodeProto: n, 461 }, p, nil, l) 462 if err != nil { 463 return nil, err 464 } 465 // This direct setting backoff seems a bit hacky, but should be OK for the 466 // tests. Or we need to make it configurable in New(). 467 c.backoff = b 468 return c, nil 469 } 470 471 func newStringP(s string) *string { 472 return &s 473 }