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 }