github.com/superfly/nomad@v0.10.5-fly/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/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 // Build the config 147 config := &taskrunner.Config{ 148 Alloc: alloc, 149 ClientConfig: conf, 150 Consul: serviceClient, 151 Task: task, 152 TaskDir: taskDir, 153 Logger: logger, 154 Vault: vclient, 155 StateDB: state.NoopDB{}, 156 StateUpdater: logUpdate, 157 DeviceManager: devicemanager.NoopMockManager(), 158 DriverManager: drivermanager.TestDriverManager(t), 159 } 160 161 tr, err := taskrunner.NewTaskRunner(config) 162 require.NoError(err) 163 go tr.Run() 164 defer func() { 165 // Make sure we always shutdown task runner when the test exits 166 select { 167 case <-tr.WaitCh(): 168 // Exited cleanly, no need to kill 169 default: 170 tr.Kill(context.Background(), &structs.TaskEvent{}) // just in case 171 } 172 }() 173 174 // Block waiting for the service to appear 175 catalog := consulClient.Catalog() 176 res, meta, err := catalog.Service("httpd2", "test", nil) 177 require.Nil(err) 178 179 for i := 0; len(res) == 0 && i < 10; i++ { 180 //Expected initial request to fail, do a blocking query 181 res, meta, err = catalog.Service("httpd2", "test", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 182 if err != nil { 183 t.Fatalf("error querying for service: %v", err) 184 } 185 } 186 require.Len(res, 1) 187 188 // Truncate results 189 res = res[:] 190 191 // Assert the service with the checks exists 192 for i := 0; len(res) == 0 && i < 10; i++ { 193 res, meta, err = catalog.Service("httpd", "http", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 194 require.Nil(err) 195 } 196 require.Len(res, 1) 197 198 // Assert the script check passes (mock_driver script checks always 199 // pass) after having time to run once 200 time.Sleep(2 * time.Second) 201 checks, _, err := consulClient.Health().Checks("httpd", nil) 202 require.Nil(err) 203 require.Len(checks, 2) 204 205 for _, check := range checks { 206 if expected := "httpd"; check.ServiceName != expected { 207 t.Fatalf("expected checks to be for %q but found service name = %q", expected, check.ServiceName) 208 } 209 switch check.Name { 210 case "httpd-http-check": 211 // Port check should fail 212 if expected := consulapi.HealthCritical; check.Status != expected { 213 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 214 } 215 case "httpd-script-check": 216 // mock_driver script checks always succeed 217 if expected := consulapi.HealthPassing; check.Status != expected { 218 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 219 } 220 default: 221 t.Errorf("unexpected check %q with status %q", check.Name, check.Status) 222 } 223 } 224 225 // Assert the service client returns all the checks for the allocation. 226 reg, err := serviceClient.AllocRegistrations(alloc.ID) 227 if err != nil { 228 t.Fatalf("unexpected error retrieving allocation checks: %v", err) 229 } 230 if reg == nil { 231 t.Fatalf("Unexpected nil allocation registration") 232 } 233 if snum := reg.NumServices(); snum != 2 { 234 t.Fatalf("Unexpected number of services registered. Got %d; want 2", snum) 235 } 236 if cnum := reg.NumChecks(); cnum != 2 { 237 t.Fatalf("Unexpected number of checks registered. Got %d; want 2", cnum) 238 } 239 240 logger.Debug("killing task") 241 242 // Kill the task 243 tr.Kill(context.Background(), &structs.TaskEvent{}) 244 245 select { 246 case <-tr.WaitCh(): 247 case <-time.After(10 * time.Second): 248 t.Fatalf("timed out waiting for Run() to exit") 249 } 250 251 // Shutdown Consul ServiceClient to ensure all pending operations complete 252 if err := serviceClient.Shutdown(); err != nil { 253 t.Errorf("error shutting down Consul ServiceClient: %v", err) 254 } 255 256 // Ensure Consul is clean 257 services, _, err := catalog.Services(nil) 258 require.Nil(err) 259 require.Len(services, 1) 260 require.Contains(services, "consul") 261 }