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