1/* 2 * 3 * Copyright 2020 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 19package v2 20 21import ( 22 "context" 23 "testing" 24 "time" 25 26 xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" 27 28 xdsclient "google.golang.org/grpc/xds/internal/client" 29 "google.golang.org/grpc/xds/internal/testutils/fakeserver" 30) 31 32// doLDS makes a LDS watch, and waits for the response and ack to finish. 33// 34// This is called by RDS tests to start LDS first, because LDS is a 35// pre-requirement for RDS, and RDS handle would fail without an existing LDS 36// watch. 37func doLDS(ctx context.Context, t *testing.T, v2c xdsclient.APIClient, fakeServer *fakeserver.Server) { 38 v2c.AddWatch(xdsclient.ListenerResource, goodLDSTarget1) 39 if _, err := fakeServer.XDSRequestChan.Receive(ctx); err != nil { 40 t.Fatalf("Timeout waiting for LDS request: %v", err) 41 } 42} 43 44// TestRDSHandleResponseWithRouting starts a fake xDS server, makes a ClientConn 45// to it, and creates a v2Client using it. Then, it registers an LDS and RDS 46// watcher and tests different RDS responses. 47func (s) TestRDSHandleResponseWithRouting(t *testing.T) { 48 tests := []struct { 49 name string 50 rdsResponse *xdspb.DiscoveryResponse 51 wantErr bool 52 wantUpdate map[string]xdsclient.RouteConfigUpdate 53 wantUpdateMD xdsclient.UpdateMetadata 54 wantUpdateErr bool 55 }{ 56 // Badly marshaled RDS response. 57 { 58 name: "badly-marshaled-response", 59 rdsResponse: badlyMarshaledRDSResponse, 60 wantErr: true, 61 wantUpdate: nil, 62 wantUpdateMD: xdsclient.UpdateMetadata{ 63 Status: xdsclient.ServiceStatusNACKed, 64 ErrState: &xdsclient.UpdateErrorMetadata{ 65 Err: errPlaceHolder, 66 }, 67 }, 68 wantUpdateErr: false, 69 }, 70 // Response does not contain RouteConfiguration proto. 71 { 72 name: "no-route-config-in-response", 73 rdsResponse: badResourceTypeInRDSResponse, 74 wantErr: true, 75 wantUpdate: nil, 76 wantUpdateMD: xdsclient.UpdateMetadata{ 77 Status: xdsclient.ServiceStatusNACKed, 78 ErrState: &xdsclient.UpdateErrorMetadata{ 79 Err: errPlaceHolder, 80 }, 81 }, 82 wantUpdateErr: false, 83 }, 84 // No VirtualHosts in the response. Just one test case here for a bad 85 // RouteConfiguration, since the others are covered in 86 // TestGetClusterFromRouteConfiguration. 87 { 88 name: "no-virtual-hosts-in-response", 89 rdsResponse: noVirtualHostsInRDSResponse, 90 wantErr: false, 91 wantUpdate: map[string]xdsclient.RouteConfigUpdate{ 92 goodRouteName1: { 93 VirtualHosts: nil, 94 Raw: marshaledNoVirtualHostsRouteConfig, 95 }, 96 }, 97 wantUpdateMD: xdsclient.UpdateMetadata{ 98 Status: xdsclient.ServiceStatusACKed, 99 }, 100 wantUpdateErr: false, 101 }, 102 // Response contains one good RouteConfiguration, uninteresting though. 103 { 104 name: "one-uninteresting-route-config", 105 rdsResponse: goodRDSResponse2, 106 wantErr: false, 107 wantUpdate: map[string]xdsclient.RouteConfigUpdate{ 108 goodRouteName2: { 109 VirtualHosts: []*xdsclient.VirtualHost{ 110 { 111 Domains: []string{uninterestingDomain}, 112 Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, 113 }, 114 { 115 Domains: []string{goodLDSTarget1}, 116 Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName2: {Weight: 1}}}}, 117 }, 118 }, 119 Raw: marshaledGoodRouteConfig2, 120 }, 121 }, 122 wantUpdateMD: xdsclient.UpdateMetadata{ 123 Status: xdsclient.ServiceStatusACKed, 124 }, 125 wantUpdateErr: false, 126 }, 127 // Response contains one good interesting RouteConfiguration. 128 { 129 name: "one-good-route-config", 130 rdsResponse: goodRDSResponse1, 131 wantErr: false, 132 wantUpdate: map[string]xdsclient.RouteConfigUpdate{ 133 goodRouteName1: { 134 VirtualHosts: []*xdsclient.VirtualHost{ 135 { 136 Domains: []string{uninterestingDomain}, 137 Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, 138 }, 139 { 140 Domains: []string{goodLDSTarget1}, 141 Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName1: {Weight: 1}}}}, 142 }, 143 }, 144 Raw: marshaledGoodRouteConfig1, 145 }, 146 }, 147 wantUpdateMD: xdsclient.UpdateMetadata{ 148 Status: xdsclient.ServiceStatusACKed, 149 }, 150 wantUpdateErr: false, 151 }, 152 } 153 for _, test := range tests { 154 t.Run(test.name, func(t *testing.T) { 155 testWatchHandle(t, &watchHandleTestcase{ 156 rType: xdsclient.RouteConfigResource, 157 resourceName: goodRouteName1, 158 responseToHandle: test.rdsResponse, 159 wantHandleErr: test.wantErr, 160 wantUpdate: test.wantUpdate, 161 wantUpdateMD: test.wantUpdateMD, 162 wantUpdateErr: test.wantUpdateErr, 163 }) 164 }) 165 } 166} 167 168// TestRDSHandleResponseWithoutRDSWatch tests the case where the v2Client 169// receives an RDS response without a registered RDS watcher. 170func (s) TestRDSHandleResponseWithoutRDSWatch(t *testing.T) { 171 fakeServer, cc, cleanup := startServerAndGetCC(t) 172 defer cleanup() 173 174 v2c, err := newV2Client(&testUpdateReceiver{ 175 f: func(xdsclient.ResourceType, map[string]interface{}, xdsclient.UpdateMetadata) {}, 176 }, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil) 177 if err != nil { 178 t.Fatal(err) 179 } 180 defer v2c.Close() 181 182 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 183 defer cancel() 184 doLDS(ctx, t, v2c, fakeServer) 185 186 if v2c.handleRDSResponse(badResourceTypeInRDSResponse) == nil { 187 t.Fatal("v2c.handleRDSResponse() succeeded, should have failed") 188 } 189 190 if v2c.handleRDSResponse(goodRDSResponse1) != nil { 191 t.Fatal("v2c.handleRDSResponse() succeeded, should have failed") 192 } 193} 194