hub.fastgit.org/hashicorp/consul.git@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 }