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  }