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 19package xdsclient_test 20 21import ( 22 "fmt" 23 "testing" 24 "time" 25 26 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 27 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 28 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 29 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 30 v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 31 "github.com/google/go-cmp/cmp" 32 "github.com/google/go-cmp/cmp/cmpopts" 33 "google.golang.org/protobuf/testing/protocmp" 34 "google.golang.org/protobuf/types/known/anypb" 35 "google.golang.org/protobuf/types/known/durationpb" 36 37 "google.golang.org/grpc" 38 "google.golang.org/grpc/credentials/insecure" 39 "google.golang.org/grpc/internal/testutils" 40 xdstestutils "google.golang.org/grpc/xds/internal/testutils" 41 "google.golang.org/grpc/xds/internal/xdsclient" 42 "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" 43) 44 45const defaultTestWatchExpiryTimeout = 500 * time.Millisecond 46 47func (s) TestLDSConfigDump(t *testing.T) { 48 const testVersion = "test-version-lds" 49 var ( 50 ldsTargets = []string{"lds.target.good:0000", "lds.target.good:1111"} 51 routeConfigNames = []string{"route-config-0", "route-config-1"} 52 listenerRaws = make(map[string]*anypb.Any, len(ldsTargets)) 53 ) 54 55 for i := range ldsTargets { 56 listenersT := &v3listenerpb.Listener{ 57 Name: ldsTargets[i], 58 ApiListener: &v3listenerpb.ApiListener{ 59 ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ 60 RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ 61 Rds: &v3httppb.Rds{ 62 ConfigSource: &v3corepb.ConfigSource{ 63 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, 64 }, 65 RouteConfigName: routeConfigNames[i], 66 }, 67 }, 68 CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{ 69 MaxStreamDuration: durationpb.New(time.Second), 70 }, 71 }), 72 }, 73 } 74 listenerRaws[ldsTargets[i]] = testutils.MarshalAny(listenersT) 75 } 76 77 client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 78 BalancerName: testXDSServer, 79 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 80 NodeProto: xdstestutils.EmptyNodeProtoV2, 81 }, defaultTestWatchExpiryTimeout) 82 if err != nil { 83 t.Fatalf("failed to create client: %v", err) 84 } 85 defer client.Close() 86 updateHandler := client.(xdsclient.UpdateHandler) 87 88 // Expected unknown. 89 if err := compareDump(client.DumpLDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil { 90 t.Fatalf(err.Error()) 91 } 92 93 wantRequested := make(map[string]xdsclient.UpdateWithMD) 94 for _, n := range ldsTargets { 95 cancel := client.WatchListener(n, func(update xdsclient.ListenerUpdate, err error) {}) 96 defer cancel() 97 wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}} 98 } 99 // Expected requested. 100 if err := compareDump(client.DumpLDS, "", wantRequested); err != nil { 101 t.Fatalf(err.Error()) 102 } 103 104 update0 := make(map[string]xdsclient.ListenerUpdateErrTuple) 105 want0 := make(map[string]xdsclient.UpdateWithMD) 106 for n, r := range listenerRaws { 107 update0[n] = xdsclient.ListenerUpdateErrTuple{Update: xdsclient.ListenerUpdate{Raw: r}} 108 want0[n] = xdsclient.UpdateWithMD{ 109 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}, 110 Raw: r, 111 } 112 } 113 updateHandler.NewListeners(update0, xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}) 114 115 // Expect ACK. 116 if err := compareDump(client.DumpLDS, testVersion, want0); err != nil { 117 t.Fatalf(err.Error()) 118 } 119 120 const nackVersion = "lds-version-nack" 121 var nackErr = fmt.Errorf("lds nack error") 122 updateHandler.NewListeners( 123 map[string]xdsclient.ListenerUpdateErrTuple{ 124 ldsTargets[0]: {Err: nackErr}, 125 ldsTargets[1]: {Update: xdsclient.ListenerUpdate{Raw: listenerRaws[ldsTargets[1]]}}, 126 }, 127 xdsclient.UpdateMetadata{ 128 Status: xdsclient.ServiceStatusNACKed, 129 ErrState: &xdsclient.UpdateErrorMetadata{ 130 Version: nackVersion, 131 Err: nackErr, 132 }, 133 }, 134 ) 135 136 // Expect NACK for [0], but old ACK for [1]. 137 wantDump := make(map[string]xdsclient.UpdateWithMD) 138 // Though resource 0 was NACKed, the dump should show the previous ACKed raw 139 // message, as well as the NACK error. 140 wantDump[ldsTargets[0]] = xdsclient.UpdateWithMD{ 141 MD: xdsclient.UpdateMetadata{ 142 Status: xdsclient.ServiceStatusNACKed, 143 Version: testVersion, 144 ErrState: &xdsclient.UpdateErrorMetadata{ 145 Version: nackVersion, 146 Err: nackErr, 147 }, 148 }, 149 Raw: listenerRaws[ldsTargets[0]], 150 } 151 152 wantDump[ldsTargets[1]] = xdsclient.UpdateWithMD{ 153 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: nackVersion}, 154 Raw: listenerRaws[ldsTargets[1]], 155 } 156 if err := compareDump(client.DumpLDS, nackVersion, wantDump); err != nil { 157 t.Fatalf(err.Error()) 158 } 159} 160 161func (s) TestRDSConfigDump(t *testing.T) { 162 const testVersion = "test-version-rds" 163 var ( 164 listenerNames = []string{"lds.target.good:0000", "lds.target.good:1111"} 165 rdsTargets = []string{"route-config-0", "route-config-1"} 166 clusterNames = []string{"cluster-0", "cluster-1"} 167 routeRaws = make(map[string]*anypb.Any, len(rdsTargets)) 168 ) 169 170 for i := range rdsTargets { 171 routeConfigT := &v3routepb.RouteConfiguration{ 172 Name: rdsTargets[i], 173 VirtualHosts: []*v3routepb.VirtualHost{ 174 { 175 Domains: []string{listenerNames[i]}, 176 Routes: []*v3routepb.Route{{ 177 Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}}, 178 Action: &v3routepb.Route_Route{ 179 Route: &v3routepb.RouteAction{ 180 ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterNames[i]}, 181 }, 182 }, 183 }}, 184 }, 185 }, 186 } 187 188 routeRaws[rdsTargets[i]] = testutils.MarshalAny(routeConfigT) 189 } 190 191 client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 192 BalancerName: testXDSServer, 193 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 194 NodeProto: xdstestutils.EmptyNodeProtoV2, 195 }, defaultTestWatchExpiryTimeout) 196 if err != nil { 197 t.Fatalf("failed to create client: %v", err) 198 } 199 defer client.Close() 200 updateHandler := client.(xdsclient.UpdateHandler) 201 202 // Expected unknown. 203 if err := compareDump(client.DumpRDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil { 204 t.Fatalf(err.Error()) 205 } 206 207 wantRequested := make(map[string]xdsclient.UpdateWithMD) 208 for _, n := range rdsTargets { 209 cancel := client.WatchRouteConfig(n, func(update xdsclient.RouteConfigUpdate, err error) {}) 210 defer cancel() 211 wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}} 212 } 213 // Expected requested. 214 if err := compareDump(client.DumpRDS, "", wantRequested); err != nil { 215 t.Fatalf(err.Error()) 216 } 217 218 update0 := make(map[string]xdsclient.RouteConfigUpdateErrTuple) 219 want0 := make(map[string]xdsclient.UpdateWithMD) 220 for n, r := range routeRaws { 221 update0[n] = xdsclient.RouteConfigUpdateErrTuple{Update: xdsclient.RouteConfigUpdate{Raw: r}} 222 want0[n] = xdsclient.UpdateWithMD{ 223 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}, 224 Raw: r, 225 } 226 } 227 updateHandler.NewRouteConfigs(update0, xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}) 228 229 // Expect ACK. 230 if err := compareDump(client.DumpRDS, testVersion, want0); err != nil { 231 t.Fatalf(err.Error()) 232 } 233 234 const nackVersion = "rds-version-nack" 235 var nackErr = fmt.Errorf("rds nack error") 236 updateHandler.NewRouteConfigs( 237 map[string]xdsclient.RouteConfigUpdateErrTuple{ 238 rdsTargets[0]: {Err: nackErr}, 239 rdsTargets[1]: {Update: xdsclient.RouteConfigUpdate{Raw: routeRaws[rdsTargets[1]]}}, 240 }, 241 xdsclient.UpdateMetadata{ 242 Status: xdsclient.ServiceStatusNACKed, 243 ErrState: &xdsclient.UpdateErrorMetadata{ 244 Version: nackVersion, 245 Err: nackErr, 246 }, 247 }, 248 ) 249 250 // Expect NACK for [0], but old ACK for [1]. 251 wantDump := make(map[string]xdsclient.UpdateWithMD) 252 // Though resource 0 was NACKed, the dump should show the previous ACKed raw 253 // message, as well as the NACK error. 254 wantDump[rdsTargets[0]] = xdsclient.UpdateWithMD{ 255 MD: xdsclient.UpdateMetadata{ 256 Status: xdsclient.ServiceStatusNACKed, 257 Version: testVersion, 258 ErrState: &xdsclient.UpdateErrorMetadata{ 259 Version: nackVersion, 260 Err: nackErr, 261 }, 262 }, 263 Raw: routeRaws[rdsTargets[0]], 264 } 265 wantDump[rdsTargets[1]] = xdsclient.UpdateWithMD{ 266 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: nackVersion}, 267 Raw: routeRaws[rdsTargets[1]], 268 } 269 if err := compareDump(client.DumpRDS, nackVersion, wantDump); err != nil { 270 t.Fatalf(err.Error()) 271 } 272} 273 274func (s) TestCDSConfigDump(t *testing.T) { 275 const testVersion = "test-version-cds" 276 var ( 277 cdsTargets = []string{"cluster-0", "cluster-1"} 278 serviceNames = []string{"service-0", "service-1"} 279 clusterRaws = make(map[string]*anypb.Any, len(cdsTargets)) 280 ) 281 282 for i := range cdsTargets { 283 clusterT := &v3clusterpb.Cluster{ 284 Name: cdsTargets[i], 285 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 286 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 287 EdsConfig: &v3corepb.ConfigSource{ 288 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 289 Ads: &v3corepb.AggregatedConfigSource{}, 290 }, 291 }, 292 ServiceName: serviceNames[i], 293 }, 294 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 295 LrsServer: &v3corepb.ConfigSource{ 296 ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ 297 Self: &v3corepb.SelfConfigSource{}, 298 }, 299 }, 300 } 301 302 clusterRaws[cdsTargets[i]] = testutils.MarshalAny(clusterT) 303 } 304 305 client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 306 BalancerName: testXDSServer, 307 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 308 NodeProto: xdstestutils.EmptyNodeProtoV2, 309 }, defaultTestWatchExpiryTimeout) 310 if err != nil { 311 t.Fatalf("failed to create client: %v", err) 312 } 313 defer client.Close() 314 updateHandler := client.(xdsclient.UpdateHandler) 315 316 // Expected unknown. 317 if err := compareDump(client.DumpCDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil { 318 t.Fatalf(err.Error()) 319 } 320 321 wantRequested := make(map[string]xdsclient.UpdateWithMD) 322 for _, n := range cdsTargets { 323 cancel := client.WatchCluster(n, func(update xdsclient.ClusterUpdate, err error) {}) 324 defer cancel() 325 wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}} 326 } 327 // Expected requested. 328 if err := compareDump(client.DumpCDS, "", wantRequested); err != nil { 329 t.Fatalf(err.Error()) 330 } 331 332 update0 := make(map[string]xdsclient.ClusterUpdateErrTuple) 333 want0 := make(map[string]xdsclient.UpdateWithMD) 334 for n, r := range clusterRaws { 335 update0[n] = xdsclient.ClusterUpdateErrTuple{Update: xdsclient.ClusterUpdate{Raw: r}} 336 want0[n] = xdsclient.UpdateWithMD{ 337 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}, 338 Raw: r, 339 } 340 } 341 updateHandler.NewClusters(update0, xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}) 342 343 // Expect ACK. 344 if err := compareDump(client.DumpCDS, testVersion, want0); err != nil { 345 t.Fatalf(err.Error()) 346 } 347 348 const nackVersion = "cds-version-nack" 349 var nackErr = fmt.Errorf("cds nack error") 350 updateHandler.NewClusters( 351 map[string]xdsclient.ClusterUpdateErrTuple{ 352 cdsTargets[0]: {Err: nackErr}, 353 cdsTargets[1]: {Update: xdsclient.ClusterUpdate{Raw: clusterRaws[cdsTargets[1]]}}, 354 }, 355 xdsclient.UpdateMetadata{ 356 Status: xdsclient.ServiceStatusNACKed, 357 ErrState: &xdsclient.UpdateErrorMetadata{ 358 Version: nackVersion, 359 Err: nackErr, 360 }, 361 }, 362 ) 363 364 // Expect NACK for [0], but old ACK for [1]. 365 wantDump := make(map[string]xdsclient.UpdateWithMD) 366 // Though resource 0 was NACKed, the dump should show the previous ACKed raw 367 // message, as well as the NACK error. 368 wantDump[cdsTargets[0]] = xdsclient.UpdateWithMD{ 369 MD: xdsclient.UpdateMetadata{ 370 Status: xdsclient.ServiceStatusNACKed, 371 Version: testVersion, 372 ErrState: &xdsclient.UpdateErrorMetadata{ 373 Version: nackVersion, 374 Err: nackErr, 375 }, 376 }, 377 Raw: clusterRaws[cdsTargets[0]], 378 } 379 wantDump[cdsTargets[1]] = xdsclient.UpdateWithMD{ 380 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: nackVersion}, 381 Raw: clusterRaws[cdsTargets[1]], 382 } 383 if err := compareDump(client.DumpCDS, nackVersion, wantDump); err != nil { 384 t.Fatalf(err.Error()) 385 } 386} 387 388func (s) TestEDSConfigDump(t *testing.T) { 389 const testVersion = "test-version-cds" 390 var ( 391 edsTargets = []string{"cluster-0", "cluster-1"} 392 localityNames = []string{"locality-0", "locality-1"} 393 addrs = []string{"addr0:123", "addr1:456"} 394 endpointRaws = make(map[string]*anypb.Any, len(edsTargets)) 395 ) 396 397 for i := range edsTargets { 398 clab0 := xdstestutils.NewClusterLoadAssignmentBuilder(edsTargets[i], nil) 399 clab0.AddLocality(localityNames[i], 1, 1, []string{addrs[i]}, nil) 400 claT := clab0.Build() 401 402 endpointRaws[edsTargets[i]] = testutils.MarshalAny(claT) 403 } 404 405 client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 406 BalancerName: testXDSServer, 407 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 408 NodeProto: xdstestutils.EmptyNodeProtoV2, 409 }, defaultTestWatchExpiryTimeout) 410 if err != nil { 411 t.Fatalf("failed to create client: %v", err) 412 } 413 defer client.Close() 414 updateHandler := client.(xdsclient.UpdateHandler) 415 416 // Expected unknown. 417 if err := compareDump(client.DumpEDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil { 418 t.Fatalf(err.Error()) 419 } 420 421 wantRequested := make(map[string]xdsclient.UpdateWithMD) 422 for _, n := range edsTargets { 423 cancel := client.WatchEndpoints(n, func(update xdsclient.EndpointsUpdate, err error) {}) 424 defer cancel() 425 wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}} 426 } 427 // Expected requested. 428 if err := compareDump(client.DumpEDS, "", wantRequested); err != nil { 429 t.Fatalf(err.Error()) 430 } 431 432 update0 := make(map[string]xdsclient.EndpointsUpdateErrTuple) 433 want0 := make(map[string]xdsclient.UpdateWithMD) 434 for n, r := range endpointRaws { 435 update0[n] = xdsclient.EndpointsUpdateErrTuple{Update: xdsclient.EndpointsUpdate{Raw: r}} 436 want0[n] = xdsclient.UpdateWithMD{ 437 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}, 438 Raw: r, 439 } 440 } 441 updateHandler.NewEndpoints(update0, xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: testVersion}) 442 443 // Expect ACK. 444 if err := compareDump(client.DumpEDS, testVersion, want0); err != nil { 445 t.Fatalf(err.Error()) 446 } 447 448 const nackVersion = "eds-version-nack" 449 var nackErr = fmt.Errorf("eds nack error") 450 updateHandler.NewEndpoints( 451 map[string]xdsclient.EndpointsUpdateErrTuple{ 452 edsTargets[0]: {Err: nackErr}, 453 edsTargets[1]: {Update: xdsclient.EndpointsUpdate{Raw: endpointRaws[edsTargets[1]]}}, 454 }, 455 xdsclient.UpdateMetadata{ 456 Status: xdsclient.ServiceStatusNACKed, 457 ErrState: &xdsclient.UpdateErrorMetadata{ 458 Version: nackVersion, 459 Err: nackErr, 460 }, 461 }, 462 ) 463 464 // Expect NACK for [0], but old ACK for [1]. 465 wantDump := make(map[string]xdsclient.UpdateWithMD) 466 // Though resource 0 was NACKed, the dump should show the previous ACKed raw 467 // message, as well as the NACK error. 468 wantDump[edsTargets[0]] = xdsclient.UpdateWithMD{ 469 MD: xdsclient.UpdateMetadata{ 470 Status: xdsclient.ServiceStatusNACKed, 471 Version: testVersion, 472 ErrState: &xdsclient.UpdateErrorMetadata{ 473 Version: nackVersion, 474 Err: nackErr, 475 }, 476 }, 477 Raw: endpointRaws[edsTargets[0]], 478 } 479 wantDump[edsTargets[1]] = xdsclient.UpdateWithMD{ 480 MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusACKed, Version: nackVersion}, 481 Raw: endpointRaws[edsTargets[1]], 482 } 483 if err := compareDump(client.DumpEDS, nackVersion, wantDump); err != nil { 484 t.Fatalf(err.Error()) 485 } 486} 487 488func compareDump(dumpFunc func() (string, map[string]xdsclient.UpdateWithMD), wantVersion string, wantDump interface{}) error { 489 v, dump := dumpFunc() 490 if v != wantVersion { 491 return fmt.Errorf("Dump() returned version %q, want %q", v, wantVersion) 492 } 493 cmpOpts := cmp.Options{ 494 cmpopts.EquateEmpty(), 495 cmp.Comparer(func(a, b time.Time) bool { return true }), 496 cmp.Comparer(func(x, y error) bool { 497 if x == nil || y == nil { 498 return x == nil && y == nil 499 } 500 return x.Error() == y.Error() 501 }), 502 protocmp.Transform(), 503 } 504 if diff := cmp.Diff(dump, wantDump, cmpOpts); diff != "" { 505 return fmt.Errorf("Dump() returned unexpected dump, diff (-got +want): %s", diff) 506 } 507 return nil 508} 509