github.com/ilhicas/nomad@v0.11.8/command/agent/consul/int_test.go (about) 1 package consul_test 2 3 import ( 4 "context" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 consulapi "github.com/hashicorp/consul/api" 12 "github.com/hashicorp/consul/sdk/testutil" 13 log "github.com/hashicorp/go-hclog" 14 "github.com/hashicorp/nomad/client/allocdir" 15 "github.com/hashicorp/nomad/client/allocrunner/taskrunner" 16 "github.com/hashicorp/nomad/client/config" 17 "github.com/hashicorp/nomad/client/devicemanager" 18 "github.com/hashicorp/nomad/client/pluginmanager/drivermanager" 19 "github.com/hashicorp/nomad/client/state" 20 "github.com/hashicorp/nomad/client/vaultclient" 21 "github.com/hashicorp/nomad/command/agent/consul" 22 "github.com/hashicorp/nomad/helper/testlog" 23 "github.com/hashicorp/nomad/nomad/mock" 24 "github.com/hashicorp/nomad/nomad/structs" 25 "github.com/stretchr/testify/require" 26 ) 27 28 type mockUpdater struct { 29 logger log.Logger 30 } 31 32 func (m *mockUpdater) TaskStateUpdated() { 33 m.logger.Named("mock.updater").Debug("Update!") 34 } 35 36 // TestConsul_Integration asserts TaskRunner properly registers and deregisters 37 // services and checks with Consul using an embedded Consul agent. 38 func TestConsul_Integration(t *testing.T) { 39 if testing.Short() { 40 t.Skip("-short set; skipping") 41 } 42 require := require.New(t) 43 44 // Create an embedded Consul server 45 testconsul, err := testutil.NewTestServerConfig(func(c *testutil.TestServerConfig) { 46 // If -v wasn't specified squelch consul logging 47 if !testing.Verbose() { 48 c.Stdout = ioutil.Discard 49 c.Stderr = ioutil.Discard 50 } 51 }) 52 if err != nil { 53 t.Fatalf("error starting test consul server: %v", err) 54 } 55 defer testconsul.Stop() 56 57 conf := config.DefaultConfig() 58 conf.Node = mock.Node() 59 conf.ConsulConfig.Addr = testconsul.HTTPAddr 60 consulConfig, err := conf.ConsulConfig.ApiConfig() 61 if err != nil { 62 t.Fatalf("error generating consul config: %v", err) 63 } 64 65 conf.StateDir, err = ioutil.TempDir("", "nomadtest-consulstate") 66 if err != nil { 67 t.Fatalf("error creating temp dir: %v", err) 68 } 69 defer os.RemoveAll(conf.StateDir) 70 conf.AllocDir, err = ioutil.TempDir("", "nomdtest-consulalloc") 71 if err != nil { 72 t.Fatalf("error creating temp dir: %v", err) 73 } 74 defer os.RemoveAll(conf.AllocDir) 75 76 alloc := mock.Alloc() 77 task := alloc.Job.TaskGroups[0].Tasks[0] 78 task.Driver = "mock_driver" 79 task.Config = map[string]interface{}{ 80 "run_for": "1h", 81 } 82 83 // Choose a port that shouldn't be in use 84 netResource := &structs.NetworkResource{ 85 Device: "eth0", 86 IP: "127.0.0.1", 87 MBits: 50, 88 ReservedPorts: []structs.Port{{Label: "http", Value: 3}}, 89 } 90 alloc.AllocatedResources.Tasks["web"].Networks[0] = netResource 91 92 task.Services = []*structs.Service{ 93 { 94 Name: "httpd", 95 PortLabel: "http", 96 Tags: []string{"nomad", "test", "http"}, 97 Checks: []*structs.ServiceCheck{ 98 { 99 Name: "httpd-http-check", 100 Type: "http", 101 Path: "/", 102 Protocol: "http", 103 Interval: 9000 * time.Hour, 104 Timeout: 1, // fail as fast as possible 105 }, 106 { 107 Name: "httpd-script-check", 108 Type: "script", 109 Command: "/bin/true", 110 Interval: 10 * time.Second, 111 Timeout: 10 * time.Second, 112 }, 113 }, 114 }, 115 { 116 Name: "httpd2", 117 PortLabel: "http", 118 Tags: []string{ 119 "test", 120 // Use URL-unfriendly tags to test #3620 121 "public-test.ettaviation.com:80/ redirect=302,https://test.ettaviation.com", 122 "public-test.ettaviation.com:443/", 123 }, 124 }, 125 } 126 127 logger := testlog.HCLogger(t) 128 logUpdate := &mockUpdater{logger} 129 allocDir := allocdir.NewAllocDir(logger, filepath.Join(conf.AllocDir, alloc.ID)) 130 if err := allocDir.Build(); err != nil { 131 t.Fatalf("error building alloc dir: %v", err) 132 } 133 taskDir := allocDir.NewTaskDir(task.Name) 134 vclient := vaultclient.NewMockVaultClient() 135 consulClient, err := consulapi.NewClient(consulConfig) 136 require.Nil(err) 137 138 serviceClient := consul.NewServiceClient(consulClient.Agent(), testlog.HCLogger(t), true) 139 defer serviceClient.Shutdown() // just-in-case cleanup 140 consulRan := make(chan struct{}) 141 go func() { 142 serviceClient.Run() 143 close(consulRan) 144 }() 145 146 // Create a closed channel to mock TaskHookCoordinator.startConditionForTask. 147 // Closed channel indicates this task is not blocked on prestart hooks. 148 closedCh := make(chan struct{}) 149 close(closedCh) 150 151 // Build the config 152 config := &taskrunner.Config{ 153 Alloc: alloc, 154 ClientConfig: conf, 155 Consul: serviceClient, 156 Task: task, 157 TaskDir: taskDir, 158 Logger: logger, 159 Vault: vclient, 160 StateDB: state.NoopDB{}, 161 StateUpdater: logUpdate, 162 DeviceManager: devicemanager.NoopMockManager(), 163 DriverManager: drivermanager.TestDriverManager(t), 164 StartConditionMetCtx: closedCh, 165 } 166 167 tr, err := taskrunner.NewTaskRunner(config) 168 require.NoError(err) 169 go tr.Run() 170 defer func() { 171 // Make sure we always shutdown task runner when the test exits 172 select { 173 case <-tr.WaitCh(): 174 // Exited cleanly, no need to kill 175 default: 176 tr.Kill(context.Background(), &structs.TaskEvent{}) // just in case 177 } 178 }() 179 180 // Block waiting for the service to appear 181 catalog := consulClient.Catalog() 182 res, meta, err := catalog.Service("httpd2", "test", nil) 183 require.Nil(err) 184 185 for i := 0; len(res) == 0 && i < 10; i++ { 186 //Expected initial request to fail, do a blocking query 187 res, meta, err = catalog.Service("httpd2", "test", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 188 if err != nil { 189 t.Fatalf("error querying for service: %v", err) 190 } 191 } 192 require.Len(res, 1) 193 194 // Truncate results 195 res = res[:] 196 197 // Assert the service with the checks exists 198 for i := 0; len(res) == 0 && i < 10; i++ { 199 res, meta, err = catalog.Service("httpd", "http", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 200 require.Nil(err) 201 } 202 require.Len(res, 1) 203 204 // Assert the script check passes (mock_driver script checks always 205 // pass) after having time to run once 206 time.Sleep(2 * time.Second) 207 checks, _, err := consulClient.Health().Checks("httpd", nil) 208 require.Nil(err) 209 require.Len(checks, 2) 210 211 for _, check := range checks { 212 if expected := "httpd"; check.ServiceName != expected { 213 t.Fatalf("expected checks to be for %q but found service name = %q", expected, check.ServiceName) 214 } 215 switch check.Name { 216 case "httpd-http-check": 217 // Port check should fail 218 if expected := consulapi.HealthCritical; check.Status != expected { 219 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 220 } 221 case "httpd-script-check": 222 // mock_driver script checks always succeed 223 if expected := consulapi.HealthPassing; check.Status != expected { 224 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 225 } 226 default: 227 t.Errorf("unexpected check %q with status %q", check.Name, check.Status) 228 } 229 } 230 231 // Assert the service client returns all the checks for the allocation. 232 reg, err := serviceClient.AllocRegistrations(alloc.ID) 233 if err != nil { 234 t.Fatalf("unexpected error retrieving allocation checks: %v", err) 235 } 236 if reg == nil { 237 t.Fatalf("Unexpected nil allocation registration") 238 } 239 if snum := reg.NumServices(); snum != 2 { 240 t.Fatalf("Unexpected number of services registered. Got %d; want 2", snum) 241 } 242 if cnum := reg.NumChecks(); cnum != 2 { 243 t.Fatalf("Unexpected number of checks registered. Got %d; want 2", cnum) 244 } 245 246 logger.Debug("killing task") 247 248 // Kill the task 249 tr.Kill(context.Background(), &structs.TaskEvent{}) 250 251 select { 252 case <-tr.WaitCh(): 253 case <-time.After(10 * time.Second): 254 t.Fatalf("timed out waiting for Run() to exit") 255 } 256 257 // Shutdown Consul ServiceClient to ensure all pending operations complete 258 if err := serviceClient.Shutdown(); err != nil { 259 t.Errorf("error shutting down Consul ServiceClient: %v", err) 260 } 261 262 // Ensure Consul is clean 263 services, _, err := catalog.Services(nil) 264 require.Nil(err) 265 require.Len(services, 1) 266 require.Contains(services, "consul") 267 }