github.com/smithx10/nomad@v0.9.1-rc1/command/agent/consul/unit_test.go (about) 1 package consul 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 "github.com/hashicorp/consul/api" 13 "github.com/hashicorp/nomad/helper/testlog" 14 "github.com/hashicorp/nomad/helper/uuid" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/hashicorp/nomad/plugins/drivers" 17 "github.com/hashicorp/nomad/testutil" 18 "github.com/kr/pretty" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 ) 22 23 const ( 24 // Ports used in testTask 25 xPort = 1234 26 yPort = 1235 27 ) 28 29 func testTask() *TaskServices { 30 return &TaskServices{ 31 AllocID: uuid.Generate(), 32 Name: "taskname", 33 Restarter: &restartRecorder{}, 34 Services: []*structs.Service{ 35 { 36 Name: "taskname-service", 37 PortLabel: "x", 38 Tags: []string{"tag1", "tag2"}, 39 }, 40 }, 41 Networks: []*structs.NetworkResource{ 42 { 43 DynamicPorts: []structs.Port{ 44 {Label: "x", Value: xPort}, 45 {Label: "y", Value: yPort}, 46 }, 47 }, 48 }, 49 DriverExec: newMockExec(), 50 } 51 } 52 53 // mockExec implements the ScriptExecutor interface and will use an alternate 54 // implementation t.ExecFunc if non-nil. 55 type mockExec struct { 56 // Ticked whenever a script is called 57 execs chan int 58 59 // If non-nil will be called by script checks 60 ExecFunc func(ctx context.Context, cmd string, args []string) ([]byte, int, error) 61 } 62 63 func newMockExec() *mockExec { 64 return &mockExec{ 65 execs: make(chan int, 100), 66 } 67 } 68 69 func (m *mockExec) Exec(dur time.Duration, cmd string, args []string) ([]byte, int, error) { 70 select { 71 case m.execs <- 1: 72 default: 73 } 74 if m.ExecFunc == nil { 75 // Default impl is just "ok" 76 return []byte("ok"), 0, nil 77 } 78 ctx, cancel := context.WithTimeout(context.Background(), dur) 79 defer cancel() 80 return m.ExecFunc(ctx, cmd, args) 81 } 82 83 // restartRecorder is a minimal TaskRestarter implementation that simply 84 // counts how many restarts were triggered. 85 type restartRecorder struct { 86 restarts int64 87 } 88 89 func (r *restartRecorder) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error { 90 atomic.AddInt64(&r.restarts, 1) 91 return nil 92 } 93 94 // testFakeCtx contains a fake Consul AgentAPI 95 type testFakeCtx struct { 96 ServiceClient *ServiceClient 97 FakeConsul *MockAgent 98 Task *TaskServices 99 MockExec *mockExec 100 } 101 102 var errNoOps = fmt.Errorf("testing error: no pending operations") 103 104 // syncOps simulates one iteration of the ServiceClient.Run loop and returns 105 // any errors returned by sync() or errNoOps if no pending operations. 106 func (t *testFakeCtx) syncOnce() error { 107 select { 108 case ops := <-t.ServiceClient.opCh: 109 t.ServiceClient.merge(ops) 110 return t.ServiceClient.sync() 111 default: 112 return errNoOps 113 } 114 } 115 116 // setupFake creates a testFakeCtx with a ServiceClient backed by a fakeConsul. 117 // A test Task is also provided. 118 func setupFake(t *testing.T) *testFakeCtx { 119 fc := NewMockAgent() 120 tt := testTask() 121 return &testFakeCtx{ 122 ServiceClient: NewServiceClient(fc, testlog.HCLogger(t), true), 123 FakeConsul: fc, 124 Task: tt, 125 MockExec: tt.DriverExec.(*mockExec), 126 } 127 } 128 129 func TestConsul_ChangeTags(t *testing.T) { 130 ctx := setupFake(t) 131 132 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 133 t.Fatalf("unexpected error registering task: %v", err) 134 } 135 136 if err := ctx.syncOnce(); err != nil { 137 t.Fatalf("unexpected error syncing task: %v", err) 138 } 139 140 if n := len(ctx.FakeConsul.services); n != 1 { 141 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 142 } 143 144 // Query the allocs registrations and then again when we update. The IDs 145 // should change 146 reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID) 147 if err != nil { 148 t.Fatalf("Looking up alloc registration failed: %v", err) 149 } 150 if reg1 == nil { 151 t.Fatalf("Nil alloc registrations: %v", err) 152 } 153 if num := reg1.NumServices(); num != 1 { 154 t.Fatalf("Wrong number of services: got %d; want 1", num) 155 } 156 if num := reg1.NumChecks(); num != 0 { 157 t.Fatalf("Wrong number of checks: got %d; want 0", num) 158 } 159 160 origKey := "" 161 for k, v := range ctx.FakeConsul.services { 162 origKey = k 163 if v.Name != ctx.Task.Services[0].Name { 164 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 165 } 166 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 167 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 168 } 169 } 170 171 origTask := ctx.Task.Copy() 172 ctx.Task.Services[0].Tags[0] = "newtag" 173 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 174 t.Fatalf("unexpected error registering task: %v", err) 175 } 176 if err := ctx.syncOnce(); err != nil { 177 t.Fatalf("unexpected error syncing task: %v", err) 178 } 179 180 if n := len(ctx.FakeConsul.services); n != 1 { 181 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 182 } 183 184 for k, v := range ctx.FakeConsul.services { 185 if k == origKey { 186 t.Errorf("expected key to change but found %q", k) 187 } 188 if v.Name != ctx.Task.Services[0].Name { 189 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 190 } 191 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 192 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 193 } 194 } 195 196 // Check again and ensure the IDs changed 197 reg2, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID) 198 if err != nil { 199 t.Fatalf("Looking up alloc registration failed: %v", err) 200 } 201 if reg2 == nil { 202 t.Fatalf("Nil alloc registrations: %v", err) 203 } 204 if num := reg2.NumServices(); num != 1 { 205 t.Fatalf("Wrong number of services: got %d; want 1", num) 206 } 207 if num := reg2.NumChecks(); num != 0 { 208 t.Fatalf("Wrong number of checks: got %d; want 0", num) 209 } 210 211 for task, treg := range reg1.Tasks { 212 otherTaskReg, ok := reg2.Tasks[task] 213 if !ok { 214 t.Fatalf("Task %q not in second reg", task) 215 } 216 217 for sID := range treg.Services { 218 if _, ok := otherTaskReg.Services[sID]; ok { 219 t.Fatalf("service ID didn't change") 220 } 221 } 222 } 223 } 224 225 // TestConsul_ChangePorts asserts that changing the ports on a service updates 226 // it in Consul. Pre-0.7.1 ports were not part of the service ID and this was a 227 // slightly different code path than changing tags. 228 func TestConsul_ChangePorts(t *testing.T) { 229 ctx := setupFake(t) 230 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 231 { 232 Name: "c1", 233 Type: "tcp", 234 Interval: time.Second, 235 Timeout: time.Second, 236 PortLabel: "x", 237 }, 238 { 239 Name: "c2", 240 Type: "script", 241 Interval: 9000 * time.Hour, 242 Timeout: time.Second, 243 }, 244 { 245 Name: "c3", 246 Type: "http", 247 Protocol: "http", 248 Path: "/", 249 Interval: time.Second, 250 Timeout: time.Second, 251 PortLabel: "y", 252 }, 253 } 254 255 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 256 t.Fatalf("unexpected error registering task: %v", err) 257 } 258 259 if err := ctx.syncOnce(); err != nil { 260 t.Fatalf("unexpected error syncing task: %v", err) 261 } 262 263 if n := len(ctx.FakeConsul.services); n != 1 { 264 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 265 } 266 267 origServiceKey := "" 268 for k, v := range ctx.FakeConsul.services { 269 origServiceKey = k 270 if v.Name != ctx.Task.Services[0].Name { 271 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 272 } 273 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 274 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 275 } 276 if v.Port != xPort { 277 t.Errorf("expected Port x=%v but found: %v", xPort, v.Port) 278 } 279 } 280 281 if n := len(ctx.FakeConsul.checks); n != 3 { 282 t.Fatalf("expected 3 checks but found %d:\n%#v", n, ctx.FakeConsul.checks) 283 } 284 285 origTCPKey := "" 286 origScriptKey := "" 287 origHTTPKey := "" 288 for k, v := range ctx.FakeConsul.checks { 289 switch v.Name { 290 case "c1": 291 origTCPKey = k 292 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 293 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 294 } 295 case "c2": 296 origScriptKey = k 297 select { 298 case <-ctx.MockExec.execs: 299 if n := len(ctx.MockExec.execs); n > 0 { 300 t.Errorf("expected 1 exec but found: %d", n+1) 301 } 302 case <-time.After(3 * time.Second): 303 t.Errorf("script not called in time") 304 } 305 case "c3": 306 origHTTPKey = k 307 if expected := fmt.Sprintf("http://:%d/", yPort); v.HTTP != expected { 308 t.Errorf("expected Port y=%v but found: %v", expected, v.HTTP) 309 } 310 default: 311 t.Fatalf("unexpected check: %q", v.Name) 312 } 313 } 314 315 // Now update the PortLabel on the Service and Check c3 316 origTask := ctx.Task.Copy() 317 ctx.Task.Services[0].PortLabel = "y" 318 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 319 { 320 Name: "c1", 321 Type: "tcp", 322 Interval: time.Second, 323 Timeout: time.Second, 324 PortLabel: "x", 325 }, 326 { 327 Name: "c2", 328 Type: "script", 329 Interval: 9000 * time.Hour, 330 Timeout: time.Second, 331 }, 332 { 333 Name: "c3", 334 Type: "http", 335 Protocol: "http", 336 Path: "/", 337 Interval: time.Second, 338 Timeout: time.Second, 339 // Removed PortLabel; should default to service's (y) 340 }, 341 } 342 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 343 t.Fatalf("unexpected error registering task: %v", err) 344 } 345 if err := ctx.syncOnce(); err != nil { 346 t.Fatalf("unexpected error syncing task: %v", err) 347 } 348 349 if n := len(ctx.FakeConsul.services); n != 1 { 350 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 351 } 352 353 for k, v := range ctx.FakeConsul.services { 354 if k == origServiceKey { 355 t.Errorf("expected key change; still: %q", k) 356 } 357 if v.Name != ctx.Task.Services[0].Name { 358 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 359 } 360 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 361 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 362 } 363 if v.Port != yPort { 364 t.Errorf("expected Port y=%v but found: %v", yPort, v.Port) 365 } 366 } 367 368 if n := len(ctx.FakeConsul.checks); n != 3 { 369 t.Fatalf("expected 3 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 370 } 371 372 for k, v := range ctx.FakeConsul.checks { 373 switch v.Name { 374 case "c1": 375 if k == origTCPKey { 376 t.Errorf("expected key change for %s from %q", v.Name, origTCPKey) 377 } 378 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 379 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 380 } 381 case "c2": 382 if k == origScriptKey { 383 t.Errorf("expected key change for %s from %q", v.Name, origScriptKey) 384 } 385 select { 386 case <-ctx.MockExec.execs: 387 if n := len(ctx.MockExec.execs); n > 0 { 388 t.Errorf("expected 1 exec but found: %d", n+1) 389 } 390 case <-time.After(3 * time.Second): 391 t.Errorf("script not called in time") 392 } 393 case "c3": 394 if k == origHTTPKey { 395 t.Errorf("expected %s key to change from %q", v.Name, k) 396 } 397 if expected := fmt.Sprintf("http://:%d/", yPort); v.HTTP != expected { 398 t.Errorf("expected Port y=%v but found: %v", expected, v.HTTP) 399 } 400 default: 401 t.Errorf("Unknown check: %q", k) 402 } 403 } 404 } 405 406 // TestConsul_ChangeChecks asserts that updating only the checks on a service 407 // properly syncs with Consul. 408 func TestConsul_ChangeChecks(t *testing.T) { 409 ctx := setupFake(t) 410 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 411 { 412 Name: "c1", 413 Type: "tcp", 414 Interval: time.Second, 415 Timeout: time.Second, 416 PortLabel: "x", 417 CheckRestart: &structs.CheckRestart{ 418 Limit: 3, 419 }, 420 }, 421 } 422 423 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 424 t.Fatalf("unexpected error registering task: %v", err) 425 } 426 427 if err := ctx.syncOnce(); err != nil { 428 t.Fatalf("unexpected error syncing task: %v", err) 429 } 430 431 if n := len(ctx.FakeConsul.services); n != 1 { 432 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 433 } 434 435 // Assert a check restart watch update was enqueued and clear it 436 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 { 437 t.Fatalf("expected 1 check restart update but found %d", n) 438 } 439 upd := <-ctx.ServiceClient.checkWatcher.checkUpdateCh 440 c1ID := upd.checkID 441 442 // Query the allocs registrations and then again when we update. The IDs 443 // should change 444 reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID) 445 if err != nil { 446 t.Fatalf("Looking up alloc registration failed: %v", err) 447 } 448 if reg1 == nil { 449 t.Fatalf("Nil alloc registrations: %v", err) 450 } 451 if num := reg1.NumServices(); num != 1 { 452 t.Fatalf("Wrong number of services: got %d; want 1", num) 453 } 454 if num := reg1.NumChecks(); num != 1 { 455 t.Fatalf("Wrong number of checks: got %d; want 1", num) 456 } 457 458 origServiceKey := "" 459 for k, v := range ctx.FakeConsul.services { 460 origServiceKey = k 461 if v.Name != ctx.Task.Services[0].Name { 462 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 463 } 464 if v.Port != xPort { 465 t.Errorf("expected Port x=%v but found: %v", xPort, v.Port) 466 } 467 } 468 469 if n := len(ctx.FakeConsul.checks); n != 1 { 470 t.Fatalf("expected 1 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 471 } 472 for _, v := range ctx.FakeConsul.checks { 473 if v.Name != "c1" { 474 t.Fatalf("expected check c1 but found %q", v.Name) 475 } 476 } 477 478 // Now add a check and modify the original 479 origTask := ctx.Task.Copy() 480 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 481 { 482 Name: "c1", 483 Type: "tcp", 484 Interval: 2 * time.Second, 485 Timeout: time.Second, 486 PortLabel: "x", 487 CheckRestart: &structs.CheckRestart{ 488 Limit: 3, 489 }, 490 }, 491 { 492 Name: "c2", 493 Type: "http", 494 Path: "/", 495 Interval: time.Second, 496 Timeout: time.Second, 497 PortLabel: "x", 498 }, 499 } 500 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 501 t.Fatalf("unexpected error registering task: %v", err) 502 } 503 504 // Assert 2 check restart watch updates was enqueued 505 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 2 { 506 t.Fatalf("expected 2 check restart updates but found %d", n) 507 } 508 509 // First the new watch 510 upd = <-ctx.ServiceClient.checkWatcher.checkUpdateCh 511 if upd.checkID == c1ID || upd.remove { 512 t.Fatalf("expected check watch update to be an add of checkID=%q but found remove=%t checkID=%q", 513 c1ID, upd.remove, upd.checkID) 514 } 515 516 // Then remove the old watch 517 upd = <-ctx.ServiceClient.checkWatcher.checkUpdateCh 518 if upd.checkID != c1ID || !upd.remove { 519 t.Fatalf("expected check watch update to be a removal of checkID=%q but found remove=%t checkID=%q", 520 c1ID, upd.remove, upd.checkID) 521 } 522 523 if err := ctx.syncOnce(); err != nil { 524 t.Fatalf("unexpected error syncing task: %v", err) 525 } 526 527 if n := len(ctx.FakeConsul.services); n != 1 { 528 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 529 } 530 531 if _, ok := ctx.FakeConsul.services[origServiceKey]; !ok { 532 t.Errorf("unexpected key change; was: %q -- but found %#v", origServiceKey, ctx.FakeConsul.services) 533 } 534 535 if n := len(ctx.FakeConsul.checks); n != 2 { 536 t.Fatalf("expected 2 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 537 } 538 539 for k, v := range ctx.FakeConsul.checks { 540 switch v.Name { 541 case "c1": 542 if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected { 543 t.Errorf("expected Port x=%v but found: %v", expected, v.TCP) 544 } 545 546 // update id 547 c1ID = k 548 case "c2": 549 if expected := fmt.Sprintf("http://:%d/", xPort); v.HTTP != expected { 550 t.Errorf("expected Port x=%v but found: %v", expected, v.HTTP) 551 } 552 default: 553 t.Errorf("Unknown check: %q", k) 554 } 555 } 556 557 // Check again and ensure the IDs changed 558 reg2, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID) 559 if err != nil { 560 t.Fatalf("Looking up alloc registration failed: %v", err) 561 } 562 if reg2 == nil { 563 t.Fatalf("Nil alloc registrations: %v", err) 564 } 565 if num := reg2.NumServices(); num != 1 { 566 t.Fatalf("Wrong number of services: got %d; want 1", num) 567 } 568 if num := reg2.NumChecks(); num != 2 { 569 t.Fatalf("Wrong number of checks: got %d; want 2", num) 570 } 571 572 for task, treg := range reg1.Tasks { 573 otherTaskReg, ok := reg2.Tasks[task] 574 if !ok { 575 t.Fatalf("Task %q not in second reg", task) 576 } 577 578 for sID, sreg := range treg.Services { 579 otherServiceReg, ok := otherTaskReg.Services[sID] 580 if !ok { 581 t.Fatalf("service ID changed") 582 } 583 584 for newID := range sreg.checkIDs { 585 if _, ok := otherServiceReg.checkIDs[newID]; ok { 586 t.Fatalf("check IDs should change") 587 } 588 } 589 } 590 } 591 592 // Alter a CheckRestart and make sure the watcher is updated but nothing else 593 origTask = ctx.Task.Copy() 594 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 595 { 596 Name: "c1", 597 Type: "tcp", 598 Interval: 2 * time.Second, 599 Timeout: time.Second, 600 PortLabel: "x", 601 CheckRestart: &structs.CheckRestart{ 602 Limit: 11, 603 }, 604 }, 605 { 606 Name: "c2", 607 Type: "http", 608 Path: "/", 609 Interval: time.Second, 610 Timeout: time.Second, 611 PortLabel: "x", 612 }, 613 } 614 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 615 t.Fatalf("unexpected error registering task: %v", err) 616 } 617 if err := ctx.syncOnce(); err != nil { 618 t.Fatalf("unexpected error syncing task: %v", err) 619 } 620 621 if n := len(ctx.FakeConsul.checks); n != 2 { 622 t.Fatalf("expected 2 check but found %d:\n%#v", n, ctx.FakeConsul.checks) 623 } 624 625 for k, v := range ctx.FakeConsul.checks { 626 if v.Name == "c1" { 627 if k != c1ID { 628 t.Errorf("expected c1 to still have id %q but found %q", c1ID, k) 629 } 630 break 631 } 632 } 633 634 // Assert a check restart watch update was enqueued for a removal and an add 635 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 { 636 t.Fatalf("expected 1 check restart update but found %d", n) 637 } 638 <-ctx.ServiceClient.checkWatcher.checkUpdateCh 639 } 640 641 // TestConsul_RegServices tests basic service registration. 642 func TestConsul_RegServices(t *testing.T) { 643 ctx := setupFake(t) 644 645 // Add a check w/restarting 646 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 647 { 648 Name: "testcheck", 649 Type: "tcp", 650 Interval: 100 * time.Millisecond, 651 CheckRestart: &structs.CheckRestart{ 652 Limit: 3, 653 }, 654 }, 655 } 656 657 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 658 t.Fatalf("unexpected error registering task: %v", err) 659 } 660 661 if err := ctx.syncOnce(); err != nil { 662 t.Fatalf("unexpected error syncing task: %v", err) 663 } 664 665 if n := len(ctx.FakeConsul.services); n != 1 { 666 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 667 } 668 669 for _, v := range ctx.FakeConsul.services { 670 if v.Name != ctx.Task.Services[0].Name { 671 t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name) 672 } 673 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 674 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 675 } 676 if v.Port != xPort { 677 t.Errorf("expected Port=%d != %d", xPort, v.Port) 678 } 679 } 680 681 // Assert the check update is pending 682 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 { 683 t.Fatalf("expected 1 check restart update but found %d", n) 684 } 685 686 // Assert the check update is properly formed 687 checkUpd := <-ctx.ServiceClient.checkWatcher.checkUpdateCh 688 if checkUpd.checkRestart.allocID != ctx.Task.AllocID { 689 t.Fatalf("expected check's allocid to be %q but found %q", "allocid", checkUpd.checkRestart.allocID) 690 } 691 if expected := 200 * time.Millisecond; checkUpd.checkRestart.timeLimit != expected { 692 t.Fatalf("expected check's time limit to be %v but found %v", expected, checkUpd.checkRestart.timeLimit) 693 } 694 695 // Make a change which will register a new service 696 ctx.Task.Services[0].Name = "taskname-service2" 697 ctx.Task.Services[0].Tags[0] = "tag3" 698 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 699 t.Fatalf("unexpected error registering task: %v", err) 700 } 701 702 // Assert check update is pending 703 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 { 704 t.Fatalf("expected 1 check restart update but found %d", n) 705 } 706 707 // Assert the check update's id has changed 708 checkUpd2 := <-ctx.ServiceClient.checkWatcher.checkUpdateCh 709 if checkUpd.checkID == checkUpd2.checkID { 710 t.Fatalf("expected new check update to have a new ID both both have: %q", checkUpd.checkID) 711 } 712 713 // Make sure changes don't take affect until sync() is called (since 714 // Run() isn't running) 715 if n := len(ctx.FakeConsul.services); n != 1 { 716 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 717 } 718 for _, v := range ctx.FakeConsul.services { 719 if reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 720 t.Errorf("expected Tags to differ, changes applied before sync()") 721 } 722 } 723 724 // Now sync() and re-check for the applied updates 725 if err := ctx.syncOnce(); err != nil { 726 t.Fatalf("unexpected error syncing task: %v", err) 727 } 728 if n := len(ctx.FakeConsul.services); n != 2 { 729 t.Fatalf("expected 2 services but found %d:\n%#v", n, ctx.FakeConsul.services) 730 } 731 found := false 732 for _, v := range ctx.FakeConsul.services { 733 if v.Name == ctx.Task.Services[0].Name { 734 if found { 735 t.Fatalf("found new service name %q twice", v.Name) 736 } 737 found = true 738 if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) { 739 t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags) 740 } 741 } 742 } 743 if !found { 744 t.Fatalf("did not find new service %q", ctx.Task.Services[0].Name) 745 } 746 747 // Remove the new task 748 ctx.ServiceClient.RemoveTask(ctx.Task) 749 if err := ctx.syncOnce(); err != nil { 750 t.Fatalf("unexpected error syncing task: %v", err) 751 } 752 if n := len(ctx.FakeConsul.services); n != 1 { 753 t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services) 754 } 755 for _, v := range ctx.FakeConsul.services { 756 if v.Name != "taskname-service" { 757 t.Errorf("expected original task to survive not %q", v.Name) 758 } 759 } 760 761 // Assert check update is pending 762 if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 { 763 t.Fatalf("expected 1 check restart update but found %d", n) 764 } 765 766 // Assert the check update's id is correct and that it's a removal 767 checkUpd3 := <-ctx.ServiceClient.checkWatcher.checkUpdateCh 768 if checkUpd2.checkID != checkUpd3.checkID { 769 t.Fatalf("expected checkid %q but found %q", checkUpd2.checkID, checkUpd3.checkID) 770 } 771 if !checkUpd3.remove { 772 t.Fatalf("expected check watch removal update but found: %#v", checkUpd3) 773 } 774 } 775 776 // TestConsul_ShutdownOK tests the ok path for the shutdown logic in 777 // ServiceClient. 778 func TestConsul_ShutdownOK(t *testing.T) { 779 require := require.New(t) 780 ctx := setupFake(t) 781 782 // Add a script check to make sure its TTL gets updated 783 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 784 { 785 Name: "scriptcheck", 786 Type: "script", 787 Command: "true", 788 // Make check block until shutdown 789 Interval: 9000 * time.Hour, 790 Timeout: 10 * time.Second, 791 InitialStatus: "warning", 792 }, 793 } 794 795 go ctx.ServiceClient.Run() 796 797 // Register a task and agent 798 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 799 t.Fatalf("unexpected error registering task: %v", err) 800 } 801 802 agentServices := []*structs.Service{ 803 { 804 Name: "http", 805 Tags: []string{"nomad"}, 806 PortLabel: "localhost:2345", 807 }, 808 } 809 if err := ctx.ServiceClient.RegisterAgent("client", agentServices); err != nil { 810 t.Fatalf("unexpected error registering agent: %v", err) 811 } 812 813 testutil.WaitForResult(func() (bool, error) { 814 return ctx.ServiceClient.hasSeen(), fmt.Errorf("error contacting Consul") 815 }, func(err error) { 816 t.Fatalf("err: %v", err) 817 }) 818 819 // Shutdown should block until scripts finish 820 if err := ctx.ServiceClient.Shutdown(); err != nil { 821 t.Errorf("unexpected error shutting down client: %v", err) 822 } 823 824 // UpdateTTL should have been called once for the script check and once 825 // for shutdown 826 if n := len(ctx.FakeConsul.checkTTLs); n != 1 { 827 t.Fatalf("expected 1 checkTTL entry but found: %d", n) 828 } 829 for _, v := range ctx.FakeConsul.checkTTLs { 830 require.Equalf(2, v, "expected 2 updates but found %d", v) 831 } 832 for _, v := range ctx.FakeConsul.checks { 833 if v.Status != "passing" { 834 t.Fatalf("expected check to be passing but found %q", v.Status) 835 } 836 } 837 } 838 839 // TestConsul_ShutdownSlow tests the slow but ok path for the shutdown logic in 840 // ServiceClient. 841 func TestConsul_ShutdownSlow(t *testing.T) { 842 t.Parallel() 843 ctx := setupFake(t) 844 845 // Add a script check to make sure its TTL gets updated 846 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 847 { 848 Name: "scriptcheck", 849 Type: "script", 850 Command: "true", 851 // Make check block until shutdown 852 Interval: 9000 * time.Hour, 853 Timeout: 5 * time.Second, 854 InitialStatus: "warning", 855 }, 856 } 857 858 // Make Exec slow, but not too slow 859 waiter := make(chan struct{}) 860 ctx.MockExec.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 861 select { 862 case <-waiter: 863 default: 864 close(waiter) 865 } 866 time.Sleep(time.Second) 867 return []byte{}, 0, nil 868 } 869 870 // Make shutdown wait time just a bit longer than ctx.Exec takes 871 ctx.ServiceClient.shutdownWait = 3 * time.Second 872 873 go ctx.ServiceClient.Run() 874 875 // Register a task and agent 876 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 877 t.Fatalf("unexpected error registering task: %v", err) 878 } 879 880 // wait for Exec to get called before shutting down 881 <-waiter 882 883 // Shutdown should block until all enqueued operations finish. 884 preShutdown := time.Now() 885 if err := ctx.ServiceClient.Shutdown(); err != nil { 886 t.Errorf("unexpected error shutting down client: %v", err) 887 } 888 889 // Shutdown time should have taken: 1s <= shutdown <= 3s 890 shutdownTime := time.Now().Sub(preShutdown) 891 if shutdownTime < time.Second || shutdownTime > ctx.ServiceClient.shutdownWait { 892 t.Errorf("expected shutdown to take >1s and <%s but took: %s", ctx.ServiceClient.shutdownWait, shutdownTime) 893 } 894 895 // UpdateTTL should have been called once for the script check 896 if n := len(ctx.FakeConsul.checkTTLs); n != 1 { 897 t.Fatalf("expected 1 checkTTL entry but found: %d", n) 898 } 899 for _, v := range ctx.FakeConsul.checkTTLs { 900 if v != 1 { 901 t.Fatalf("expected script check to be updated once but found %d", v) 902 } 903 } 904 for _, v := range ctx.FakeConsul.checks { 905 if v.Status != "passing" { 906 t.Fatalf("expected check to be passing but found %q", v.Status) 907 } 908 } 909 } 910 911 // TestConsul_ShutdownBlocked tests the blocked past deadline path for the 912 // shutdown logic in ServiceClient. 913 func TestConsul_ShutdownBlocked(t *testing.T) { 914 t.Parallel() 915 ctx := setupFake(t) 916 917 // Add a script check to make sure its TTL gets updated 918 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 919 { 920 Name: "scriptcheck", 921 Type: "script", 922 Command: "true", 923 // Make check block until shutdown 924 Interval: 9000 * time.Hour, 925 Timeout: 9000 * time.Hour, 926 InitialStatus: "warning", 927 }, 928 } 929 930 block := make(chan struct{}) 931 defer close(block) // cleanup after test 932 933 // Make Exec block forever 934 waiter := make(chan struct{}) 935 ctx.MockExec.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 936 close(waiter) 937 <-block 938 return []byte{}, 0, nil 939 } 940 941 // Use a short shutdown deadline since we're intentionally blocking forever 942 ctx.ServiceClient.shutdownWait = time.Second 943 944 go ctx.ServiceClient.Run() 945 946 // Register a task and agent 947 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 948 t.Fatalf("unexpected error registering task: %v", err) 949 } 950 951 // Wait for exec to be called 952 <-waiter 953 954 // Shutdown should block until all enqueued operations finish. 955 preShutdown := time.Now() 956 err := ctx.ServiceClient.Shutdown() 957 if err == nil { 958 t.Errorf("expected a timed out error from shutdown") 959 } 960 961 // Shutdown time should have taken shutdownWait; to avoid timing 962 // related errors simply test for wait <= shutdown <= wait+3s 963 shutdownTime := time.Now().Sub(preShutdown) 964 maxWait := ctx.ServiceClient.shutdownWait + (3 * time.Second) 965 if shutdownTime < ctx.ServiceClient.shutdownWait || shutdownTime > maxWait { 966 t.Errorf("expected shutdown to take >%s and <%s but took: %s", ctx.ServiceClient.shutdownWait, maxWait, shutdownTime) 967 } 968 969 // UpdateTTL should not have been called for the script check 970 if n := len(ctx.FakeConsul.checkTTLs); n != 0 { 971 t.Fatalf("expected 0 checkTTL entry but found: %d", n) 972 } 973 for _, v := range ctx.FakeConsul.checks { 974 if expected := "warning"; v.Status != expected { 975 t.Fatalf("expected check to be %q but found %q", expected, v.Status) 976 } 977 } 978 } 979 980 // TestConsul_RemoveScript assert removing a script check removes all objects 981 // related to that check. 982 func TestConsul_CancelScript(t *testing.T) { 983 ctx := setupFake(t) 984 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 985 { 986 Name: "scriptcheckDel", 987 Type: "script", 988 Interval: 9000 * time.Hour, 989 Timeout: 9000 * time.Hour, 990 }, 991 { 992 Name: "scriptcheckKeep", 993 Type: "script", 994 Interval: 9000 * time.Hour, 995 Timeout: 9000 * time.Hour, 996 }, 997 } 998 999 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 1000 t.Fatalf("unexpected error registering task: %v", err) 1001 } 1002 1003 if err := ctx.syncOnce(); err != nil { 1004 t.Fatalf("unexpected error syncing task: %v", err) 1005 } 1006 1007 if len(ctx.FakeConsul.checks) != 2 { 1008 t.Errorf("expected 2 checks but found %d", len(ctx.FakeConsul.checks)) 1009 } 1010 1011 if len(ctx.ServiceClient.scripts) != 2 && len(ctx.ServiceClient.runningScripts) != 2 { 1012 t.Errorf("expected 2 running script but found scripts=%d runningScripts=%d", 1013 len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts)) 1014 } 1015 1016 for i := 0; i < 2; i++ { 1017 select { 1018 case <-ctx.MockExec.execs: 1019 // Script ran as expected! 1020 case <-time.After(3 * time.Second): 1021 t.Fatalf("timed out waiting for script check to run") 1022 } 1023 } 1024 1025 // Remove a check and update the task 1026 origTask := ctx.Task.Copy() 1027 ctx.Task.Services[0].Checks = []*structs.ServiceCheck{ 1028 { 1029 Name: "scriptcheckKeep", 1030 Type: "script", 1031 Interval: 9000 * time.Hour, 1032 Timeout: 9000 * time.Hour, 1033 }, 1034 } 1035 1036 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 1037 t.Fatalf("unexpected error registering task: %v", err) 1038 } 1039 1040 if err := ctx.syncOnce(); err != nil { 1041 t.Fatalf("unexpected error syncing task: %v", err) 1042 } 1043 1044 if len(ctx.FakeConsul.checks) != 1 { 1045 t.Errorf("expected 1 check but found %d", len(ctx.FakeConsul.checks)) 1046 } 1047 1048 if len(ctx.ServiceClient.scripts) != 1 && len(ctx.ServiceClient.runningScripts) != 1 { 1049 t.Errorf("expected 1 running script but found scripts=%d runningScripts=%d", 1050 len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts)) 1051 } 1052 1053 // Make sure exec wasn't called again 1054 select { 1055 case <-ctx.MockExec.execs: 1056 t.Errorf("unexpected execution of script; was goroutine not cancelled?") 1057 case <-time.After(100 * time.Millisecond): 1058 // No unexpected script execs 1059 } 1060 1061 // Don't leak goroutines 1062 for _, scriptHandle := range ctx.ServiceClient.runningScripts { 1063 scriptHandle.cancel() 1064 } 1065 } 1066 1067 // TestConsul_DriverNetwork_AutoUse asserts that if a driver network has 1068 // auto-use set then services should advertise it unless explicitly set to 1069 // host. Checks should always use host. 1070 func TestConsul_DriverNetwork_AutoUse(t *testing.T) { 1071 t.Parallel() 1072 ctx := setupFake(t) 1073 1074 ctx.Task.Services = []*structs.Service{ 1075 { 1076 Name: "auto-advertise-x", 1077 PortLabel: "x", 1078 AddressMode: structs.AddressModeAuto, 1079 Checks: []*structs.ServiceCheck{ 1080 { 1081 Name: "default-check-x", 1082 Type: "tcp", 1083 Interval: time.Second, 1084 Timeout: time.Second, 1085 }, 1086 { 1087 Name: "weird-y-check", 1088 Type: "http", 1089 Interval: time.Second, 1090 Timeout: time.Second, 1091 PortLabel: "y", 1092 }, 1093 }, 1094 }, 1095 { 1096 Name: "driver-advertise-y", 1097 PortLabel: "y", 1098 AddressMode: structs.AddressModeDriver, 1099 Checks: []*structs.ServiceCheck{ 1100 { 1101 Name: "default-check-y", 1102 Type: "tcp", 1103 Interval: time.Second, 1104 Timeout: time.Second, 1105 }, 1106 }, 1107 }, 1108 { 1109 Name: "host-advertise-y", 1110 PortLabel: "y", 1111 AddressMode: structs.AddressModeHost, 1112 }, 1113 } 1114 1115 ctx.Task.DriverNetwork = &drivers.DriverNetwork{ 1116 PortMap: map[string]int{ 1117 "x": 8888, 1118 "y": 9999, 1119 }, 1120 IP: "172.18.0.2", 1121 AutoAdvertise: true, 1122 } 1123 1124 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 1125 t.Fatalf("unexpected error registering task: %v", err) 1126 } 1127 1128 if err := ctx.syncOnce(); err != nil { 1129 t.Fatalf("unexpected error syncing task: %v", err) 1130 } 1131 1132 if n := len(ctx.FakeConsul.services); n != 3 { 1133 t.Fatalf("expected 2 services but found: %d", n) 1134 } 1135 1136 for _, v := range ctx.FakeConsul.services { 1137 switch v.Name { 1138 case ctx.Task.Services[0].Name: // x 1139 // Since DriverNetwork.AutoAdvertise=true, driver ports should be used 1140 if v.Port != ctx.Task.DriverNetwork.PortMap["x"] { 1141 t.Errorf("expected service %s's port to be %d but found %d", 1142 v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port) 1143 } 1144 // The order of checks in Consul is not guaranteed to 1145 // be the same as their order in the Task definition, 1146 // so check in a loop 1147 if expected := 2; len(v.Checks) != expected { 1148 t.Errorf("expected %d checks but found %d", expected, len(v.Checks)) 1149 } 1150 for _, c := range v.Checks { 1151 // No name on AgentServiceChecks, use type 1152 switch { 1153 case c.TCP != "": 1154 // Checks should always use host port though 1155 if c.TCP != ":1234" { // xPort 1156 t.Errorf("expected service %s check 1's port to be %d but found %q", 1157 v.Name, xPort, c.TCP) 1158 } 1159 case c.HTTP != "": 1160 if c.HTTP != "http://:1235" { // yPort 1161 t.Errorf("expected service %s check 2's port to be %d but found %q", 1162 v.Name, yPort, c.HTTP) 1163 } 1164 default: 1165 t.Errorf("unexpected check %#v on service %q", c, v.Name) 1166 } 1167 } 1168 case ctx.Task.Services[1].Name: // y 1169 // Service should be container ip:port 1170 if v.Address != ctx.Task.DriverNetwork.IP { 1171 t.Errorf("expected service %s's address to be %s but found %s", 1172 v.Name, ctx.Task.DriverNetwork.IP, v.Address) 1173 } 1174 if v.Port != ctx.Task.DriverNetwork.PortMap["y"] { 1175 t.Errorf("expected service %s's port to be %d but found %d", 1176 v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port) 1177 } 1178 // Check should be host ip:port 1179 if v.Checks[0].TCP != ":1235" { // yPort 1180 t.Errorf("expected service %s check's port to be %d but found %s", 1181 v.Name, yPort, v.Checks[0].TCP) 1182 } 1183 case ctx.Task.Services[2].Name: // y + host mode 1184 if v.Port != yPort { 1185 t.Errorf("expected service %s's port to be %d but found %d", 1186 v.Name, yPort, v.Port) 1187 } 1188 default: 1189 t.Errorf("unexpected service name: %q", v.Name) 1190 } 1191 } 1192 } 1193 1194 // TestConsul_DriverNetwork_NoAutoUse asserts that if a driver network doesn't 1195 // set auto-use only services which request the driver's network should 1196 // advertise it. 1197 func TestConsul_DriverNetwork_NoAutoUse(t *testing.T) { 1198 t.Parallel() 1199 ctx := setupFake(t) 1200 1201 ctx.Task.Services = []*structs.Service{ 1202 { 1203 Name: "auto-advertise-x", 1204 PortLabel: "x", 1205 AddressMode: structs.AddressModeAuto, 1206 }, 1207 { 1208 Name: "driver-advertise-y", 1209 PortLabel: "y", 1210 AddressMode: structs.AddressModeDriver, 1211 }, 1212 { 1213 Name: "host-advertise-y", 1214 PortLabel: "y", 1215 AddressMode: structs.AddressModeHost, 1216 }, 1217 } 1218 1219 ctx.Task.DriverNetwork = &drivers.DriverNetwork{ 1220 PortMap: map[string]int{ 1221 "x": 8888, 1222 "y": 9999, 1223 }, 1224 IP: "172.18.0.2", 1225 AutoAdvertise: false, 1226 } 1227 1228 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 1229 t.Fatalf("unexpected error registering task: %v", err) 1230 } 1231 1232 if err := ctx.syncOnce(); err != nil { 1233 t.Fatalf("unexpected error syncing task: %v", err) 1234 } 1235 1236 if n := len(ctx.FakeConsul.services); n != 3 { 1237 t.Fatalf("expected 3 services but found: %d", n) 1238 } 1239 1240 for _, v := range ctx.FakeConsul.services { 1241 switch v.Name { 1242 case ctx.Task.Services[0].Name: // x + auto 1243 // Since DriverNetwork.AutoAdvertise=false, host ports should be used 1244 if v.Port != xPort { 1245 t.Errorf("expected service %s's port to be %d but found %d", 1246 v.Name, xPort, v.Port) 1247 } 1248 case ctx.Task.Services[1].Name: // y + driver mode 1249 // Service should be container ip:port 1250 if v.Address != ctx.Task.DriverNetwork.IP { 1251 t.Errorf("expected service %s's address to be %s but found %s", 1252 v.Name, ctx.Task.DriverNetwork.IP, v.Address) 1253 } 1254 if v.Port != ctx.Task.DriverNetwork.PortMap["y"] { 1255 t.Errorf("expected service %s's port to be %d but found %d", 1256 v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port) 1257 } 1258 case ctx.Task.Services[2].Name: // y + host mode 1259 if v.Port != yPort { 1260 t.Errorf("expected service %s's port to be %d but found %d", 1261 v.Name, yPort, v.Port) 1262 } 1263 default: 1264 t.Errorf("unexpected service name: %q", v.Name) 1265 } 1266 } 1267 } 1268 1269 // TestConsul_DriverNetwork_Change asserts that if a driver network is 1270 // specified and a service updates its use its properly updated in Consul. 1271 func TestConsul_DriverNetwork_Change(t *testing.T) { 1272 t.Parallel() 1273 ctx := setupFake(t) 1274 1275 ctx.Task.Services = []*structs.Service{ 1276 { 1277 Name: "service-foo", 1278 PortLabel: "x", 1279 AddressMode: structs.AddressModeAuto, 1280 }, 1281 } 1282 1283 ctx.Task.DriverNetwork = &drivers.DriverNetwork{ 1284 PortMap: map[string]int{ 1285 "x": 8888, 1286 "y": 9999, 1287 }, 1288 IP: "172.18.0.2", 1289 AutoAdvertise: false, 1290 } 1291 1292 syncAndAssertPort := func(port int) { 1293 if err := ctx.syncOnce(); err != nil { 1294 t.Fatalf("unexpected error syncing task: %v", err) 1295 } 1296 1297 if n := len(ctx.FakeConsul.services); n != 1 { 1298 t.Fatalf("expected 1 service but found: %d", n) 1299 } 1300 1301 for _, v := range ctx.FakeConsul.services { 1302 switch v.Name { 1303 case ctx.Task.Services[0].Name: 1304 if v.Port != port { 1305 t.Errorf("expected service %s's port to be %d but found %d", 1306 v.Name, port, v.Port) 1307 } 1308 default: 1309 t.Errorf("unexpected service name: %q", v.Name) 1310 } 1311 } 1312 } 1313 1314 // Initial service should advertise host port x 1315 if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil { 1316 t.Fatalf("unexpected error registering task: %v", err) 1317 } 1318 1319 syncAndAssertPort(xPort) 1320 1321 // UpdateTask to use Host (shouldn't change anything) 1322 origTask := ctx.Task.Copy() 1323 ctx.Task.Services[0].AddressMode = structs.AddressModeHost 1324 1325 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 1326 t.Fatalf("unexpected error updating task: %v", err) 1327 } 1328 1329 syncAndAssertPort(xPort) 1330 1331 // UpdateTask to use Driver (*should* change IP and port) 1332 origTask = ctx.Task.Copy() 1333 ctx.Task.Services[0].AddressMode = structs.AddressModeDriver 1334 1335 if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil { 1336 t.Fatalf("unexpected error updating task: %v", err) 1337 } 1338 1339 syncAndAssertPort(ctx.Task.DriverNetwork.PortMap["x"]) 1340 } 1341 1342 // TestConsul_CanaryTags asserts CanaryTags are used when Canary=true 1343 func TestConsul_CanaryTags(t *testing.T) { 1344 t.Parallel() 1345 require := require.New(t) 1346 ctx := setupFake(t) 1347 1348 canaryTags := []string{"tag1", "canary"} 1349 ctx.Task.Canary = true 1350 ctx.Task.Services[0].CanaryTags = canaryTags 1351 1352 require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task)) 1353 require.NoError(ctx.syncOnce()) 1354 require.Len(ctx.FakeConsul.services, 1) 1355 for _, service := range ctx.FakeConsul.services { 1356 require.Equal(canaryTags, service.Tags) 1357 } 1358 1359 // Disable canary and assert tags are not the canary tags 1360 origTask := ctx.Task.Copy() 1361 ctx.Task.Canary = false 1362 require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task)) 1363 require.NoError(ctx.syncOnce()) 1364 require.Len(ctx.FakeConsul.services, 1) 1365 for _, service := range ctx.FakeConsul.services { 1366 require.NotEqual(canaryTags, service.Tags) 1367 } 1368 1369 ctx.ServiceClient.RemoveTask(ctx.Task) 1370 require.NoError(ctx.syncOnce()) 1371 require.Len(ctx.FakeConsul.services, 0) 1372 } 1373 1374 // TestConsul_CanaryTags_NoTags asserts Tags are used when Canary=true and there 1375 // are no specified canary tags 1376 func TestConsul_CanaryTags_NoTags(t *testing.T) { 1377 t.Parallel() 1378 require := require.New(t) 1379 ctx := setupFake(t) 1380 1381 tags := []string{"tag1", "foo"} 1382 ctx.Task.Canary = true 1383 ctx.Task.Services[0].Tags = tags 1384 1385 require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task)) 1386 require.NoError(ctx.syncOnce()) 1387 require.Len(ctx.FakeConsul.services, 1) 1388 for _, service := range ctx.FakeConsul.services { 1389 require.Equal(tags, service.Tags) 1390 } 1391 1392 // Disable canary and assert tags dont change 1393 origTask := ctx.Task.Copy() 1394 ctx.Task.Canary = false 1395 require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task)) 1396 require.NoError(ctx.syncOnce()) 1397 require.Len(ctx.FakeConsul.services, 1) 1398 for _, service := range ctx.FakeConsul.services { 1399 require.Equal(tags, service.Tags) 1400 } 1401 1402 ctx.ServiceClient.RemoveTask(ctx.Task) 1403 require.NoError(ctx.syncOnce()) 1404 require.Len(ctx.FakeConsul.services, 0) 1405 } 1406 1407 // TestConsul_PeriodicSync asserts that Nomad periodically reconciles with 1408 // Consul. 1409 func TestConsul_PeriodicSync(t *testing.T) { 1410 t.Parallel() 1411 1412 ctx := setupFake(t) 1413 defer ctx.ServiceClient.Shutdown() 1414 1415 // Lower periodic sync interval to speed up test 1416 ctx.ServiceClient.periodicInterval = 2 * time.Millisecond 1417 1418 // Run for 10ms and assert hits >= 5 because each sync() calls multiple 1419 // Consul APIs 1420 go ctx.ServiceClient.Run() 1421 1422 select { 1423 case <-ctx.ServiceClient.exitCh: 1424 t.Fatalf("exited unexpectedly") 1425 case <-time.After(10 * time.Millisecond): 1426 } 1427 1428 minHits := 5 1429 if hits := ctx.FakeConsul.getHits(); hits < minHits { 1430 t.Fatalf("expected at least %d hits but found %d", minHits, hits) 1431 } 1432 } 1433 1434 // TestIsNomadService asserts the isNomadService helper returns true for Nomad 1435 // task IDs and false for unknown IDs and Nomad agent IDs (see #2827). 1436 func TestIsNomadService(t *testing.T) { 1437 t.Parallel() 1438 1439 tests := []struct { 1440 id string 1441 result bool 1442 }{ 1443 {"_nomad-client-nomad-client-http", false}, 1444 {"_nomad-server-nomad-serf", false}, 1445 1446 // Pre-0.7.1 style IDs still match 1447 {"_nomad-executor-abc", true}, 1448 {"_nomad-executor", true}, 1449 1450 // Post-0.7.1 style IDs match 1451 {"_nomad-task-FBBK265QN4TMT25ND4EP42TJVMYJ3HR4", true}, 1452 1453 {"not-nomad", false}, 1454 {"_nomad", false}, 1455 } 1456 1457 for _, test := range tests { 1458 t.Run(test.id, func(t *testing.T) { 1459 actual := isNomadService(test.id) 1460 if actual != test.result { 1461 t.Errorf("%q should be %t but found %t", test.id, test.result, actual) 1462 } 1463 }) 1464 } 1465 } 1466 1467 // TestCreateCheckReg_HTTP asserts Nomad ServiceCheck structs are properly 1468 // converted to Consul API AgentCheckRegistrations for HTTP checks. 1469 func TestCreateCheckReg_HTTP(t *testing.T) { 1470 t.Parallel() 1471 check := &structs.ServiceCheck{ 1472 Name: "name", 1473 Type: "http", 1474 Path: "/path", 1475 PortLabel: "label", 1476 Method: "POST", 1477 Header: map[string][]string{ 1478 "Foo": {"bar"}, 1479 }, 1480 } 1481 1482 serviceID := "testService" 1483 checkID := check.Hash(serviceID) 1484 host := "localhost" 1485 port := 41111 1486 1487 expected := &api.AgentCheckRegistration{ 1488 ID: checkID, 1489 Name: "name", 1490 ServiceID: serviceID, 1491 AgentServiceCheck: api.AgentServiceCheck{ 1492 Timeout: "0s", 1493 Interval: "0s", 1494 HTTP: fmt.Sprintf("http://%s:%d/path", host, port), 1495 Method: "POST", 1496 Header: map[string][]string{ 1497 "Foo": {"bar"}, 1498 }, 1499 }, 1500 } 1501 1502 actual, err := createCheckReg(serviceID, checkID, check, host, port) 1503 if err != nil { 1504 t.Fatalf("err: %v", err) 1505 } 1506 1507 if diff := pretty.Diff(actual, expected); len(diff) > 0 { 1508 t.Fatalf("diff:\n%s\n", strings.Join(diff, "\n")) 1509 } 1510 } 1511 1512 // TestCreateCheckReg_GRPC asserts Nomad ServiceCheck structs are properly 1513 // converted to Consul API AgentCheckRegistrations for GRPC checks. 1514 func TestCreateCheckReg_GRPC(t *testing.T) { 1515 t.Parallel() 1516 check := &structs.ServiceCheck{ 1517 Name: "name", 1518 Type: "grpc", 1519 PortLabel: "label", 1520 GRPCService: "foo.Bar", 1521 GRPCUseTLS: true, 1522 TLSSkipVerify: true, 1523 Timeout: time.Second, 1524 Interval: time.Minute, 1525 } 1526 1527 serviceID := "testService" 1528 checkID := check.Hash(serviceID) 1529 1530 expected := &api.AgentCheckRegistration{ 1531 ID: checkID, 1532 Name: "name", 1533 ServiceID: serviceID, 1534 AgentServiceCheck: api.AgentServiceCheck{ 1535 Timeout: "1s", 1536 Interval: "1m0s", 1537 GRPC: "localhost:8080/foo.Bar", 1538 GRPCUseTLS: true, 1539 TLSSkipVerify: true, 1540 }, 1541 } 1542 1543 actual, err := createCheckReg(serviceID, checkID, check, "localhost", 8080) 1544 require.NoError(t, err) 1545 require.Equal(t, expected, actual) 1546 } 1547 1548 // TestGetAddress asserts Nomad uses the correct ip and port for services and 1549 // checks depending on port labels, driver networks, and address mode. 1550 func TestGetAddress(t *testing.T) { 1551 const HostIP = "127.0.0.1" 1552 1553 cases := []struct { 1554 Name string 1555 1556 // Parameters 1557 Mode string 1558 PortLabel string 1559 Host map[string]int // will be converted to structs.Networks 1560 Driver *drivers.DriverNetwork 1561 1562 // Results 1563 ExpectedIP string 1564 ExpectedPort int 1565 ExpectedErr string 1566 }{ 1567 // Valid Configurations 1568 { 1569 Name: "ExampleService", 1570 Mode: structs.AddressModeAuto, 1571 PortLabel: "db", 1572 Host: map[string]int{"db": 12435}, 1573 Driver: &drivers.DriverNetwork{ 1574 PortMap: map[string]int{"db": 6379}, 1575 IP: "10.1.2.3", 1576 }, 1577 ExpectedIP: HostIP, 1578 ExpectedPort: 12435, 1579 }, 1580 { 1581 Name: "Host", 1582 Mode: structs.AddressModeHost, 1583 PortLabel: "db", 1584 Host: map[string]int{"db": 12345}, 1585 Driver: &drivers.DriverNetwork{ 1586 PortMap: map[string]int{"db": 6379}, 1587 IP: "10.1.2.3", 1588 }, 1589 ExpectedIP: HostIP, 1590 ExpectedPort: 12345, 1591 }, 1592 { 1593 Name: "Driver", 1594 Mode: structs.AddressModeDriver, 1595 PortLabel: "db", 1596 Host: map[string]int{"db": 12345}, 1597 Driver: &drivers.DriverNetwork{ 1598 PortMap: map[string]int{"db": 6379}, 1599 IP: "10.1.2.3", 1600 }, 1601 ExpectedIP: "10.1.2.3", 1602 ExpectedPort: 6379, 1603 }, 1604 { 1605 Name: "AutoDriver", 1606 Mode: structs.AddressModeAuto, 1607 PortLabel: "db", 1608 Host: map[string]int{"db": 12345}, 1609 Driver: &drivers.DriverNetwork{ 1610 PortMap: map[string]int{"db": 6379}, 1611 IP: "10.1.2.3", 1612 AutoAdvertise: true, 1613 }, 1614 ExpectedIP: "10.1.2.3", 1615 ExpectedPort: 6379, 1616 }, 1617 { 1618 Name: "DriverCustomPort", 1619 Mode: structs.AddressModeDriver, 1620 PortLabel: "7890", 1621 Host: map[string]int{"db": 12345}, 1622 Driver: &drivers.DriverNetwork{ 1623 PortMap: map[string]int{"db": 6379}, 1624 IP: "10.1.2.3", 1625 }, 1626 ExpectedIP: "10.1.2.3", 1627 ExpectedPort: 7890, 1628 }, 1629 1630 // Invalid Configurations 1631 { 1632 Name: "DriverWithoutNetwork", 1633 Mode: structs.AddressModeDriver, 1634 PortLabel: "db", 1635 Host: map[string]int{"db": 12345}, 1636 Driver: nil, 1637 ExpectedErr: "no driver network exists", 1638 }, 1639 { 1640 Name: "DriverBadPort", 1641 Mode: structs.AddressModeDriver, 1642 PortLabel: "bad-port-label", 1643 Host: map[string]int{"db": 12345}, 1644 Driver: &drivers.DriverNetwork{ 1645 PortMap: map[string]int{"db": 6379}, 1646 IP: "10.1.2.3", 1647 }, 1648 ExpectedErr: "invalid port", 1649 }, 1650 { 1651 Name: "DriverZeroPort", 1652 Mode: structs.AddressModeDriver, 1653 PortLabel: "0", 1654 Driver: &drivers.DriverNetwork{ 1655 IP: "10.1.2.3", 1656 }, 1657 ExpectedErr: "invalid port", 1658 }, 1659 { 1660 Name: "HostBadPort", 1661 Mode: structs.AddressModeHost, 1662 PortLabel: "bad-port-label", 1663 ExpectedErr: "invalid port", 1664 }, 1665 { 1666 Name: "InvalidMode", 1667 Mode: "invalid-mode", 1668 PortLabel: "80", 1669 ExpectedErr: "invalid address mode", 1670 }, 1671 { 1672 Name: "NoPort_AutoMode", 1673 Mode: structs.AddressModeAuto, 1674 ExpectedIP: HostIP, 1675 }, 1676 { 1677 Name: "NoPort_HostMode", 1678 Mode: structs.AddressModeHost, 1679 ExpectedIP: HostIP, 1680 }, 1681 { 1682 Name: "NoPort_DriverMode", 1683 Mode: structs.AddressModeDriver, 1684 Driver: &drivers.DriverNetwork{ 1685 IP: "10.1.2.3", 1686 }, 1687 ExpectedIP: "10.1.2.3", 1688 }, 1689 } 1690 1691 for _, tc := range cases { 1692 t.Run(tc.Name, func(t *testing.T) { 1693 // convert host port map into a structs.Networks 1694 networks := []*structs.NetworkResource{ 1695 { 1696 IP: HostIP, 1697 ReservedPorts: make([]structs.Port, len(tc.Host)), 1698 }, 1699 } 1700 1701 i := 0 1702 for label, port := range tc.Host { 1703 networks[0].ReservedPorts[i].Label = label 1704 networks[0].ReservedPorts[i].Value = port 1705 i++ 1706 } 1707 1708 // Run getAddress 1709 ip, port, err := getAddress(tc.Mode, tc.PortLabel, networks, tc.Driver) 1710 1711 // Assert the results 1712 assert.Equal(t, tc.ExpectedIP, ip, "IP mismatch") 1713 assert.Equal(t, tc.ExpectedPort, port, "Port mismatch") 1714 if tc.ExpectedErr == "" { 1715 assert.Nil(t, err) 1716 } else { 1717 if err == nil { 1718 t.Fatalf("expected error containing %q but err=nil", tc.ExpectedErr) 1719 } else { 1720 assert.Contains(t, err.Error(), tc.ExpectedErr) 1721 } 1722 } 1723 }) 1724 } 1725 }