github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/allocrunner/consulsock_hook_test.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"testing"
    13  
    14  	"github.com/hashicorp/nomad/client/allocdir"
    15  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    16  	"github.com/hashicorp/nomad/helper/testlog"
    17  	"github.com/hashicorp/nomad/nomad/mock"
    18  	"github.com/hashicorp/nomad/nomad/structs/config"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  // TestConsulSockHook_PrerunPostrun_Ok asserts that a proxy is started when the
    24  // Consul unix socket hook's Prerun method is called and stopped with the
    25  // Postrun method is called.
    26  func TestConsulSockHook_PrerunPostrun_Ok(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	// As of Consul 1.6.0 the test server does not support the gRPC
    30  	// endpoint so we have to fake it.
    31  	fakeConsul, err := net.Listen("tcp", "127.0.0.1:0")
    32  	require.NoError(t, err)
    33  	defer fakeConsul.Close()
    34  	consulConfig := &config.ConsulConfig{
    35  		GRPCAddr: fakeConsul.Addr().String(),
    36  	}
    37  
    38  	alloc := mock.ConnectAlloc()
    39  
    40  	logger := testlog.HCLogger(t)
    41  
    42  	allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap")
    43  	defer cleanup()
    44  
    45  	// Start the unix socket proxy
    46  	h := newConsulSockHook(logger, alloc, allocDir, consulConfig)
    47  	require.NoError(t, h.Prerun())
    48  
    49  	gRPCSock := filepath.Join(allocDir.AllocDir, allocdir.AllocGRPCSocket)
    50  	envoyConn, err := net.Dial("unix", gRPCSock)
    51  	require.NoError(t, err)
    52  
    53  	// Write to Consul to ensure data is proxied out of the netns
    54  	input := bytes.Repeat([]byte{'X'}, 5*1024)
    55  	errCh := make(chan error, 1)
    56  	go func() {
    57  		_, err := envoyConn.Write(input)
    58  		errCh <- err
    59  	}()
    60  
    61  	// Accept the connection from the netns
    62  	consulConn, err := fakeConsul.Accept()
    63  	require.NoError(t, err)
    64  	defer consulConn.Close()
    65  
    66  	output := make([]byte, len(input))
    67  	_, err = consulConn.Read(output)
    68  	require.NoError(t, err)
    69  	require.NoError(t, <-errCh)
    70  	require.Equal(t, input, output)
    71  
    72  	// Read from Consul to ensure data is proxied into the netns
    73  	input = bytes.Repeat([]byte{'Y'}, 5*1024)
    74  	go func() {
    75  		_, err := consulConn.Write(input)
    76  		errCh <- err
    77  	}()
    78  
    79  	_, err = envoyConn.Read(output)
    80  	require.NoError(t, err)
    81  	require.NoError(t, <-errCh)
    82  	require.Equal(t, input, output)
    83  
    84  	// Stop the unix socket proxy
    85  	require.NoError(t, h.Postrun())
    86  
    87  	// Consul reads should error
    88  	n, err := consulConn.Read(output)
    89  	require.Error(t, err)
    90  	require.Zero(t, n)
    91  
    92  	// Envoy reads and writes should error
    93  	n, err = envoyConn.Write(input)
    94  	require.Error(t, err)
    95  	require.Zero(t, n)
    96  	n, err = envoyConn.Read(output)
    97  	require.Error(t, err)
    98  	require.Zero(t, n)
    99  }
   100  
   101  // TestConsulSockHook_Prerun_Error asserts that invalid Consul addresses cause
   102  // Prerun to return an error if the alloc requires a grpc proxy.
   103  func TestConsulSockHook_Prerun_Error(t *testing.T) {
   104  	t.Parallel()
   105  
   106  	logger := testlog.HCLogger(t)
   107  
   108  	allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap")
   109  	defer cleanup()
   110  
   111  	// A config without an Addr or GRPCAddr is invalid.
   112  	consulConfig := &config.ConsulConfig{}
   113  
   114  	alloc := mock.Alloc()
   115  	connectAlloc := mock.ConnectAlloc()
   116  
   117  	{
   118  		// An alloc without a Connect proxy sidecar should not return
   119  		// an error.
   120  		h := newConsulSockHook(logger, alloc, allocDir, consulConfig)
   121  		require.NoError(t, h.Prerun())
   122  
   123  		// Postrun should be a noop
   124  		require.NoError(t, h.Postrun())
   125  	}
   126  
   127  	{
   128  		// An alloc *with* a Connect proxy sidecar *should* return an error
   129  		// when Consul is not configured.
   130  		h := newConsulSockHook(logger, connectAlloc, allocDir, consulConfig)
   131  		require.Error(t, h.Prerun())
   132  
   133  		// Postrun should be a noop
   134  		require.NoError(t, h.Postrun())
   135  	}
   136  
   137  	{
   138  		// Updating an alloc without a sidecar to have a sidecar should
   139  		// error when the sidecar is added.
   140  		h := newConsulSockHook(logger, alloc, allocDir, consulConfig)
   141  		require.NoError(t, h.Prerun())
   142  
   143  		req := &interfaces.RunnerUpdateRequest{
   144  			Alloc: connectAlloc,
   145  		}
   146  		require.Error(t, h.Update(req))
   147  
   148  		// Postrun should be a noop
   149  		require.NoError(t, h.Postrun())
   150  	}
   151  }
   152  
   153  // TestConsulSockHook_proxy_Unix asserts that the destination can be a unix
   154  // socket path.
   155  func TestConsulSockHook_proxy_Unix(t *testing.T) {
   156  	t.Parallel()
   157  
   158  	dir, err := ioutil.TempDir("", "nomadtest_proxy_Unix")
   159  	require.NoError(t, err)
   160  	defer func() {
   161  		require.NoError(t, os.RemoveAll(dir))
   162  	}()
   163  
   164  	// Setup fake listener that would be inside the netns (normally a unix
   165  	// socket, but it doesn't matter for this test).
   166  	src, err := net.Listen("tcp", "127.0.0.1:0")
   167  	require.NoError(t, err)
   168  	defer src.Close()
   169  
   170  	// Setup fake listener that would be Consul outside the netns. Use a
   171  	// socket as Consul may be configured to listen on a unix socket.
   172  	destFn := filepath.Join(dir, "fakeconsul.sock")
   173  	dest, err := net.Listen("unix", destFn)
   174  	require.NoError(t, err)
   175  	defer dest.Close()
   176  
   177  	// Collect errors (must have len > goroutines)
   178  	errCh := make(chan error, 10)
   179  
   180  	// Block until completion
   181  	wg := sync.WaitGroup{}
   182  
   183  	ctx, cancel := context.WithCancel(context.Background())
   184  	defer cancel()
   185  
   186  	wg.Add(1)
   187  	go func() {
   188  		defer wg.Done()
   189  		proxy(ctx, testlog.HCLogger(t), "unix://"+destFn, src)
   190  	}()
   191  
   192  	// Fake Envoy
   193  	// Connect and write to the src (netns) side of the proxy; then read
   194  	// and exit.
   195  	wg.Add(1)
   196  	go func() {
   197  		defer func() {
   198  			// Cancel after final read has completed (or an error
   199  			// has occurred)
   200  			cancel()
   201  
   202  			wg.Done()
   203  		}()
   204  
   205  		addr := src.Addr()
   206  		conn, err := net.Dial(addr.Network(), addr.String())
   207  		if err != nil {
   208  			errCh <- err
   209  			return
   210  		}
   211  
   212  		defer conn.Close()
   213  
   214  		if _, err := conn.Write([]byte{'X'}); err != nil {
   215  			errCh <- err
   216  			return
   217  		}
   218  
   219  		recv := make([]byte, 1)
   220  		if _, err := conn.Read(recv); err != nil {
   221  			errCh <- err
   222  			return
   223  		}
   224  
   225  		if expected := byte('Y'); recv[0] != expected {
   226  			errCh <- fmt.Errorf("expected %q but received: %q", expected, recv[0])
   227  			return
   228  		}
   229  	}()
   230  
   231  	// Fake Consul on a unix socket
   232  	// Listen, receive 1 byte, write a response, and exit
   233  	wg.Add(1)
   234  	go func() {
   235  		defer wg.Done()
   236  
   237  		conn, err := dest.Accept()
   238  		if err != nil {
   239  			errCh <- err
   240  			return
   241  		}
   242  
   243  		// Close listener now. No more connections expected.
   244  		if err := dest.Close(); err != nil {
   245  			errCh <- err
   246  			return
   247  		}
   248  
   249  		defer conn.Close()
   250  
   251  		recv := make([]byte, 1)
   252  		if _, err := conn.Read(recv); err != nil {
   253  			errCh <- err
   254  			return
   255  		}
   256  
   257  		if expected := byte('X'); recv[0] != expected {
   258  			errCh <- fmt.Errorf("expected %q but received: %q", expected, recv[0])
   259  			return
   260  		}
   261  
   262  		if _, err := conn.Write([]byte{'Y'}); err != nil {
   263  			errCh <- err
   264  			return
   265  		}
   266  	}()
   267  
   268  	// Wait for goroutines to complete
   269  	wg.Wait()
   270  
   271  	// Make sure no errors occurred
   272  	for len(errCh) > 0 {
   273  		assert.NoError(t, <-errCh)
   274  	}
   275  }