google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/transport/transport_resource_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  // Package transport_test contains e2e style tests for the xDS transport
    19  // implementation. It uses the envoy-go-control-plane as the management server.
    20  package transport_test
    21  
    22  import (
    23  	"context"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/uuid"
    29  	"google.golang.org/grpc/internal/grpctest"
    30  	"google.golang.org/grpc/internal/testutils"
    31  	"google.golang.org/grpc/internal/testutils/xds/fakeserver"
    32  	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
    33  	"google.golang.org/grpc/xds/internal/xdsclient/transport"
    34  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
    35  	"google.golang.org/protobuf/testing/protocmp"
    36  	"google.golang.org/protobuf/types/known/anypb"
    37  
    38  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    39  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    40  	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    41  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    42  )
    43  
    44  type s struct {
    45  	grpctest.Tester
    46  }
    47  
    48  func Test(t *testing.T) {
    49  	grpctest.RunSubTests(t, s{})
    50  }
    51  
    52  const (
    53  	defaultTestTimeout      = 5 * time.Second
    54  	defaultTestShortTimeout = 10 * time.Millisecond
    55  )
    56  
    57  // startFakeManagementServer starts a fake xDS management server and returns a
    58  // cleanup function to close the fake server.
    59  func startFakeManagementServer(t *testing.T) (*fakeserver.Server, func()) {
    60  	t.Helper()
    61  	fs, sCleanup, err := fakeserver.StartServer(nil)
    62  	if err != nil {
    63  		t.Fatalf("Failed to start fake xDS server: %v", err)
    64  	}
    65  	return fs, sCleanup
    66  }
    67  
    68  // resourcesWithTypeURL wraps resources and type URL received from server.
    69  type resourcesWithTypeURL struct {
    70  	resources []*anypb.Any
    71  	url       string
    72  }
    73  
    74  // TestHandleResponseFromManagementServer covers different scenarios of the
    75  // transport receiving a response from the management server. In all scenarios,
    76  // the trasport is expected to pass the received responses as-is to the data
    77  // model layer for validation and not perform any validation on its own.
    78  func (s) TestHandleResponseFromManagementServer(t *testing.T) {
    79  	const (
    80  		resourceName1 = "resource-name-1"
    81  		resourceName2 = "resource-name-2"
    82  	)
    83  	var (
    84  		badlyMarshaledResource = &anypb.Any{
    85  			TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
    86  			Value:   []byte{1, 2, 3, 4},
    87  		}
    88  		apiListener = &v3listenerpb.ApiListener{
    89  			ApiListener: func() *anypb.Any {
    90  				return testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
    91  					RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
    92  						Rds: &v3httppb.Rds{
    93  							ConfigSource: &v3corepb.ConfigSource{
    94  								ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
    95  							},
    96  							RouteConfigName: "route-configuration-name",
    97  						},
    98  					},
    99  				})
   100  			}(),
   101  		}
   102  		resource1 = &v3listenerpb.Listener{
   103  			Name:        resourceName1,
   104  			ApiListener: apiListener,
   105  		}
   106  		resource2 = &v3listenerpb.Listener{
   107  			Name:        resourceName2,
   108  			ApiListener: apiListener,
   109  		}
   110  	)
   111  
   112  	tests := []struct {
   113  		desc                     string
   114  		resourceNamesToRequest   []string
   115  		managementServerResponse *v3discoverypb.DiscoveryResponse
   116  		wantURL                  string
   117  		wantResources            []*anypb.Any
   118  	}{
   119  		{
   120  			desc:                   "badly marshaled response",
   121  			resourceNamesToRequest: []string{resourceName1},
   122  			managementServerResponse: &v3discoverypb.DiscoveryResponse{
   123  				TypeUrl:   "type.googleapis.com/envoy.config.listener.v3.Listener",
   124  				Resources: []*anypb.Any{badlyMarshaledResource},
   125  			},
   126  			wantURL:       "type.googleapis.com/envoy.config.listener.v3.Listener",
   127  			wantResources: []*anypb.Any{badlyMarshaledResource},
   128  		},
   129  		{
   130  			desc:                     "empty response",
   131  			resourceNamesToRequest:   []string{resourceName1},
   132  			managementServerResponse: &v3discoverypb.DiscoveryResponse{},
   133  			wantURL:                  "",
   134  			wantResources:            nil,
   135  		},
   136  		{
   137  			desc:                   "one good resource",
   138  			resourceNamesToRequest: []string{resourceName1},
   139  			managementServerResponse: &v3discoverypb.DiscoveryResponse{
   140  				TypeUrl:   "type.googleapis.com/envoy.config.listener.v3.Listener",
   141  				Resources: []*anypb.Any{testutils.MarshalAny(t, resource1)},
   142  			},
   143  			wantURL:       "type.googleapis.com/envoy.config.listener.v3.Listener",
   144  			wantResources: []*anypb.Any{testutils.MarshalAny(t, resource1)},
   145  		},
   146  		{
   147  			desc:                   "two good resources",
   148  			resourceNamesToRequest: []string{resourceName1, resourceName2},
   149  			managementServerResponse: &v3discoverypb.DiscoveryResponse{
   150  				TypeUrl:   "type.googleapis.com/envoy.config.listener.v3.Listener",
   151  				Resources: []*anypb.Any{testutils.MarshalAny(t, resource1), testutils.MarshalAny(t, resource2)},
   152  			},
   153  			wantURL:       "type.googleapis.com/envoy.config.listener.v3.Listener",
   154  			wantResources: []*anypb.Any{testutils.MarshalAny(t, resource1), testutils.MarshalAny(t, resource2)},
   155  		},
   156  		{
   157  			desc:                   "two resources when we requested one",
   158  			resourceNamesToRequest: []string{resourceName1},
   159  			managementServerResponse: &v3discoverypb.DiscoveryResponse{
   160  				TypeUrl:   "type.googleapis.com/envoy.config.listener.v3.Listener",
   161  				Resources: []*anypb.Any{testutils.MarshalAny(t, resource1), testutils.MarshalAny(t, resource2)},
   162  			},
   163  			wantURL:       "type.googleapis.com/envoy.config.listener.v3.Listener",
   164  			wantResources: []*anypb.Any{testutils.MarshalAny(t, resource1), testutils.MarshalAny(t, resource2)},
   165  		},
   166  	}
   167  
   168  	for _, test := range tests {
   169  		t.Run(test.desc, func(t *testing.T) {
   170  			// Create a fake xDS management server listening on a local port,
   171  			// and set it up with the response to send.
   172  			mgmtServer, cleanup := startFakeManagementServer(t)
   173  			defer cleanup()
   174  			t.Logf("Started xDS management server on %s", mgmtServer.Address)
   175  			mgmtServer.XDSResponseChan <- &fakeserver.Response{Resp: test.managementServerResponse}
   176  
   177  			// Create a new transport.
   178  			resourcesCh := testutils.NewChannel()
   179  			tr, err := transport.New(transport.Options{
   180  				ServerCfg: *xdstestutils.ServerConfigForAddress(t, mgmtServer.Address),
   181  				// No validation. Simply push received resources on a channel.
   182  				OnRecvHandler: func(update transport.ResourceUpdate) error {
   183  					resourcesCh.Send(&resourcesWithTypeURL{
   184  						resources: update.Resources,
   185  						url:       update.URL,
   186  						// Ignore resource version here.
   187  					})
   188  					return nil
   189  				},
   190  				OnSendHandler:  func(*transport.ResourceSendInfo) {},                // No onSend handling.
   191  				OnErrorHandler: func(error) {},                                      // No stream error handling.
   192  				Backoff:        func(int) time.Duration { return time.Duration(0) }, // No backoff.
   193  				NodeProto:      &v3corepb.Node{Id: uuid.New().String()},
   194  			})
   195  			if err != nil {
   196  				t.Fatalf("Failed to create xDS transport: %v", err)
   197  			}
   198  			defer tr.Close()
   199  
   200  			// Send the request, and validate that the response sent by the
   201  			// management server is propagated to the data model layer.
   202  			tr.SendRequest(version.V3ListenerURL, test.resourceNamesToRequest)
   203  			ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   204  			defer cancel()
   205  			v, err := resourcesCh.Receive(ctx)
   206  			if err != nil {
   207  				t.Fatalf("Failed to receive resources at the data model layer: %v", err)
   208  			}
   209  			gotURL := v.(*resourcesWithTypeURL).url
   210  			gotResources := v.(*resourcesWithTypeURL).resources
   211  			if gotURL != test.wantURL {
   212  				t.Fatalf("Received resource URL in response: %s, want %s", gotURL, test.wantURL)
   213  			}
   214  			if diff := cmp.Diff(gotResources, test.wantResources, protocmp.Transform()); diff != "" {
   215  				t.Fatalf("Received unexpected resources. Diff (-got, +want):\n%s", diff)
   216  			}
   217  		})
   218  	}
   219  }