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