github.com/lmb/consul@v1.4.1/connect/proxy/listener_test.go (about) 1 package proxy 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "log" 8 "net" 9 "os" 10 "testing" 11 "time" 12 13 metrics "github.com/armon/go-metrics" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 agConnect "github.com/hashicorp/consul/agent/connect" 18 "github.com/hashicorp/consul/connect" 19 "github.com/hashicorp/consul/lib/freeport" 20 ) 21 22 func testSetupMetrics(t *testing.T) *metrics.InmemSink { 23 // Record for ages (5 mins) so we can be confident that our assertions won't 24 // fail on silly long test runs due to dropped data. 25 s := metrics.NewInmemSink(10*time.Second, 300*time.Second) 26 cfg := metrics.DefaultConfig("consul.proxy.test") 27 cfg.EnableHostname = false 28 metrics.NewGlobal(cfg, s) 29 return s 30 } 31 32 func assertCurrentGaugeValue(t *testing.T, sink *metrics.InmemSink, 33 name string, value float32) { 34 t.Helper() 35 36 data := sink.Data() 37 38 // Loop backward through intervals until there is a non-empty one 39 // Addresses flakiness around recording to one interval but accessing during the next 40 var got float32 41 for i := len(data) - 1; i >= 0; i-- { 42 currentInterval := data[i] 43 44 currentInterval.RLock() 45 if len(currentInterval.Gauges) > 0 { 46 got = currentInterval.Gauges[name].Value 47 break 48 } 49 currentInterval.RUnlock() 50 } 51 52 if !assert.Equal(t, value, got) { 53 buf := bytes.NewBuffer(nil) 54 for _, intv := range data { 55 intv.RLock() 56 for name, val := range intv.Gauges { 57 fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val.Value) 58 } 59 intv.RUnlock() 60 } 61 t.Log(buf.String()) 62 } 63 } 64 65 func assertAllTimeCounterValue(t *testing.T, sink *metrics.InmemSink, 66 name string, value float64) { 67 t.Helper() 68 69 data := sink.Data() 70 71 var got float64 72 for _, intv := range data { 73 intv.RLock() 74 // Note that InMemSink uses SampledValue and treats the _Sum_ not the Count 75 // as the entire value. 76 if sample, ok := intv.Counters[name]; ok { 77 got += sample.Sum 78 } 79 intv.RUnlock() 80 } 81 82 if !assert.Equal(t, value, got) { 83 // no nice way to dump this - this is copied from private method in 84 // InMemSink used for dumping to stdout on SIGUSR1. 85 buf := bytes.NewBuffer(nil) 86 for _, intv := range data { 87 intv.RLock() 88 for name, val := range intv.Gauges { 89 fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val.Value) 90 } 91 for name, vals := range intv.Points { 92 for _, val := range vals { 93 fmt.Fprintf(buf, "[%v][P] '%s': %0.3f\n", intv.Interval, name, val) 94 } 95 } 96 for name, agg := range intv.Counters { 97 fmt.Fprintf(buf, "[%v][C] '%s': %s\n", intv.Interval, name, agg.AggregateSample) 98 } 99 for name, agg := range intv.Samples { 100 fmt.Fprintf(buf, "[%v][S] '%s': %s\n", intv.Interval, name, agg.AggregateSample) 101 } 102 intv.RUnlock() 103 } 104 t.Log(buf.String()) 105 } 106 } 107 108 func TestPublicListener(t *testing.T) { 109 // Can't enable t.Parallel since we rely on the global metrics instance. 110 111 ca := agConnect.TestCA(t, nil) 112 ports := freeport.GetT(t, 1) 113 114 testApp := NewTestTCPServer(t) 115 defer testApp.Close() 116 117 cfg := PublicListenerConfig{ 118 BindAddress: "127.0.0.1", 119 BindPort: ports[0], 120 LocalServiceAddress: testApp.Addr().String(), 121 HandshakeTimeoutMs: 100, 122 LocalConnectTimeoutMs: 100, 123 } 124 125 // Setup metrics to test they are recorded 126 sink := testSetupMetrics(t) 127 128 svc := connect.TestService(t, "db", ca) 129 l := NewPublicListener(svc, cfg, log.New(os.Stderr, "", log.LstdFlags)) 130 131 // Run proxy 132 go func() { 133 err := l.Serve() 134 require.NoError(t, err) 135 }() 136 defer l.Close() 137 l.Wait() 138 139 // Proxy and backend are running, play the part of a TLS client using same 140 // cert for now. 141 conn, err := svc.Dial(context.Background(), &connect.StaticResolver{ 142 Addr: TestLocalAddr(ports[0]), 143 CertURI: agConnect.TestSpiffeIDService(t, "db"), 144 }) 145 require.NoError(t, err) 146 147 TestEchoConn(t, conn, "") 148 149 // Check active conn is tracked in gauges 150 assertCurrentGaugeValue(t, sink, "consul.proxy.test.inbound.conns;dst=db", 1) 151 152 // Close listener to ensure all conns are closed and have reported their metrics 153 l.Close() 154 155 // Check all the tx/rx counters got added 156 assertAllTimeCounterValue(t, sink, "consul.proxy.test.inbound.tx_bytes;dst=db", 11) 157 assertAllTimeCounterValue(t, sink, "consul.proxy.test.inbound.rx_bytes;dst=db", 11) 158 } 159 160 func TestUpstreamListener(t *testing.T) { 161 // Can't enable t.Parallel since we rely on the global metrics instance. 162 163 ca := agConnect.TestCA(t, nil) 164 ports := freeport.GetT(t, 1) 165 166 // Run a test server that we can dial. 167 testSvr := connect.NewTestServer(t, "db", ca) 168 go func() { 169 err := testSvr.Serve() 170 require.NoError(t, err) 171 }() 172 defer testSvr.Close() 173 <-testSvr.Listening 174 175 cfg := UpstreamConfig{ 176 DestinationType: "service", 177 DestinationNamespace: "default", 178 DestinationName: "db", 179 Config: map[string]interface{}{"connect_timeout_ms": 100}, 180 LocalBindAddress: "localhost", 181 LocalBindPort: ports[0], 182 } 183 184 // Setup metrics to test they are recorded 185 sink := testSetupMetrics(t) 186 187 svc := connect.TestService(t, "web", ca) 188 189 // Setup with a statuc resolver instead 190 rf := TestStaticUpstreamResolverFunc(&connect.StaticResolver{ 191 Addr: testSvr.Addr, 192 CertURI: agConnect.TestSpiffeIDService(t, "db"), 193 }) 194 l := newUpstreamListenerWithResolver(svc, cfg, rf, log.New(os.Stderr, "", log.LstdFlags)) 195 196 // Run proxy 197 go func() { 198 err := l.Serve() 199 require.NoError(t, err) 200 }() 201 defer l.Close() 202 l.Wait() 203 204 // Proxy and fake remote service are running, play the part of the app 205 // connecting to a remote connect service over TCP. 206 conn, err := net.Dial("tcp", 207 fmt.Sprintf("%s:%d", cfg.LocalBindAddress, cfg.LocalBindPort)) 208 require.NoError(t, err) 209 210 TestEchoConn(t, conn, "") 211 212 // Check active conn is tracked in gauges 213 assertCurrentGaugeValue(t, sink, "consul.proxy.test.upstream.conns;src=web;dst_type=service;dst=db", 1) 214 215 // Close listener to ensure all conns are closed and have reported their metrics 216 l.Close() 217 218 // Check all the tx/rx counters got added 219 assertAllTimeCounterValue(t, sink, "consul.proxy.test.upstream.tx_bytes;src=web;dst_type=service;dst=db", 11) 220 assertAllTimeCounterValue(t, sink, "consul.proxy.test.upstream.rx_bytes;src=web;dst_type=service;dst=db", 11) 221 }