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