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 v2 20 21import ( 22 "testing" 23 "time" 24 25 v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 28 "google.golang.org/grpc/xds/internal/xdsclient" 29) 30 31// TestLDSHandleResponse starts a fake xDS server, makes a ClientConn to it, 32// and creates a client using it. Then, it registers a watchLDS and tests 33// different LDS responses. 34func (s) TestLDSHandleResponse(t *testing.T) { 35 tests := []struct { 36 name string 37 ldsResponse *v2xdspb.DiscoveryResponse 38 wantErr bool 39 wantUpdate map[string]xdsclient.ListenerUpdateErrTuple 40 wantUpdateMD xdsclient.UpdateMetadata 41 wantUpdateErr bool 42 }{ 43 // Badly marshaled LDS response. 44 { 45 name: "badly-marshaled-response", 46 ldsResponse: badlyMarshaledLDSResponse, 47 wantErr: true, 48 wantUpdate: nil, 49 wantUpdateMD: xdsclient.UpdateMetadata{ 50 Status: xdsclient.ServiceStatusNACKed, 51 ErrState: &xdsclient.UpdateErrorMetadata{ 52 Err: cmpopts.AnyError, 53 }, 54 }, 55 wantUpdateErr: false, 56 }, 57 // Response does not contain Listener proto. 58 { 59 name: "no-listener-proto-in-response", 60 ldsResponse: badResourceTypeInLDSResponse, 61 wantErr: true, 62 wantUpdate: nil, 63 wantUpdateMD: xdsclient.UpdateMetadata{ 64 Status: xdsclient.ServiceStatusNACKed, 65 ErrState: &xdsclient.UpdateErrorMetadata{ 66 Err: cmpopts.AnyError, 67 }, 68 }, 69 wantUpdateErr: false, 70 }, 71 // No APIListener in the response. Just one test case here for a bad 72 // ApiListener, since the others are covered in 73 // TestGetRouteConfigNameFromListener. 74 { 75 name: "no-apiListener-in-response", 76 ldsResponse: noAPIListenerLDSResponse, 77 wantErr: true, 78 wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{ 79 goodLDSTarget1: {Err: cmpopts.AnyError}, 80 }, 81 wantUpdateMD: xdsclient.UpdateMetadata{ 82 Status: xdsclient.ServiceStatusNACKed, 83 ErrState: &xdsclient.UpdateErrorMetadata{ 84 Err: cmpopts.AnyError, 85 }, 86 }, 87 wantUpdateErr: false, 88 }, 89 // Response contains one listener and it is good. 90 { 91 name: "one-good-listener", 92 ldsResponse: goodLDSResponse1, 93 wantErr: false, 94 wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{ 95 goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}}, 96 }, 97 wantUpdateMD: xdsclient.UpdateMetadata{ 98 Status: xdsclient.ServiceStatusACKed, 99 }, 100 wantUpdateErr: false, 101 }, 102 // Response contains multiple good listeners, including the one we are 103 // interested in. 104 { 105 name: "multiple-good-listener", 106 ldsResponse: ldsResponseWithMultipleResources, 107 wantErr: false, 108 wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{ 109 goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}}, 110 goodLDSTarget2: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener2}}, 111 }, 112 wantUpdateMD: xdsclient.UpdateMetadata{ 113 Status: xdsclient.ServiceStatusACKed, 114 }, 115 wantUpdateErr: false, 116 }, 117 // Response contains two good listeners (one interesting and one 118 // uninteresting), and one badly marshaled listener. This will cause a 119 // nack because the uninteresting listener will still be parsed. 120 { 121 name: "good-bad-ugly-listeners", 122 ldsResponse: goodBadUglyLDSResponse, 123 wantErr: true, 124 wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{ 125 goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}}, 126 goodLDSTarget2: {Err: cmpopts.AnyError}, 127 }, 128 wantUpdateMD: xdsclient.UpdateMetadata{ 129 Status: xdsclient.ServiceStatusNACKed, 130 ErrState: &xdsclient.UpdateErrorMetadata{ 131 Err: cmpopts.AnyError, 132 }, 133 }, 134 wantUpdateErr: false, 135 }, 136 // Response contains one listener, but we are not interested in it. 137 { 138 name: "one-uninteresting-listener", 139 ldsResponse: goodLDSResponse2, 140 wantErr: false, 141 wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{ 142 goodLDSTarget2: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener2}}, 143 }, 144 wantUpdateMD: xdsclient.UpdateMetadata{ 145 Status: xdsclient.ServiceStatusACKed, 146 }, 147 wantUpdateErr: false, 148 }, 149 // Response constains no resources. This is the case where the server 150 // does not know about the target we are interested in. 151 { 152 name: "empty-response", 153 ldsResponse: emptyLDSResponse, 154 wantErr: false, 155 wantUpdate: nil, 156 wantUpdateMD: xdsclient.UpdateMetadata{ 157 Status: xdsclient.ServiceStatusACKed, 158 }, 159 wantUpdateErr: false, 160 }, 161 } 162 163 for _, test := range tests { 164 t.Run(test.name, func(t *testing.T) { 165 testWatchHandle(t, &watchHandleTestcase{ 166 rType: xdsclient.ListenerResource, 167 resourceName: goodLDSTarget1, 168 responseToHandle: test.ldsResponse, 169 wantHandleErr: test.wantErr, 170 wantUpdate: test.wantUpdate, 171 wantUpdateMD: test.wantUpdateMD, 172 wantUpdateErr: test.wantUpdateErr, 173 }) 174 }) 175 } 176} 177 178// TestLDSHandleResponseWithoutWatch tests the case where the client receives 179// an LDS response without a registered watcher. 180func (s) TestLDSHandleResponseWithoutWatch(t *testing.T) { 181 _, cc, cleanup := startServerAndGetCC(t) 182 defer cleanup() 183 184 v2c, err := newV2Client(&testUpdateReceiver{ 185 f: func(xdsclient.ResourceType, map[string]interface{}, xdsclient.UpdateMetadata) {}, 186 }, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil) 187 if err != nil { 188 t.Fatal(err) 189 } 190 defer v2c.Close() 191 192 if v2c.handleLDSResponse(badResourceTypeInLDSResponse) == nil { 193 t.Fatal("v2c.handleLDSResponse() succeeded, should have failed") 194 } 195 196 if v2c.handleLDSResponse(goodLDSResponse1) != nil { 197 t.Fatal("v2c.handleLDSResponse() succeeded, should have failed") 198 } 199} 200