github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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/driver" 18 "github.com/hashicorp/nomad/client/vaultclient" 19 "github.com/hashicorp/nomad/command/agent/consul" 20 "github.com/hashicorp/nomad/nomad/mock" 21 "github.com/hashicorp/nomad/nomad/structs" 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 _, ok := driver.BuiltinDrivers["mock_driver"]; !ok { 35 t.Skip(`test requires mock_driver; run with "-tags nomad_test"`) 36 } 37 if testing.Short() { 38 t.Skip("-short set; skipping") 39 } 40 // Create an embedded Consul server 41 testconsul, err := testutil.NewTestServerConfig(func(c *testutil.TestServerConfig) { 42 // If -v wasn't specified squelch consul logging 43 if !testing.Verbose() { 44 c.Stdout = ioutil.Discard 45 c.Stderr = ioutil.Discard 46 } 47 }) 48 if err != nil { 49 t.Fatalf("error starting test consul server: %v", err) 50 } 51 defer testconsul.Stop() 52 53 conf := config.DefaultConfig() 54 conf.Node = mock.Node() 55 conf.ConsulConfig.Addr = testconsul.HTTPAddr 56 consulConfig, err := conf.ConsulConfig.ApiConfig() 57 if err != nil { 58 t.Fatalf("error generating consul config: %v", err) 59 } 60 61 conf.StateDir, err = ioutil.TempDir("", "nomadtest-consulstate") 62 if err != nil { 63 t.Fatalf("error creating temp dir: %v", err) 64 } 65 defer os.RemoveAll(conf.StateDir) 66 conf.AllocDir, err = ioutil.TempDir("", "nomdtest-consulalloc") 67 if err != nil { 68 t.Fatalf("error creating temp dir: %v", err) 69 } 70 defer os.RemoveAll(conf.AllocDir) 71 72 tmp, err := ioutil.TempFile("", "state-db") 73 if err != nil { 74 t.Fatalf("error creating state db file: %v", err) 75 } 76 db, err := bolt.Open(tmp.Name(), 0600, nil) 77 if err != nil { 78 t.Fatalf("error creating state db: %v", err) 79 } 80 81 alloc := mock.Alloc() 82 task := alloc.Job.TaskGroups[0].Tasks[0] 83 task.Driver = "mock_driver" 84 task.Config = map[string]interface{}{ 85 "run_for": "1h", 86 } 87 // Choose a port that shouldn't be in use 88 task.Resources.Networks[0].ReservedPorts = []structs.Port{{Label: "http", Value: 3}} 89 task.Services = []*structs.Service{ 90 { 91 Name: "httpd", 92 PortLabel: "http", 93 Tags: []string{"nomad", "test", "http"}, 94 Checks: []*structs.ServiceCheck{ 95 { 96 Name: "httpd-http-check", 97 Type: "http", 98 Path: "/", 99 Protocol: "http", 100 PortLabel: "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{"test", "http2"}, 117 }, 118 } 119 120 logger := testLogger() 121 logUpdate := func(name, state string, event *structs.TaskEvent) { 122 logger.Printf("[TEST] test.updater: name=%q state=%q event=%v", name, state, event) 123 } 124 allocDir := allocdir.NewAllocDir(logger, filepath.Join(conf.AllocDir, alloc.ID)) 125 if err := allocDir.Build(); err != nil { 126 t.Fatalf("error building alloc dir: %v", err) 127 } 128 taskDir := allocDir.NewTaskDir(task.Name) 129 vclient := vaultclient.NewMockVaultClient() 130 consulClient, err := consulapi.NewClient(consulConfig) 131 if err != nil { 132 t.Fatalf("error creating consul client: %v", err) 133 } 134 serviceClient := consul.NewServiceClient(consulClient.Agent(), true, logger) 135 defer serviceClient.Shutdown() // just-in-case cleanup 136 consulRan := make(chan struct{}) 137 go func() { 138 serviceClient.Run() 139 close(consulRan) 140 }() 141 tr := client.NewTaskRunner(logger, conf, db, logUpdate, taskDir, alloc, task, vclient, serviceClient) 142 tr.MarkReceived() 143 go tr.Run() 144 defer func() { 145 // Make sure we always shutdown task runner when the test exits 146 select { 147 case <-tr.WaitCh(): 148 // Exited cleanly, no need to kill 149 default: 150 tr.Kill("", "", true) // just in case 151 } 152 }() 153 154 // Block waiting for the service to appear 155 catalog := consulClient.Catalog() 156 res, meta, err := catalog.Service("httpd2", "test", nil) 157 for i := 0; len(res) == 0 && i < 10; i++ { 158 //Expected initial request to fail, do a blocking query 159 res, meta, err = catalog.Service("httpd2", "test", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 160 if err != nil { 161 t.Fatalf("error querying for service: %v", err) 162 } 163 } 164 if len(res) != 1 { 165 t.Fatalf("expected 1 service but found %d:\n%#v", len(res), res) 166 } 167 res = res[:] 168 169 // Assert the service with the checks exists 170 for i := 0; len(res) == 0 && i < 10; i++ { 171 res, meta, err = catalog.Service("httpd", "http", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 172 if err != nil { 173 t.Fatalf("error querying for service: %v", err) 174 } 175 } 176 if len(res) != 1 { 177 t.Fatalf("exepcted 1 service but found %d:\n%#v", len(res), res) 178 } 179 180 // Assert the script check passes (mock_driver script checks always 181 // pass) after having time to run once 182 time.Sleep(2 * time.Second) 183 checks, _, err := consulClient.Health().Checks("httpd", nil) 184 if err != nil { 185 t.Fatalf("error querying checks: %v", err) 186 } 187 if expected := 2; len(checks) != expected { 188 t.Fatalf("expected %d checks but found %d:\n%#v", expected, len(checks), checks) 189 } 190 for _, check := range checks { 191 if expected := "httpd"; check.ServiceName != expected { 192 t.Fatalf("expected checks to be for %q but found service name = %q", expected, check.ServiceName) 193 } 194 switch check.Name { 195 case "httpd-http-check": 196 // Port check should fail 197 if expected := consulapi.HealthCritical; check.Status != expected { 198 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 199 } 200 case "httpd-script-check": 201 // mock_driver script checks always succeed 202 if expected := consulapi.HealthPassing; check.Status != expected { 203 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 204 } 205 default: 206 t.Errorf("unexpected check %q with status %q", check.Name, check.Status) 207 } 208 } 209 210 // Assert the service client returns all the checks for the allocation. 211 checksOut, err := serviceClient.Checks(alloc) 212 if err != nil { 213 t.Fatalf("unexpected error retrieving allocation checks: %v", err) 214 } 215 216 if l := len(checksOut); l != 2 { 217 t.Fatalf("got %d checks; want %d", l, 2) 218 } 219 220 logger.Printf("[TEST] consul.test: killing task") 221 222 // Kill the task 223 tr.Kill("", "", false) 224 225 select { 226 case <-tr.WaitCh(): 227 case <-time.After(10 * time.Second): 228 t.Fatalf("timed out waiting for Run() to exit") 229 } 230 231 // Shutdown Consul ServiceClient to ensure all pending operations complete 232 if err := serviceClient.Shutdown(); err != nil { 233 t.Errorf("error shutting down Consul ServiceClient: %v", err) 234 } 235 236 // Ensure Consul is clean 237 services, _, err := catalog.Services(nil) 238 if err != nil { 239 t.Fatalf("error query services: %v", err) 240 } 241 if len(services) != 1 { 242 t.Fatalf("expected only 1 service in Consul but found %d:\n%#v", len(services), services) 243 } 244 if _, ok := services["consul"]; !ok { 245 t.Fatalf(`expected only the "consul" key in Consul but found: %#v`, services) 246 } 247 }