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  }