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