google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/tests/dump_test.go (about) 1 /* 2 * 3 * Copyright 2022 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 xdsclient_test 20 21 import ( 22 "context" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "google.golang.org/grpc/internal/testutils" 30 "google.golang.org/grpc/internal/testutils/xds/e2e" 31 "google.golang.org/grpc/xds/internal/xdsclient" 32 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 33 "google.golang.org/protobuf/testing/protocmp" 34 "google.golang.org/protobuf/types/known/anypb" 35 36 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 37 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 38 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 39 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 40 ) 41 42 func compareDump(ctx context.Context, client xdsclient.XDSClient, want map[string]map[string]xdsresource.UpdateWithMD) error { 43 var lastErr error 44 for { 45 if err := ctx.Err(); err != nil { 46 return fmt.Errorf("Timeout when waiting for expected dump: %v", lastErr) 47 } 48 cmpOpts := cmp.Options{ 49 cmpopts.EquateEmpty(), 50 cmp.Comparer(func(a, b time.Time) bool { return true }), 51 cmpopts.EquateErrors(), 52 protocmp.Transform(), 53 } 54 diff := cmp.Diff(want, client.DumpResources(), cmpOpts) 55 if diff == "" { 56 return nil 57 } 58 lastErr = fmt.Errorf("DumpResources() returned unexpected dump, diff (-want +got):\n%s", diff) 59 time.Sleep(100 * time.Millisecond) 60 } 61 } 62 63 func (s) TestDumpResources(t *testing.T) { 64 // Initialize the xDS resources to be used in this test. 65 ldsTargets := []string{"lds.target.good:0000", "lds.target.good:1111"} 66 rdsTargets := []string{"route-config-0", "route-config-1"} 67 cdsTargets := []string{"cluster-0", "cluster-1"} 68 edsTargets := []string{"endpoints-0", "endpoints-1"} 69 listeners := make([]*v3listenerpb.Listener, len(ldsTargets)) 70 listenerAnys := make([]*anypb.Any, len(ldsTargets)) 71 for i := range ldsTargets { 72 listeners[i] = e2e.DefaultClientListener(ldsTargets[i], rdsTargets[i]) 73 listenerAnys[i] = testutils.MarshalAny(t, listeners[i]) 74 } 75 routes := make([]*v3routepb.RouteConfiguration, len(rdsTargets)) 76 routeAnys := make([]*anypb.Any, len(rdsTargets)) 77 for i := range rdsTargets { 78 routes[i] = e2e.DefaultRouteConfig(rdsTargets[i], ldsTargets[i], cdsTargets[i]) 79 routeAnys[i] = testutils.MarshalAny(t, routes[i]) 80 } 81 clusters := make([]*v3clusterpb.Cluster, len(cdsTargets)) 82 clusterAnys := make([]*anypb.Any, len(cdsTargets)) 83 for i := range cdsTargets { 84 clusters[i] = e2e.DefaultCluster(cdsTargets[i], edsTargets[i], e2e.SecurityLevelNone) 85 clusterAnys[i] = testutils.MarshalAny(t, clusters[i]) 86 } 87 endpoints := make([]*v3endpointpb.ClusterLoadAssignment, len(edsTargets)) 88 endpointAnys := make([]*anypb.Any, len(edsTargets)) 89 ips := []string{"0.0.0.0", "1.1.1.1"} 90 ports := []uint32{123, 456} 91 for i := range edsTargets { 92 endpoints[i] = e2e.DefaultEndpoint(edsTargets[i], ips[i], ports[i:i+1]) 93 endpointAnys[i] = testutils.MarshalAny(t, endpoints[i]) 94 } 95 96 // Spin up an xDS management server on a local port. 97 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 98 defer cleanup() 99 100 // Create an xDS client with the above bootstrap contents. 101 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 102 if err != nil { 103 t.Fatalf("Failed to create xDS client: %v", err) 104 } 105 defer close() 106 107 // Dump resources and expect empty configs. 108 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 109 defer cancel() 110 if err := compareDump(ctx, client, nil); err != nil { 111 t.Fatal(err) 112 } 113 114 // Register watches, dump resources and expect configs in requested state. 115 for _, target := range ldsTargets { 116 xdsresource.WatchListener(client, target, noopListenerWatcher{}) 117 } 118 for _, target := range rdsTargets { 119 xdsresource.WatchRouteConfig(client, target, noopRouteConfigWatcher{}) 120 } 121 for _, target := range cdsTargets { 122 xdsresource.WatchCluster(client, target, noopClusterWatcher{}) 123 } 124 for _, target := range edsTargets { 125 xdsresource.WatchEndpoints(client, target, noopEndpointsWatcher{}) 126 } 127 want := map[string]map[string]xdsresource.UpdateWithMD{ 128 "type.googleapis.com/envoy.config.listener.v3.Listener": { 129 ldsTargets[0]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 130 ldsTargets[1]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 131 }, 132 "type.googleapis.com/envoy.config.route.v3.RouteConfiguration": { 133 rdsTargets[0]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 134 rdsTargets[1]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 135 }, 136 "type.googleapis.com/envoy.config.cluster.v3.Cluster": { 137 cdsTargets[0]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 138 cdsTargets[1]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 139 }, 140 "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment": { 141 edsTargets[0]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 142 edsTargets[1]: {MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}}, 143 }, 144 } 145 if err := compareDump(ctx, client, want); err != nil { 146 t.Fatal(err) 147 } 148 149 // Configure the resources on the management server. 150 if err := mgmtServer.Update(ctx, e2e.UpdateOptions{ 151 NodeID: nodeID, 152 Listeners: listeners, 153 Routes: routes, 154 Clusters: clusters, 155 Endpoints: endpoints, 156 }); err != nil { 157 t.Fatal(err) 158 } 159 160 // Dump resources and expect ACK configs. 161 want = map[string]map[string]xdsresource.UpdateWithMD{ 162 "type.googleapis.com/envoy.config.listener.v3.Listener": { 163 ldsTargets[0]: {Raw: listenerAnys[0], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 164 ldsTargets[1]: {Raw: listenerAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 165 }, 166 "type.googleapis.com/envoy.config.route.v3.RouteConfiguration": { 167 rdsTargets[0]: {Raw: routeAnys[0], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 168 rdsTargets[1]: {Raw: routeAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 169 }, 170 "type.googleapis.com/envoy.config.cluster.v3.Cluster": { 171 cdsTargets[0]: {Raw: clusterAnys[0], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 172 cdsTargets[1]: {Raw: clusterAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 173 }, 174 "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment": { 175 edsTargets[0]: {Raw: endpointAnys[0], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 176 edsTargets[1]: {Raw: endpointAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "1"}}, 177 }, 178 } 179 if err := compareDump(ctx, client, want); err != nil { 180 t.Fatal(err) 181 } 182 183 // Update the first resource of each type in the management server to a 184 // value which is expected to be NACK'ed by the xDS client. 185 const nackResourceIdx = 0 186 listeners[nackResourceIdx].ApiListener = &v3listenerpb.ApiListener{} 187 routes[nackResourceIdx].VirtualHosts = []*v3routepb.VirtualHost{{Routes: []*v3routepb.Route{{}}}} 188 clusters[nackResourceIdx].ClusterDiscoveryType = &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC} 189 endpoints[nackResourceIdx].Endpoints = []*v3endpointpb.LocalityLbEndpoints{{}} 190 if err := mgmtServer.Update(ctx, e2e.UpdateOptions{ 191 NodeID: nodeID, 192 Listeners: listeners, 193 Routes: routes, 194 Clusters: clusters, 195 Endpoints: endpoints, 196 SkipValidation: true, 197 }); err != nil { 198 t.Fatal(err) 199 } 200 201 // Verify that the xDS client reports the first resource of each type as 202 // being in "NACKed" state, and the second resource of each type to be in 203 // "ACKed" state. The version for the ACKed resource would be "2", while 204 // that for the NACKed resource would be "1". In the NACKed resource, the 205 // version which is NACKed is stored in the ErrorState field. 206 want = map[string]map[string]xdsresource.UpdateWithMD{ 207 "type.googleapis.com/envoy.config.listener.v3.Listener": { 208 ldsTargets[0]: { 209 Raw: listenerAnys[0], 210 MD: xdsresource.UpdateMetadata{ 211 Status: xdsresource.ServiceStatusNACKed, 212 Version: "1", 213 ErrState: &xdsresource.UpdateErrorMetadata{Version: "2", Err: cmpopts.AnyError}, 214 }, 215 }, 216 ldsTargets[1]: {Raw: listenerAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "2"}}, 217 }, 218 "type.googleapis.com/envoy.config.route.v3.RouteConfiguration": { 219 rdsTargets[0]: { 220 Raw: routeAnys[0], 221 MD: xdsresource.UpdateMetadata{ 222 Status: xdsresource.ServiceStatusNACKed, 223 Version: "1", 224 ErrState: &xdsresource.UpdateErrorMetadata{Version: "2", Err: cmpopts.AnyError}, 225 }, 226 }, 227 rdsTargets[1]: {Raw: routeAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "2"}}, 228 }, 229 "type.googleapis.com/envoy.config.cluster.v3.Cluster": { 230 cdsTargets[0]: { 231 Raw: clusterAnys[0], 232 MD: xdsresource.UpdateMetadata{ 233 Status: xdsresource.ServiceStatusNACKed, 234 Version: "1", 235 ErrState: &xdsresource.UpdateErrorMetadata{Version: "2", Err: cmpopts.AnyError}, 236 }, 237 }, 238 cdsTargets[1]: {Raw: clusterAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "2"}}, 239 }, 240 "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment": { 241 edsTargets[0]: { 242 Raw: endpointAnys[0], 243 MD: xdsresource.UpdateMetadata{ 244 Status: xdsresource.ServiceStatusNACKed, 245 Version: "1", 246 ErrState: &xdsresource.UpdateErrorMetadata{Version: "2", Err: cmpopts.AnyError}, 247 }, 248 }, 249 edsTargets[1]: {Raw: endpointAnys[1], MD: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusACKed, Version: "2"}}, 250 }, 251 } 252 if err := compareDump(ctx, client, want); err != nil { 253 t.Fatal(err) 254 } 255 }