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