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