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  }