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 }