istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/adstest.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 sds "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" 25 "google.golang.org/genproto/googleapis/rpc/status" 26 "google.golang.org/grpc" 27 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 NewAdsTest(t test.Failer, conn *grpc.ClientConn) *AdsTest { 34 return NewXdsTest(t, conn, func(conn *grpc.ClientConn) (DiscoveryClient, error) { 35 xds := discovery.NewAggregatedDiscoveryServiceClient(conn) 36 return xds.StreamAggregatedResources(context.Background()) 37 }) 38 } 39 40 func NewSdsTest(t test.Failer, conn *grpc.ClientConn) *AdsTest { 41 return NewXdsTest(t, conn, func(conn *grpc.ClientConn) (DiscoveryClient, error) { 42 xds := sds.NewSecretDiscoveryServiceClient(conn) 43 return xds.StreamSecrets(context.Background()) 44 }).WithType(v3.SecretType) 45 } 46 47 func NewXdsTest(t test.Failer, conn *grpc.ClientConn, getClient func(conn *grpc.ClientConn) (DiscoveryClient, error)) *AdsTest { 48 ctx, cancel := context.WithCancel(context.Background()) 49 50 cl, err := getClient(conn) 51 if err != nil { 52 t.Fatal(err) 53 } 54 resp := &AdsTest{ 55 client: cl, 56 conn: conn, 57 context: ctx, 58 cancelContext: cancel, 59 ID: "sidecar~1.1.1.1~test.default~default.svc.cluster.local", 60 timeout: time.Second, 61 Type: v3.ClusterType, 62 responses: make(chan *discovery.DiscoveryResponse), 63 error: make(chan error), 64 } 65 t.Cleanup(resp.Cleanup) 66 67 go resp.adsReceiveChannel() 68 69 return resp 70 } 71 72 type AdsTest struct { 73 client DiscoveryClient 74 responses chan *discovery.DiscoveryResponse 75 error chan error 76 conn *grpc.ClientConn 77 metadata model.NodeMetadata 78 79 ID string 80 Type string 81 82 cancelOnce sync.Once 83 context context.Context 84 cancelContext context.CancelFunc 85 timeout time.Duration 86 } 87 88 func (a *AdsTest) Cleanup() { 89 // Place in once to avoid race when two callers attempt to cleanup 90 a.cancelOnce.Do(func() { 91 a.cancelContext() 92 _ = a.client.CloseSend() 93 if a.conn != nil { 94 _ = a.conn.Close() 95 } 96 }) 97 } 98 99 func (a *AdsTest) adsReceiveChannel() { 100 context.AfterFunc(a.context, a.Cleanup) 101 for { 102 resp, err := a.client.Recv() 103 if err != nil { 104 if isUnexpectedError(err) { 105 log.Warnf("ads received error: %v", err) 106 } 107 select { 108 case a.error <- err: 109 case <-a.context.Done(): 110 } 111 return 112 } 113 select { 114 case a.responses <- resp: 115 case <-a.context.Done(): 116 return 117 } 118 } 119 } 120 121 // DrainResponses reads all responses, but does nothing to them 122 func (a *AdsTest) DrainResponses() { 123 for { 124 select { 125 case <-a.context.Done(): 126 return 127 case r := <-a.responses: 128 log.Infof("drained response %v", r.TypeUrl) 129 } 130 } 131 } 132 133 // ExpectResponse waits until a response is received and returns it 134 func (a *AdsTest) ExpectResponse(t test.Failer) *discovery.DiscoveryResponse { 135 t.Helper() 136 select { 137 case <-time.After(a.timeout): 138 t.Fatalf("did not get response in time") 139 case resp := <-a.responses: 140 if resp == nil || len(resp.Resources) == 0 { 141 t.Fatalf("got empty response") 142 } 143 return resp 144 case err := <-a.error: 145 t.Fatalf("got error: %v", err) 146 } 147 return nil 148 } 149 150 // ExpectError waits until an error is received and returns it 151 func (a *AdsTest) ExpectError(t test.Failer) error { 152 t.Helper() 153 select { 154 case <-time.After(a.timeout): 155 t.Fatalf("did not get error in time") 156 case err := <-a.error: 157 return err 158 } 159 return nil 160 } 161 162 // ExpectNoResponse waits a short period of time and ensures no response is received 163 func (a *AdsTest) ExpectNoResponse(t test.Failer) { 164 t.Helper() 165 select { 166 case <-time.After(time.Millisecond * 50): 167 return 168 case resp := <-a.responses: 169 t.Fatalf("got unexpected response: %v", resp) 170 } 171 } 172 173 func (a *AdsTest) fillInRequestDefaults(req *discovery.DiscoveryRequest) *discovery.DiscoveryRequest { 174 if req == nil { 175 req = &discovery.DiscoveryRequest{} 176 } 177 if req.TypeUrl == "" { 178 req.TypeUrl = a.Type 179 } 180 if req.Node == nil { 181 req.Node = &core.Node{ 182 Id: a.ID, 183 Metadata: a.metadata.ToStruct(), 184 } 185 } 186 return req 187 } 188 189 func (a *AdsTest) Request(t test.Failer, req *discovery.DiscoveryRequest) { 190 t.Helper() 191 req = a.fillInRequestDefaults(req) 192 if err := a.client.Send(req); err != nil { 193 t.Fatal(err) 194 } 195 } 196 197 // RequestResponseAck does a full XDS exchange: Send a request, get a response, and ACK the response 198 func (a *AdsTest) RequestResponseAck(t test.Failer, req *discovery.DiscoveryRequest) *discovery.DiscoveryResponse { 199 t.Helper() 200 req = a.fillInRequestDefaults(req) 201 a.Request(t, req) 202 resp := a.ExpectResponse(t) 203 req.ResponseNonce = resp.Nonce 204 req.VersionInfo = resp.VersionInfo 205 a.Request(t, req) 206 return resp 207 } 208 209 // RequestResponseAck does a full XDS exchange with an error: Send a request, get a response, and NACK the response 210 func (a *AdsTest) RequestResponseNack(t test.Failer, req *discovery.DiscoveryRequest) *discovery.DiscoveryResponse { 211 t.Helper() 212 if req == nil { 213 req = &discovery.DiscoveryRequest{} 214 } 215 a.Request(t, req) 216 resp := a.ExpectResponse(t) 217 req.ResponseNonce = resp.Nonce 218 req.ErrorDetail = &status.Status{Message: "Test request NACK"} 219 a.Request(t, req) 220 return resp 221 } 222 223 func (a *AdsTest) WithID(id string) *AdsTest { 224 a.ID = id 225 return a 226 } 227 228 func (a *AdsTest) WithType(typeURL string) *AdsTest { 229 a.Type = typeURL 230 return a 231 } 232 233 func (a *AdsTest) WithMetadata(m model.NodeMetadata) *AdsTest { 234 a.metadata = m 235 return a 236 } 237 238 func (a *AdsTest) WithTimeout(t time.Duration) *AdsTest { 239 a.timeout = t 240 return a 241 }