github.com/hernad/nomad@v1.6.112/command/agent/consul/unit_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package consul 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "strings" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/consul/api" 16 "github.com/hernad/nomad/ci" 17 "github.com/hernad/nomad/client/serviceregistration" 18 "github.com/hernad/nomad/helper/testlog" 19 "github.com/hernad/nomad/helper/uuid" 20 "github.com/hernad/nomad/nomad/structs" 21 "github.com/hernad/nomad/plugins/drivers" 22 "github.com/kr/pretty" 23 "github.com/shoenig/test/must" 24 "github.com/stretchr/testify/require" 25 ) 26 27 const ( 28 // Ports used in testWorkload 29 xPort = 1234 30 yPort = 1235 31 ) 32 33 func testWorkload() *serviceregistration.WorkloadServices { 34 return &serviceregistration.WorkloadServices{ 35 AllocInfo: structs.AllocInfo{ 36 AllocID: uuid.Generate(), 37 Task: "taskname", 38 }, 39 Restarter: &restartRecorder{}, 40 Services: []*structs.Service{ 41 { 42 Name: "taskname-service", 43 PortLabel: "x", 44 Tags: []string{"tag1", "tag2"}, 45 Meta: map[string]string{"meta1": "foo"}, 46 }, 47 }, 48 Networks: []*structs.NetworkResource{ 49 { 50 DynamicPorts: []structs.Port{ 51 {Label: "x", Value: xPort}, 52 {Label: "y", Value: yPort}, 53 }, 54 }, 55 }, 56 } 57 } 58 59 // restartRecorder is a minimal WorkloadRestarter implementation that simply 60 // counts how many restarts were triggered. 61 type restartRecorder struct { 62 restarts int64 63 } 64 65 func (r *restartRecorder) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error { 66 atomic.AddInt64(&r.restarts, 1) 67 return nil 68 } 69 70 // testFakeCtx contains a fake Consul AgentAPI 71 type testFakeCtx struct { 72 ServiceClient *ServiceClient 73 FakeConsul *MockAgent 74 Workload *serviceregistration.WorkloadServices 75 } 76 77 var errNoOps = fmt.Errorf("testing error: no pending operations") 78 79 // syncOps simulates one iteration of the ServiceClient.Run loop and returns 80 // any errors returned by sync() or errNoOps if no pending operations. 81 func (t *testFakeCtx) syncOnce(reason syncReason) error { 82 switch reason { 83 84 case syncPeriodic: 85 err := t.ServiceClient.sync(syncPeriodic) 86 if err == nil { 87 t.ServiceClient.clearExplicitlyDeregistered() 88 } 89 return err 90 91 case syncNewOps: 92 select { 93 case ops := <-t.ServiceClient.opCh: 94 t.ServiceClient.merge(ops) 95 err := t.ServiceClient.sync(syncNewOps) 96 if err == nil { 97 t.ServiceClient.clearExplicitlyDeregistered() 98 } 99 return err 100 default: 101 return errNoOps 102 } 103 104 case syncShutdown: 105 return errors.New("no test for sync due to shutdown") 106 } 107 108 return errors.New("bad sync reason") 109 } 110 111 // setupFake creates a testFakeCtx with a ServiceClient backed by a fakeConsul. 112 // A test Workload is also provided. 113 func setupFake(t *testing.T) *testFakeCtx { 114 agentClient := NewMockAgent(ossFeatures) 115 nsClient := NewNamespacesClient(NewMockNamespaces(nil), agentClient) 116 workload := testWorkload() 117 118 // by default start fake client being out of probation 119 serviceClient := NewServiceClient(agentClient, nsClient, testlog.HCLogger(t), true) 120 serviceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Minute) 121 122 return &testFakeCtx{ 123 ServiceClient: serviceClient, 124 FakeConsul: agentClient, 125 Workload: workload, 126 } 127 } 128 129 func TestConsul_ChangeTags(t *testing.T) { 130 ci.Parallel(t) 131 132 ctx := setupFake(t) 133 r := require.New(t) 134 135 r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 136 r.NoError(ctx.syncOnce(syncNewOps)) 137 r.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul") 138 139 // Validate the alloc registration 140 reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Workload.AllocInfo.AllocID) 141 r.NoError(err) 142 r.NotNil(reg1, "Unexpected nil alloc registration") 143 r.Equal(1, reg1.NumServices()) 144 r.Equal(0, reg1.NumChecks()) 145 146 serviceBefore := ctx.FakeConsul.lookupService("default", "taskname-service")[0] 147 r.Equal(serviceBefore.Name, ctx.Workload.Services[0].Name) 148 r.Equal(serviceBefore.Tags, ctx.Workload.Services[0].Tags) 149 150 // Update the task definition 151 origWorkload := ctx.Workload.Copy() 152 ctx.Workload.Services[0].Tags[0] = "new-tag" 153 154 // Register and sync the update 155 r.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 156 r.NoError(ctx.syncOnce(syncNewOps)) 157 r.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul") 158 159 // Validate the consul service definition changed 160 serviceAfter := ctx.FakeConsul.lookupService("default", "taskname-service")[0] 161 r.Equal(serviceAfter.Name, ctx.Workload.Services[0].Name) 162 r.Equal(serviceAfter.Tags, ctx.Workload.Services[0].Tags) 163 r.Equal("new-tag", serviceAfter.Tags[0]) 164 } 165 166 func TestConsul_EnableTagOverride_Syncs(t *testing.T) { 167 ci.Parallel(t) 168 169 ctx := setupFake(t) 170 r := require.New(t) 171 172 // Configure our test service to set EnableTagOverride = true 173 ctx.Workload.Services[0].EnableTagOverride = true 174 175 r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 176 r.NoError(ctx.syncOnce(syncNewOps)) 177 r.Equal(1, len(ctx.FakeConsul.services)) 178 179 // Validate the alloc registration 180 reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Workload.AllocInfo.AllocID) 181 r.NoError(err) 182 r.NotNil(reg1) 183 r.Equal(1, reg1.NumServices()) 184 r.Equal(0, reg1.NumChecks()) 185 186 const service = "taskname-service" 187 188 // check things are what we expect 189 consulServiceDefBefore := ctx.FakeConsul.lookupService("default", service)[0] 190 r.Equal(ctx.Workload.Services[0].Name, consulServiceDefBefore.Name) 191 r.Equal([]string{"tag1", "tag2"}, consulServiceDefBefore.Tags) 192 r.True(consulServiceDefBefore.EnableTagOverride) 193 194 // manually set the tags in consul 195 ctx.FakeConsul.lookupService("default", service)[0].Tags = []string{"new", "tags"} 196 197 // do a periodic sync (which will respect EnableTagOverride) 198 r.NoError(ctx.syncOnce(syncPeriodic)) 199 r.Equal(1, len(ctx.FakeConsul.services)) 200 consulServiceDefAfter := ctx.FakeConsul.lookupService("default", service)[0] 201 r.Equal([]string{"new", "tags"}, consulServiceDefAfter.Tags) // manually set tags should still be there 202 203 // now do a new-ops sync (which will override EnableTagOverride) 204 r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 205 r.NoError(ctx.syncOnce(syncNewOps)) 206 r.Equal(1, len(ctx.FakeConsul.services)) 207 consulServiceDefUpdated := ctx.FakeConsul.lookupService("default", service)[0] 208 r.Equal([]string{"tag1", "tag2"}, consulServiceDefUpdated.Tags) // jobspec tags should be set now 209 } 210 211 // TestConsul_ChangePorts asserts that changing the ports on a service updates 212 // it in Consul. Pre-0.7.1 ports were not part of the service ID and this was a 213 // slightly different code path than changing tags. 214 func TestConsul_ChangePorts(t *testing.T) { 215 ci.Parallel(t) 216 217 ctx := setupFake(t) 218 219 ctx.Workload.Services[0].Checks = []*structs.ServiceCheck{ 220 { 221 Name: "c1", 222 Type: "tcp", 223 Interval: time.Second, 224 Timeout: time.Second, 225 PortLabel: "x", 226 }, 227 { 228 Name: "c2", 229 Type: "script", 230 Interval: 9000 * time.Hour, 231 Timeout: time.Second, 232 }, 233 { 234 Name: "c3", 235 Type: "http", 236 Protocol: "http", 237 Path: "/", 238 Interval: time.Second, 239 Timeout: time.Second, 240 PortLabel: "y", 241 }, 242 } 243 244 must.NoError(t, ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 245 must.NoError(t, ctx.syncOnce(syncNewOps)) 246 must.MapLen(t, 1, ctx.FakeConsul.services["default"]) 247 248 for _, v := range ctx.FakeConsul.services["default"] { 249 must.Eq(t, ctx.Workload.Services[0].Name, v.Name) 250 must.Eq(t, ctx.Workload.Services[0].Tags, v.Tags) 251 must.Eq(t, xPort, v.Port) 252 } 253 254 must.MapLen(t, 3, ctx.FakeConsul.checks["default"], must.Sprintf("checks %#v", ctx.FakeConsul.checks)) 255 256 origTCPKey := "" 257 origScriptKey := "" 258 origHTTPKey := "" 259 for k, v := range ctx.FakeConsul.checks["default"] { 260 switch v.Name { 261 case "c1": 262 origTCPKey = k 263 must.Eq(t, fmt.Sprintf(":%d", xPort), v.TCP) 264 case "c2": 265 origScriptKey = k 266 case "c3": 267 origHTTPKey = k 268 must.Eq(t, fmt.Sprintf("http://:%d/", yPort), v.HTTP) 269 default: 270 t.Fatalf("unexpected check: %q", v.Name) 271 } 272 } 273 274 must.StrHasPrefix(t, "_nomad-check-", origTCPKey) 275 must.StrHasPrefix(t, "_nomad-check-", origScriptKey) 276 must.StrHasPrefix(t, "_nomad-check-", origHTTPKey) 277 278 // Now update the PortLabel on the Service and Check c3 279 origWorkload := ctx.Workload.Copy() 280 ctx.Workload.Services[0].PortLabel = "y" 281 ctx.Workload.Services[0].Checks = []*structs.ServiceCheck{ 282 { 283 Name: "c1", 284 Type: "tcp", 285 Interval: time.Second, 286 Timeout: time.Second, 287 PortLabel: "x", 288 }, 289 { 290 Name: "c2", 291 Type: "script", 292 Interval: 9000 * time.Hour, 293 Timeout: time.Second, 294 }, 295 { 296 Name: "c3", 297 Type: "http", 298 Protocol: "http", 299 Path: "/", 300 Interval: time.Second, 301 Timeout: time.Second, 302 // Removed PortLabel; should default to service's (y) 303 }, 304 } 305 306 must.NoError(t, ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 307 must.NoError(t, ctx.syncOnce(syncNewOps)) 308 must.MapLen(t, 1, ctx.FakeConsul.services["default"]) 309 310 for _, v := range ctx.FakeConsul.services["default"] { 311 must.Eq(t, ctx.Workload.Services[0].Name, v.Name) 312 must.Eq(t, ctx.Workload.Services[0].Tags, v.Tags) 313 must.Eq(t, yPort, v.Port) 314 } 315 must.MapLen(t, 3, ctx.FakeConsul.checks["default"]) 316 317 for k, v := range ctx.FakeConsul.checks["default"] { 318 switch v.Name { 319 case "c1": 320 // C1 is changed because the service was re-registered 321 must.NotEq(t, origTCPKey, k) 322 must.Eq(t, fmt.Sprintf(":%d", xPort), v.TCP) 323 case "c2": 324 // C2 is changed because the service was re-registered 325 must.NotEq(t, origScriptKey, k) 326 case "c3": 327 must.NotEq(t, origHTTPKey, k) 328 must.Eq(t, fmt.Sprintf("http://:%d/", yPort), v.HTTP) 329 default: 330 must.Unreachable(t, must.Sprintf("unknown check: %q", k)) 331 } 332 } 333 } 334 335 // TestConsul_ShutdownOK tests the ok path for the shutdown logic in 336 // ServiceClient. 337 func TestConsul_ShutdownOK(t *testing.T) { 338 ci.Parallel(t) 339 340 require := require.New(t) 341 ctx := setupFake(t) 342 go ctx.ServiceClient.Run() 343 344 // register the Nomad agent service and check 345 agentServices := []*structs.Service{ 346 { 347 Name: "http", 348 Tags: []string{"nomad"}, 349 PortLabel: "localhost:2345", 350 Checks: []*structs.ServiceCheck{ 351 { 352 Name: "nomad-tcp", 353 Type: "tcp", 354 Interval: 9000 * time.Hour, // make check block 355 Timeout: 10 * time.Second, 356 InitialStatus: "warning", 357 }, 358 }, 359 }, 360 } 361 require.NoError(ctx.ServiceClient.RegisterAgent("client", agentServices)) 362 require.Eventually(ctx.ServiceClient.hasSeen, time.Second, 10*time.Millisecond) 363 364 // assert successful registration 365 require.Len(ctx.FakeConsul.services["default"], 1, "expected agent service to be registered") 366 require.Len(ctx.FakeConsul.checks["default"], 1, "expected agent check to be registered") 367 require.Contains(ctx.FakeConsul.services["default"], makeAgentServiceID("client", agentServices[0])) 368 369 // Shutdown() should block until Nomad agent service/check is deregistered 370 require.NoError(ctx.ServiceClient.Shutdown()) 371 require.Len(ctx.FakeConsul.services["default"], 0, "expected agent service to be deregistered") 372 require.Len(ctx.FakeConsul.checks["default"], 0, "expected agent check to be deregistered") 373 } 374 375 // TestConsul_ShutdownBlocked tests the blocked past deadline path for the 376 // shutdown logic in ServiceClient. 377 func TestConsul_ShutdownBlocked(t *testing.T) { 378 ci.Parallel(t) 379 380 require := require.New(t) 381 ctx := setupFake(t) 382 // can be short because we're intentionally blocking, but needs to 383 // be longer than the time we'll block Consul so we can be sure 384 // we're not delayed either. 385 ctx.ServiceClient.shutdownWait = time.Second 386 go ctx.ServiceClient.Run() 387 388 // register the Nomad agent service and check 389 agentServices := []*structs.Service{ 390 { 391 Name: "http", 392 Tags: []string{"nomad"}, 393 PortLabel: "localhost:2345", 394 Checks: []*structs.ServiceCheck{ 395 { 396 Name: "nomad-tcp", 397 Type: "tcp", 398 Interval: 9000 * time.Hour, // make check block 399 Timeout: 10 * time.Second, 400 InitialStatus: "warning", 401 }, 402 }, 403 }, 404 } 405 require.NoError(ctx.ServiceClient.RegisterAgent("client", agentServices)) 406 require.Eventually(ctx.ServiceClient.hasSeen, time.Second, 10*time.Millisecond) 407 require.Len(ctx.FakeConsul.services["default"], 1, "expected agent service to be registered") 408 require.Len(ctx.FakeConsul.checks["default"], 1, "expected agent check to be registered") 409 410 // prevent normal shutdown by blocking Consul. the shutdown should wait 411 // until agent deregistration has finished 412 waiter := make(chan struct{}) 413 result := make(chan error) 414 go func() { 415 ctx.FakeConsul.mu.Lock() 416 close(waiter) 417 result <- ctx.ServiceClient.Shutdown() 418 }() 419 420 <-waiter // wait for lock to be hit 421 422 // Shutdown should block until all enqueued operations finish. 423 preShutdown := time.Now() 424 select { 425 case <-time.After(200 * time.Millisecond): 426 ctx.FakeConsul.mu.Unlock() 427 require.NoError(<-result) 428 case <-result: 429 t.Fatal("should not have received result until Consul unblocked") 430 } 431 shutdownTime := time.Now().Sub(preShutdown).Seconds() 432 require.Less(shutdownTime, time.Second.Seconds(), 433 "expected shutdown to take >200ms and <1s") 434 require.Greater(shutdownTime, 200*time.Millisecond.Seconds(), 435 "expected shutdown to take >200ms and <1s") 436 require.Len(ctx.FakeConsul.services["default"], 0, 437 "expected agent service to be deregistered") 438 require.Len(ctx.FakeConsul.checks["default"], 0, 439 "expected agent check to be deregistered") 440 } 441 442 // TestConsul_DriverNetwork_AutoUse asserts that if a driver network has 443 // auto-use set then services should advertise it unless explicitly set to 444 // host. Checks should always use host. 445 func TestConsul_DriverNetwork_AutoUse(t *testing.T) { 446 ci.Parallel(t) 447 448 ctx := setupFake(t) 449 450 ctx.Workload.Services = []*structs.Service{ 451 { 452 Name: "auto-advertise-x", 453 PortLabel: "x", 454 AddressMode: structs.AddressModeAuto, 455 Checks: []*structs.ServiceCheck{ 456 { 457 Name: "default-check-x", 458 Type: "tcp", 459 Interval: time.Second, 460 Timeout: time.Second, 461 }, 462 { 463 Name: "weird-y-check", 464 Type: "http", 465 Interval: time.Second, 466 Timeout: time.Second, 467 PortLabel: "y", 468 }, 469 }, 470 }, 471 { 472 Name: "driver-advertise-y", 473 PortLabel: "y", 474 AddressMode: structs.AddressModeDriver, 475 Checks: []*structs.ServiceCheck{ 476 { 477 Name: "default-check-y", 478 Type: "tcp", 479 Interval: time.Second, 480 Timeout: time.Second, 481 }, 482 }, 483 }, 484 { 485 Name: "host-advertise-y", 486 PortLabel: "y", 487 AddressMode: structs.AddressModeHost, 488 }, 489 } 490 491 ctx.Workload.DriverNetwork = &drivers.DriverNetwork{ 492 PortMap: map[string]int{ 493 "x": 8888, 494 "y": 9999, 495 }, 496 IP: "172.18.0.2", 497 AutoAdvertise: true, 498 } 499 500 if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil { 501 t.Fatalf("unexpected error registering task: %v", err) 502 } 503 504 if err := ctx.syncOnce(syncNewOps); err != nil { 505 t.Fatalf("unexpected error syncing task: %v", err) 506 } 507 508 if n := len(ctx.FakeConsul.services["default"]); n != 3 { 509 t.Fatalf("expected 2 services but found: %d", n) 510 } 511 512 for _, v := range ctx.FakeConsul.services["default"] { 513 switch v.Name { 514 case ctx.Workload.Services[0].Name: // x 515 // Since DriverNetwork.AutoAdvertise=true, driver ports should be used 516 if v.Port != ctx.Workload.DriverNetwork.PortMap["x"] { 517 t.Errorf("expected service %s's port to be %d but found %d", 518 v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port) 519 } 520 // The order of checks in Consul is not guaranteed to 521 // be the same as their order in the Workload definition, 522 // so check in a loop 523 if expected := 2; len(v.Checks) != expected { 524 t.Errorf("expected %d checks but found %d", expected, len(v.Checks)) 525 } 526 for _, c := range v.Checks { 527 // No name on AgentServiceChecks, use type 528 switch { 529 case c.TCP != "": 530 // Checks should always use host port though 531 if c.TCP != ":1234" { // xPort 532 t.Errorf("expected service %s check 1's port to be %d but found %q", 533 v.Name, xPort, c.TCP) 534 } 535 case c.HTTP != "": 536 if c.HTTP != "http://:1235" { // yPort 537 t.Errorf("expected service %s check 2's port to be %d but found %q", 538 v.Name, yPort, c.HTTP) 539 } 540 default: 541 t.Errorf("unexpected check %#v on service %q", c, v.Name) 542 } 543 } 544 case ctx.Workload.Services[1].Name: // y 545 // Service should be container ip:port 546 if v.Address != ctx.Workload.DriverNetwork.IP { 547 t.Errorf("expected service %s's address to be %s but found %s", 548 v.Name, ctx.Workload.DriverNetwork.IP, v.Address) 549 } 550 if v.Port != ctx.Workload.DriverNetwork.PortMap["y"] { 551 t.Errorf("expected service %s's port to be %d but found %d", 552 v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port) 553 } 554 // Check should be host ip:port 555 if v.Checks[0].TCP != ":1235" { // yPort 556 t.Errorf("expected service %s check's port to be %d but found %s", 557 v.Name, yPort, v.Checks[0].TCP) 558 } 559 case ctx.Workload.Services[2].Name: // y + host mode 560 if v.Port != yPort { 561 t.Errorf("expected service %s's port to be %d but found %d", 562 v.Name, yPort, v.Port) 563 } 564 default: 565 t.Errorf("unexpected service name: %q", v.Name) 566 } 567 } 568 } 569 570 // TestConsul_DriverNetwork_NoAutoUse asserts that if a driver network doesn't 571 // set auto-use only services which request the driver's network should 572 // advertise it. 573 func TestConsul_DriverNetwork_NoAutoUse(t *testing.T) { 574 ci.Parallel(t) 575 576 ctx := setupFake(t) 577 578 ctx.Workload.Services = []*structs.Service{ 579 { 580 Name: "auto-advertise-x", 581 PortLabel: "x", 582 AddressMode: structs.AddressModeAuto, 583 }, 584 { 585 Name: "driver-advertise-y", 586 PortLabel: "y", 587 AddressMode: structs.AddressModeDriver, 588 }, 589 { 590 Name: "host-advertise-y", 591 PortLabel: "y", 592 AddressMode: structs.AddressModeHost, 593 }, 594 } 595 596 ctx.Workload.DriverNetwork = &drivers.DriverNetwork{ 597 PortMap: map[string]int{ 598 "x": 8888, 599 "y": 9999, 600 }, 601 IP: "172.18.0.2", 602 AutoAdvertise: false, 603 } 604 605 if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil { 606 t.Fatalf("unexpected error registering task: %v", err) 607 } 608 609 if err := ctx.syncOnce(syncNewOps); err != nil { 610 t.Fatalf("unexpected error syncing task: %v", err) 611 } 612 613 if n := len(ctx.FakeConsul.services["default"]); n != 3 { 614 t.Fatalf("expected 3 services but found: %d", n) 615 } 616 617 for _, v := range ctx.FakeConsul.services["default"] { 618 switch v.Name { 619 case ctx.Workload.Services[0].Name: // x + auto 620 // Since DriverNetwork.AutoAdvertise=false, host ports should be used 621 if v.Port != xPort { 622 t.Errorf("expected service %s's port to be %d but found %d", 623 v.Name, xPort, v.Port) 624 } 625 case ctx.Workload.Services[1].Name: // y + driver mode 626 // Service should be container ip:port 627 if v.Address != ctx.Workload.DriverNetwork.IP { 628 t.Errorf("expected service %s's address to be %s but found %s", 629 v.Name, ctx.Workload.DriverNetwork.IP, v.Address) 630 } 631 if v.Port != ctx.Workload.DriverNetwork.PortMap["y"] { 632 t.Errorf("expected service %s's port to be %d but found %d", 633 v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port) 634 } 635 case ctx.Workload.Services[2].Name: // y + host mode 636 if v.Port != yPort { 637 t.Errorf("expected service %s's port to be %d but found %d", 638 v.Name, yPort, v.Port) 639 } 640 default: 641 t.Errorf("unexpected service name: %q", v.Name) 642 } 643 } 644 } 645 646 // TestConsul_DriverNetwork_Change asserts that if a driver network is 647 // specified and a service updates its use its properly updated in Consul. 648 func TestConsul_DriverNetwork_Change(t *testing.T) { 649 ci.Parallel(t) 650 651 ctx := setupFake(t) 652 653 ctx.Workload.Services = []*structs.Service{ 654 { 655 Name: "service-foo", 656 PortLabel: "x", 657 AddressMode: structs.AddressModeAuto, 658 }, 659 } 660 661 ctx.Workload.DriverNetwork = &drivers.DriverNetwork{ 662 PortMap: map[string]int{ 663 "x": 8888, 664 "y": 9999, 665 }, 666 IP: "172.18.0.2", 667 AutoAdvertise: false, 668 } 669 670 syncAndAssertPort := func(port int) { 671 if err := ctx.syncOnce(syncNewOps); err != nil { 672 t.Fatalf("unexpected error syncing task: %v", err) 673 } 674 675 if n := len(ctx.FakeConsul.services["default"]); n != 1 { 676 t.Fatalf("expected 1 service but found: %d", n) 677 } 678 679 for _, v := range ctx.FakeConsul.services["default"] { 680 switch v.Name { 681 case ctx.Workload.Services[0].Name: 682 if v.Port != port { 683 t.Errorf("expected service %s's port to be %d but found %d", 684 v.Name, port, v.Port) 685 } 686 default: 687 t.Errorf("unexpected service name: %q", v.Name) 688 } 689 } 690 } 691 692 // Initial service should advertise host port x 693 if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil { 694 t.Fatalf("unexpected error registering task: %v", err) 695 } 696 697 syncAndAssertPort(xPort) 698 699 // UpdateWorkload to use Host (shouldn't change anything) 700 origWorkload := ctx.Workload.Copy() 701 ctx.Workload.Services[0].AddressMode = structs.AddressModeHost 702 703 if err := ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload); err != nil { 704 t.Fatalf("unexpected error updating task: %v", err) 705 } 706 707 syncAndAssertPort(xPort) 708 709 // UpdateWorkload to use Driver (*should* change IP and port) 710 origWorkload = ctx.Workload.Copy() 711 ctx.Workload.Services[0].AddressMode = structs.AddressModeDriver 712 713 if err := ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload); err != nil { 714 t.Fatalf("unexpected error updating task: %v", err) 715 } 716 717 syncAndAssertPort(ctx.Workload.DriverNetwork.PortMap["x"]) 718 } 719 720 // TestConsul_CanaryTags asserts CanaryTags are used when Canary=true 721 func TestConsul_CanaryTags(t *testing.T) { 722 ci.Parallel(t) 723 724 require := require.New(t) 725 ctx := setupFake(t) 726 727 canaryTags := []string{"tag1", "canary"} 728 ctx.Workload.Canary = true 729 ctx.Workload.Services[0].CanaryTags = canaryTags 730 731 require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 732 require.NoError(ctx.syncOnce(syncNewOps)) 733 require.Len(ctx.FakeConsul.services["default"], 1) 734 for _, service := range ctx.FakeConsul.services["default"] { 735 require.Equal(canaryTags, service.Tags) 736 } 737 738 // Disable canary and assert tags are not the canary tags 739 origWorkload := ctx.Workload.Copy() 740 ctx.Workload.Canary = false 741 require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 742 require.NoError(ctx.syncOnce(syncNewOps)) 743 require.Len(ctx.FakeConsul.services["default"], 1) 744 for _, service := range ctx.FakeConsul.services["default"] { 745 require.NotEqual(canaryTags, service.Tags) 746 } 747 748 ctx.ServiceClient.RemoveWorkload(ctx.Workload) 749 require.NoError(ctx.syncOnce(syncNewOps)) 750 require.Len(ctx.FakeConsul.services["default"], 0) 751 } 752 753 // TestConsul_CanaryTags_NoTags asserts Tags are used when Canary=true and there 754 // are no specified canary tags 755 func TestConsul_CanaryTags_NoTags(t *testing.T) { 756 ci.Parallel(t) 757 758 require := require.New(t) 759 ctx := setupFake(t) 760 761 tags := []string{"tag1", "foo"} 762 ctx.Workload.Canary = true 763 ctx.Workload.Services[0].Tags = tags 764 765 require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 766 require.NoError(ctx.syncOnce(syncNewOps)) 767 require.Len(ctx.FakeConsul.services["default"], 1) 768 for _, service := range ctx.FakeConsul.services["default"] { 769 require.Equal(tags, service.Tags) 770 } 771 772 // Disable canary and assert tags dont change 773 origWorkload := ctx.Workload.Copy() 774 ctx.Workload.Canary = false 775 require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 776 require.NoError(ctx.syncOnce(syncNewOps)) 777 require.Len(ctx.FakeConsul.services["default"], 1) 778 for _, service := range ctx.FakeConsul.services["default"] { 779 require.Equal(tags, service.Tags) 780 } 781 782 ctx.ServiceClient.RemoveWorkload(ctx.Workload) 783 require.NoError(ctx.syncOnce(syncNewOps)) 784 require.Len(ctx.FakeConsul.services["default"], 0) 785 } 786 787 // TestConsul_CanaryMeta asserts CanaryMeta are used when Canary=true 788 func TestConsul_CanaryMeta(t *testing.T) { 789 ci.Parallel(t) 790 791 require := require.New(t) 792 ctx := setupFake(t) 793 794 canaryMeta := map[string]string{"meta1": "canary"} 795 canaryMeta["external-source"] = "nomad" 796 ctx.Workload.Canary = true 797 ctx.Workload.Services[0].CanaryMeta = canaryMeta 798 799 require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 800 require.NoError(ctx.syncOnce(syncNewOps)) 801 require.Len(ctx.FakeConsul.services["default"], 1) 802 for _, service := range ctx.FakeConsul.services["default"] { 803 require.Equal(canaryMeta, service.Meta) 804 } 805 806 // Disable canary and assert meta are not the canary meta 807 origWorkload := ctx.Workload.Copy() 808 ctx.Workload.Canary = false 809 require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 810 require.NoError(ctx.syncOnce(syncNewOps)) 811 require.Len(ctx.FakeConsul.services["default"], 1) 812 for _, service := range ctx.FakeConsul.services["default"] { 813 require.NotEqual(canaryMeta, service.Meta) 814 } 815 816 ctx.ServiceClient.RemoveWorkload(ctx.Workload) 817 require.NoError(ctx.syncOnce(syncNewOps)) 818 require.Len(ctx.FakeConsul.services["default"], 0) 819 } 820 821 // TestConsul_CanaryMeta_NoMeta asserts Meta are used when Canary=true and there 822 // are no specified canary meta 823 func TestConsul_CanaryMeta_NoMeta(t *testing.T) { 824 ci.Parallel(t) 825 826 require := require.New(t) 827 ctx := setupFake(t) 828 829 meta := map[string]string{"meta1": "foo"} 830 meta["external-source"] = "nomad" 831 ctx.Workload.Canary = true 832 ctx.Workload.Services[0].Meta = meta 833 834 require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 835 require.NoError(ctx.syncOnce(syncNewOps)) 836 require.Len(ctx.FakeConsul.services["default"], 1) 837 for _, service := range ctx.FakeConsul.services["default"] { 838 require.Equal(meta, service.Meta) 839 } 840 841 // Disable canary and assert meta dont change 842 origWorkload := ctx.Workload.Copy() 843 ctx.Workload.Canary = false 844 require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload)) 845 require.NoError(ctx.syncOnce(syncNewOps)) 846 require.Len(ctx.FakeConsul.services["default"], 1) 847 for _, service := range ctx.FakeConsul.services["default"] { 848 require.Equal(meta, service.Meta) 849 } 850 851 ctx.ServiceClient.RemoveWorkload(ctx.Workload) 852 require.NoError(ctx.syncOnce(syncNewOps)) 853 require.Len(ctx.FakeConsul.services["default"], 0) 854 } 855 856 // TestConsul_PeriodicSync asserts that Nomad periodically reconciles with 857 // Consul. 858 func TestConsul_PeriodicSync(t *testing.T) { 859 ci.Parallel(t) 860 861 ctx := setupFake(t) 862 defer ctx.ServiceClient.Shutdown() 863 864 // Lower periodic sync interval to speed up test 865 ctx.ServiceClient.periodicInterval = 1 * time.Millisecond 866 867 // Run for 20ms and assert hits >= 5 because each sync() calls multiple 868 // Consul APIs 869 go ctx.ServiceClient.Run() 870 871 select { 872 case <-ctx.ServiceClient.exitCh: 873 t.Fatalf("exited unexpectedly") 874 case <-time.After(20 * time.Millisecond): 875 } 876 877 minHits := 5 878 if hits := ctx.FakeConsul.getHits(); hits < minHits { 879 t.Fatalf("expected at least %d hits but found %d", minHits, hits) 880 } 881 } 882 883 // TestIsNomadService asserts the isNomadService helper returns true for Nomad 884 // task IDs and false for unknown IDs and Nomad agent IDs (see #2827). 885 func TestIsNomadService(t *testing.T) { 886 ci.Parallel(t) 887 888 tests := []struct { 889 id string 890 result bool 891 }{ 892 {"_nomad-client-nomad-client-http", false}, 893 {"_nomad-server-nomad-serf", false}, 894 895 // Pre-0.7.1 style IDs still match 896 {"_nomad-executor-abc", true}, 897 {"_nomad-executor", true}, 898 899 // Post-0.7.1 style IDs match 900 {"_nomad-task-FBBK265QN4TMT25ND4EP42TJVMYJ3HR4", true}, 901 902 {"not-nomad", false}, 903 {"_nomad", false}, 904 } 905 906 for _, test := range tests { 907 t.Run(test.id, func(t *testing.T) { 908 actual := isNomadService(test.id) 909 if actual != test.result { 910 t.Errorf("%q should be %t but found %t", test.id, test.result, actual) 911 } 912 }) 913 } 914 } 915 916 // TestCreateCheckReg_HTTP asserts Nomad ServiceCheck structs are properly 917 // converted to Consul API AgentCheckRegistrations for HTTP checks. 918 func TestCreateCheckReg_HTTP(t *testing.T) { 919 ci.Parallel(t) 920 921 check := &structs.ServiceCheck{ 922 Name: "name", 923 Type: "http", 924 Path: "/path", 925 PortLabel: "label", 926 Method: "POST", 927 Header: map[string][]string{ 928 "Foo": {"bar"}, 929 }, 930 } 931 932 serviceID := "testService" 933 checkID := check.Hash(serviceID) 934 host := "localhost" 935 port := 41111 936 namespace := "" 937 938 expected := &api.AgentCheckRegistration{ 939 Namespace: namespace, 940 ID: checkID, 941 Name: "name", 942 ServiceID: serviceID, 943 AgentServiceCheck: api.AgentServiceCheck{ 944 Timeout: "0s", 945 Interval: "0s", 946 HTTP: fmt.Sprintf("http://%s:%d/path", host, port), 947 Method: "POST", 948 Header: map[string][]string{ 949 "Foo": {"bar"}, 950 }, 951 }, 952 } 953 954 actual, err := createCheckReg(serviceID, checkID, check, host, port, namespace) 955 if err != nil { 956 t.Fatalf("err: %v", err) 957 } 958 959 if diff := pretty.Diff(actual, expected); len(diff) > 0 { 960 t.Fatalf("diff:\n%s\n", strings.Join(diff, "\n")) 961 } 962 } 963 964 // TestCreateCheckReg_GRPC asserts Nomad ServiceCheck structs are properly 965 // converted to Consul API AgentCheckRegistrations for GRPC checks. 966 func TestCreateCheckReg_GRPC(t *testing.T) { 967 ci.Parallel(t) 968 969 check := &structs.ServiceCheck{ 970 Name: "name", 971 Type: "grpc", 972 PortLabel: "label", 973 GRPCService: "foo.Bar", 974 GRPCUseTLS: true, 975 TLSServerName: "localhost", 976 TLSSkipVerify: true, 977 Timeout: time.Second, 978 Interval: time.Minute, 979 } 980 981 serviceID := "testService" 982 checkID := check.Hash(serviceID) 983 984 expected := &api.AgentCheckRegistration{ 985 Namespace: "", 986 ID: checkID, 987 Name: check.Name, 988 ServiceID: serviceID, 989 AgentServiceCheck: api.AgentServiceCheck{ 990 Timeout: "1s", 991 Interval: "1m0s", 992 GRPC: "127.0.0.1:8080/foo.Bar", 993 GRPCUseTLS: true, 994 TLSServerName: "localhost", 995 TLSSkipVerify: true, 996 }, 997 } 998 999 actual, err := createCheckReg(serviceID, checkID, check, "127.0.0.1", 8080, "default") 1000 must.NoError(t, err) 1001 must.Eq(t, expected, actual) 1002 } 1003 1004 func TestConsul_ServiceName_Duplicates(t *testing.T) { 1005 ci.Parallel(t) 1006 ctx := setupFake(t) 1007 1008 ctx.Workload.Services = []*structs.Service{ 1009 { 1010 Name: "best-service", 1011 PortLabel: "x", 1012 Tags: []string{"foo"}, 1013 Checks: []*structs.ServiceCheck{ 1014 { 1015 Name: "check-a", 1016 Type: "tcp", 1017 Interval: time.Second, 1018 Timeout: time.Second, 1019 }, 1020 }, 1021 }, 1022 { 1023 Name: "best-service", 1024 PortLabel: "y", 1025 Tags: []string{"bar"}, 1026 Checks: []*structs.ServiceCheck{ 1027 { 1028 Name: "check-b", 1029 Type: "tcp", 1030 Interval: time.Second, 1031 Timeout: time.Second, 1032 }, 1033 }, 1034 }, 1035 { 1036 Name: "worst-service", 1037 PortLabel: "y", 1038 }, 1039 } 1040 1041 must.NoError(t, ctx.ServiceClient.RegisterWorkload(ctx.Workload)) 1042 must.NoError(t, ctx.syncOnce(syncNewOps)) 1043 must.MapLen(t, 3, ctx.FakeConsul.services["default"]) 1044 1045 for _, s := range ctx.FakeConsul.services["default"] { 1046 switch { 1047 case s.Name == "best-service" && s.Port == xPort: 1048 must.SliceContainsAll(t, s.Tags, ctx.Workload.Services[0].Tags) 1049 must.SliceLen(t, 1, s.Checks) 1050 case s.Name == "best-service" && s.Port == yPort: 1051 must.SliceContainsAll(t, s.Tags, ctx.Workload.Services[1].Tags) 1052 must.SliceLen(t, 1, s.Checks) 1053 case s.Name == "worst-service": 1054 must.SliceEmpty(t, s.Checks) 1055 } 1056 } 1057 } 1058 1059 // TestConsul_ServiceDeregistration_OutOfProbation asserts that during in steady 1060 // state we remove any services we don't reconize locally 1061 func TestConsul_ServiceDeregistration_OutProbation(t *testing.T) { 1062 ci.Parallel(t) 1063 1064 ctx := setupFake(t) 1065 require := require.New(t) 1066 1067 ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour) 1068 1069 remainingWorkload := testWorkload() 1070 remainingWorkload.Services = []*structs.Service{ 1071 { 1072 Name: "remaining-service", 1073 PortLabel: "x", 1074 Checks: []*structs.ServiceCheck{ 1075 { 1076 Name: "check", 1077 Type: "tcp", 1078 Interval: time.Second, 1079 Timeout: time.Second, 1080 }, 1081 }, 1082 }, 1083 } 1084 remainingWorkloadServiceID := serviceregistration.MakeAllocServiceID(remainingWorkload.AllocInfo.AllocID, 1085 remainingWorkload.Name(), remainingWorkload.Services[0]) 1086 1087 require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload)) 1088 require.NoError(ctx.syncOnce(syncNewOps)) 1089 require.Len(ctx.FakeConsul.services, 1) 1090 require.Len(ctx.FakeConsul.checks, 1) 1091 1092 explicitlyRemovedWorkload := testWorkload() 1093 explicitlyRemovedWorkload.Services = []*structs.Service{ 1094 { 1095 Name: "explicitly-removed-service", 1096 PortLabel: "y", 1097 Checks: []*structs.ServiceCheck{ 1098 { 1099 Name: "check", 1100 Type: "tcp", 1101 Interval: time.Second, 1102 Timeout: time.Second, 1103 }, 1104 }, 1105 }, 1106 } 1107 explicitlyRemovedWorkloadServiceID := serviceregistration.MakeAllocServiceID(explicitlyRemovedWorkload.AllocInfo.AllocID, 1108 explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0]) 1109 1110 require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload)) 1111 1112 require.NoError(ctx.syncOnce(syncNewOps)) 1113 require.Len(ctx.FakeConsul.services["default"], 2) 1114 require.Len(ctx.FakeConsul.checks["default"], 2) 1115 1116 // we register a task through nomad API then remove it out of band 1117 outofbandWorkload := testWorkload() 1118 outofbandWorkload.Services = []*structs.Service{ 1119 { 1120 Name: "unknown-service", 1121 PortLabel: "x", 1122 Checks: []*structs.ServiceCheck{ 1123 { 1124 Name: "check", 1125 Type: "tcp", 1126 Interval: time.Second, 1127 Timeout: time.Second, 1128 }, 1129 }, 1130 }, 1131 } 1132 outofbandWorkloadServiceID := serviceregistration.MakeAllocServiceID(outofbandWorkload.AllocInfo.AllocID, 1133 outofbandWorkload.Name(), outofbandWorkload.Services[0]) 1134 1135 require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload)) 1136 require.NoError(ctx.syncOnce(syncNewOps)) 1137 1138 require.Len(ctx.FakeConsul.services["default"], 3) 1139 1140 // remove outofbandWorkload from local services so it appears unknown to client 1141 require.Len(ctx.ServiceClient.services, 3) 1142 require.Len(ctx.ServiceClient.checks, 3) 1143 1144 delete(ctx.ServiceClient.services, outofbandWorkloadServiceID) 1145 delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0])) 1146 1147 require.Len(ctx.ServiceClient.services, 2) 1148 require.Len(ctx.ServiceClient.checks, 2) 1149 1150 // Sync and ensure that explicitly removed service as well as outofbandWorkload were removed 1151 1152 ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload) 1153 require.NoError(ctx.syncOnce(syncNewOps)) 1154 require.NoError(ctx.ServiceClient.sync(syncNewOps)) 1155 require.Len(ctx.FakeConsul.services["default"], 1) 1156 require.Len(ctx.FakeConsul.checks["default"], 1) 1157 1158 require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID) 1159 require.NotContains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID) 1160 require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID) 1161 1162 require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0])) 1163 require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0])) 1164 require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0])) 1165 } 1166 1167 // TestConsul_ServiceDeregistration_InProbation asserts that during initialization 1168 // we only deregister services that were explicitly removed and leave unknown 1169 // services untouched. This adds a grace period for restoring recovered tasks 1170 // before deregistering them 1171 func TestConsul_ServiceDeregistration_InProbation(t *testing.T) { 1172 ci.Parallel(t) 1173 1174 ctx := setupFake(t) 1175 require := require.New(t) 1176 1177 ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(1 * time.Hour) 1178 1179 remainingWorkload := testWorkload() 1180 remainingWorkload.Services = []*structs.Service{ 1181 { 1182 Name: "remaining-service", 1183 PortLabel: "x", 1184 Checks: []*structs.ServiceCheck{ 1185 { 1186 Name: "check", 1187 Type: "tcp", 1188 Interval: time.Second, 1189 Timeout: time.Second, 1190 }, 1191 }, 1192 }, 1193 } 1194 remainingWorkloadServiceID := serviceregistration.MakeAllocServiceID(remainingWorkload.AllocInfo.AllocID, 1195 remainingWorkload.Name(), remainingWorkload.Services[0]) 1196 1197 require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload)) 1198 require.NoError(ctx.syncOnce(syncNewOps)) 1199 require.Len(ctx.FakeConsul.services, 1) 1200 require.Len(ctx.FakeConsul.checks, 1) 1201 1202 explicitlyRemovedWorkload := testWorkload() 1203 explicitlyRemovedWorkload.Services = []*structs.Service{ 1204 { 1205 Name: "explicitly-removed-service", 1206 PortLabel: "y", 1207 Checks: []*structs.ServiceCheck{ 1208 { 1209 Name: "check", 1210 Type: "tcp", 1211 Interval: time.Second, 1212 Timeout: time.Second, 1213 }, 1214 }, 1215 }, 1216 } 1217 explicitlyRemovedWorkloadServiceID := serviceregistration.MakeAllocServiceID(explicitlyRemovedWorkload.AllocInfo.AllocID, 1218 explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0]) 1219 1220 require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload)) 1221 1222 require.NoError(ctx.syncOnce(syncNewOps)) 1223 require.Len(ctx.FakeConsul.services["default"], 2) 1224 require.Len(ctx.FakeConsul.checks["default"], 2) 1225 1226 // we register a task through nomad API then remove it out of band 1227 outofbandWorkload := testWorkload() 1228 outofbandWorkload.Services = []*structs.Service{ 1229 { 1230 Name: "unknown-service", 1231 PortLabel: "x", 1232 Checks: []*structs.ServiceCheck{ 1233 { 1234 Name: "check", 1235 Type: "tcp", 1236 Interval: time.Second, 1237 Timeout: time.Second, 1238 }, 1239 }, 1240 }, 1241 } 1242 outofbandWorkloadServiceID := serviceregistration.MakeAllocServiceID(outofbandWorkload.AllocInfo.AllocID, 1243 outofbandWorkload.Name(), outofbandWorkload.Services[0]) 1244 1245 require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload)) 1246 require.NoError(ctx.syncOnce(syncNewOps)) 1247 1248 require.Len(ctx.FakeConsul.services["default"], 3) 1249 1250 // remove outofbandWorkload from local services so it appears unknown to client 1251 require.Len(ctx.ServiceClient.services, 3) 1252 require.Len(ctx.ServiceClient.checks, 3) 1253 1254 delete(ctx.ServiceClient.services, outofbandWorkloadServiceID) 1255 delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0])) 1256 1257 require.Len(ctx.ServiceClient.services, 2) 1258 require.Len(ctx.ServiceClient.checks, 2) 1259 1260 // Sync and ensure that explicitly removed service was removed, but outofbandWorkload remains 1261 1262 ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload) 1263 require.NoError(ctx.syncOnce(syncNewOps)) 1264 require.NoError(ctx.ServiceClient.sync(syncNewOps)) 1265 require.Len(ctx.FakeConsul.services["default"], 2) 1266 require.Len(ctx.FakeConsul.checks["default"], 2) 1267 1268 require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID) 1269 require.Contains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID) 1270 require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID) 1271 1272 require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0])) 1273 require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0])) 1274 require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0])) 1275 1276 // after probation, outofband services and checks are removed 1277 ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour) 1278 1279 require.NoError(ctx.ServiceClient.sync(syncNewOps)) 1280 require.Len(ctx.FakeConsul.services["default"], 1) 1281 require.Len(ctx.FakeConsul.checks["default"], 1) 1282 1283 require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID) 1284 require.NotContains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID) 1285 require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID) 1286 1287 require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0])) 1288 require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0])) 1289 require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0])) 1290 }