github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/envoybootstrap_hook_test.go (about) 1 package taskrunner 2 3 import ( 4 "context" 5 "encoding/json" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "testing" 10 11 consulapi "github.com/hashicorp/consul/api" 12 consultest "github.com/hashicorp/consul/testutil" 13 "github.com/hashicorp/nomad/client/allocdir" 14 "github.com/hashicorp/nomad/client/allocrunner/interfaces" 15 "github.com/hashicorp/nomad/client/taskenv" 16 "github.com/hashicorp/nomad/client/testutil" 17 agentconsul "github.com/hashicorp/nomad/command/agent/consul" 18 "github.com/hashicorp/nomad/helper/args" 19 "github.com/hashicorp/nomad/helper/testlog" 20 "github.com/hashicorp/nomad/nomad/mock" 21 "github.com/hashicorp/nomad/nomad/structs" 22 "github.com/stretchr/testify/require" 23 ) 24 25 var _ interfaces.TaskPrestartHook = (*envoyBootstrapHook)(nil) 26 27 // TestTaskRunner_EnvoyBootstrapHook_Prestart asserts the EnvoyBootstrapHook 28 // creates Envoy's bootstrap.json configuration based on Connect proxy sidecars 29 // registered for the task. 30 func TestTaskRunner_EnvoyBootstrapHook_Ok(t *testing.T) { 31 t.Parallel() 32 testutil.RequireConsul(t) 33 34 testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) { 35 // If -v wasn't specified squelch consul logging 36 if !testing.Verbose() { 37 c.Stdout = ioutil.Discard 38 c.Stderr = ioutil.Discard 39 } 40 }) 41 if err != nil { 42 t.Fatalf("error starting test consul server: %v", err) 43 } 44 defer testconsul.Stop() 45 46 alloc := mock.Alloc() 47 alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{ 48 { 49 Mode: "bridge", 50 IP: "10.0.0.1", 51 DynamicPorts: []structs.Port{ 52 { 53 Label: "connect-proxy-foo", 54 Value: 9999, 55 To: 9999, 56 }, 57 }, 58 }, 59 } 60 tg := alloc.Job.TaskGroups[0] 61 tg.Services = []*structs.Service{ 62 { 63 Name: "foo", 64 PortLabel: "9999", // Just need a valid port, nothing will bind to it 65 Connect: &structs.ConsulConnect{ 66 SidecarService: &structs.ConsulSidecarService{}, 67 }, 68 }, 69 } 70 sidecarTask := &structs.Task{ 71 Name: "sidecar", 72 Kind: "connect-proxy:foo", 73 } 74 tg.Tasks = append(tg.Tasks, sidecarTask) 75 76 logger := testlog.HCLogger(t) 77 78 allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap") 79 defer cleanup() 80 81 // Register Group Services 82 consulConfig := consulapi.DefaultConfig() 83 consulConfig.Address = testconsul.HTTPAddr 84 consulAPIClient, err := consulapi.NewClient(consulConfig) 85 require.NoError(t, err) 86 consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true) 87 go consulClient.Run() 88 defer consulClient.Shutdown() 89 require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter()))) 90 91 // Run Connect bootstrap Hook 92 h := newEnvoyBootstrapHook(alloc, testconsul.HTTPAddr, logger) 93 req := &interfaces.TaskPrestartRequest{ 94 Task: sidecarTask, 95 TaskDir: allocDir.NewTaskDir(sidecarTask.Name), 96 } 97 require.NoError(t, req.TaskDir.Build(false, nil)) 98 99 resp := &interfaces.TaskPrestartResponse{} 100 101 // Run the hook 102 require.NoError(t, h.Prestart(context.Background(), req, resp)) 103 104 // Assert it is Done 105 require.True(t, resp.Done) 106 107 require.NotNil(t, resp.Env) 108 require.Equal(t, "localhost:19001", resp.Env[envoyAdminBindEnvPrefix+"foo"]) 109 110 // Ensure the default path matches 111 env := map[string]string{ 112 taskenv.SecretsDir: req.TaskDir.SecretsDir, 113 } 114 f, err := os.Open(args.ReplaceEnv(structs.EnvoyBootstrapPath, env)) 115 require.NoError(t, err) 116 defer f.Close() 117 118 // Assert bootstrap configuration is valid json 119 var out map[string]interface{} 120 require.NoError(t, json.NewDecoder(f).Decode(&out)) 121 } 122 123 // TestTaskRunner_EnvoyBootstrapHook_Noop asserts that the Envoy bootstrap hook 124 // is a noop for non-Connect proxy sidecar tasks. 125 func TestTaskRunner_EnvoyBootstrapHook_Noop(t *testing.T) { 126 t.Parallel() 127 logger := testlog.HCLogger(t) 128 129 allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap") 130 defer cleanup() 131 132 alloc := mock.Alloc() 133 task := alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks[0] 134 135 // Run Envoy bootstrap Hook. Use invalid Consul address as it should 136 // not get hit. 137 h := newEnvoyBootstrapHook(alloc, "http://127.0.0.2:1", logger) 138 req := &interfaces.TaskPrestartRequest{ 139 Task: task, 140 TaskDir: allocDir.NewTaskDir(task.Name), 141 } 142 require.NoError(t, req.TaskDir.Build(false, nil)) 143 144 resp := &interfaces.TaskPrestartResponse{} 145 146 // Run the hook 147 require.NoError(t, h.Prestart(context.Background(), req, resp)) 148 149 // Assert it is Done 150 require.True(t, resp.Done) 151 152 // Assert no file was written 153 _, err := os.Open(filepath.Join(req.TaskDir.SecretsDir, "envoy_bootstrap.json")) 154 require.Error(t, err) 155 require.True(t, os.IsNotExist(err)) 156 } 157 158 // TestTaskRunner_EnvoyBootstrapHook_RecoverableError asserts the Envoy 159 // bootstrap hook returns a Recoverable error if the bootstrap command runs but 160 // fails. 161 func TestTaskRunner_EnvoyBootstrapHook_RecoverableError(t *testing.T) { 162 t.Parallel() 163 testutil.RequireConsul(t) 164 165 testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) { 166 // If -v wasn't specified squelch consul logging 167 if !testing.Verbose() { 168 c.Stdout = ioutil.Discard 169 c.Stderr = ioutil.Discard 170 } 171 }) 172 if err != nil { 173 t.Fatalf("error starting test consul server: %v", err) 174 } 175 defer testconsul.Stop() 176 177 alloc := mock.Alloc() 178 alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{ 179 { 180 Mode: "bridge", 181 IP: "10.0.0.1", 182 DynamicPorts: []structs.Port{ 183 { 184 Label: "connect-proxy-foo", 185 Value: 9999, 186 To: 9999, 187 }, 188 }, 189 }, 190 } 191 tg := alloc.Job.TaskGroups[0] 192 tg.Services = []*structs.Service{ 193 { 194 Name: "foo", 195 PortLabel: "9999", // Just need a valid port, nothing will bind to it 196 Connect: &structs.ConsulConnect{ 197 SidecarService: &structs.ConsulSidecarService{}, 198 }, 199 }, 200 } 201 sidecarTask := &structs.Task{ 202 Name: "sidecar", 203 Kind: "connect-proxy:foo", 204 } 205 tg.Tasks = append(tg.Tasks, sidecarTask) 206 207 logger := testlog.HCLogger(t) 208 209 allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap") 210 defer cleanup() 211 212 // Unlike the successful test above, do NOT register the group services 213 // yet. This should cause a recoverable error similar to if Consul was 214 // not running. 215 216 // Run Connect bootstrap Hook 217 h := newEnvoyBootstrapHook(alloc, testconsul.HTTPAddr, logger) 218 req := &interfaces.TaskPrestartRequest{ 219 Task: sidecarTask, 220 TaskDir: allocDir.NewTaskDir(sidecarTask.Name), 221 } 222 require.NoError(t, req.TaskDir.Build(false, nil)) 223 224 resp := &interfaces.TaskPrestartResponse{} 225 226 // Run the hook 227 err = h.Prestart(context.Background(), req, resp) 228 require.Error(t, err) 229 require.True(t, structs.IsRecoverable(err)) 230 231 // Assert it is not Done 232 require.False(t, resp.Done) 233 234 // Assert no file was written 235 _, err = os.Open(filepath.Join(req.TaskDir.SecretsDir, "envoy_bootstrap.json")) 236 require.Error(t, err) 237 require.True(t, os.IsNotExist(err)) 238 }