github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/consul/int_test.go (about) 1 package consul_test 2 3 import ( 4 "context" 5 "io/ioutil" 6 "testing" 7 "time" 8 9 consulapi "github.com/hashicorp/consul/api" 10 "github.com/hashicorp/consul/sdk/testutil" 11 log "github.com/hashicorp/go-hclog" 12 "github.com/hashicorp/nomad/ci" 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/devicemanager" 17 "github.com/hashicorp/nomad/client/pluginmanager/drivermanager" 18 regMock "github.com/hashicorp/nomad/client/serviceregistration/mock" 19 "github.com/hashicorp/nomad/client/serviceregistration/wrapper" 20 "github.com/hashicorp/nomad/client/state" 21 "github.com/hashicorp/nomad/client/vaultclient" 22 "github.com/hashicorp/nomad/command/agent/consul" 23 "github.com/hashicorp/nomad/helper/testlog" 24 "github.com/hashicorp/nomad/nomad/mock" 25 "github.com/hashicorp/nomad/nomad/structs" 26 "github.com/stretchr/testify/require" 27 ) 28 29 type mockUpdater struct { 30 logger log.Logger 31 } 32 33 func (m *mockUpdater) TaskStateUpdated() { 34 m.logger.Named("mock.updater").Debug("Update!") 35 } 36 37 // TestConsul_Integration asserts TaskRunner properly registers and deregisters 38 // services and checks with Consul using an embedded Consul agent. 39 func TestConsul_Integration(t *testing.T) { 40 ci.Parallel(t) 41 42 if testing.Short() { 43 t.Skip("-short set; skipping") 44 } 45 r := require.New(t) 46 47 // Create an embedded Consul server 48 testconsul, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { 49 c.Peering = nil // fix for older versions of Consul (<1.13.0) that don't support peering 50 // If -v wasn't specified squelch consul logging 51 if !testing.Verbose() { 52 c.Stdout = ioutil.Discard 53 c.Stderr = ioutil.Discard 54 } 55 }) 56 if err != nil { 57 t.Fatalf("error starting test consul server: %v", err) 58 } 59 defer testconsul.Stop() 60 61 conf := config.DefaultConfig() 62 conf.Node = mock.Node() 63 conf.ConsulConfig.Addr = testconsul.HTTPAddr 64 consulConfig, err := conf.ConsulConfig.ApiConfig() 65 if err != nil { 66 t.Fatalf("error generating consul config: %v", err) 67 } 68 69 conf.StateDir = t.TempDir() 70 conf.AllocDir = t.TempDir() 71 72 alloc := mock.Alloc() 73 task := alloc.Job.TaskGroups[0].Tasks[0] 74 task.Driver = "mock_driver" 75 task.Config = map[string]interface{}{ 76 "run_for": "1h", 77 } 78 79 // Choose a port that shouldn't be in use 80 netResource := &structs.NetworkResource{ 81 Device: "eth0", 82 IP: "127.0.0.1", 83 MBits: 50, 84 ReservedPorts: []structs.Port{{Label: "http", Value: 3}}, 85 } 86 alloc.AllocatedResources.Tasks["web"].Networks[0] = netResource 87 88 task.Services = []*structs.Service{ 89 { 90 Name: "httpd", 91 PortLabel: "http", 92 Tags: []string{"nomad", "test", "http"}, 93 Provider: structs.ServiceProviderConsul, 94 Checks: []*structs.ServiceCheck{ 95 { 96 Name: "httpd-http-check", 97 Type: "http", 98 Path: "/", 99 Protocol: "http", 100 Interval: 9000 * time.Hour, 101 Timeout: 1, // fail as fast as possible 102 }, 103 { 104 Name: "httpd-script-check", 105 Type: "script", 106 Command: "/bin/true", 107 Interval: 10 * time.Second, 108 Timeout: 10 * time.Second, 109 }, 110 }, 111 }, 112 { 113 Name: "httpd2", 114 PortLabel: "http", 115 Provider: structs.ServiceProviderConsul, 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.HCLogger(t) 126 logUpdate := &mockUpdater{logger} 127 allocDir := allocdir.NewAllocDir(logger, conf.AllocDir, alloc.ID) 128 if err := allocDir.Build(); err != nil { 129 t.Fatalf("error building alloc dir: %v", err) 130 } 131 t.Cleanup(func() { 132 r.NoError(allocDir.Destroy()) 133 }) 134 taskDir := allocDir.NewTaskDir(task.Name) 135 vclient := vaultclient.NewMockVaultClient() 136 consulClient, err := consulapi.NewClient(consulConfig) 137 r.Nil(err) 138 139 namespacesClient := consul.NewNamespacesClient(consulClient.Namespaces(), consulClient.Agent()) 140 serviceClient := consul.NewServiceClient(consulClient.Agent(), namespacesClient, testlog.HCLogger(t), true) 141 defer serviceClient.Shutdown() // just-in-case cleanup 142 consulRan := make(chan struct{}) 143 go func() { 144 serviceClient.Run() 145 close(consulRan) 146 }() 147 148 // Create a closed channel to mock TaskCoordinator.startConditionForTask. 149 // Closed channel indicates this task is not blocked on prestart hooks. 150 closedCh := make(chan struct{}) 151 close(closedCh) 152 153 // Build the config 154 config := &taskrunner.Config{ 155 Alloc: alloc, 156 ClientConfig: conf, 157 Consul: serviceClient, 158 Task: task, 159 TaskDir: taskDir, 160 Logger: logger, 161 Vault: vclient, 162 StateDB: state.NoopDB{}, 163 StateUpdater: logUpdate, 164 DeviceManager: devicemanager.NoopMockManager(), 165 DriverManager: drivermanager.TestDriverManager(t), 166 StartConditionMetCh: closedCh, 167 ServiceRegWrapper: wrapper.NewHandlerWrapper(logger, serviceClient, regMock.NewServiceRegistrationHandler(logger)), 168 } 169 170 tr, err := taskrunner.NewTaskRunner(config) 171 r.NoError(err) 172 go tr.Run() 173 defer func() { 174 // Make sure we always shutdown task runner when the test exits 175 select { 176 case <-tr.WaitCh(): 177 // Exited cleanly, no need to kill 178 default: 179 tr.Kill(context.Background(), &structs.TaskEvent{}) // just in case 180 } 181 }() 182 183 // Block waiting for the service to appear 184 catalog := consulClient.Catalog() 185 res, meta, err := catalog.Service("httpd2", "test", nil) 186 r.Nil(err) 187 188 for i := 0; len(res) == 0 && i < 10; i++ { 189 //Expected initial request to fail, do a blocking query 190 res, meta, err = catalog.Service("httpd2", "test", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 191 if err != nil { 192 t.Fatalf("error querying for service: %v", err) 193 } 194 } 195 r.Len(res, 1) 196 197 // Truncate results 198 res = res[:] 199 200 // Assert the service with the checks exists 201 for i := 0; len(res) == 0 && i < 10; i++ { 202 res, meta, err = catalog.Service("httpd", "http", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second}) 203 r.Nil(err) 204 } 205 r.Len(res, 1) 206 207 // Assert the script check passes (mock_driver script checks always 208 // pass) after having time to run once 209 time.Sleep(2 * time.Second) 210 checks, _, err := consulClient.Health().Checks("httpd", nil) 211 r.Nil(err) 212 r.Len(checks, 2) 213 214 for _, check := range checks { 215 if expected := "httpd"; check.ServiceName != expected { 216 t.Fatalf("expected checks to be for %q but found service name = %q", expected, check.ServiceName) 217 } 218 switch check.Name { 219 case "httpd-http-check": 220 // Port check should fail 221 if expected := consulapi.HealthCritical; check.Status != expected { 222 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 223 } 224 case "httpd-script-check": 225 // mock_driver script checks always succeed 226 if expected := consulapi.HealthPassing; check.Status != expected { 227 t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status) 228 } 229 default: 230 t.Errorf("unexpected check %q with status %q", check.Name, check.Status) 231 } 232 } 233 234 // Assert the service client returns all the checks for the allocation. 235 reg, err := serviceClient.AllocRegistrations(alloc.ID) 236 if err != nil { 237 t.Fatalf("unexpected error retrieving allocation checks: %v", err) 238 } 239 if reg == nil { 240 t.Fatalf("Unexpected nil allocation registration") 241 } 242 if snum := reg.NumServices(); snum != 2 { 243 t.Fatalf("Unexpected number of services registered. Got %d; want 2", snum) 244 } 245 if cnum := reg.NumChecks(); cnum != 2 { 246 t.Fatalf("Unexpected number of checks registered. Got %d; want 2", cnum) 247 } 248 249 logger.Debug("killing task") 250 251 // Kill the task 252 tr.Kill(context.Background(), &structs.TaskEvent{}) 253 254 select { 255 case <-tr.WaitCh(): 256 case <-time.After(10 * time.Second): 257 t.Fatalf("timed out waiting for Run() to exit") 258 } 259 260 // Shutdown Consul ServiceClient to ensure all pending operations complete 261 if err := serviceClient.Shutdown(); err != nil { 262 t.Errorf("error shutting down Consul ServiceClient: %v", err) 263 } 264 265 // Ensure Consul is clean 266 services, _, err := catalog.Services(nil) 267 r.Nil(err) 268 r.Len(services, 1) 269 r.Contains(services, "consul") 270 }