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  }