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