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