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 19package client 20 21import ( 22 "testing" 23 "time" 24 25 xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" 26 corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 27 "github.com/golang/protobuf/proto" 28 anypb "github.com/golang/protobuf/ptypes/any" 29 "github.com/google/go-cmp/cmp" 30) 31 32const ( 33 serviceName1 = "foo-service" 34 serviceName2 = "bar-service" 35) 36 37func (s) TestValidateCluster(t *testing.T) { 38 emptyUpdate := ClusterUpdate{ServiceName: "", EnableLRS: false} 39 tests := []struct { 40 name string 41 cluster *xdspb.Cluster 42 wantUpdate ClusterUpdate 43 wantErr bool 44 }{ 45 { 46 name: "non-eds-cluster-type", 47 cluster: &xdspb.Cluster{ 48 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_STATIC}, 49 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 50 EdsConfig: &corepb.ConfigSource{ 51 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 52 Ads: &corepb.AggregatedConfigSource{}, 53 }, 54 }, 55 }, 56 LbPolicy: xdspb.Cluster_LEAST_REQUEST, 57 }, 58 wantUpdate: emptyUpdate, 59 wantErr: true, 60 }, 61 { 62 name: "no-eds-config", 63 cluster: &xdspb.Cluster{ 64 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 65 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 66 }, 67 wantUpdate: emptyUpdate, 68 wantErr: true, 69 }, 70 { 71 name: "no-ads-config-source", 72 cluster: &xdspb.Cluster{ 73 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 74 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{}, 75 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 76 }, 77 wantUpdate: emptyUpdate, 78 wantErr: true, 79 }, 80 { 81 name: "non-round-robin-lb-policy", 82 cluster: &xdspb.Cluster{ 83 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 84 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 85 EdsConfig: &corepb.ConfigSource{ 86 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 87 Ads: &corepb.AggregatedConfigSource{}, 88 }, 89 }, 90 }, 91 LbPolicy: xdspb.Cluster_LEAST_REQUEST, 92 }, 93 wantUpdate: emptyUpdate, 94 wantErr: true, 95 }, 96 { 97 name: "happy-case-no-service-name-no-lrs", 98 cluster: &xdspb.Cluster{ 99 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 100 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 101 EdsConfig: &corepb.ConfigSource{ 102 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 103 Ads: &corepb.AggregatedConfigSource{}, 104 }, 105 }, 106 }, 107 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 108 }, 109 wantUpdate: emptyUpdate, 110 }, 111 { 112 name: "happy-case-no-lrs", 113 cluster: &xdspb.Cluster{ 114 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 115 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 116 EdsConfig: &corepb.ConfigSource{ 117 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 118 Ads: &corepb.AggregatedConfigSource{}, 119 }, 120 }, 121 ServiceName: serviceName1, 122 }, 123 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 124 }, 125 wantUpdate: ClusterUpdate{ServiceName: serviceName1, EnableLRS: false}, 126 }, 127 { 128 name: "happiest-case", 129 cluster: goodCluster1, 130 wantUpdate: ClusterUpdate{ServiceName: serviceName1, EnableLRS: true}, 131 }, 132 } 133 134 for _, test := range tests { 135 t.Run(test.name, func(t *testing.T) { 136 gotUpdate, gotErr := validateCluster(test.cluster) 137 if (gotErr != nil) != test.wantErr { 138 t.Errorf("validateCluster(%+v) returned error: %v, wantErr: %v", test.cluster, gotErr, test.wantErr) 139 } 140 if !cmp.Equal(gotUpdate, test.wantUpdate) { 141 t.Errorf("validateCluster(%+v) = %v, want: %v", test.cluster, gotUpdate, test.wantUpdate) 142 } 143 }) 144 } 145} 146 147// TestCDSHandleResponse starts a fake xDS server, makes a ClientConn to it, 148// and creates a v2Client using it. Then, it registers a CDS watcher and tests 149// different CDS responses. 150func (s) TestCDSHandleResponse(t *testing.T) { 151 tests := []struct { 152 name string 153 cdsResponse *xdspb.DiscoveryResponse 154 wantErr bool 155 wantUpdate *ClusterUpdate 156 wantUpdateErr bool 157 }{ 158 // Badly marshaled CDS response. 159 { 160 name: "badly-marshaled-response", 161 cdsResponse: badlyMarshaledCDSResponse, 162 wantErr: true, 163 wantUpdate: nil, 164 wantUpdateErr: false, 165 }, 166 // Response does not contain Cluster proto. 167 { 168 name: "no-cluster-proto-in-response", 169 cdsResponse: badResourceTypeInLDSResponse, 170 wantErr: true, 171 wantUpdate: nil, 172 wantUpdateErr: false, 173 }, 174 // Response contains no clusters. 175 { 176 name: "no-cluster", 177 cdsResponse: &xdspb.DiscoveryResponse{}, 178 wantErr: false, 179 wantUpdate: nil, 180 wantUpdateErr: false, 181 }, 182 // Response contains one good cluster we are not interested in. 183 { 184 name: "one-uninteresting-cluster", 185 cdsResponse: goodCDSResponse2, 186 wantErr: false, 187 wantUpdate: nil, 188 wantUpdateErr: false, 189 }, 190 // Response contains one cluster and it is good. 191 { 192 name: "one-good-cluster", 193 cdsResponse: goodCDSResponse1, 194 wantErr: false, 195 wantUpdate: &ClusterUpdate{ServiceName: serviceName1, EnableLRS: true}, 196 wantUpdateErr: false, 197 }, 198 } 199 for _, test := range tests { 200 t.Run(test.name, func(t *testing.T) { 201 testWatchHandle(t, &watchHandleTestcase{ 202 typeURL: cdsURL, 203 resourceName: goodClusterName1, 204 205 responseToHandle: test.cdsResponse, 206 wantHandleErr: test.wantErr, 207 wantUpdate: test.wantUpdate, 208 wantUpdateErr: test.wantUpdateErr, 209 }) 210 }) 211 } 212} 213 214// TestCDSHandleResponseWithoutWatch tests the case where the v2Client receives 215// a CDS response without a registered watcher. 216func (s) TestCDSHandleResponseWithoutWatch(t *testing.T) { 217 _, cc, cleanup := startServerAndGetCC(t) 218 defer cleanup() 219 220 v2c := newV2Client(&testUpdateReceiver{ 221 f: func(string, map[string]interface{}) {}, 222 }, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil) 223 defer v2c.close() 224 225 if v2c.handleCDSResponse(badResourceTypeInLDSResponse) == nil { 226 t.Fatal("v2c.handleCDSResponse() succeeded, should have failed") 227 } 228 229 if v2c.handleCDSResponse(goodCDSResponse1) != nil { 230 t.Fatal("v2c.handleCDSResponse() succeeded, should have failed") 231 } 232} 233 234var ( 235 badlyMarshaledCDSResponse = &xdspb.DiscoveryResponse{ 236 Resources: []*anypb.Any{ 237 { 238 TypeUrl: cdsURL, 239 Value: []byte{1, 2, 3, 4}, 240 }, 241 }, 242 TypeUrl: cdsURL, 243 } 244 goodCluster1 = &xdspb.Cluster{ 245 Name: goodClusterName1, 246 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 247 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 248 EdsConfig: &corepb.ConfigSource{ 249 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 250 Ads: &corepb.AggregatedConfigSource{}, 251 }, 252 }, 253 ServiceName: serviceName1, 254 }, 255 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 256 LrsServer: &corepb.ConfigSource{ 257 ConfigSourceSpecifier: &corepb.ConfigSource_Self{ 258 Self: &corepb.SelfConfigSource{}, 259 }, 260 }, 261 } 262 marshaledCluster1, _ = proto.Marshal(goodCluster1) 263 goodCluster2 = &xdspb.Cluster{ 264 Name: goodClusterName2, 265 ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS}, 266 EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{ 267 EdsConfig: &corepb.ConfigSource{ 268 ConfigSourceSpecifier: &corepb.ConfigSource_Ads{ 269 Ads: &corepb.AggregatedConfigSource{}, 270 }, 271 }, 272 ServiceName: serviceName2, 273 }, 274 LbPolicy: xdspb.Cluster_ROUND_ROBIN, 275 } 276 marshaledCluster2, _ = proto.Marshal(goodCluster2) 277 goodCDSResponse1 = &xdspb.DiscoveryResponse{ 278 Resources: []*anypb.Any{ 279 { 280 TypeUrl: cdsURL, 281 Value: marshaledCluster1, 282 }, 283 }, 284 TypeUrl: cdsURL, 285 } 286 goodCDSResponse2 = &xdspb.DiscoveryResponse{ 287 Resources: []*anypb.Any{ 288 { 289 TypeUrl: cdsURL, 290 Value: marshaledCluster2, 291 }, 292 }, 293 TypeUrl: cdsURL, 294 } 295) 296