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