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