github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/nomad/structs/structs_test.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/consul/api" 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/nomad/helper/uuid" 14 "github.com/kr/pretty" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 func TestJob_Validate(t *testing.T) { 19 j := &Job{} 20 err := j.Validate() 21 mErr := err.(*multierror.Error) 22 if !strings.Contains(mErr.Errors[0].Error(), "job region") { 23 t.Fatalf("err: %s", err) 24 } 25 if !strings.Contains(mErr.Errors[1].Error(), "job ID") { 26 t.Fatalf("err: %s", err) 27 } 28 if !strings.Contains(mErr.Errors[2].Error(), "job name") { 29 t.Fatalf("err: %s", err) 30 } 31 if !strings.Contains(mErr.Errors[3].Error(), "namespace") { 32 t.Fatalf("err: %s", err) 33 } 34 if !strings.Contains(mErr.Errors[4].Error(), "job type") { 35 t.Fatalf("err: %s", err) 36 } 37 if !strings.Contains(mErr.Errors[5].Error(), "priority") { 38 t.Fatalf("err: %s", err) 39 } 40 if !strings.Contains(mErr.Errors[6].Error(), "datacenters") { 41 t.Fatalf("err: %s", err) 42 } 43 if !strings.Contains(mErr.Errors[7].Error(), "task groups") { 44 t.Fatalf("err: %s", err) 45 } 46 47 j = &Job{ 48 Type: "invalid-job-type", 49 } 50 err = j.Validate() 51 if expected := `Invalid job type: "invalid-job-type"`; !strings.Contains(err.Error(), expected) { 52 t.Errorf("expected %s but found: %v", expected, err) 53 } 54 55 j = &Job{ 56 Type: JobTypeService, 57 Periodic: &PeriodicConfig{ 58 Enabled: true, 59 }, 60 } 61 err = j.Validate() 62 mErr = err.(*multierror.Error) 63 if !strings.Contains(mErr.Error(), "Periodic") { 64 t.Fatalf("err: %s", err) 65 } 66 67 j = &Job{ 68 Region: "global", 69 ID: uuid.Generate(), 70 Namespace: "test", 71 Name: "my-job", 72 Type: JobTypeService, 73 Priority: 50, 74 Datacenters: []string{"dc1"}, 75 TaskGroups: []*TaskGroup{ 76 { 77 Name: "web", 78 RestartPolicy: &RestartPolicy{ 79 Interval: 5 * time.Minute, 80 Delay: 10 * time.Second, 81 Attempts: 10, 82 }, 83 }, 84 { 85 Name: "web", 86 RestartPolicy: &RestartPolicy{ 87 Interval: 5 * time.Minute, 88 Delay: 10 * time.Second, 89 Attempts: 10, 90 }, 91 }, 92 { 93 RestartPolicy: &RestartPolicy{ 94 Interval: 5 * time.Minute, 95 Delay: 10 * time.Second, 96 Attempts: 10, 97 }, 98 }, 99 }, 100 } 101 err = j.Validate() 102 mErr = err.(*multierror.Error) 103 if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") { 104 t.Fatalf("err: %s", err) 105 } 106 if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") { 107 t.Fatalf("err: %s", err) 108 } 109 if !strings.Contains(mErr.Errors[2].Error(), "Task group web validation failed") { 110 t.Fatalf("err: %s", err) 111 } 112 } 113 114 func TestJob_Warnings(t *testing.T) { 115 cases := []struct { 116 Name string 117 Job *Job 118 Expected []string 119 }{ 120 { 121 Name: "Higher counts for update stanza", 122 Expected: []string{"max parallel count is greater"}, 123 Job: &Job{ 124 Type: JobTypeService, 125 TaskGroups: []*TaskGroup{ 126 { 127 Name: "foo", 128 Count: 2, 129 Update: &UpdateStrategy{ 130 MaxParallel: 10, 131 }, 132 }, 133 }, 134 }, 135 }, 136 } 137 138 for _, c := range cases { 139 t.Run(c.Name, func(t *testing.T) { 140 warnings := c.Job.Warnings() 141 if warnings == nil { 142 if len(c.Expected) == 0 { 143 return 144 } else { 145 t.Fatal("Got no warnings when they were expected") 146 } 147 } 148 149 a := warnings.Error() 150 for _, e := range c.Expected { 151 if !strings.Contains(a, e) { 152 t.Fatalf("Got warnings %q; didn't contain %q", a, e) 153 } 154 } 155 }) 156 } 157 } 158 159 func TestJob_Canonicalize_Update(t *testing.T) { 160 cases := []struct { 161 Name string 162 Job *Job 163 Expected *Job 164 Warnings []string 165 }{ 166 { 167 Name: "One task group", 168 Warnings: []string{"conversion to new update stanza"}, 169 Job: &Job{ 170 Namespace: "test", 171 Type: JobTypeService, 172 Update: UpdateStrategy{ 173 MaxParallel: 2, 174 Stagger: 10 * time.Second, 175 }, 176 TaskGroups: []*TaskGroup{ 177 { 178 Name: "foo", 179 Count: 2, 180 }, 181 }, 182 }, 183 Expected: &Job{ 184 Namespace: "test", 185 Type: JobTypeService, 186 Update: UpdateStrategy{ 187 MaxParallel: 2, 188 Stagger: 10 * time.Second, 189 }, 190 TaskGroups: []*TaskGroup{ 191 { 192 Name: "foo", 193 Count: 2, 194 RestartPolicy: NewRestartPolicy(JobTypeService), 195 EphemeralDisk: DefaultEphemeralDisk(), 196 Update: &UpdateStrategy{ 197 Stagger: 30 * time.Second, 198 MaxParallel: 2, 199 HealthCheck: UpdateStrategyHealthCheck_Checks, 200 MinHealthyTime: 10 * time.Second, 201 HealthyDeadline: 5 * time.Minute, 202 AutoRevert: false, 203 Canary: 0, 204 }, 205 }, 206 }, 207 }, 208 }, 209 { 210 Name: "One task group batch", 211 Warnings: []string{"Update stanza is disallowed for batch jobs"}, 212 Job: &Job{ 213 Namespace: "test", 214 Type: JobTypeBatch, 215 Update: UpdateStrategy{ 216 MaxParallel: 2, 217 Stagger: 10 * time.Second, 218 }, 219 TaskGroups: []*TaskGroup{ 220 { 221 Name: "foo", 222 Count: 2, 223 }, 224 }, 225 }, 226 Expected: &Job{ 227 Namespace: "test", 228 Type: JobTypeBatch, 229 Update: UpdateStrategy{}, 230 TaskGroups: []*TaskGroup{ 231 { 232 Name: "foo", 233 Count: 2, 234 RestartPolicy: NewRestartPolicy(JobTypeBatch), 235 EphemeralDisk: DefaultEphemeralDisk(), 236 }, 237 }, 238 }, 239 }, 240 { 241 Name: "One task group batch - new spec", 242 Warnings: []string{"Update stanza is disallowed for batch jobs"}, 243 Job: &Job{ 244 Namespace: "test", 245 Type: JobTypeBatch, 246 Update: UpdateStrategy{ 247 Stagger: 2 * time.Second, 248 MaxParallel: 2, 249 Canary: 2, 250 MinHealthyTime: 2 * time.Second, 251 HealthyDeadline: 10 * time.Second, 252 HealthCheck: UpdateStrategyHealthCheck_Checks, 253 }, 254 TaskGroups: []*TaskGroup{ 255 { 256 Name: "foo", 257 Count: 2, 258 Update: &UpdateStrategy{ 259 Stagger: 2 * time.Second, 260 MaxParallel: 2, 261 Canary: 2, 262 MinHealthyTime: 2 * time.Second, 263 HealthyDeadline: 10 * time.Second, 264 HealthCheck: UpdateStrategyHealthCheck_Checks, 265 }, 266 }, 267 }, 268 }, 269 Expected: &Job{ 270 Namespace: "test", 271 Type: JobTypeBatch, 272 Update: UpdateStrategy{}, 273 TaskGroups: []*TaskGroup{ 274 { 275 Name: "foo", 276 Count: 2, 277 RestartPolicy: NewRestartPolicy(JobTypeBatch), 278 EphemeralDisk: DefaultEphemeralDisk(), 279 }, 280 }, 281 }, 282 }, 283 { 284 Name: "One task group service - new spec", 285 Job: &Job{ 286 Namespace: "test", 287 Type: JobTypeService, 288 Update: UpdateStrategy{ 289 Stagger: 2 * time.Second, 290 MaxParallel: 2, 291 Canary: 2, 292 MinHealthyTime: 2 * time.Second, 293 HealthyDeadline: 10 * time.Second, 294 HealthCheck: UpdateStrategyHealthCheck_Checks, 295 }, 296 TaskGroups: []*TaskGroup{ 297 { 298 Name: "foo", 299 Count: 2, 300 Update: &UpdateStrategy{ 301 Stagger: 2 * time.Second, 302 MaxParallel: 2, 303 Canary: 2, 304 MinHealthyTime: 2 * time.Second, 305 HealthyDeadline: 10 * time.Second, 306 HealthCheck: UpdateStrategyHealthCheck_Checks, 307 }, 308 }, 309 }, 310 }, 311 Expected: &Job{ 312 Namespace: "test", 313 Type: JobTypeService, 314 Update: UpdateStrategy{ 315 Stagger: 2 * time.Second, 316 MaxParallel: 2, 317 Canary: 2, 318 MinHealthyTime: 2 * time.Second, 319 HealthyDeadline: 10 * time.Second, 320 HealthCheck: UpdateStrategyHealthCheck_Checks, 321 }, 322 TaskGroups: []*TaskGroup{ 323 { 324 Name: "foo", 325 Count: 2, 326 RestartPolicy: NewRestartPolicy(JobTypeService), 327 EphemeralDisk: DefaultEphemeralDisk(), 328 Update: &UpdateStrategy{ 329 Stagger: 2 * time.Second, 330 MaxParallel: 2, 331 Canary: 2, 332 MinHealthyTime: 2 * time.Second, 333 HealthyDeadline: 10 * time.Second, 334 HealthCheck: UpdateStrategyHealthCheck_Checks, 335 }, 336 }, 337 }, 338 }, 339 }, 340 { 341 Name: "One task group; too high of parallelism", 342 Warnings: []string{"conversion to new update stanza"}, 343 Job: &Job{ 344 Namespace: "test", 345 Type: JobTypeService, 346 Update: UpdateStrategy{ 347 MaxParallel: 200, 348 Stagger: 10 * time.Second, 349 }, 350 TaskGroups: []*TaskGroup{ 351 { 352 Name: "foo", 353 Count: 2, 354 }, 355 }, 356 }, 357 Expected: &Job{ 358 Namespace: "test", 359 Type: JobTypeService, 360 Update: UpdateStrategy{ 361 MaxParallel: 200, 362 Stagger: 10 * time.Second, 363 }, 364 TaskGroups: []*TaskGroup{ 365 { 366 Name: "foo", 367 Count: 2, 368 RestartPolicy: NewRestartPolicy(JobTypeService), 369 EphemeralDisk: DefaultEphemeralDisk(), 370 Update: &UpdateStrategy{ 371 Stagger: 30 * time.Second, 372 MaxParallel: 2, 373 HealthCheck: UpdateStrategyHealthCheck_Checks, 374 MinHealthyTime: 10 * time.Second, 375 HealthyDeadline: 5 * time.Minute, 376 AutoRevert: false, 377 Canary: 0, 378 }, 379 }, 380 }, 381 }, 382 }, 383 { 384 Name: "Multiple task group; rounding", 385 Warnings: []string{"conversion to new update stanza"}, 386 Job: &Job{ 387 Namespace: "test", 388 Type: JobTypeService, 389 Update: UpdateStrategy{ 390 MaxParallel: 2, 391 Stagger: 10 * time.Second, 392 }, 393 TaskGroups: []*TaskGroup{ 394 { 395 Name: "foo", 396 Count: 2, 397 }, 398 { 399 Name: "bar", 400 Count: 14, 401 }, 402 { 403 Name: "foo", 404 Count: 26, 405 }, 406 }, 407 }, 408 Expected: &Job{ 409 Namespace: "test", 410 Type: JobTypeService, 411 Update: UpdateStrategy{ 412 MaxParallel: 2, 413 Stagger: 10 * time.Second, 414 }, 415 TaskGroups: []*TaskGroup{ 416 { 417 Name: "foo", 418 Count: 2, 419 RestartPolicy: NewRestartPolicy(JobTypeService), 420 EphemeralDisk: DefaultEphemeralDisk(), 421 Update: &UpdateStrategy{ 422 Stagger: 30 * time.Second, 423 MaxParallel: 1, 424 HealthCheck: UpdateStrategyHealthCheck_Checks, 425 MinHealthyTime: 10 * time.Second, 426 HealthyDeadline: 5 * time.Minute, 427 AutoRevert: false, 428 Canary: 0, 429 }, 430 }, 431 { 432 Name: "bar", 433 Count: 14, 434 RestartPolicy: NewRestartPolicy(JobTypeService), 435 EphemeralDisk: DefaultEphemeralDisk(), 436 Update: &UpdateStrategy{ 437 Stagger: 30 * time.Second, 438 MaxParallel: 1, 439 HealthCheck: UpdateStrategyHealthCheck_Checks, 440 MinHealthyTime: 10 * time.Second, 441 HealthyDeadline: 5 * time.Minute, 442 AutoRevert: false, 443 Canary: 0, 444 }, 445 }, 446 { 447 Name: "foo", 448 Count: 26, 449 EphemeralDisk: DefaultEphemeralDisk(), 450 RestartPolicy: NewRestartPolicy(JobTypeService), 451 Update: &UpdateStrategy{ 452 Stagger: 30 * time.Second, 453 MaxParallel: 3, 454 HealthCheck: UpdateStrategyHealthCheck_Checks, 455 MinHealthyTime: 10 * time.Second, 456 HealthyDeadline: 5 * time.Minute, 457 AutoRevert: false, 458 Canary: 0, 459 }, 460 }, 461 }, 462 }, 463 }, 464 } 465 466 for _, c := range cases { 467 t.Run(c.Name, func(t *testing.T) { 468 warnings := c.Job.Canonicalize() 469 if !reflect.DeepEqual(c.Job, c.Expected) { 470 t.Fatalf("Diff %#v", pretty.Diff(c.Job, c.Expected)) 471 } 472 473 wErr := "" 474 if warnings != nil { 475 wErr = warnings.Error() 476 } 477 for _, w := range c.Warnings { 478 if !strings.Contains(wErr, w) { 479 t.Fatalf("Wanted warning %q: got %q", w, wErr) 480 } 481 } 482 483 if len(c.Warnings) == 0 && warnings != nil { 484 t.Fatalf("Wanted no warnings: got %q", wErr) 485 } 486 }) 487 } 488 } 489 490 func TestJob_SpecChanged(t *testing.T) { 491 // Get a base test job 492 base := testJob() 493 494 // Only modify the indexes/mutable state of the job 495 mutatedBase := base.Copy() 496 mutatedBase.Status = "foo" 497 mutatedBase.ModifyIndex = base.ModifyIndex + 100 498 499 // changed contains a spec change that should be detected 500 change := base.Copy() 501 change.Priority = 99 502 503 cases := []struct { 504 Name string 505 Original *Job 506 New *Job 507 Changed bool 508 }{ 509 { 510 Name: "Same job except mutable indexes", 511 Changed: false, 512 Original: base, 513 New: mutatedBase, 514 }, 515 { 516 Name: "Different", 517 Changed: true, 518 Original: base, 519 New: change, 520 }, 521 } 522 523 for _, c := range cases { 524 t.Run(c.Name, func(t *testing.T) { 525 if actual := c.Original.SpecChanged(c.New); actual != c.Changed { 526 t.Fatalf("SpecChanged() returned %v; want %v", actual, c.Changed) 527 } 528 }) 529 } 530 } 531 532 func testJob() *Job { 533 return &Job{ 534 Region: "global", 535 ID: uuid.Generate(), 536 Namespace: "test", 537 Name: "my-job", 538 Type: JobTypeService, 539 Priority: 50, 540 AllAtOnce: false, 541 Datacenters: []string{"dc1"}, 542 Constraints: []*Constraint{ 543 { 544 LTarget: "$attr.kernel.name", 545 RTarget: "linux", 546 Operand: "=", 547 }, 548 }, 549 Periodic: &PeriodicConfig{ 550 Enabled: false, 551 }, 552 TaskGroups: []*TaskGroup{ 553 { 554 Name: "web", 555 Count: 10, 556 EphemeralDisk: DefaultEphemeralDisk(), 557 RestartPolicy: &RestartPolicy{ 558 Mode: RestartPolicyModeFail, 559 Attempts: 3, 560 Interval: 10 * time.Minute, 561 Delay: 1 * time.Minute, 562 }, 563 Tasks: []*Task{ 564 { 565 Name: "web", 566 Driver: "exec", 567 Config: map[string]interface{}{ 568 "command": "/bin/date", 569 }, 570 Env: map[string]string{ 571 "FOO": "bar", 572 }, 573 Artifacts: []*TaskArtifact{ 574 { 575 GetterSource: "http://foo.com", 576 }, 577 }, 578 Services: []*Service{ 579 { 580 Name: "${TASK}-frontend", 581 PortLabel: "http", 582 }, 583 }, 584 Resources: &Resources{ 585 CPU: 500, 586 MemoryMB: 256, 587 Networks: []*NetworkResource{ 588 { 589 MBits: 50, 590 DynamicPorts: []Port{{Label: "http"}}, 591 }, 592 }, 593 }, 594 LogConfig: &LogConfig{ 595 MaxFiles: 10, 596 MaxFileSizeMB: 1, 597 }, 598 }, 599 }, 600 Meta: map[string]string{ 601 "elb_check_type": "http", 602 "elb_check_interval": "30s", 603 "elb_check_min": "3", 604 }, 605 }, 606 }, 607 Meta: map[string]string{ 608 "owner": "armon", 609 }, 610 } 611 } 612 613 func TestJob_Copy(t *testing.T) { 614 j := testJob() 615 c := j.Copy() 616 if !reflect.DeepEqual(j, c) { 617 t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j) 618 } 619 } 620 621 func TestJob_IsPeriodic(t *testing.T) { 622 j := &Job{ 623 Type: JobTypeService, 624 Periodic: &PeriodicConfig{ 625 Enabled: true, 626 }, 627 } 628 if !j.IsPeriodic() { 629 t.Fatalf("IsPeriodic() returned false on periodic job") 630 } 631 632 j = &Job{ 633 Type: JobTypeService, 634 } 635 if j.IsPeriodic() { 636 t.Fatalf("IsPeriodic() returned true on non-periodic job") 637 } 638 } 639 640 func TestJob_IsPeriodicActive(t *testing.T) { 641 cases := []struct { 642 job *Job 643 active bool 644 }{ 645 { 646 job: &Job{ 647 Type: JobTypeService, 648 Periodic: &PeriodicConfig{ 649 Enabled: true, 650 }, 651 }, 652 active: true, 653 }, 654 { 655 job: &Job{ 656 Type: JobTypeService, 657 Periodic: &PeriodicConfig{ 658 Enabled: false, 659 }, 660 }, 661 active: false, 662 }, 663 { 664 job: &Job{ 665 Type: JobTypeService, 666 Periodic: &PeriodicConfig{ 667 Enabled: true, 668 }, 669 Stop: true, 670 }, 671 active: false, 672 }, 673 { 674 job: &Job{ 675 Type: JobTypeService, 676 Periodic: &PeriodicConfig{ 677 Enabled: false, 678 }, 679 ParameterizedJob: &ParameterizedJobConfig{}, 680 }, 681 active: false, 682 }, 683 } 684 685 for i, c := range cases { 686 if act := c.job.IsPeriodicActive(); act != c.active { 687 t.Fatalf("case %d failed: got %v; want %v", i, act, c.active) 688 } 689 } 690 } 691 692 func TestJob_SystemJob_Validate(t *testing.T) { 693 j := testJob() 694 j.Type = JobTypeSystem 695 j.Canonicalize() 696 697 err := j.Validate() 698 if err == nil || !strings.Contains(err.Error(), "exceed") { 699 t.Fatalf("expect error due to count") 700 } 701 702 j.TaskGroups[0].Count = 0 703 if err := j.Validate(); err != nil { 704 t.Fatalf("unexpected err: %v", err) 705 } 706 707 j.TaskGroups[0].Count = 1 708 if err := j.Validate(); err != nil { 709 t.Fatalf("unexpected err: %v", err) 710 } 711 } 712 713 func TestJob_VaultPolicies(t *testing.T) { 714 j0 := &Job{} 715 e0 := make(map[string]map[string]*Vault, 0) 716 717 vj1 := &Vault{ 718 Policies: []string{ 719 "p1", 720 "p2", 721 }, 722 } 723 vj2 := &Vault{ 724 Policies: []string{ 725 "p3", 726 "p4", 727 }, 728 } 729 vj3 := &Vault{ 730 Policies: []string{ 731 "p5", 732 }, 733 } 734 j1 := &Job{ 735 TaskGroups: []*TaskGroup{ 736 { 737 Name: "foo", 738 Tasks: []*Task{ 739 { 740 Name: "t1", 741 }, 742 { 743 Name: "t2", 744 Vault: vj1, 745 }, 746 }, 747 }, 748 { 749 Name: "bar", 750 Tasks: []*Task{ 751 { 752 Name: "t3", 753 Vault: vj2, 754 }, 755 { 756 Name: "t4", 757 Vault: vj3, 758 }, 759 }, 760 }, 761 }, 762 } 763 764 e1 := map[string]map[string]*Vault{ 765 "foo": { 766 "t2": vj1, 767 }, 768 "bar": { 769 "t3": vj2, 770 "t4": vj3, 771 }, 772 } 773 774 cases := []struct { 775 Job *Job 776 Expected map[string]map[string]*Vault 777 }{ 778 { 779 Job: j0, 780 Expected: e0, 781 }, 782 { 783 Job: j1, 784 Expected: e1, 785 }, 786 } 787 788 for i, c := range cases { 789 got := c.Job.VaultPolicies() 790 if !reflect.DeepEqual(got, c.Expected) { 791 t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected) 792 } 793 } 794 } 795 796 func TestJob_RequiredSignals(t *testing.T) { 797 j0 := &Job{} 798 e0 := make(map[string]map[string][]string, 0) 799 800 vj1 := &Vault{ 801 Policies: []string{"p1"}, 802 ChangeMode: VaultChangeModeNoop, 803 } 804 vj2 := &Vault{ 805 Policies: []string{"p1"}, 806 ChangeMode: VaultChangeModeSignal, 807 ChangeSignal: "SIGUSR1", 808 } 809 tj1 := &Template{ 810 SourcePath: "foo", 811 DestPath: "bar", 812 ChangeMode: TemplateChangeModeNoop, 813 } 814 tj2 := &Template{ 815 SourcePath: "foo", 816 DestPath: "bar", 817 ChangeMode: TemplateChangeModeSignal, 818 ChangeSignal: "SIGUSR2", 819 } 820 j1 := &Job{ 821 TaskGroups: []*TaskGroup{ 822 { 823 Name: "foo", 824 Tasks: []*Task{ 825 { 826 Name: "t1", 827 }, 828 { 829 Name: "t2", 830 Vault: vj2, 831 Templates: []*Template{tj2}, 832 }, 833 }, 834 }, 835 { 836 Name: "bar", 837 Tasks: []*Task{ 838 { 839 Name: "t3", 840 Vault: vj1, 841 Templates: []*Template{tj1}, 842 }, 843 { 844 Name: "t4", 845 Vault: vj2, 846 }, 847 }, 848 }, 849 }, 850 } 851 852 e1 := map[string]map[string][]string{ 853 "foo": { 854 "t2": {"SIGUSR1", "SIGUSR2"}, 855 }, 856 "bar": { 857 "t4": {"SIGUSR1"}, 858 }, 859 } 860 861 j2 := &Job{ 862 TaskGroups: []*TaskGroup{ 863 { 864 Name: "foo", 865 Tasks: []*Task{ 866 { 867 Name: "t1", 868 KillSignal: "SIGQUIT", 869 }, 870 }, 871 }, 872 }, 873 } 874 875 e2 := map[string]map[string][]string{ 876 "foo": { 877 "t1": {"SIGQUIT"}, 878 }, 879 } 880 881 cases := []struct { 882 Job *Job 883 Expected map[string]map[string][]string 884 }{ 885 { 886 Job: j0, 887 Expected: e0, 888 }, 889 { 890 Job: j1, 891 Expected: e1, 892 }, 893 { 894 Job: j2, 895 Expected: e2, 896 }, 897 } 898 899 for i, c := range cases { 900 got := c.Job.RequiredSignals() 901 if !reflect.DeepEqual(got, c.Expected) { 902 t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected) 903 } 904 } 905 } 906 907 func TestTaskGroup_Validate(t *testing.T) { 908 j := testJob() 909 tg := &TaskGroup{ 910 Count: -1, 911 RestartPolicy: &RestartPolicy{ 912 Interval: 5 * time.Minute, 913 Delay: 10 * time.Second, 914 Attempts: 10, 915 Mode: RestartPolicyModeDelay, 916 }, 917 } 918 err := tg.Validate(j) 919 mErr := err.(*multierror.Error) 920 if !strings.Contains(mErr.Errors[0].Error(), "group name") { 921 t.Fatalf("err: %s", err) 922 } 923 if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") { 924 t.Fatalf("err: %s", err) 925 } 926 if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") { 927 t.Fatalf("err: %s", err) 928 } 929 930 tg = &TaskGroup{ 931 Tasks: []*Task{ 932 { 933 Name: "task-a", 934 Resources: &Resources{ 935 Networks: []*NetworkResource{ 936 { 937 ReservedPorts: []Port{{Label: "foo", Value: 123}}, 938 }, 939 }, 940 }, 941 }, 942 { 943 Name: "task-b", 944 Resources: &Resources{ 945 Networks: []*NetworkResource{ 946 { 947 ReservedPorts: []Port{{Label: "foo", Value: 123}}, 948 }, 949 }, 950 }, 951 }, 952 }, 953 } 954 err = tg.Validate(&Job{}) 955 expected := `Static port 123 already reserved by task-a:foo` 956 if !strings.Contains(err.Error(), expected) { 957 t.Errorf("expected %s but found: %v", expected, err) 958 } 959 960 tg = &TaskGroup{ 961 Tasks: []*Task{ 962 { 963 Name: "task-a", 964 Resources: &Resources{ 965 Networks: []*NetworkResource{ 966 { 967 ReservedPorts: []Port{ 968 {Label: "foo", Value: 123}, 969 {Label: "bar", Value: 123}, 970 }, 971 }, 972 }, 973 }, 974 }, 975 }, 976 } 977 err = tg.Validate(&Job{}) 978 expected = `Static port 123 already reserved by task-a:foo` 979 if !strings.Contains(err.Error(), expected) { 980 t.Errorf("expected %s but found: %v", expected, err) 981 } 982 983 tg = &TaskGroup{ 984 Name: "web", 985 Count: 1, 986 Tasks: []*Task{ 987 {Name: "web", Leader: true}, 988 {Name: "web", Leader: true}, 989 {}, 990 }, 991 RestartPolicy: &RestartPolicy{ 992 Interval: 5 * time.Minute, 993 Delay: 10 * time.Second, 994 Attempts: 10, 995 Mode: RestartPolicyModeDelay, 996 }, 997 } 998 999 err = tg.Validate(j) 1000 mErr = err.(*multierror.Error) 1001 if !strings.Contains(mErr.Errors[0].Error(), "should have an ephemeral disk object") { 1002 t.Fatalf("err: %s", err) 1003 } 1004 if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") { 1005 t.Fatalf("err: %s", err) 1006 } 1007 if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") { 1008 t.Fatalf("err: %s", err) 1009 } 1010 if !strings.Contains(mErr.Errors[3].Error(), "Only one task may be marked as leader") { 1011 t.Fatalf("err: %s", err) 1012 } 1013 if !strings.Contains(mErr.Errors[4].Error(), "Task web validation failed") { 1014 t.Fatalf("err: %s", err) 1015 } 1016 1017 // COMPAT: Enable in 0.7.0 1018 //j.Type = JobTypeBatch 1019 //err = tg.Validate(j) 1020 //if !strings.Contains(err.Error(), "does not allow update block") { 1021 //t.Fatalf("err: %s", err) 1022 //} 1023 } 1024 1025 func TestTask_Validate(t *testing.T) { 1026 task := &Task{} 1027 ephemeralDisk := DefaultEphemeralDisk() 1028 err := task.Validate(ephemeralDisk) 1029 mErr := err.(*multierror.Error) 1030 if !strings.Contains(mErr.Errors[0].Error(), "task name") { 1031 t.Fatalf("err: %s", err) 1032 } 1033 if !strings.Contains(mErr.Errors[1].Error(), "task driver") { 1034 t.Fatalf("err: %s", err) 1035 } 1036 if !strings.Contains(mErr.Errors[2].Error(), "task resources") { 1037 t.Fatalf("err: %s", err) 1038 } 1039 1040 task = &Task{Name: "web/foo"} 1041 err = task.Validate(ephemeralDisk) 1042 mErr = err.(*multierror.Error) 1043 if !strings.Contains(mErr.Errors[0].Error(), "slashes") { 1044 t.Fatalf("err: %s", err) 1045 } 1046 1047 task = &Task{ 1048 Name: "web", 1049 Driver: "docker", 1050 Resources: &Resources{ 1051 CPU: 100, 1052 MemoryMB: 100, 1053 IOPS: 10, 1054 }, 1055 LogConfig: DefaultLogConfig(), 1056 } 1057 ephemeralDisk.SizeMB = 200 1058 err = task.Validate(ephemeralDisk) 1059 if err != nil { 1060 t.Fatalf("err: %s", err) 1061 } 1062 1063 task.Constraints = append(task.Constraints, 1064 &Constraint{ 1065 Operand: ConstraintDistinctHosts, 1066 }, 1067 &Constraint{ 1068 Operand: ConstraintDistinctProperty, 1069 LTarget: "${meta.rack}", 1070 }) 1071 1072 err = task.Validate(ephemeralDisk) 1073 mErr = err.(*multierror.Error) 1074 if !strings.Contains(mErr.Errors[0].Error(), "task level: distinct_hosts") { 1075 t.Fatalf("err: %s", err) 1076 } 1077 if !strings.Contains(mErr.Errors[1].Error(), "task level: distinct_property") { 1078 t.Fatalf("err: %s", err) 1079 } 1080 } 1081 1082 func TestTask_Validate_Services(t *testing.T) { 1083 s1 := &Service{ 1084 Name: "service-name", 1085 PortLabel: "bar", 1086 Checks: []*ServiceCheck{ 1087 { 1088 Name: "check-name", 1089 Type: ServiceCheckTCP, 1090 Interval: 0 * time.Second, 1091 }, 1092 { 1093 Name: "check-name", 1094 Type: ServiceCheckTCP, 1095 Timeout: 2 * time.Second, 1096 }, 1097 { 1098 Name: "check-name", 1099 Type: ServiceCheckTCP, 1100 Interval: 1 * time.Second, 1101 }, 1102 }, 1103 } 1104 1105 s2 := &Service{ 1106 Name: "service-name", 1107 PortLabel: "bar", 1108 } 1109 1110 s3 := &Service{ 1111 Name: "service-A", 1112 PortLabel: "a", 1113 } 1114 s4 := &Service{ 1115 Name: "service-A", 1116 PortLabel: "b", 1117 } 1118 1119 ephemeralDisk := DefaultEphemeralDisk() 1120 ephemeralDisk.SizeMB = 200 1121 task := &Task{ 1122 Name: "web", 1123 Driver: "docker", 1124 Resources: &Resources{ 1125 CPU: 100, 1126 MemoryMB: 100, 1127 IOPS: 10, 1128 }, 1129 Services: []*Service{s1, s2}, 1130 } 1131 1132 task1 := &Task{ 1133 Name: "web", 1134 Driver: "docker", 1135 Resources: DefaultResources(), 1136 Services: []*Service{s3, s4}, 1137 LogConfig: DefaultLogConfig(), 1138 } 1139 task1.Resources.Networks = []*NetworkResource{ 1140 { 1141 MBits: 10, 1142 DynamicPorts: []Port{ 1143 { 1144 Label: "a", 1145 Value: 1000, 1146 }, 1147 { 1148 Label: "b", 1149 Value: 2000, 1150 }, 1151 }, 1152 }, 1153 } 1154 1155 err := task.Validate(ephemeralDisk) 1156 if err == nil { 1157 t.Fatal("expected an error") 1158 } 1159 1160 if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") { 1161 t.Fatalf("err: %v", err) 1162 } 1163 1164 if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") { 1165 t.Fatalf("err: %v", err) 1166 } 1167 1168 if !strings.Contains(err.Error(), "missing required value interval") { 1169 t.Fatalf("err: %v", err) 1170 } 1171 1172 if !strings.Contains(err.Error(), "cannot be less than") { 1173 t.Fatalf("err: %v", err) 1174 } 1175 1176 if err = task1.Validate(ephemeralDisk); err != nil { 1177 t.Fatalf("err : %v", err) 1178 } 1179 } 1180 1181 func TestTask_Validate_Service_Check(t *testing.T) { 1182 1183 invalidCheck := ServiceCheck{ 1184 Name: "check-name", 1185 Command: "/bin/true", 1186 Type: ServiceCheckScript, 1187 Interval: 10 * time.Second, 1188 } 1189 1190 err := invalidCheck.validate() 1191 if err == nil || !strings.Contains(err.Error(), "Timeout cannot be less") { 1192 t.Fatalf("expected a timeout validation error but received: %q", err) 1193 } 1194 1195 check1 := ServiceCheck{ 1196 Name: "check-name", 1197 Type: ServiceCheckTCP, 1198 Interval: 10 * time.Second, 1199 Timeout: 2 * time.Second, 1200 } 1201 1202 if err := check1.validate(); err != nil { 1203 t.Fatalf("err: %v", err) 1204 } 1205 1206 check1.InitialStatus = "foo" 1207 err = check1.validate() 1208 if err == nil { 1209 t.Fatal("Expected an error") 1210 } 1211 1212 if !strings.Contains(err.Error(), "invalid initial check state (foo)") { 1213 t.Fatalf("err: %v", err) 1214 } 1215 1216 check1.InitialStatus = api.HealthCritical 1217 err = check1.validate() 1218 if err != nil { 1219 t.Fatalf("err: %v", err) 1220 } 1221 1222 check1.InitialStatus = api.HealthPassing 1223 err = check1.validate() 1224 if err != nil { 1225 t.Fatalf("err: %v", err) 1226 } 1227 1228 check1.InitialStatus = "" 1229 err = check1.validate() 1230 if err != nil { 1231 t.Fatalf("err: %v", err) 1232 } 1233 } 1234 1235 // TestTask_Validate_Service_Check_AddressMode asserts that checks do not 1236 // inherit address mode but do inherit ports. 1237 func TestTask_Validate_Service_Check_AddressMode(t *testing.T) { 1238 getTask := func(s *Service) *Task { 1239 return &Task{ 1240 Resources: &Resources{ 1241 Networks: []*NetworkResource{ 1242 { 1243 DynamicPorts: []Port{ 1244 { 1245 Label: "http", 1246 Value: 9999, 1247 }, 1248 }, 1249 }, 1250 }, 1251 }, 1252 Services: []*Service{s}, 1253 } 1254 } 1255 1256 cases := []struct { 1257 Service *Service 1258 ErrContains string 1259 }{ 1260 { 1261 Service: &Service{ 1262 Name: "invalid-driver", 1263 PortLabel: "80", 1264 AddressMode: "host", 1265 }, 1266 ErrContains: `port label "80" referenced`, 1267 }, 1268 { 1269 Service: &Service{ 1270 Name: "http-driver-fail-1", 1271 PortLabel: "80", 1272 AddressMode: "driver", 1273 Checks: []*ServiceCheck{ 1274 { 1275 Name: "invalid-check-1", 1276 Type: "tcp", 1277 Interval: time.Second, 1278 Timeout: time.Second, 1279 }, 1280 }, 1281 }, 1282 ErrContains: `check "invalid-check-1" cannot use a numeric port`, 1283 }, 1284 { 1285 Service: &Service{ 1286 Name: "http-driver-fail-2", 1287 PortLabel: "80", 1288 AddressMode: "driver", 1289 Checks: []*ServiceCheck{ 1290 { 1291 Name: "invalid-check-2", 1292 Type: "tcp", 1293 PortLabel: "80", 1294 Interval: time.Second, 1295 Timeout: time.Second, 1296 }, 1297 }, 1298 }, 1299 ErrContains: `check "invalid-check-2" cannot use a numeric port`, 1300 }, 1301 { 1302 Service: &Service{ 1303 Name: "http-driver-fail-3", 1304 PortLabel: "80", 1305 AddressMode: "driver", 1306 Checks: []*ServiceCheck{ 1307 { 1308 Name: "invalid-check-3", 1309 Type: "tcp", 1310 PortLabel: "missing-port-label", 1311 Interval: time.Second, 1312 Timeout: time.Second, 1313 }, 1314 }, 1315 }, 1316 ErrContains: `port label "missing-port-label" referenced`, 1317 }, 1318 { 1319 Service: &Service{ 1320 Name: "http-driver-passes", 1321 PortLabel: "80", 1322 AddressMode: "driver", 1323 Checks: []*ServiceCheck{ 1324 { 1325 Name: "valid-script-check", 1326 Type: "script", 1327 Command: "ok", 1328 Interval: time.Second, 1329 Timeout: time.Second, 1330 }, 1331 { 1332 Name: "valid-host-check", 1333 Type: "tcp", 1334 PortLabel: "http", 1335 Interval: time.Second, 1336 Timeout: time.Second, 1337 }, 1338 { 1339 Name: "valid-driver-check", 1340 Type: "tcp", 1341 AddressMode: "driver", 1342 Interval: time.Second, 1343 Timeout: time.Second, 1344 }, 1345 }, 1346 }, 1347 }, 1348 { 1349 Service: &Service{ 1350 Name: "empty-address-3673-passes-1", 1351 Checks: []*ServiceCheck{ 1352 { 1353 Name: "valid-port-label", 1354 Type: "tcp", 1355 PortLabel: "http", 1356 Interval: time.Second, 1357 Timeout: time.Second, 1358 }, 1359 { 1360 Name: "empty-is-ok", 1361 Type: "script", 1362 Command: "ok", 1363 Interval: time.Second, 1364 Timeout: time.Second, 1365 }, 1366 }, 1367 }, 1368 }, 1369 { 1370 Service: &Service{ 1371 Name: "empty-address-3673-passes-2", 1372 }, 1373 }, 1374 { 1375 Service: &Service{ 1376 Name: "empty-address-3673-fails", 1377 Checks: []*ServiceCheck{ 1378 { 1379 Name: "empty-is-not-ok", 1380 Type: "tcp", 1381 Interval: time.Second, 1382 Timeout: time.Second, 1383 }, 1384 }, 1385 }, 1386 ErrContains: `invalid: check requires a port but neither check nor service`, 1387 }, 1388 } 1389 1390 for _, tc := range cases { 1391 tc := tc 1392 task := getTask(tc.Service) 1393 t.Run(tc.Service.Name, func(t *testing.T) { 1394 err := validateServices(task) 1395 if err == nil && tc.ErrContains == "" { 1396 // Ok! 1397 return 1398 } 1399 if err == nil { 1400 t.Fatalf("no error returned. expected: %s", tc.ErrContains) 1401 } 1402 if !strings.Contains(err.Error(), tc.ErrContains) { 1403 t.Fatalf("expected %q but found: %v", tc.ErrContains, err) 1404 } 1405 }) 1406 } 1407 } 1408 1409 func TestTask_Validate_Service_Check_CheckRestart(t *testing.T) { 1410 invalidCheckRestart := &CheckRestart{ 1411 Limit: -1, 1412 Grace: -1, 1413 } 1414 1415 err := invalidCheckRestart.Validate() 1416 assert.NotNil(t, err, "invalidateCheckRestart.Validate()") 1417 assert.Len(t, err.(*multierror.Error).Errors, 2) 1418 1419 validCheckRestart := &CheckRestart{} 1420 assert.Nil(t, validCheckRestart.Validate()) 1421 1422 validCheckRestart.Limit = 1 1423 validCheckRestart.Grace = 1 1424 assert.Nil(t, validCheckRestart.Validate()) 1425 } 1426 1427 func TestTask_Validate_LogConfig(t *testing.T) { 1428 task := &Task{ 1429 LogConfig: DefaultLogConfig(), 1430 } 1431 ephemeralDisk := &EphemeralDisk{ 1432 SizeMB: 1, 1433 } 1434 1435 err := task.Validate(ephemeralDisk) 1436 mErr := err.(*multierror.Error) 1437 if !strings.Contains(mErr.Errors[3].Error(), "log storage") { 1438 t.Fatalf("err: %s", err) 1439 } 1440 } 1441 1442 func TestTask_Validate_Template(t *testing.T) { 1443 1444 bad := &Template{} 1445 task := &Task{ 1446 Templates: []*Template{bad}, 1447 } 1448 ephemeralDisk := &EphemeralDisk{ 1449 SizeMB: 1, 1450 } 1451 1452 err := task.Validate(ephemeralDisk) 1453 if !strings.Contains(err.Error(), "Template 1 validation failed") { 1454 t.Fatalf("err: %s", err) 1455 } 1456 1457 // Have two templates that share the same destination 1458 good := &Template{ 1459 SourcePath: "foo", 1460 DestPath: "local/foo", 1461 ChangeMode: "noop", 1462 } 1463 1464 task.Templates = []*Template{good, good} 1465 err = task.Validate(ephemeralDisk) 1466 if !strings.Contains(err.Error(), "same destination as") { 1467 t.Fatalf("err: %s", err) 1468 } 1469 1470 // Env templates can't use signals 1471 task.Templates = []*Template{ 1472 { 1473 Envvars: true, 1474 ChangeMode: "signal", 1475 }, 1476 } 1477 1478 err = task.Validate(ephemeralDisk) 1479 if err == nil { 1480 t.Fatalf("expected error from Template.Validate") 1481 } 1482 if expected := "cannot use signals"; !strings.Contains(err.Error(), expected) { 1483 t.Errorf("expected to find %q but found %v", expected, err) 1484 } 1485 } 1486 1487 func TestTemplate_Validate(t *testing.T) { 1488 cases := []struct { 1489 Tmpl *Template 1490 Fail bool 1491 ContainsErrs []string 1492 }{ 1493 { 1494 Tmpl: &Template{}, 1495 Fail: true, 1496 ContainsErrs: []string{ 1497 "specify a source path", 1498 "specify a destination", 1499 TemplateChangeModeInvalidError.Error(), 1500 }, 1501 }, 1502 { 1503 Tmpl: &Template{ 1504 Splay: -100, 1505 }, 1506 Fail: true, 1507 ContainsErrs: []string{ 1508 "positive splay", 1509 }, 1510 }, 1511 { 1512 Tmpl: &Template{ 1513 ChangeMode: "foo", 1514 }, 1515 Fail: true, 1516 ContainsErrs: []string{ 1517 TemplateChangeModeInvalidError.Error(), 1518 }, 1519 }, 1520 { 1521 Tmpl: &Template{ 1522 ChangeMode: "signal", 1523 }, 1524 Fail: true, 1525 ContainsErrs: []string{ 1526 "specify signal value", 1527 }, 1528 }, 1529 { 1530 Tmpl: &Template{ 1531 SourcePath: "foo", 1532 DestPath: "../../root", 1533 ChangeMode: "noop", 1534 }, 1535 Fail: true, 1536 ContainsErrs: []string{ 1537 "destination escapes", 1538 }, 1539 }, 1540 { 1541 Tmpl: &Template{ 1542 SourcePath: "foo", 1543 DestPath: "local/foo", 1544 ChangeMode: "noop", 1545 }, 1546 Fail: false, 1547 }, 1548 { 1549 Tmpl: &Template{ 1550 SourcePath: "foo", 1551 DestPath: "local/foo", 1552 ChangeMode: "noop", 1553 Perms: "0444", 1554 }, 1555 Fail: false, 1556 }, 1557 { 1558 Tmpl: &Template{ 1559 SourcePath: "foo", 1560 DestPath: "local/foo", 1561 ChangeMode: "noop", 1562 Perms: "zza", 1563 }, 1564 Fail: true, 1565 ContainsErrs: []string{ 1566 "as octal", 1567 }, 1568 }, 1569 } 1570 1571 for i, c := range cases { 1572 err := c.Tmpl.Validate() 1573 if err != nil { 1574 if !c.Fail { 1575 t.Fatalf("Case %d: shouldn't have failed: %v", i+1, err) 1576 } 1577 1578 e := err.Error() 1579 for _, exp := range c.ContainsErrs { 1580 if !strings.Contains(e, exp) { 1581 t.Fatalf("Cased %d: should have contained error %q: %q", i+1, exp, e) 1582 } 1583 } 1584 } else if c.Fail { 1585 t.Fatalf("Case %d: should have failed: %v", i+1, err) 1586 } 1587 } 1588 } 1589 1590 func TestConstraint_Validate(t *testing.T) { 1591 c := &Constraint{} 1592 err := c.Validate() 1593 mErr := err.(*multierror.Error) 1594 if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") { 1595 t.Fatalf("err: %s", err) 1596 } 1597 1598 c = &Constraint{ 1599 LTarget: "$attr.kernel.name", 1600 RTarget: "linux", 1601 Operand: "=", 1602 } 1603 err = c.Validate() 1604 if err != nil { 1605 t.Fatalf("err: %v", err) 1606 } 1607 1608 // Perform additional regexp validation 1609 c.Operand = ConstraintRegex 1610 c.RTarget = "(foo" 1611 err = c.Validate() 1612 mErr = err.(*multierror.Error) 1613 if !strings.Contains(mErr.Errors[0].Error(), "missing closing") { 1614 t.Fatalf("err: %s", err) 1615 } 1616 1617 // Perform version validation 1618 c.Operand = ConstraintVersion 1619 c.RTarget = "~> foo" 1620 err = c.Validate() 1621 mErr = err.(*multierror.Error) 1622 if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") { 1623 t.Fatalf("err: %s", err) 1624 } 1625 1626 // Perform distinct_property validation 1627 c.Operand = ConstraintDistinctProperty 1628 c.RTarget = "0" 1629 err = c.Validate() 1630 mErr = err.(*multierror.Error) 1631 if !strings.Contains(mErr.Errors[0].Error(), "count of 1 or greater") { 1632 t.Fatalf("err: %s", err) 1633 } 1634 1635 c.RTarget = "-1" 1636 err = c.Validate() 1637 mErr = err.(*multierror.Error) 1638 if !strings.Contains(mErr.Errors[0].Error(), "to uint64") { 1639 t.Fatalf("err: %s", err) 1640 } 1641 1642 // Perform distinct_hosts validation 1643 c.Operand = ConstraintDistinctHosts 1644 c.LTarget = "" 1645 c.RTarget = "" 1646 if err := c.Validate(); err != nil { 1647 t.Fatalf("expected valid constraint: %v", err) 1648 } 1649 1650 // Perform set_contains validation 1651 c.Operand = ConstraintSetContains 1652 c.RTarget = "" 1653 err = c.Validate() 1654 mErr = err.(*multierror.Error) 1655 if !strings.Contains(mErr.Errors[0].Error(), "requires an RTarget") { 1656 t.Fatalf("err: %s", err) 1657 } 1658 1659 // Perform LTarget validation 1660 c.Operand = ConstraintRegex 1661 c.RTarget = "foo" 1662 c.LTarget = "" 1663 err = c.Validate() 1664 mErr = err.(*multierror.Error) 1665 if !strings.Contains(mErr.Errors[0].Error(), "No LTarget") { 1666 t.Fatalf("err: %s", err) 1667 } 1668 1669 // Perform constraint type validation 1670 c.Operand = "foo" 1671 err = c.Validate() 1672 mErr = err.(*multierror.Error) 1673 if !strings.Contains(mErr.Errors[0].Error(), "Unknown constraint type") { 1674 t.Fatalf("err: %s", err) 1675 } 1676 } 1677 1678 func TestUpdateStrategy_Validate(t *testing.T) { 1679 u := &UpdateStrategy{ 1680 MaxParallel: 0, 1681 HealthCheck: "foo", 1682 MinHealthyTime: -10, 1683 HealthyDeadline: -15, 1684 AutoRevert: false, 1685 Canary: -1, 1686 } 1687 1688 err := u.Validate() 1689 mErr := err.(*multierror.Error) 1690 if !strings.Contains(mErr.Errors[0].Error(), "Invalid health check given") { 1691 t.Fatalf("err: %s", err) 1692 } 1693 if !strings.Contains(mErr.Errors[1].Error(), "Max parallel can not be less than one") { 1694 t.Fatalf("err: %s", err) 1695 } 1696 if !strings.Contains(mErr.Errors[2].Error(), "Canary count can not be less than zero") { 1697 t.Fatalf("err: %s", err) 1698 } 1699 if !strings.Contains(mErr.Errors[3].Error(), "Minimum healthy time may not be less than zero") { 1700 t.Fatalf("err: %s", err) 1701 } 1702 if !strings.Contains(mErr.Errors[4].Error(), "Healthy deadline must be greater than zero") { 1703 t.Fatalf("err: %s", err) 1704 } 1705 if !strings.Contains(mErr.Errors[5].Error(), "Minimum healthy time must be less than healthy deadline") { 1706 t.Fatalf("err: %s", err) 1707 } 1708 } 1709 1710 func TestResource_NetIndex(t *testing.T) { 1711 r := &Resources{ 1712 Networks: []*NetworkResource{ 1713 {Device: "eth0"}, 1714 {Device: "lo0"}, 1715 {Device: ""}, 1716 }, 1717 } 1718 if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 { 1719 t.Fatalf("Bad: %d", idx) 1720 } 1721 if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 { 1722 t.Fatalf("Bad: %d", idx) 1723 } 1724 if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 { 1725 t.Fatalf("Bad: %d", idx) 1726 } 1727 } 1728 1729 func TestResource_Superset(t *testing.T) { 1730 r1 := &Resources{ 1731 CPU: 2000, 1732 MemoryMB: 2048, 1733 DiskMB: 10000, 1734 IOPS: 100, 1735 } 1736 r2 := &Resources{ 1737 CPU: 2000, 1738 MemoryMB: 1024, 1739 DiskMB: 5000, 1740 IOPS: 50, 1741 } 1742 1743 if s, _ := r1.Superset(r1); !s { 1744 t.Fatalf("bad") 1745 } 1746 if s, _ := r1.Superset(r2); !s { 1747 t.Fatalf("bad") 1748 } 1749 if s, _ := r2.Superset(r1); s { 1750 t.Fatalf("bad") 1751 } 1752 if s, _ := r2.Superset(r2); !s { 1753 t.Fatalf("bad") 1754 } 1755 } 1756 1757 func TestResource_Add(t *testing.T) { 1758 r1 := &Resources{ 1759 CPU: 2000, 1760 MemoryMB: 2048, 1761 DiskMB: 10000, 1762 IOPS: 100, 1763 Networks: []*NetworkResource{ 1764 { 1765 CIDR: "10.0.0.0/8", 1766 MBits: 100, 1767 ReservedPorts: []Port{{"ssh", 22}}, 1768 }, 1769 }, 1770 } 1771 r2 := &Resources{ 1772 CPU: 2000, 1773 MemoryMB: 1024, 1774 DiskMB: 5000, 1775 IOPS: 50, 1776 Networks: []*NetworkResource{ 1777 { 1778 IP: "10.0.0.1", 1779 MBits: 50, 1780 ReservedPorts: []Port{{"web", 80}}, 1781 }, 1782 }, 1783 } 1784 1785 err := r1.Add(r2) 1786 if err != nil { 1787 t.Fatalf("Err: %v", err) 1788 } 1789 1790 expect := &Resources{ 1791 CPU: 3000, 1792 MemoryMB: 3072, 1793 DiskMB: 15000, 1794 IOPS: 150, 1795 Networks: []*NetworkResource{ 1796 { 1797 CIDR: "10.0.0.0/8", 1798 MBits: 150, 1799 ReservedPorts: []Port{{"ssh", 22}, {"web", 80}}, 1800 }, 1801 }, 1802 } 1803 1804 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 1805 t.Fatalf("bad: %#v %#v", expect, r1) 1806 } 1807 } 1808 1809 func TestResource_Add_Network(t *testing.T) { 1810 r1 := &Resources{} 1811 r2 := &Resources{ 1812 Networks: []*NetworkResource{ 1813 { 1814 MBits: 50, 1815 DynamicPorts: []Port{{"http", 0}, {"https", 0}}, 1816 }, 1817 }, 1818 } 1819 r3 := &Resources{ 1820 Networks: []*NetworkResource{ 1821 { 1822 MBits: 25, 1823 DynamicPorts: []Port{{"admin", 0}}, 1824 }, 1825 }, 1826 } 1827 1828 err := r1.Add(r2) 1829 if err != nil { 1830 t.Fatalf("Err: %v", err) 1831 } 1832 err = r1.Add(r3) 1833 if err != nil { 1834 t.Fatalf("Err: %v", err) 1835 } 1836 1837 expect := &Resources{ 1838 Networks: []*NetworkResource{ 1839 { 1840 MBits: 75, 1841 DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}}, 1842 }, 1843 }, 1844 } 1845 1846 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 1847 t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0]) 1848 } 1849 } 1850 1851 func TestEncodeDecode(t *testing.T) { 1852 type FooRequest struct { 1853 Foo string 1854 Bar int 1855 Baz bool 1856 } 1857 arg := &FooRequest{ 1858 Foo: "test", 1859 Bar: 42, 1860 Baz: true, 1861 } 1862 buf, err := Encode(1, arg) 1863 if err != nil { 1864 t.Fatalf("err: %v", err) 1865 } 1866 1867 var out FooRequest 1868 err = Decode(buf[1:], &out) 1869 if err != nil { 1870 t.Fatalf("err: %v", err) 1871 } 1872 1873 if !reflect.DeepEqual(arg, &out) { 1874 t.Fatalf("bad: %#v %#v", arg, out) 1875 } 1876 } 1877 1878 func BenchmarkEncodeDecode(b *testing.B) { 1879 job := testJob() 1880 1881 for i := 0; i < b.N; i++ { 1882 buf, err := Encode(1, job) 1883 if err != nil { 1884 b.Fatalf("err: %v", err) 1885 } 1886 1887 var out Job 1888 err = Decode(buf[1:], &out) 1889 if err != nil { 1890 b.Fatalf("err: %v", err) 1891 } 1892 } 1893 } 1894 1895 func TestInvalidServiceCheck(t *testing.T) { 1896 s := Service{ 1897 Name: "service-name", 1898 PortLabel: "bar", 1899 Checks: []*ServiceCheck{ 1900 { 1901 Name: "check-name", 1902 Type: "lol", 1903 }, 1904 }, 1905 } 1906 if err := s.Validate(); err == nil { 1907 t.Fatalf("Service should be invalid (invalid type)") 1908 } 1909 1910 s = Service{ 1911 Name: "service.name", 1912 PortLabel: "bar", 1913 } 1914 if err := s.ValidateName(s.Name); err == nil { 1915 t.Fatalf("Service should be invalid (contains a dot): %v", err) 1916 } 1917 1918 s = Service{ 1919 Name: "-my-service", 1920 PortLabel: "bar", 1921 } 1922 if err := s.Validate(); err == nil { 1923 t.Fatalf("Service should be invalid (begins with a hyphen): %v", err) 1924 } 1925 1926 s = Service{ 1927 Name: "my-service-${NOMAD_META_FOO}", 1928 PortLabel: "bar", 1929 } 1930 if err := s.Validate(); err != nil { 1931 t.Fatalf("Service should be valid: %v", err) 1932 } 1933 1934 s = Service{ 1935 Name: "my_service-${NOMAD_META_FOO}", 1936 PortLabel: "bar", 1937 } 1938 if err := s.Validate(); err == nil { 1939 t.Fatalf("Service should be invalid (contains underscore but not in a variable name): %v", err) 1940 } 1941 1942 s = Service{ 1943 Name: "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456", 1944 PortLabel: "bar", 1945 } 1946 if err := s.ValidateName(s.Name); err == nil { 1947 t.Fatalf("Service should be invalid (too long): %v", err) 1948 } 1949 1950 s = Service{ 1951 Name: "service-name", 1952 Checks: []*ServiceCheck{ 1953 { 1954 Name: "check-tcp", 1955 Type: ServiceCheckTCP, 1956 Interval: 5 * time.Second, 1957 Timeout: 2 * time.Second, 1958 }, 1959 { 1960 Name: "check-http", 1961 Type: ServiceCheckHTTP, 1962 Path: "/foo", 1963 Interval: 5 * time.Second, 1964 Timeout: 2 * time.Second, 1965 }, 1966 }, 1967 } 1968 if err := s.Validate(); err == nil { 1969 t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err) 1970 } 1971 1972 s = Service{ 1973 Name: "service-name", 1974 Checks: []*ServiceCheck{ 1975 { 1976 Name: "check-script", 1977 Type: ServiceCheckScript, 1978 Command: "/bin/date", 1979 Interval: 5 * time.Second, 1980 Timeout: 2 * time.Second, 1981 }, 1982 }, 1983 } 1984 if err := s.Validate(); err != nil { 1985 t.Fatalf("un-expected error: %v", err) 1986 } 1987 } 1988 1989 func TestDistinctCheckID(t *testing.T) { 1990 c1 := ServiceCheck{ 1991 Name: "web-health", 1992 Type: "http", 1993 Path: "/health", 1994 Interval: 2 * time.Second, 1995 Timeout: 3 * time.Second, 1996 } 1997 c2 := ServiceCheck{ 1998 Name: "web-health", 1999 Type: "http", 2000 Path: "/health1", 2001 Interval: 2 * time.Second, 2002 Timeout: 3 * time.Second, 2003 } 2004 2005 c3 := ServiceCheck{ 2006 Name: "web-health", 2007 Type: "http", 2008 Path: "/health", 2009 Interval: 4 * time.Second, 2010 Timeout: 3 * time.Second, 2011 } 2012 serviceID := "123" 2013 c1Hash := c1.Hash(serviceID) 2014 c2Hash := c2.Hash(serviceID) 2015 c3Hash := c3.Hash(serviceID) 2016 2017 if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash { 2018 t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash) 2019 } 2020 2021 } 2022 2023 func TestService_Canonicalize(t *testing.T) { 2024 job := "example" 2025 taskGroup := "cache" 2026 task := "redis" 2027 2028 s := Service{ 2029 Name: "${TASK}-db", 2030 } 2031 2032 s.Canonicalize(job, taskGroup, task) 2033 if s.Name != "redis-db" { 2034 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 2035 } 2036 2037 s.Name = "db" 2038 s.Canonicalize(job, taskGroup, task) 2039 if s.Name != "db" { 2040 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 2041 } 2042 2043 s.Name = "${JOB}-${TASKGROUP}-${TASK}-db" 2044 s.Canonicalize(job, taskGroup, task) 2045 if s.Name != "example-cache-redis-db" { 2046 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 2047 } 2048 2049 s.Name = "${BASE}-db" 2050 s.Canonicalize(job, taskGroup, task) 2051 if s.Name != "example-cache-redis-db" { 2052 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 2053 } 2054 2055 } 2056 2057 func TestJob_ExpandServiceNames(t *testing.T) { 2058 j := &Job{ 2059 Name: "my-job", 2060 TaskGroups: []*TaskGroup{ 2061 { 2062 Name: "web", 2063 Tasks: []*Task{ 2064 { 2065 Name: "frontend", 2066 Services: []*Service{ 2067 { 2068 Name: "${BASE}-default", 2069 }, 2070 { 2071 Name: "jmx", 2072 }, 2073 }, 2074 }, 2075 }, 2076 }, 2077 { 2078 Name: "admin", 2079 Tasks: []*Task{ 2080 { 2081 Name: "admin-web", 2082 }, 2083 }, 2084 }, 2085 }, 2086 } 2087 2088 j.Canonicalize() 2089 2090 service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name 2091 if service1Name != "my-job-web-frontend-default" { 2092 t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name) 2093 } 2094 2095 service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name 2096 if service2Name != "jmx" { 2097 t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name) 2098 } 2099 2100 } 2101 2102 func TestPeriodicConfig_EnabledInvalid(t *testing.T) { 2103 // Create a config that is enabled but with no interval specified. 2104 p := &PeriodicConfig{Enabled: true} 2105 if err := p.Validate(); err == nil { 2106 t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid") 2107 } 2108 2109 // Create a config that is enabled, with a spec but no type specified. 2110 p = &PeriodicConfig{Enabled: true, Spec: "foo"} 2111 if err := p.Validate(); err == nil { 2112 t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid") 2113 } 2114 2115 // Create a config that is enabled, with a spec type but no spec specified. 2116 p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron} 2117 if err := p.Validate(); err == nil { 2118 t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid") 2119 } 2120 2121 // Create a config that is enabled, with a bad time zone. 2122 p = &PeriodicConfig{Enabled: true, TimeZone: "FOO"} 2123 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "time zone") { 2124 t.Fatalf("Enabled PeriodicConfig with bad time zone shouldn't be valid: %v", err) 2125 } 2126 } 2127 2128 func TestPeriodicConfig_InvalidCron(t *testing.T) { 2129 specs := []string{"foo", "* *", "@foo"} 2130 for _, spec := range specs { 2131 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 2132 p.Canonicalize() 2133 if err := p.Validate(); err == nil { 2134 t.Fatal("Invalid cron spec") 2135 } 2136 } 2137 } 2138 2139 func TestPeriodicConfig_ValidCron(t *testing.T) { 2140 specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"} 2141 for _, spec := range specs { 2142 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 2143 p.Canonicalize() 2144 if err := p.Validate(); err != nil { 2145 t.Fatal("Passed valid cron") 2146 } 2147 } 2148 } 2149 2150 func TestPeriodicConfig_NextCron(t *testing.T) { 2151 from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC) 2152 specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"} 2153 expected := []time.Time{{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)} 2154 for i, spec := range specs { 2155 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 2156 p.Canonicalize() 2157 n := p.Next(from) 2158 if expected[i] != n { 2159 t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i]) 2160 } 2161 } 2162 } 2163 2164 func TestPeriodicConfig_ValidTimeZone(t *testing.T) { 2165 zones := []string{"Africa/Abidjan", "America/Chicago", "Europe/Minsk", "UTC"} 2166 for _, zone := range zones { 2167 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: "0 0 29 2 * 1980", TimeZone: zone} 2168 p.Canonicalize() 2169 if err := p.Validate(); err != nil { 2170 t.Fatalf("Valid tz errored: %v", err) 2171 } 2172 } 2173 } 2174 2175 func TestPeriodicConfig_DST(t *testing.T) { 2176 // On Sun, Mar 12, 2:00 am 2017: +1 hour UTC 2177 p := &PeriodicConfig{ 2178 Enabled: true, 2179 SpecType: PeriodicSpecCron, 2180 Spec: "0 2 11-12 3 * 2017", 2181 TimeZone: "America/Los_Angeles", 2182 } 2183 p.Canonicalize() 2184 2185 t1 := time.Date(2017, time.March, 11, 1, 0, 0, 0, p.location) 2186 t2 := time.Date(2017, time.March, 12, 1, 0, 0, 0, p.location) 2187 2188 // E1 is an 8 hour adjustment, E2 is a 7 hour adjustment 2189 e1 := time.Date(2017, time.March, 11, 10, 0, 0, 0, time.UTC) 2190 e2 := time.Date(2017, time.March, 12, 9, 0, 0, 0, time.UTC) 2191 2192 n1 := p.Next(t1).UTC() 2193 n2 := p.Next(t2).UTC() 2194 2195 if !reflect.DeepEqual(e1, n1) { 2196 t.Fatalf("Got %v; want %v", n1, e1) 2197 } 2198 if !reflect.DeepEqual(e2, n2) { 2199 t.Fatalf("Got %v; want %v", n1, e1) 2200 } 2201 } 2202 2203 func TestRestartPolicy_Validate(t *testing.T) { 2204 // Policy with acceptable restart options passes 2205 p := &RestartPolicy{ 2206 Mode: RestartPolicyModeFail, 2207 Attempts: 0, 2208 Interval: 5 * time.Second, 2209 } 2210 if err := p.Validate(); err != nil { 2211 t.Fatalf("err: %v", err) 2212 } 2213 2214 // Policy with ambiguous restart options fails 2215 p = &RestartPolicy{ 2216 Mode: RestartPolicyModeDelay, 2217 Attempts: 0, 2218 Interval: 5 * time.Second, 2219 } 2220 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") { 2221 t.Fatalf("expect ambiguity error, got: %v", err) 2222 } 2223 2224 // Bad policy mode fails 2225 p = &RestartPolicy{ 2226 Mode: "nope", 2227 Attempts: 1, 2228 Interval: 5 * time.Second, 2229 } 2230 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") { 2231 t.Fatalf("expect mode error, got: %v", err) 2232 } 2233 2234 // Fails when attempts*delay does not fit inside interval 2235 p = &RestartPolicy{ 2236 Mode: RestartPolicyModeDelay, 2237 Attempts: 3, 2238 Delay: 5 * time.Second, 2239 Interval: 5 * time.Second, 2240 } 2241 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") { 2242 t.Fatalf("expect restart interval error, got: %v", err) 2243 } 2244 2245 // Fails when interval is to small 2246 p = &RestartPolicy{ 2247 Mode: RestartPolicyModeDelay, 2248 Attempts: 3, 2249 Delay: 5 * time.Second, 2250 Interval: 2 * time.Second, 2251 } 2252 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "Interval can not be less than") { 2253 t.Fatalf("expect interval too small error, got: %v", err) 2254 } 2255 } 2256 2257 func TestAllocation_Index(t *testing.T) { 2258 a1 := Allocation{ 2259 Name: "example.cache[1]", 2260 TaskGroup: "cache", 2261 JobID: "example", 2262 Job: &Job{ 2263 ID: "example", 2264 TaskGroups: []*TaskGroup{{Name: "cache"}}}, 2265 } 2266 e1 := uint(1) 2267 a2 := a1.Copy() 2268 a2.Name = "example.cache[713127]" 2269 e2 := uint(713127) 2270 2271 if a1.Index() != e1 || a2.Index() != e2 { 2272 t.Fatalf("Got %d and %d", a1.Index(), a2.Index()) 2273 } 2274 } 2275 2276 func TestTaskArtifact_Validate_Source(t *testing.T) { 2277 valid := &TaskArtifact{GetterSource: "google.com"} 2278 if err := valid.Validate(); err != nil { 2279 t.Fatalf("unexpected error: %v", err) 2280 } 2281 } 2282 2283 func TestTaskArtifact_Validate_Dest(t *testing.T) { 2284 valid := &TaskArtifact{GetterSource: "google.com"} 2285 if err := valid.Validate(); err != nil { 2286 t.Fatalf("unexpected error: %v", err) 2287 } 2288 2289 valid.RelativeDest = "local/" 2290 if err := valid.Validate(); err != nil { 2291 t.Fatalf("unexpected error: %v", err) 2292 } 2293 2294 valid.RelativeDest = "local/.." 2295 if err := valid.Validate(); err != nil { 2296 t.Fatalf("unexpected error: %v", err) 2297 } 2298 2299 valid.RelativeDest = "local/../../.." 2300 if err := valid.Validate(); err == nil { 2301 t.Fatalf("expected error: %v", err) 2302 } 2303 } 2304 2305 func TestAllocation_ShouldMigrate(t *testing.T) { 2306 alloc := Allocation{ 2307 PreviousAllocation: "123", 2308 TaskGroup: "foo", 2309 Job: &Job{ 2310 TaskGroups: []*TaskGroup{ 2311 { 2312 Name: "foo", 2313 EphemeralDisk: &EphemeralDisk{ 2314 Migrate: true, 2315 Sticky: true, 2316 }, 2317 }, 2318 }, 2319 }, 2320 } 2321 2322 if !alloc.ShouldMigrate() { 2323 t.Fatalf("bad: %v", alloc) 2324 } 2325 2326 alloc1 := Allocation{ 2327 PreviousAllocation: "123", 2328 TaskGroup: "foo", 2329 Job: &Job{ 2330 TaskGroups: []*TaskGroup{ 2331 { 2332 Name: "foo", 2333 EphemeralDisk: &EphemeralDisk{}, 2334 }, 2335 }, 2336 }, 2337 } 2338 2339 if alloc1.ShouldMigrate() { 2340 t.Fatalf("bad: %v", alloc) 2341 } 2342 2343 alloc2 := Allocation{ 2344 PreviousAllocation: "123", 2345 TaskGroup: "foo", 2346 Job: &Job{ 2347 TaskGroups: []*TaskGroup{ 2348 { 2349 Name: "foo", 2350 EphemeralDisk: &EphemeralDisk{ 2351 Sticky: false, 2352 Migrate: true, 2353 }, 2354 }, 2355 }, 2356 }, 2357 } 2358 2359 if alloc2.ShouldMigrate() { 2360 t.Fatalf("bad: %v", alloc) 2361 } 2362 2363 alloc3 := Allocation{ 2364 PreviousAllocation: "123", 2365 TaskGroup: "foo", 2366 Job: &Job{ 2367 TaskGroups: []*TaskGroup{ 2368 { 2369 Name: "foo", 2370 }, 2371 }, 2372 }, 2373 } 2374 2375 if alloc3.ShouldMigrate() { 2376 t.Fatalf("bad: %v", alloc) 2377 } 2378 2379 // No previous 2380 alloc4 := Allocation{ 2381 TaskGroup: "foo", 2382 Job: &Job{ 2383 TaskGroups: []*TaskGroup{ 2384 { 2385 Name: "foo", 2386 EphemeralDisk: &EphemeralDisk{ 2387 Migrate: true, 2388 Sticky: true, 2389 }, 2390 }, 2391 }, 2392 }, 2393 } 2394 2395 if alloc4.ShouldMigrate() { 2396 t.Fatalf("bad: %v", alloc4) 2397 } 2398 } 2399 2400 func TestTaskArtifact_Validate_Checksum(t *testing.T) { 2401 cases := []struct { 2402 Input *TaskArtifact 2403 Err bool 2404 }{ 2405 { 2406 &TaskArtifact{ 2407 GetterSource: "foo.com", 2408 GetterOptions: map[string]string{ 2409 "checksum": "no-type", 2410 }, 2411 }, 2412 true, 2413 }, 2414 { 2415 &TaskArtifact{ 2416 GetterSource: "foo.com", 2417 GetterOptions: map[string]string{ 2418 "checksum": "md5:toosmall", 2419 }, 2420 }, 2421 true, 2422 }, 2423 { 2424 &TaskArtifact{ 2425 GetterSource: "foo.com", 2426 GetterOptions: map[string]string{ 2427 "checksum": "invalid:type", 2428 }, 2429 }, 2430 true, 2431 }, 2432 } 2433 2434 for i, tc := range cases { 2435 err := tc.Input.Validate() 2436 if (err != nil) != tc.Err { 2437 t.Fatalf("case %d: %v", i, err) 2438 continue 2439 } 2440 } 2441 } 2442 2443 func TestAllocation_Terminated(t *testing.T) { 2444 type desiredState struct { 2445 ClientStatus string 2446 DesiredStatus string 2447 Terminated bool 2448 } 2449 2450 harness := []desiredState{ 2451 { 2452 ClientStatus: AllocClientStatusPending, 2453 DesiredStatus: AllocDesiredStatusStop, 2454 Terminated: false, 2455 }, 2456 { 2457 ClientStatus: AllocClientStatusRunning, 2458 DesiredStatus: AllocDesiredStatusStop, 2459 Terminated: false, 2460 }, 2461 { 2462 ClientStatus: AllocClientStatusFailed, 2463 DesiredStatus: AllocDesiredStatusStop, 2464 Terminated: true, 2465 }, 2466 { 2467 ClientStatus: AllocClientStatusFailed, 2468 DesiredStatus: AllocDesiredStatusRun, 2469 Terminated: true, 2470 }, 2471 } 2472 2473 for _, state := range harness { 2474 alloc := Allocation{} 2475 alloc.DesiredStatus = state.DesiredStatus 2476 alloc.ClientStatus = state.ClientStatus 2477 if alloc.Terminated() != state.Terminated { 2478 t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated()) 2479 } 2480 } 2481 } 2482 2483 func TestVault_Validate(t *testing.T) { 2484 v := &Vault{ 2485 Env: true, 2486 ChangeMode: VaultChangeModeNoop, 2487 } 2488 2489 if err := v.Validate(); err == nil || !strings.Contains(err.Error(), "Policy list") { 2490 t.Fatalf("Expected policy list empty error") 2491 } 2492 2493 v.Policies = []string{"foo", "root"} 2494 v.ChangeMode = VaultChangeModeSignal 2495 2496 err := v.Validate() 2497 if err == nil { 2498 t.Fatalf("Expected validation errors") 2499 } 2500 2501 if !strings.Contains(err.Error(), "Signal must") { 2502 t.Fatalf("Expected signal empty error") 2503 } 2504 if !strings.Contains(err.Error(), "root") { 2505 t.Fatalf("Expected root error") 2506 } 2507 } 2508 2509 func TestParameterizedJobConfig_Validate(t *testing.T) { 2510 d := &ParameterizedJobConfig{ 2511 Payload: "foo", 2512 } 2513 2514 if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "payload") { 2515 t.Fatalf("Expected unknown payload requirement: %v", err) 2516 } 2517 2518 d.Payload = DispatchPayloadOptional 2519 d.MetaOptional = []string{"foo", "bar"} 2520 d.MetaRequired = []string{"bar", "baz"} 2521 2522 if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "disjoint") { 2523 t.Fatalf("Expected meta not being disjoint error: %v", err) 2524 } 2525 } 2526 2527 func TestParameterizedJobConfig_Validate_NonBatch(t *testing.T) { 2528 job := testJob() 2529 job.ParameterizedJob = &ParameterizedJobConfig{ 2530 Payload: DispatchPayloadOptional, 2531 } 2532 job.Type = JobTypeSystem 2533 2534 if err := job.Validate(); err == nil || !strings.Contains(err.Error(), "only be used with") { 2535 t.Fatalf("Expected bad scheduler tpye: %v", err) 2536 } 2537 } 2538 2539 func TestParameterizedJobConfig_Canonicalize(t *testing.T) { 2540 d := &ParameterizedJobConfig{} 2541 d.Canonicalize() 2542 if d.Payload != DispatchPayloadOptional { 2543 t.Fatalf("Canonicalize failed") 2544 } 2545 } 2546 2547 func TestDispatchPayloadConfig_Validate(t *testing.T) { 2548 d := &DispatchPayloadConfig{ 2549 File: "foo", 2550 } 2551 2552 // task/local/haha 2553 if err := d.Validate(); err != nil { 2554 t.Fatalf("bad: %v", err) 2555 } 2556 2557 // task/haha 2558 d.File = "../haha" 2559 if err := d.Validate(); err != nil { 2560 t.Fatalf("bad: %v", err) 2561 } 2562 2563 // ../haha 2564 d.File = "../../../haha" 2565 if err := d.Validate(); err == nil { 2566 t.Fatalf("bad: %v", err) 2567 } 2568 } 2569 2570 func TestIsRecoverable(t *testing.T) { 2571 if IsRecoverable(nil) { 2572 t.Errorf("nil should not be recoverable") 2573 } 2574 if IsRecoverable(NewRecoverableError(nil, true)) { 2575 t.Errorf("NewRecoverableError(nil, true) should not be recoverable") 2576 } 2577 if IsRecoverable(fmt.Errorf("i promise im recoverable")) { 2578 t.Errorf("Custom errors should not be recoverable") 2579 } 2580 if IsRecoverable(NewRecoverableError(fmt.Errorf(""), false)) { 2581 t.Errorf("Explicitly unrecoverable errors should not be recoverable") 2582 } 2583 if !IsRecoverable(NewRecoverableError(fmt.Errorf(""), true)) { 2584 t.Errorf("Explicitly recoverable errors *should* be recoverable") 2585 } 2586 } 2587 2588 func TestACLTokenValidate(t *testing.T) { 2589 tk := &ACLToken{} 2590 2591 // Mising a type 2592 err := tk.Validate() 2593 assert.NotNil(t, err) 2594 if !strings.Contains(err.Error(), "client or management") { 2595 t.Fatalf("bad: %v", err) 2596 } 2597 2598 // Missing policies 2599 tk.Type = ACLClientToken 2600 err = tk.Validate() 2601 assert.NotNil(t, err) 2602 if !strings.Contains(err.Error(), "missing policies") { 2603 t.Fatalf("bad: %v", err) 2604 } 2605 2606 // Invalid policices 2607 tk.Type = ACLManagementToken 2608 tk.Policies = []string{"foo"} 2609 err = tk.Validate() 2610 assert.NotNil(t, err) 2611 if !strings.Contains(err.Error(), "associated with policies") { 2612 t.Fatalf("bad: %v", err) 2613 } 2614 2615 // Name too long policices 2616 tk.Name = uuid.Generate() + uuid.Generate() 2617 tk.Policies = nil 2618 err = tk.Validate() 2619 assert.NotNil(t, err) 2620 if !strings.Contains(err.Error(), "too long") { 2621 t.Fatalf("bad: %v", err) 2622 } 2623 2624 // Make it valid 2625 tk.Name = "foo" 2626 err = tk.Validate() 2627 assert.Nil(t, err) 2628 } 2629 2630 func TestACLTokenPolicySubset(t *testing.T) { 2631 tk := &ACLToken{ 2632 Type: ACLClientToken, 2633 Policies: []string{"foo", "bar", "baz"}, 2634 } 2635 2636 assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "baz"})) 2637 assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar"})) 2638 assert.Equal(t, true, tk.PolicySubset([]string{"foo"})) 2639 assert.Equal(t, true, tk.PolicySubset([]string{})) 2640 assert.Equal(t, false, tk.PolicySubset([]string{"foo", "bar", "new"})) 2641 assert.Equal(t, false, tk.PolicySubset([]string{"new"})) 2642 2643 tk = &ACLToken{ 2644 Type: ACLManagementToken, 2645 } 2646 2647 assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "baz"})) 2648 assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar"})) 2649 assert.Equal(t, true, tk.PolicySubset([]string{"foo"})) 2650 assert.Equal(t, true, tk.PolicySubset([]string{})) 2651 assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "new"})) 2652 assert.Equal(t, true, tk.PolicySubset([]string{"new"})) 2653 } 2654 2655 func TestACLTokenSetHash(t *testing.T) { 2656 tk := &ACLToken{ 2657 Name: "foo", 2658 Type: ACLClientToken, 2659 Policies: []string{"foo", "bar"}, 2660 Global: false, 2661 } 2662 out1 := tk.SetHash() 2663 assert.NotNil(t, out1) 2664 assert.NotNil(t, tk.Hash) 2665 assert.Equal(t, out1, tk.Hash) 2666 2667 tk.Policies = []string{"foo"} 2668 out2 := tk.SetHash() 2669 assert.NotNil(t, out2) 2670 assert.NotNil(t, tk.Hash) 2671 assert.Equal(t, out2, tk.Hash) 2672 assert.NotEqual(t, out1, out2) 2673 } 2674 2675 func TestACLPolicySetHash(t *testing.T) { 2676 ap := &ACLPolicy{ 2677 Name: "foo", 2678 Description: "great policy", 2679 Rules: "node { policy = \"read\" }", 2680 } 2681 out1 := ap.SetHash() 2682 assert.NotNil(t, out1) 2683 assert.NotNil(t, ap.Hash) 2684 assert.Equal(t, out1, ap.Hash) 2685 2686 ap.Rules = "node { policy = \"write\" }" 2687 out2 := ap.SetHash() 2688 assert.NotNil(t, out2) 2689 assert.NotNil(t, ap.Hash) 2690 assert.Equal(t, out2, ap.Hash) 2691 assert.NotEqual(t, out1, out2) 2692 } 2693 2694 func TestTaskEventPopulate(t *testing.T) { 2695 prepopulatedEvent := NewTaskEvent(TaskSetup) 2696 prepopulatedEvent.DisplayMessage = "Hola" 2697 testcases := []struct { 2698 event *TaskEvent 2699 expectedMsg string 2700 }{ 2701 {nil, ""}, 2702 {prepopulatedEvent, "Hola"}, 2703 {NewTaskEvent(TaskSetup).SetMessage("Setup"), "Setup"}, 2704 {NewTaskEvent(TaskStarted), "Task started by client"}, 2705 {NewTaskEvent(TaskReceived), "Task received by client"}, 2706 {NewTaskEvent(TaskFailedValidation), "Validation of task failed"}, 2707 {NewTaskEvent(TaskFailedValidation).SetValidationError(fmt.Errorf("task failed validation")), "task failed validation"}, 2708 {NewTaskEvent(TaskSetupFailure), "Task setup failed"}, 2709 {NewTaskEvent(TaskSetupFailure).SetSetupError(fmt.Errorf("task failed setup")), "task failed setup"}, 2710 {NewTaskEvent(TaskDriverFailure), "Failed to start task"}, 2711 {NewTaskEvent(TaskDownloadingArtifacts), "Client is downloading artifacts"}, 2712 {NewTaskEvent(TaskArtifactDownloadFailed), "Failed to download artifacts"}, 2713 {NewTaskEvent(TaskArtifactDownloadFailed).SetDownloadError(fmt.Errorf("connection reset by peer")), "connection reset by peer"}, 2714 {NewTaskEvent(TaskRestarting).SetRestartDelay(2 * time.Second).SetRestartReason(ReasonWithinPolicy), "Task restarting in 2s"}, 2715 {NewTaskEvent(TaskRestarting).SetRestartReason("Chaos Monkey did it"), "Chaos Monkey did it - Task restarting in 0s"}, 2716 {NewTaskEvent(TaskKilling), "Sent interrupt"}, 2717 {NewTaskEvent(TaskKilling).SetKillReason("Its time for you to die"), "Killing task: Its time for you to die"}, 2718 {NewTaskEvent(TaskKilling).SetKillTimeout(1 * time.Second), "Sent interrupt. Waiting 1s before force killing"}, 2719 {NewTaskEvent(TaskTerminated).SetExitCode(-1).SetSignal(3), "Exit Code: -1, Signal: 3"}, 2720 {NewTaskEvent(TaskTerminated).SetMessage("Goodbye"), "Exit Code: 0, Exit Message: \"Goodbye\""}, 2721 {NewTaskEvent(TaskKilled), "Task successfully killed"}, 2722 {NewTaskEvent(TaskKilled).SetKillError(fmt.Errorf("undead creatures can't be killed")), "undead creatures can't be killed"}, 2723 {NewTaskEvent(TaskNotRestarting).SetRestartReason("Chaos Monkey did it"), "Chaos Monkey did it"}, 2724 {NewTaskEvent(TaskNotRestarting), "Task exceeded restart policy"}, 2725 {NewTaskEvent(TaskLeaderDead), "Leader Task in Group dead"}, 2726 {NewTaskEvent(TaskSiblingFailed), "Task's sibling failed"}, 2727 {NewTaskEvent(TaskSiblingFailed).SetFailedSibling("patient zero"), "Task's sibling \"patient zero\" failed"}, 2728 {NewTaskEvent(TaskSignaling), "Task being sent a signal"}, 2729 {NewTaskEvent(TaskSignaling).SetTaskSignal(os.Interrupt), "Task being sent signal interrupt"}, 2730 {NewTaskEvent(TaskSignaling).SetTaskSignal(os.Interrupt).SetTaskSignalReason("process interrupted"), "Task being sent signal interrupt: process interrupted"}, 2731 {NewTaskEvent(TaskRestartSignal), "Task signaled to restart"}, 2732 {NewTaskEvent(TaskRestartSignal).SetRestartReason("Chaos Monkey restarted it"), "Chaos Monkey restarted it"}, 2733 {NewTaskEvent(TaskDriverMessage).SetDriverMessage("YOLO"), "YOLO"}, 2734 {NewTaskEvent("Unknown Type, No message"), ""}, 2735 {NewTaskEvent("Unknown Type").SetMessage("Hello world"), "Hello world"}, 2736 } 2737 2738 for _, tc := range testcases { 2739 tc.event.PopulateEventDisplayMessage() 2740 if tc.event != nil && tc.event.DisplayMessage != tc.expectedMsg { 2741 t.Fatalf("Expected %v but got %v", tc.expectedMsg, tc.event.DisplayMessage) 2742 } 2743 } 2744 }