github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/command/agent/consul/unit_test.go (about) 1 package consul 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "reflect" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/api" 15 cstructs "github.com/hashicorp/nomad/client/structs" 16 "github.com/hashicorp/nomad/nomad/structs" 17 ) 18 19 const ( 20 // Ports used in testTask 21 xPort = 1234 22 yPort = 1235 23 ) 24 25 func testLogger() *log.Logger { 26 if testing.Verbose() { 27 return log.New(os.Stderr, "", log.LstdFlags) 28 } 29 return log.New(ioutil.Discard, "", 0) 30 } 31 32 func testTask() *structs.Task { 33 return &structs.Task{ 34 Name: "taskname", 35 Resources: &structs.Resources{ 36 Networks: []*structs.NetworkResource{ 37 { 38 DynamicPorts: []structs.Port{ 39 {Label: "x", Value: xPort}, 40 {Label: "y", Value: yPort}, 41 }, 42 }, 43 }, 44 }, 45 Services: []*structs.Service{ 46 { 47 Name: "taskname-service", 48 PortLabel: "x", 49 Tags: []string{"tag1", "tag2"}, 50 }, 51 }, 52 } 53 } 54 55 // testFakeCtx contains a fake Consul AgentAPI and implements the Exec 56 // interface to allow testing without running Consul. 57 type testFakeCtx struct { 58 ServiceClient *ServiceClient 59 FakeConsul *fakeConsul 60 Task *structs.Task 61 62 // Ticked whenever a script is called 63 execs chan int 64 65 // If non-nil will be called by script checks 66 ExecFunc func(ctx context.Context, cmd string, args []string) ([]byte, int, error) 67 } 68 69 // Exec implements the ScriptExecutor interface and will use an alternate 70 // implementation t.ExecFunc if non-nil. 71 func (t *testFakeCtx) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 72 select { 73 case t.execs <- 1: 74 default: 75 } 76 if t.ExecFunc == nil { 77 // Default impl is just "ok" 78 return []byte("ok"), 0, nil 79 } 80 return t.ExecFunc(ctx, cmd, args) 81 } 82 83 var errNoOps = fmt.Errorf("testing error: no pending operations") 84 85 // syncOps simulates one iteration of the ServiceClient.Run loop and returns 86 // any errors returned by sync() or errNoOps if no pending operations. 87 func (t *testFakeCtx) syncOnce() error { 88 select { 89 case ops := <-t.ServiceClient.opCh: 90 t.ServiceClient.merge(ops) 91 return t.ServiceClient.sync() 92 default: 93 return errNoOps 94 } 95 } 96 97 // setupFake creates a testFakeCtx with a ServiceClient backed by a fakeConsul. 98 // A test Task is also provided. 99 func setupFake() *testFakeCtx { 100 fc := newFakeConsul() 101 return &testFakeCtx{ 102 ServiceClient: NewServiceClient(fc, true, testLogger()), 103 FakeConsul: fc, 104 Task: testTask(), 105 execs: make(chan int, 100), 106 } 107 } 108 109 // fakeConsul is a fake in-memory Consul backend for ServiceClient. 110 type fakeConsul struct { 111 // maps of what services and checks have been registered 112 services map[string]*api.AgentServiceRegistration 113 checks map[string]*api.AgentCheckRegistration 114 mu sync.Mutex 115 116 // when UpdateTTL is called the check ID will have its counter inc'd 117 checkTTLs map[string]int 118 119 // What check status to return from Checks() 120 checkStatus string 121 } 122 123 func newFakeConsul() *fakeConsul { 124 return &fakeConsul{ 125 services: make(map[string]*api.AgentServiceRegistration), 126 checks: make(map[string]*api.AgentCheckRegistration), 127 checkTTLs: make(map[string]int), 128 checkStatus: api.HealthPassing, 129 } 130 } 131 132 func (c *fakeConsul) Services() (map[string]*api.AgentService, error) { 133 c.mu.Lock() 134 defer c.mu.Unlock() 135 136 r := make(map[string]*api.AgentService, len(c.services)) 137 for k, v := range c.services { 138 r[k] = &api.AgentService{ 139 ID: v.ID, 140 Service: v.Name, 141 Tags: make([]string, len(v.Tags)), 142 Port: v.Port, 143 Address: v.Address, 144 EnableTagOverride: v.EnableTagOverride, 145 } 146 copy(r[k].Tags, v.Tags) 147 } 148 return r, nil 149 } 150 151 func (c *fakeConsul) Checks() (map[string]*api.AgentCheck, error) { 152 c.mu.Lock() 153 defer c.mu.Unlock() 154 155 r := make(map[string]*api.AgentCheck, len(c.checks)) 156 for k, v := range c.checks { 157 r[k] = &api.AgentCheck{ 158 CheckID: v.ID, 159 Name: v.Name, 160 Status: c.checkStatus, 161 Notes: v.Notes, 162 ServiceID: v.ServiceID, 163 ServiceName: c.services[v.ServiceID].Name, 164 } 165 } 166 return r, nil 167 } 168 169 func (c *fakeConsul) CheckRegister(check *api.AgentCheckRegistration) error { 170 c.mu.Lock() 171 defer c.mu.Unlock() 172 c.checks[check.ID] = check 173 174 // Be nice and make checks reachable-by-service 175 scheck := check.AgentServiceCheck 176 c.services[check.ServiceID].Checks = append(c.services[check.ServiceID].Checks, &scheck) 177 return nil 178 } 179 180 func (c *fakeConsul) CheckDeregister(checkID string) error { 181 c.mu.Lock() 182 defer c.mu.Unlock() 183 delete(c.checks, checkID) 184 delete(c.checkTTLs, checkID) 185 return nil 186 } 187 188 func (c *fakeConsul) ServiceRegister(service *api.AgentServiceRegistration) error { 189 c.mu.Lock() 190 defer c.mu.Unlock() 191 c.services[service.ID] = service 192 return nil 193 } 194 195 func (c *fakeConsul) ServiceDeregister(serviceID string) error { 196 c.mu.Lock() 197 defer c.mu.Unlock() 198 delete(c.services, serviceID) 199 return nil 200 } 201 202 func (c *fakeConsul) UpdateTTL(id string, output string, status string) error { 203 c.mu.Lock() 204 defer c.mu.Unlock() 205 check, ok := c.checks[id] 206 if !ok { 207 return fmt.Errorf("unknown check id: %q", id) 208 } 209 // Flip initial status to passing 210 check.Status = "passing" 211 c.checkTTLs[id]++ 212 return nil 213 } 214 215 func TestConsul_ChangeTags(t *testing.T) { 216 ctx := setupFake() 217 218 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, nil, nil); err != nil { 219 t.Fatalf("unexpected error registering task: %v", err) 220 } 221 222 if err := ctx.syncOnce(); err != nil { 223 t.Fatalf("unexpected error syncing task: %v", err) 224 } 225 226 if n := len(ctx.FakeConsul.services); n != 1 { 227 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 228 } 229 230 origKey := "" 231 for k, v := range ctx.FakeConsul.services { 232 origKey = k 233 if v.Name != ctx.Task.Services[0].Name { 234 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 235 } 236 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 237 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 238 } 239 } 240 241 origTask := ctx.Task 242 ctx.Task = testTask() 243 ctx.Task.Services[0].Tags[0] = "newtag" 244 if err := ctx.ServiceClient.UpdateTask("allocid", origTask, ctx.Task, nil, nil); err != nil { 245 t.Fatalf("unexpected error registering task: %v", err) 246 } 247 if err := ctx.syncOnce(); err != nil { 248 t.Fatalf("unexpected error syncing task: %v", err) 249 } 250 251 if n := len(ctx.FakeConsul.services); n != 1 { 252 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 253 } 254 255 for k, v := range ctx.FakeConsul.services { 256 if k == origKey { 257 t.Errorf("expected key to change but found %q", k) 258 } 259 if v.Name != ctx.Task.Services[0].Name { 260 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 261 } 262 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 263 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 264 } 265 } 266 } 267 268 // TestConsul_ChangePorts asserts that changing the ports on a service updates 269 // it in Consul. Since ports are not part of the service ID this is a slightly 270 // different code path than changing tags. 271 func TestConsul_ChangePorts(t *testing.T) { 272 ctx := setupFake() 273 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 274 { 275 Name: "c1", 276 Type: "tcp", 277 Interval: time.Second, 278 Timeout: time.Second, 279 PortLabel: "x", 280 }, 281 { 282 Name: "c2", 283 Type: "script", 284 Interval: 9000 * time.Hour, 285 Timeout: time.Second, 286 }, 287 { 288 Name: "c3", 289 Type: "http", 290 Protocol: "http", 291 Path: "/", 292 Interval: time.Second, 293 Timeout: time.Second, 294 PortLabel: "y", 295 }, 296 } 297 298 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 299 t.Fatalf("unexpected error registering task: %v", err) 300 } 301 302 if err := ctx.syncOnce(); err != nil { 303 t.Fatalf("unexpected error syncing task: %v", err) 304 } 305 306 if n := len(ctx.FakeConsul.services); n != 1 { 307 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 308 } 309 310 origServiceKey := "" 311 for k, v := range ctx.FakeConsul.services { 312 origServiceKey = k 313 if v.Name != ctx.Task.Services[0].Name { 314 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 315 } 316 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 317 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 318 } 319 if v.Port != xPort { 320 t.Errorf("expected Port x=%v but found: %v", xPort, v.Port) 321 } 322 } 323 324 if n := len(ctx.FakeConsul.checks); n != 3 { 325 t.Fatalf("expected 3 checks but found %d:\n%#v", n, ctx.FakeConsul.checks) 326 } 327 328 origTCPKey := "" 329 origScriptKey := "" 330 origHTTPKey := "" 331 for k, v := range ctx.FakeConsul.checks { 332 switch v.Name { 333 case "c1": 334 origTCPKey = k 335 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 336 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 337 } 338 case "c2": 339 origScriptKey = k 340 select { 341 case <-ctx.execs: 342 if n := len(ctx.execs); n > 0 { 343 t.Errorf("expected 1 exec but found: %d", n+1) 344 } 345 case <-time.After(3 * time.Second): 346 t.Errorf("script not called in time") 347 } 348 case "c3": 349 origHTTPKey = k 350 if expected := fmt.Sprintf("http://:%d/", yPort); v.HTTP != expected { 351 t.Errorf("expected Port y=%v but found: %v", expected, v.HTTP) 352 } 353 default: 354 t.Fatalf("unexpected check: %q", v.Name) 355 } 356 } 357 358 // Now update the PortLabel on the Service and Check c3 359 origTask := ctx.Task 360 ctx.Task = testTask() 361 ctx.Task.Services[0].PortLabel = "y" 362 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 363 { 364 Name: "c1", 365 Type: "tcp", 366 Interval: time.Second, 367 Timeout: time.Second, 368 PortLabel: "x", 369 }, 370 { 371 Name: "c2", 372 Type: "script", 373 Interval: 9000 * time.Hour, 374 Timeout: time.Second, 375 }, 376 { 377 Name: "c3", 378 Type: "http", 379 Protocol: "http", 380 Path: "/", 381 Interval: time.Second, 382 Timeout: time.Second, 383 // Removed PortLabel; should default to service's (y) 384 }, 385 } 386 if err := ctx.ServiceClient.UpdateTask("allocid", origTask, ctx.Task, ctx, nil); err != nil { 387 t.Fatalf("unexpected error registering task: %v", err) 388 } 389 if err := ctx.syncOnce(); err != nil { 390 t.Fatalf("unexpected error syncing task: %v", err) 391 } 392 393 if n := len(ctx.FakeConsul.services); n != 1 { 394 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 395 } 396 397 for k, v := range ctx.FakeConsul.services { 398 if k != origServiceKey { 399 t.Errorf("unexpected key change; was: %q -- but found %q", origServiceKey, k) 400 } 401 if v.Name != ctx.Task.Services[0].Name { 402 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 403 } 404 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 405 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 406 } 407 if v.Port != yPort { 408 t.Errorf("expected Port y=%v but found: %v", yPort, v.Port) 409 } 410 } 411 412 if n := len(ctx.FakeConsul.checks); n != 3 { 413 t.Fatalf("expected 3 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 414 } 415 416 for k, v := range ctx.FakeConsul.checks { 417 switch v.Name { 418 case "c1": 419 if k != origTCPKey { 420 t.Errorf("unexpected key change for %s from %q to %q", v.Name, origTCPKey, k) 421 } 422 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 423 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 424 } 425 case "c2": 426 if k != origScriptKey { 427 t.Errorf("unexpected key change for %s from %q to %q", v.Name, origScriptKey, k) 428 } 429 select { 430 case <-ctx.execs: 431 if n := len(ctx.execs); n > 0 { 432 t.Errorf("expected 1 exec but found: %d", n+1) 433 } 434 case <-time.After(3 * time.Second): 435 t.Errorf("script not called in time") 436 } 437 case "c3": 438 if k == origHTTPKey { 439 t.Errorf("expected %s key to change from %q", v.Name, k) 440 } 441 if expected := fmt.Sprintf("http://:%d/", yPort); v.HTTP != expected { 442 t.Errorf("expected Port y=%v but found: %v", expected, v.HTTP) 443 } 444 default: 445 t.Errorf("Unkown check: %q", k) 446 } 447 } 448 } 449 450 // TestConsul_ChangeChecks asserts that updating only the checks on a service 451 // properly syncs with Consul. 452 func TestConsul_ChangeChecks(t *testing.T) { 453 ctx := setupFake() 454 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 455 { 456 Name: "c1", 457 Type: "tcp", 458 Interval: time.Second, 459 Timeout: time.Second, 460 PortLabel: "x", 461 }, 462 } 463 464 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 465 t.Fatalf("unexpected error registering task: %v", err) 466 } 467 468 if err := ctx.syncOnce(); err != nil { 469 t.Fatalf("unexpected error syncing task: %v", err) 470 } 471 472 if n := len(ctx.FakeConsul.services); n != 1 { 473 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 474 } 475 476 origServiceKey := "" 477 for k, v := range ctx.FakeConsul.services { 478 origServiceKey = k 479 if v.Name != ctx.Task.Services[0].Name { 480 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 481 } 482 if v.Port != xPort { 483 t.Errorf("expected Port x=%v but found: %v", xPort, v.Port) 484 } 485 } 486 487 if n := len(ctx.FakeConsul.checks); n != 1 { 488 t.Fatalf("expected 1 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 489 } 490 for _, v := range ctx.FakeConsul.checks { 491 if v.Name != "c1" { 492 t.Fatalf("expected check c1 but found %q", v.Name) 493 } 494 } 495 496 // Now add a check 497 origTask := ctx.Task.Copy() 498 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 499 { 500 Name: "c1", 501 Type: "tcp", 502 Interval: time.Second, 503 Timeout: time.Second, 504 PortLabel: "x", 505 }, 506 { 507 Name: "c2", 508 Type: "http", 509 Path: "/", 510 Interval: time.Second, 511 Timeout: time.Second, 512 PortLabel: "x", 513 }, 514 } 515 if err := ctx.ServiceClient.UpdateTask("allocid", origTask, ctx.Task, ctx, nil); err != nil { 516 t.Fatalf("unexpected error registering task: %v", err) 517 } 518 if err := ctx.syncOnce(); err != nil { 519 t.Fatalf("unexpected error syncing task: %v", err) 520 } 521 522 if n := len(ctx.FakeConsul.services); n != 1 { 523 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 524 } 525 526 if _, ok := ctx.FakeConsul.services[origServiceKey]; !ok { 527 t.Errorf("unexpected key change; was: %q -- but found %#v", origServiceKey, ctx.FakeConsul.services) 528 } 529 530 if n := len(ctx.FakeConsul.checks); n != 2 { 531 t.Fatalf("expected 2 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 532 } 533 534 for k, v := range ctx.FakeConsul.checks { 535 switch v.Name { 536 case "c1": 537 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 538 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 539 } 540 case "c2": 541 if expected := fmt.Sprintf("http://:%d/", xPort); v.HTTP != expected { 542 t.Errorf("expected Port x=%v but found: %v", expected, v.HTTP) 543 } 544 default: 545 t.Errorf("Unkown check: %q", k) 546 } 547 } 548 } 549 550 // TestConsul_RegServices tests basic service registration. 551 func TestConsul_RegServices(t *testing.T) { 552 ctx := setupFake() 553 554 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, nil, nil); err != nil { 555 t.Fatalf("unexpected error registering task: %v", err) 556 } 557 558 if err := ctx.syncOnce(); err != nil { 559 t.Fatalf("unexpected error syncing task: %v", err) 560 } 561 562 if n := len(ctx.FakeConsul.services); n != 1 { 563 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 564 } 565 for _, v := range ctx.FakeConsul.services { 566 if v.Name != ctx.Task.Services[0].Name { 567 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 568 } 569 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 570 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 571 } 572 if v.Port != xPort { 573 t.Errorf("expected Port=%d != %d", xPort, v.Port) 574 } 575 } 576 577 // Make a change which will register a new service 578 ctx.Task.Services[0].Name = "taskname-service2" 579 ctx.Task.Services[0].Tags[0] = "tag3" 580 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, nil, nil); err != nil { 581 t.Fatalf("unpexpected error registering task: %v", err) 582 } 583 584 // Make sure changes don't take affect until sync() is called (since 585 // Run() isn't running) 586 if n := len(ctx.FakeConsul.services); n != 1 { 587 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 588 } 589 for _, v := range ctx.FakeConsul.services { 590 if reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 591 t.Errorf("expected Tags to differ, changes applied before sync()") 592 } 593 } 594 595 // Now sync() and re-check for the applied updates 596 if err := ctx.syncOnce(); err != nil { 597 t.Fatalf("unexpected error syncing task: %v", err) 598 } 599 if n := len(ctx.FakeConsul.services); n != 2 { 600 t.Fatalf("expected 2 services but found %d:\n%#v", n, ctx.FakeConsul.services) 601 } 602 found := false 603 for _, v := range ctx.FakeConsul.services { 604 if v.Name == ctx.Task.Services[0].Name { 605 if found { 606 t.Fatalf("found new service name %q twice", v.Name) 607 } 608 found = true 609 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 610 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 611 } 612 } 613 } 614 if !found { 615 t.Fatalf("did not find new service %q", ctx.Task.Services[0].Name) 616 } 617 618 // Remove the new task 619 ctx.ServiceClient.RemoveTask("allocid", ctx.Task) 620 if err := ctx.syncOnce(); err != nil { 621 t.Fatalf("unexpected error syncing task: %v", err) 622 } 623 if n := len(ctx.FakeConsul.services); n != 1 { 624 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 625 } 626 for _, v := range ctx.FakeConsul.services { 627 if v.Name != "taskname-service" { 628 t.Errorf("expected original task to survive not %q", v.Name) 629 } 630 } 631 } 632 633 // TestConsul_ShutdownOK tests the ok path for the shutdown logic in 634 // ServiceClient. 635 func TestConsul_ShutdownOK(t *testing.T) { 636 ctx := setupFake() 637 638 // Add a script check to make sure its TTL gets updated 639 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 640 { 641 Name: "scriptcheck", 642 Type: "script", 643 Command: "true", 644 // Make check block until shutdown 645 Interval: 9000 * time.Hour, 646 Timeout: 10 * time.Second, 647 InitialStatus: "warning", 648 }, 649 } 650 651 go ctx.ServiceClient.Run() 652 653 // Register a task and agent 654 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 655 t.Fatalf("unexpected error registering task: %v", err) 656 } 657 658 agentServices := []*structs.Service{ 659 { 660 Name: "http", 661 Tags: []string{"nomad"}, 662 PortLabel: "localhost:2345", 663 }, 664 } 665 if err := ctx.ServiceClient.RegisterAgent("client", agentServices); err != nil { 666 t.Fatalf("unexpected error registering agent: %v", err) 667 } 668 669 // Shutdown should block until scripts finish 670 if err := ctx.ServiceClient.Shutdown(); err != nil { 671 t.Errorf("unexpected error shutting down client: %v", err) 672 } 673 674 // UpdateTTL should have been called once for the script check 675 if n := len(ctx.FakeConsul.checkTTLs); n != 1 { 676 t.Fatalf("expected 1 checkTTL entry but found: %d", n) 677 } 678 for _, v := range ctx.FakeConsul.checkTTLs { 679 if v != 1 { 680 t.Fatalf("expected script check to be updated once but found %d", v) 681 } 682 } 683 for _, v := range ctx.FakeConsul.checks { 684 if v.Status != "passing" { 685 t.Fatalf("expected check to be passing but found %q", v.Status) 686 } 687 } 688 } 689 690 // TestConsul_ShutdownSlow tests the slow but ok path for the shutdown logic in 691 // ServiceClient. 692 func TestConsul_ShutdownSlow(t *testing.T) { 693 t.Parallel() // run the slow tests in parallel 694 ctx := setupFake() 695 696 // Add a script check to make sure its TTL gets updated 697 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 698 { 699 Name: "scriptcheck", 700 Type: "script", 701 Command: "true", 702 // Make check block until shutdown 703 Interval: 9000 * time.Hour, 704 Timeout: 5 * time.Second, 705 InitialStatus: "warning", 706 }, 707 } 708 709 // Make Exec slow, but not too slow 710 waiter := make(chan struct{}) 711 ctx.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 712 select { 713 case <-waiter: 714 default: 715 close(waiter) 716 } 717 time.Sleep(time.Second) 718 return []byte{}, 0, nil 719 } 720 721 // Make shutdown wait time just a bit longer than ctx.Exec takes 722 ctx.ServiceClient.shutdownWait = 3 * time.Second 723 724 go ctx.ServiceClient.Run() 725 726 // Register a task and agent 727 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 728 t.Fatalf("unexpected error registering task: %v", err) 729 } 730 731 // wait for Exec to get called before shutting down 732 <-waiter 733 734 // Shutdown should block until all enqueued operations finish. 735 preShutdown := time.Now() 736 if err := ctx.ServiceClient.Shutdown(); err != nil { 737 t.Errorf("unexpected error shutting down client: %v", err) 738 } 739 740 // Shutdown time should have taken: 1s <= shutdown <= 3s 741 shutdownTime := time.Now().Sub(preShutdown) 742 if shutdownTime < time.Second || shutdownTime > ctx.ServiceClient.shutdownWait { 743 t.Errorf("expected shutdown to take >1s and <%s but took: %s", ctx.ServiceClient.shutdownWait, shutdownTime) 744 } 745 746 // UpdateTTL should have been called once for the script check 747 if n := len(ctx.FakeConsul.checkTTLs); n != 1 { 748 t.Fatalf("expected 1 checkTTL entry but found: %d", n) 749 } 750 for _, v := range ctx.FakeConsul.checkTTLs { 751 if v != 1 { 752 t.Fatalf("expected script check to be updated once but found %d", v) 753 } 754 } 755 for _, v := range ctx.FakeConsul.checks { 756 if v.Status != "passing" { 757 t.Fatalf("expected check to be passing but found %q", v.Status) 758 } 759 } 760 } 761 762 // TestConsul_ShutdownBlocked tests the blocked past deadline path for the 763 // shutdown logic in ServiceClient. 764 func TestConsul_ShutdownBlocked(t *testing.T) { 765 t.Parallel() // run the slow tests in parallel 766 ctx := setupFake() 767 768 // Add a script check to make sure its TTL gets updated 769 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 770 { 771 Name: "scriptcheck", 772 Type: "script", 773 Command: "true", 774 // Make check block until shutdown 775 Interval: 9000 * time.Hour, 776 Timeout: 9000 * time.Hour, 777 InitialStatus: "warning", 778 }, 779 } 780 781 block := make(chan struct{}) 782 defer close(block) // cleanup after test 783 784 // Make Exec block forever 785 waiter := make(chan struct{}) 786 ctx.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 787 close(waiter) 788 <-block 789 return []byte{}, 0, nil 790 } 791 792 // Use a short shutdown deadline since we're intentionally blocking forever 793 ctx.ServiceClient.shutdownWait = time.Second 794 795 go ctx.ServiceClient.Run() 796 797 // Register a task and agent 798 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 799 t.Fatalf("unexpected error registering task: %v", err) 800 } 801 802 // Wait for exec to be called 803 <-waiter 804 805 // Shutdown should block until all enqueued operations finish. 806 preShutdown := time.Now() 807 err := ctx.ServiceClient.Shutdown() 808 if err == nil { 809 t.Errorf("expected a timed out error from shutdown") 810 } 811 812 // Shutdown time should have taken shutdownWait; to avoid timing 813 // related errors simply test for wait <= shutdown <= wait+3s 814 shutdownTime := time.Now().Sub(preShutdown) 815 maxWait := ctx.ServiceClient.shutdownWait + (3 * time.Second) 816 if shutdownTime < ctx.ServiceClient.shutdownWait || shutdownTime > maxWait { 817 t.Errorf("expected shutdown to take >%s and <%s but took: %s", ctx.ServiceClient.shutdownWait, maxWait, shutdownTime) 818 } 819 820 // UpdateTTL should not have been called for the script check 821 if n := len(ctx.FakeConsul.checkTTLs); n != 0 { 822 t.Fatalf("expected 0 checkTTL entry but found: %d", n) 823 } 824 for _, v := range ctx.FakeConsul.checks { 825 if expected := "warning"; v.Status != expected { 826 t.Fatalf("expected check to be %q but found %q", expected, v.Status) 827 } 828 } 829 } 830 831 // TestConsul_NoTLSSkipVerifySupport asserts that checks with 832 // TLSSkipVerify=true are skipped when Consul doesn't support TLSSkipVerify. 833 func TestConsul_NoTLSSkipVerifySupport(t *testing.T) { 834 ctx := setupFake() 835 ctx.ServiceClient = NewServiceClient(ctx.FakeConsul, false, testLogger()) 836 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 837 // This check sets TLSSkipVerify so it should get dropped 838 { 839 Name: "tls-check-skip", 840 Type: "http", 841 Protocol: "https", 842 Path: "/", 843 TLSSkipVerify: true, 844 }, 845 // This check doesn't set TLSSkipVerify so it should work fine 846 { 847 Name: "tls-check-noskip", 848 Type: "http", 849 Protocol: "https", 850 Path: "/", 851 TLSSkipVerify: false, 852 }, 853 } 854 855 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, nil, nil); err != nil { 856 t.Fatalf("unexpected error registering task: %v", err) 857 } 858 859 if err := ctx.syncOnce(); err != nil { 860 t.Fatalf("unexpected error syncing task: %v", err) 861 } 862 863 if len(ctx.FakeConsul.checks) != 1 { 864 t.Errorf("expected 1 check but found %d", len(ctx.FakeConsul.checks)) 865 } 866 for _, v := range ctx.FakeConsul.checks { 867 if expected := "tls-check-noskip"; v.Name != expected { 868 t.Errorf("only expected %q but found: %q", expected, v.Name) 869 } 870 if v.TLSSkipVerify { 871 t.Errorf("TLSSkipVerify=true when TLSSkipVerify not supported!") 872 } 873 } 874 } 875 876 // TestConsul_RemoveScript assert removing a script check removes all objects 877 // related to that check. 878 func TestConsul_CancelScript(t *testing.T) { 879 ctx := setupFake() 880 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 881 { 882 Name: "scriptcheckDel", 883 Type: "script", 884 Interval: 9000 * time.Hour, 885 Timeout: 9000 * time.Hour, 886 }, 887 { 888 Name: "scriptcheckKeep", 889 Type: "script", 890 Interval: 9000 * time.Hour, 891 Timeout: 9000 * time.Hour, 892 }, 893 } 894 895 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, nil); err != nil { 896 t.Fatalf("unexpected error registering task: %v", err) 897 } 898 899 if err := ctx.syncOnce(); err != nil { 900 t.Fatalf("unexpected error syncing task: %v", err) 901 } 902 903 if len(ctx.FakeConsul.checks) != 2 { 904 t.Errorf("expected 2 checks but found %d", len(ctx.FakeConsul.checks)) 905 } 906 907 if len(ctx.ServiceClient.scripts) != 2 && len(ctx.ServiceClient.runningScripts) != 2 { 908 t.Errorf("expected 2 running script but found scripts=%d runningScripts=%d", 909 len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts)) 910 } 911 912 for i := 0; i < 2; i++ { 913 select { 914 case <-ctx.execs: 915 // Script ran as expected! 916 case <-time.After(3 * time.Second): 917 t.Fatalf("timed out waiting for script check to run") 918 } 919 } 920 921 // Remove a check and update the task 922 origTask := ctx.Task.Copy() 923 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 924 { 925 Name: "scriptcheckKeep", 926 Type: "script", 927 Interval: 9000 * time.Hour, 928 Timeout: 9000 * time.Hour, 929 }, 930 } 931 932 if err := ctx.ServiceClient.UpdateTask("allocid", origTask, ctx.Task, ctx, nil); err != nil { 933 t.Fatalf("unexpected error registering task: %v", err) 934 } 935 936 if err := ctx.syncOnce(); err != nil { 937 t.Fatalf("unexpected error syncing task: %v", err) 938 } 939 940 if len(ctx.FakeConsul.checks) != 1 { 941 t.Errorf("expected 1 check but found %d", len(ctx.FakeConsul.checks)) 942 } 943 944 if len(ctx.ServiceClient.scripts) != 1 && len(ctx.ServiceClient.runningScripts) != 1 { 945 t.Errorf("expected 1 running script but found scripts=%d runningScripts=%d", 946 len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts)) 947 } 948 949 // Make sure exec wasn't called again 950 select { 951 case <-ctx.execs: 952 t.Errorf("unexpected execution of script; was goroutine not cancelled?") 953 case <-time.After(100 * time.Millisecond): 954 // No unexpected script execs 955 } 956 957 // Don't leak goroutines 958 for _, scriptHandle := range ctx.ServiceClient.runningScripts { 959 scriptHandle.cancel() 960 } 961 } 962 963 // TestConsul_DriverNetwork_AutoUse asserts that if a driver network has 964 // auto-use set then services should advertise it unless explicitly set to 965 // host. Checks should always use host. 966 func TestConsul_DriverNetwork_AutoUse(t *testing.T) { 967 ctx := setupFake() 968 969 ctx.Task.Services = []*structs.Service{ 970 { 971 Name: "auto-advertise-x", 972 PortLabel: "x", 973 AddressMode: structs.AddressModeAuto, 974 Checks: []*structs.ServiceCheck{ 975 { 976 Name: "default-check-x", 977 Type: "tcp", 978 Interval: time.Second, 979 Timeout: time.Second, 980 }, 981 { 982 Name: "weird-y-check", 983 Type: "http", 984 Interval: time.Second, 985 Timeout: time.Second, 986 PortLabel: "y", 987 }, 988 }, 989 }, 990 { 991 Name: "driver-advertise-y", 992 PortLabel: "y", 993 AddressMode: structs.AddressModeDriver, 994 Checks: []*structs.ServiceCheck{ 995 { 996 Name: "default-check-y", 997 Type: "tcp", 998 Interval: time.Second, 999 Timeout: time.Second, 1000 }, 1001 }, 1002 }, 1003 { 1004 Name: "host-advertise-y", 1005 PortLabel: "y", 1006 AddressMode: structs.AddressModeHost, 1007 }, 1008 } 1009 1010 net := &cstructs.DriverNetwork{ 1011 PortMap: map[string]int{ 1012 "x": 8888, 1013 "y": 9999, 1014 }, 1015 IP: "172.18.0.2", 1016 AutoAdvertise: true, 1017 } 1018 1019 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, net); err != nil { 1020 t.Fatalf("unexpected error registering task: %v", err) 1021 } 1022 1023 if err := ctx.syncOnce(); err != nil { 1024 t.Fatalf("unexpected error syncing task: %v", err) 1025 } 1026 1027 if n := len(ctx.FakeConsul.services); n != 3 { 1028 t.Fatalf("expected 2 services but found: %d", n) 1029 } 1030 1031 for _, v := range ctx.FakeConsul.services { 1032 switch v.Name { 1033 case ctx.Task.Services[0].Name: // x 1034 // Since DriverNetwork.AutoAdvertise=true, driver ports should be used 1035 if v.Port != net.PortMap["x"] { 1036 t.Errorf("expected service %s's port to be %d but found %d", 1037 v.Name, net.PortMap["x"], v.Port) 1038 } 1039 // The order of checks in Consul is not guaranteed to 1040 // be the same as their order in the Task definition, 1041 // so check in a loop 1042 if expected := 2; len(v.Checks) != expected { 1043 t.Errorf("expected %d checks but found %d", expected, len(v.Checks)) 1044 } 1045 for _, c := range v.Checks { 1046 // No name on AgentServiceChecks, use type 1047 switch { 1048 case c.TCP != "": 1049 // Checks should always use host port though 1050 if c.TCP != ":1234" { // xPort 1051 t.Errorf("exepcted service %s check 1's port to be %d but found %q", 1052 v.Name, xPort, c.TCP) 1053 } 1054 case c.HTTP != "": 1055 if c.HTTP != "http://:1235" { // yPort 1056 t.Errorf("exepcted service %s check 2's port to be %d but found %q", 1057 v.Name, yPort, c.HTTP) 1058 } 1059 default: 1060 t.Errorf("unexpected check %#v on service %q", c, v.Name) 1061 } 1062 } 1063 case ctx.Task.Services[1].Name: // y 1064 // Service should be container ip:port 1065 if v.Address != net.IP { 1066 t.Errorf("expected service %s's address to be %s but found %s", 1067 v.Name, net.IP, v.Address) 1068 } 1069 if v.Port != net.PortMap["y"] { 1070 t.Errorf("expected service %s's port to be %d but found %d", 1071 v.Name, net.PortMap["x"], v.Port) 1072 } 1073 // Check should be host ip:port 1074 if v.Checks[0].TCP != ":1235" { // yPort 1075 t.Errorf("expected service %s check's port to be %d but found %s", 1076 v.Name, yPort, v.Checks[0].TCP) 1077 } 1078 case ctx.Task.Services[2].Name: // y + host mode 1079 if v.Port != yPort { 1080 t.Errorf("expected service %s's port to be %d but found %d", 1081 v.Name, yPort, v.Port) 1082 } 1083 default: 1084 t.Errorf("unexpected service name: %q", v.Name) 1085 } 1086 } 1087 } 1088 1089 // TestConsul_DriverNetwork_NoAutoUse asserts that if a driver network doesn't 1090 // set auto-use only services which request the driver's network should 1091 // advertise it. 1092 func TestConsul_DriverNetwork_NoAutoUse(t *testing.T) { 1093 ctx := setupFake() 1094 1095 ctx.Task.Services = []*structs.Service{ 1096 { 1097 Name: "auto-advertise-x", 1098 PortLabel: "x", 1099 AddressMode: structs.AddressModeAuto, 1100 }, 1101 { 1102 Name: "driver-advertise-y", 1103 PortLabel: "y", 1104 AddressMode: structs.AddressModeDriver, 1105 }, 1106 { 1107 Name: "host-advertise-y", 1108 PortLabel: "y", 1109 AddressMode: structs.AddressModeHost, 1110 }, 1111 } 1112 1113 net := &cstructs.DriverNetwork{ 1114 PortMap: map[string]int{ 1115 "x": 8888, 1116 "y": 9999, 1117 }, 1118 IP: "172.18.0.2", 1119 AutoAdvertise: false, 1120 } 1121 1122 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, net); err != nil { 1123 t.Fatalf("unexpected error registering task: %v", err) 1124 } 1125 1126 if err := ctx.syncOnce(); err != nil { 1127 t.Fatalf("unexpected error syncing task: %v", err) 1128 } 1129 1130 if n := len(ctx.FakeConsul.services); n != 3 { 1131 t.Fatalf("expected 3 services but found: %d", n) 1132 } 1133 1134 for _, v := range ctx.FakeConsul.services { 1135 switch v.Name { 1136 case ctx.Task.Services[0].Name: // x + auto 1137 // Since DriverNetwork.AutoAdvertise=false, host ports should be used 1138 if v.Port != xPort { 1139 t.Errorf("expected service %s's port to be %d but found %d", 1140 v.Name, xPort, v.Port) 1141 } 1142 case ctx.Task.Services[1].Name: // y + driver mode 1143 // Service should be container ip:port 1144 if v.Address != net.IP { 1145 t.Errorf("expected service %s's address to be %s but found %s", 1146 v.Name, net.IP, v.Address) 1147 } 1148 if v.Port != net.PortMap["y"] { 1149 t.Errorf("expected service %s's port to be %d but found %d", 1150 v.Name, net.PortMap["x"], v.Port) 1151 } 1152 case ctx.Task.Services[2].Name: // y + host mode 1153 if v.Port != yPort { 1154 t.Errorf("expected service %s's port to be %d but found %d", 1155 v.Name, yPort, v.Port) 1156 } 1157 default: 1158 t.Errorf("unexpected service name: %q", v.Name) 1159 } 1160 } 1161 } 1162 1163 // TestConsul_DriverNetwork_Change asserts that if a driver network is 1164 // specified and a service updates its use its properly updated in Consul. 1165 func TestConsul_DriverNetwork_Change(t *testing.T) { 1166 ctx := setupFake() 1167 1168 ctx.Task.Services = []*structs.Service{ 1169 { 1170 Name: "service-foo", 1171 PortLabel: "x", 1172 AddressMode: structs.AddressModeAuto, 1173 }, 1174 } 1175 1176 net := &cstructs.DriverNetwork{ 1177 PortMap: map[string]int{ 1178 "x": 8888, 1179 "y": 9999, 1180 }, 1181 IP: "172.18.0.2", 1182 AutoAdvertise: false, 1183 } 1184 1185 syncAndAssertPort := func(port int) { 1186 if err := ctx.syncOnce(); err != nil { 1187 t.Fatalf("unexpected error syncing task: %v", err) 1188 } 1189 1190 if n := len(ctx.FakeConsul.services); n != 1 { 1191 t.Fatalf("expected 1 service but found: %d", n) 1192 } 1193 1194 for _, v := range ctx.FakeConsul.services { 1195 switch v.Name { 1196 case ctx.Task.Services[0].Name: 1197 if v.Port != port { 1198 t.Errorf("expected service %s's port to be %d but found %d", 1199 v.Name, port, v.Port) 1200 } 1201 default: 1202 t.Errorf("unexpected service name: %q", v.Name) 1203 } 1204 } 1205 } 1206 1207 // Initial service should advertise host port x 1208 if err := ctx.ServiceClient.RegisterTask("allocid", ctx.Task, ctx, net); err != nil { 1209 t.Fatalf("unexpected error registering task: %v", err) 1210 } 1211 1212 syncAndAssertPort(xPort) 1213 1214 // UpdateTask to use Host (shouldn't change anything) 1215 orig := ctx.Task.Copy() 1216 ctx.Task.Services[0].AddressMode = structs.AddressModeHost 1217 1218 if err := ctx.ServiceClient.UpdateTask("allocid", orig, ctx.Task, ctx, net); err != nil { 1219 t.Fatalf("unexpected error updating task: %v", err) 1220 } 1221 1222 syncAndAssertPort(xPort) 1223 1224 // UpdateTask to use Driver (*should* change IP and port) 1225 orig = ctx.Task.Copy() 1226 ctx.Task.Services[0].AddressMode = structs.AddressModeDriver 1227 1228 if err := ctx.ServiceClient.UpdateTask("allocid", orig, ctx.Task, ctx, net); err != nil { 1229 t.Fatalf("unexpected error updating task: %v", err) 1230 } 1231 1232 syncAndAssertPort(net.PortMap["x"]) 1233 } 1234 1235 // TestIsNomadService asserts the isNomadService helper returns true for Nomad 1236 // task IDs and false for unknown IDs and Nomad agent IDs (see #2827). 1237 func TestIsNomadService(t *testing.T) { 1238 tests := []struct { 1239 id string 1240 result bool 1241 }{ 1242 {"_nomad-client-nomad-client-http", false}, 1243 {"_nomad-server-nomad-serf", false}, 1244 {"_nomad-executor-abc", true}, 1245 {"_nomad-executor", true}, 1246 {"not-nomad", false}, 1247 } 1248 1249 for _, test := range tests { 1250 t.Run(test.id, func(t *testing.T) { 1251 actual := isNomadService(test.id) 1252 if actual != test.result { 1253 t.Errorf("%q should be %t but found %t", test.id, test.result, actual) 1254 } 1255 }) 1256 } 1257 }