github.com/kjdelisle/consul@v1.4.5/agent/xds/testing.go (about)

     1  package xds
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
    11  	envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
    12  	envoyauth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2alpha"
    13  	"github.com/mitchellh/go-testing-interface"
    14  	"google.golang.org/grpc/metadata"
    15  
    16  	"github.com/hashicorp/consul/agent/connect"
    17  )
    18  
    19  // TestADSStream mocks
    20  // discovery.AggregatedDiscoveryService_StreamAggregatedResourcesServer to allow
    21  // testing ADS handler.
    22  type TestADSStream struct {
    23  	ctx    context.Context
    24  	sendCh chan *envoy.DiscoveryResponse
    25  	recvCh chan *envoy.DiscoveryRequest
    26  }
    27  
    28  // NewTestADSStream makes a new TestADSStream
    29  func NewTestADSStream(t testing.T, ctx context.Context) *TestADSStream {
    30  	return &TestADSStream{
    31  		ctx:    ctx,
    32  		sendCh: make(chan *envoy.DiscoveryResponse, 1),
    33  		recvCh: make(chan *envoy.DiscoveryRequest, 1),
    34  	}
    35  }
    36  
    37  // Send implements ADSStream
    38  func (s *TestADSStream) Send(r *envoy.DiscoveryResponse) error {
    39  	s.sendCh <- r
    40  	return nil
    41  }
    42  
    43  // Recv implements ADSStream
    44  func (s *TestADSStream) Recv() (*envoy.DiscoveryRequest, error) {
    45  	r := <-s.recvCh
    46  	if r == nil {
    47  		return nil, io.EOF
    48  	}
    49  	return r, nil
    50  }
    51  
    52  // SetHeader implements ADSStream
    53  func (s *TestADSStream) SetHeader(metadata.MD) error {
    54  	return nil
    55  }
    56  
    57  // SendHeader implements ADSStream
    58  func (s *TestADSStream) SendHeader(metadata.MD) error {
    59  	return nil
    60  }
    61  
    62  // SetTrailer implements ADSStream
    63  func (s *TestADSStream) SetTrailer(metadata.MD) {
    64  }
    65  
    66  // Context implements ADSStream
    67  func (s *TestADSStream) Context() context.Context {
    68  	return s.ctx
    69  }
    70  
    71  // SendMsg implements ADSStream
    72  func (s *TestADSStream) SendMsg(m interface{}) error {
    73  	return nil
    74  }
    75  
    76  // RecvMsg implements ADSStream
    77  func (s *TestADSStream) RecvMsg(m interface{}) error {
    78  	return nil
    79  }
    80  
    81  type configState struct {
    82  	lastNonce, lastVersion, acceptedVersion string
    83  }
    84  
    85  // TestEnvoy is a helper to simulate Envoy ADS requests.
    86  type TestEnvoy struct {
    87  	sync.Mutex
    88  	stream  *TestADSStream
    89  	proxyID string
    90  	token   string
    91  	state   map[string]configState
    92  	ctx     context.Context
    93  	cancel  func()
    94  }
    95  
    96  // NewTestEnvoy creates a TestEnvoy instance.
    97  func NewTestEnvoy(t testing.T, proxyID, token string) *TestEnvoy {
    98  	ctx, cancel := context.WithCancel(context.Background())
    99  	// If a token is given, attach it to the context in the same way gRPC attaches
   100  	// metadata in calls and stream contexts.
   101  	if token != "" {
   102  		ctx = metadata.NewIncomingContext(ctx,
   103  			metadata.Pairs("x-consul-token", token))
   104  	}
   105  	return &TestEnvoy{
   106  		stream:  NewTestADSStream(t, ctx),
   107  		state:   make(map[string]configState),
   108  		ctx:     ctx,
   109  		cancel:  cancel,
   110  		proxyID: proxyID,
   111  		token:   token,
   112  	}
   113  }
   114  
   115  func hexString(v uint64) string {
   116  	if v == 0 {
   117  		return ""
   118  	}
   119  	return fmt.Sprintf("%08x", v)
   120  }
   121  
   122  // SendReq sends a request from the test server.
   123  func (e *TestEnvoy) SendReq(t testing.T, typeURL string, version, nonce uint64) {
   124  	e.Lock()
   125  	defer e.Unlock()
   126  
   127  	req := &envoy.DiscoveryRequest{
   128  		VersionInfo: hexString(version),
   129  		Node: &envoycore.Node{
   130  			Id:      e.proxyID,
   131  			Cluster: e.proxyID,
   132  		},
   133  		ResponseNonce: hexString(nonce),
   134  		TypeUrl:       typeURL,
   135  	}
   136  	select {
   137  	case e.stream.recvCh <- req:
   138  	case <-time.After(50 * time.Millisecond):
   139  		t.Fatalf("send to stream blocked for too long")
   140  	}
   141  }
   142  
   143  // Close closes the client and cancels it's request context.
   144  func (e *TestEnvoy) Close() error {
   145  	e.Lock()
   146  	defer e.Unlock()
   147  
   148  	// unblock the recv chan to simulate recv error when client disconnects
   149  	if e.stream != nil && e.stream.recvCh != nil {
   150  		close(e.stream.recvCh)
   151  		e.stream.recvCh = nil
   152  	}
   153  	if e.cancel != nil {
   154  		e.cancel()
   155  	}
   156  	return nil
   157  }
   158  
   159  // TestCheckRequest creates an envoyauth.CheckRequest with the source and
   160  // destination service names.
   161  func TestCheckRequest(t testing.T, source, dest string) *envoyauth.CheckRequest {
   162  	return &envoyauth.CheckRequest{
   163  		Attributes: &envoyauth.AttributeContext{
   164  			Source:      makeAttributeContextPeer(t, source),
   165  			Destination: makeAttributeContextPeer(t, dest),
   166  		},
   167  	}
   168  }
   169  
   170  func makeAttributeContextPeer(t testing.T, svc string) *envoyauth.AttributeContext_Peer {
   171  	spiffeID := connect.TestSpiffeIDService(t, svc)
   172  	return &envoyauth.AttributeContext_Peer{
   173  		// We don't care about IP for now might later though
   174  		Address: makeAddressPtr("10.0.0.1", 1234),
   175  		// Note we don't set Service since that is an advisory only mechanism in
   176  		// Envoy triggered by self-declared headers. We rely on the actual TLS Peer
   177  		// identity.
   178  		Principal: spiffeID.URI().String(),
   179  	}
   180  }