istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/deltaadstest.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package xds 16 17 import ( 18 "context" 19 "sync" 20 "time" 21 22 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 23 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 24 "google.golang.org/genproto/googleapis/rpc/status" 25 "google.golang.org/grpc" 26 27 "istio.io/istio/pilot/pkg/features" 28 "istio.io/istio/pilot/pkg/model" 29 v3 "istio.io/istio/pilot/pkg/xds/v3" 30 "istio.io/istio/pkg/test" 31 ) 32 33 func NewDeltaAdsTest(t test.Failer, conn *grpc.ClientConn) *DeltaAdsTest { 34 test.SetForTest(t, &features.DeltaXds, true) 35 return NewDeltaXdsTest(t, conn, func(conn *grpc.ClientConn) (DeltaDiscoveryClient, error) { 36 xds := discovery.NewAggregatedDiscoveryServiceClient(conn) 37 return xds.DeltaAggregatedResources(context.Background()) 38 }) 39 } 40 41 func NewDeltaXdsTest(t test.Failer, conn *grpc.ClientConn, 42 getClient func(conn *grpc.ClientConn) (DeltaDiscoveryClient, error), 43 ) *DeltaAdsTest { 44 ctx, cancel := context.WithCancel(context.Background()) 45 46 cl, err := getClient(conn) 47 if err != nil { 48 t.Fatal(err) 49 } 50 resp := &DeltaAdsTest{ 51 client: cl, 52 conn: conn, 53 context: ctx, 54 cancelContext: cancel, 55 t: t, 56 ID: "sidecar~1.1.1.1~test.default~default.svc.cluster.local", 57 timeout: time.Second, 58 Type: v3.ClusterType, 59 responses: make(chan *discovery.DeltaDiscoveryResponse), 60 error: make(chan error), 61 } 62 t.Cleanup(resp.Cleanup) 63 64 go resp.adsReceiveChannel() 65 66 return resp 67 } 68 69 type DeltaAdsTest struct { 70 client DeltaDiscoveryClient 71 responses chan *discovery.DeltaDiscoveryResponse 72 error chan error 73 t test.Failer 74 conn *grpc.ClientConn 75 metadata model.NodeMetadata 76 77 ID string 78 Type string 79 80 cancelOnce sync.Once 81 context context.Context 82 cancelContext context.CancelFunc 83 timeout time.Duration 84 } 85 86 func (a *DeltaAdsTest) Cleanup() { 87 // Place in once to avoid race when two callers attempt to cleanup 88 a.cancelOnce.Do(func() { 89 a.cancelContext() 90 _ = a.client.CloseSend() 91 if a.conn != nil { 92 _ = a.conn.Close() 93 } 94 }) 95 } 96 97 func (a *DeltaAdsTest) adsReceiveChannel() { 98 context.AfterFunc(a.context, a.Cleanup) 99 for { 100 resp, err := a.client.Recv() 101 if err != nil { 102 if isUnexpectedError(err) { 103 log.Warnf("ads received error: %v", err) 104 } 105 select { 106 case a.error <- err: 107 case <-a.context.Done(): 108 } 109 return 110 } 111 select { 112 case a.responses <- resp: 113 case <-a.context.Done(): 114 return 115 } 116 } 117 } 118 119 // DrainResponses reads all responses, but does nothing to them 120 func (a *DeltaAdsTest) DrainResponses() { 121 a.t.Helper() 122 for { 123 select { 124 case <-a.context.Done(): 125 return 126 case r := <-a.responses: 127 log.Infof("drained response %v", r.TypeUrl) 128 } 129 } 130 } 131 132 // ExpectResponse waits until a response is received and returns it 133 func (a *DeltaAdsTest) ExpectResponse() *discovery.DeltaDiscoveryResponse { 134 a.t.Helper() 135 select { 136 case <-time.After(a.timeout): 137 a.t.Fatalf("did not get response in time") 138 case resp := <-a.responses: 139 if resp == nil || (len(resp.Resources) == 0 && len(resp.RemovedResources) == 0) { 140 a.t.Fatalf("got empty response") 141 } 142 return resp 143 case err := <-a.error: 144 a.t.Fatalf("got error: %v", err) 145 } 146 return nil 147 } 148 149 // ExpectResponse waits until a response is received and returns it 150 func (a *DeltaAdsTest) ExpectEmptyResponse() *discovery.DeltaDiscoveryResponse { 151 a.t.Helper() 152 select { 153 case <-time.After(a.timeout): 154 a.t.Fatalf("did not get response in time") 155 case resp := <-a.responses: 156 if resp == nil { 157 a.t.Fatalf("expected response") 158 } 159 if resp != nil && (len(resp.RemovedResources) > 0 || len(resp.Resources) > 0) { 160 a.t.Fatalf("expected empty response. received %v", resp) 161 } 162 return resp 163 case err := <-a.error: 164 a.t.Fatalf("got error: %v", err) 165 } 166 return nil 167 } 168 169 // ExpectError waits until an error is received and returns it 170 func (a *DeltaAdsTest) ExpectError() error { 171 a.t.Helper() 172 select { 173 case <-time.After(a.timeout): 174 a.t.Fatalf("did not get error in time") 175 case err := <-a.error: 176 return err 177 } 178 return nil 179 } 180 181 // ExpectNoResponse waits a short period of time and ensures no response is received 182 func (a *DeltaAdsTest) ExpectNoResponse() { 183 a.t.Helper() 184 select { 185 case <-time.After(time.Millisecond * 50): 186 return 187 case resp := <-a.responses: 188 a.t.Fatalf("got unexpected response: %v", resp) 189 } 190 } 191 192 func (a *DeltaAdsTest) fillInRequestDefaults(req *discovery.DeltaDiscoveryRequest) *discovery.DeltaDiscoveryRequest { 193 if req == nil { 194 req = &discovery.DeltaDiscoveryRequest{} 195 } 196 if req.TypeUrl == "" { 197 req.TypeUrl = a.Type 198 } 199 if req.Node == nil { 200 req.Node = &core.Node{ 201 Id: a.ID, 202 Metadata: a.metadata.ToStruct(), 203 } 204 } 205 return req 206 } 207 208 func (a *DeltaAdsTest) Request(req *discovery.DeltaDiscoveryRequest) { 209 req = a.fillInRequestDefaults(req) 210 if err := a.client.Send(req); err != nil { 211 a.t.Fatal(err) 212 } 213 } 214 215 // RequestResponseAck does a full XDS exchange: Send a request, get a response, and ACK the response 216 func (a *DeltaAdsTest) RequestResponseAck(req *discovery.DeltaDiscoveryRequest) *discovery.DeltaDiscoveryResponse { 217 a.t.Helper() 218 req = a.fillInRequestDefaults(req) 219 a.Request(req) 220 resp := a.ExpectResponse() 221 req.ResponseNonce = resp.Nonce 222 a.Request(&discovery.DeltaDiscoveryRequest{ 223 Node: req.Node, 224 TypeUrl: req.TypeUrl, 225 ResponseNonce: req.ResponseNonce, 226 }) 227 return resp 228 } 229 230 // RequestResponseNack does a full XDS exchange with an error: Send a request, get a response, and NACK the response 231 func (a *DeltaAdsTest) RequestResponseNack(req *discovery.DeltaDiscoveryRequest) *discovery.DeltaDiscoveryResponse { 232 a.t.Helper() 233 if req == nil { 234 req = &discovery.DeltaDiscoveryRequest{} 235 } 236 a.Request(req) 237 resp := a.ExpectResponse() 238 a.Request(&discovery.DeltaDiscoveryRequest{ 239 Node: req.Node, 240 TypeUrl: req.TypeUrl, 241 ResponseNonce: req.ResponseNonce, 242 ErrorDetail: &status.Status{Message: "Test request NACK"}, 243 }) 244 return resp 245 } 246 247 func (a *DeltaAdsTest) WithID(id string) *DeltaAdsTest { 248 a.ID = id 249 return a 250 } 251 252 func (a *DeltaAdsTest) WithType(typeURL string) *DeltaAdsTest { 253 a.Type = typeURL 254 return a 255 } 256 257 func (a *DeltaAdsTest) WithMetadata(m model.NodeMetadata) *DeltaAdsTest { 258 a.metadata = m 259 return a 260 } 261 262 func (a *DeltaAdsTest) WithTimeout(t time.Duration) *DeltaAdsTest { 263 a.timeout = t 264 return a 265 } 266 267 func (a *DeltaAdsTest) WithNodeType(t model.NodeType) *DeltaAdsTest { 268 a.ID = string(t) + "~1.1.1.1~test.default~default.svc.cluster.local" 269 return a 270 }