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