github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/nomad/structs/structs_test.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/consul/api" 11 "github.com/hashicorp/go-multierror" 12 ) 13 14 func TestJob_Validate(t *testing.T) { 15 j := &Job{} 16 err := j.Validate() 17 mErr := err.(*multierror.Error) 18 if !strings.Contains(mErr.Errors[0].Error(), "job region") { 19 t.Fatalf("err: %s", err) 20 } 21 if !strings.Contains(mErr.Errors[1].Error(), "job ID") { 22 t.Fatalf("err: %s", err) 23 } 24 if !strings.Contains(mErr.Errors[2].Error(), "job name") { 25 t.Fatalf("err: %s", err) 26 } 27 if !strings.Contains(mErr.Errors[3].Error(), "job type") { 28 t.Fatalf("err: %s", err) 29 } 30 if !strings.Contains(mErr.Errors[4].Error(), "priority") { 31 t.Fatalf("err: %s", err) 32 } 33 if !strings.Contains(mErr.Errors[5].Error(), "datacenters") { 34 t.Fatalf("err: %s", err) 35 } 36 if !strings.Contains(mErr.Errors[6].Error(), "task groups") { 37 t.Fatalf("err: %s", err) 38 } 39 40 j = &Job{ 41 Type: JobTypeService, 42 Periodic: &PeriodicConfig{ 43 Enabled: true, 44 }, 45 } 46 err = j.Validate() 47 mErr = err.(*multierror.Error) 48 if !strings.Contains(mErr.Error(), "Periodic") { 49 t.Fatalf("err: %s", err) 50 } 51 52 j = &Job{ 53 Region: "global", 54 ID: GenerateUUID(), 55 Name: "my-job", 56 Type: JobTypeService, 57 Priority: 50, 58 Datacenters: []string{"dc1"}, 59 TaskGroups: []*TaskGroup{ 60 &TaskGroup{ 61 Name: "web", 62 RestartPolicy: &RestartPolicy{ 63 Interval: 5 * time.Minute, 64 Delay: 10 * time.Second, 65 Attempts: 10, 66 }, 67 }, 68 &TaskGroup{ 69 Name: "web", 70 RestartPolicy: &RestartPolicy{ 71 Interval: 5 * time.Minute, 72 Delay: 10 * time.Second, 73 Attempts: 10, 74 }, 75 }, 76 &TaskGroup{ 77 RestartPolicy: &RestartPolicy{ 78 Interval: 5 * time.Minute, 79 Delay: 10 * time.Second, 80 Attempts: 10, 81 }, 82 }, 83 }, 84 } 85 err = j.Validate() 86 mErr = err.(*multierror.Error) 87 if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") { 88 t.Fatalf("err: %s", err) 89 } 90 if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") { 91 t.Fatalf("err: %s", err) 92 } 93 if !strings.Contains(mErr.Errors[2].Error(), "Task group web validation failed") { 94 t.Fatalf("err: %s", err) 95 } 96 } 97 98 func testJob() *Job { 99 return &Job{ 100 Region: "global", 101 ID: GenerateUUID(), 102 Name: "my-job", 103 Type: JobTypeService, 104 Priority: 50, 105 AllAtOnce: false, 106 Datacenters: []string{"dc1"}, 107 Constraints: []*Constraint{ 108 &Constraint{ 109 LTarget: "$attr.kernel.name", 110 RTarget: "linux", 111 Operand: "=", 112 }, 113 }, 114 Periodic: &PeriodicConfig{ 115 Enabled: false, 116 }, 117 TaskGroups: []*TaskGroup{ 118 &TaskGroup{ 119 Name: "web", 120 Count: 10, 121 EphemeralDisk: DefaultEphemeralDisk(), 122 RestartPolicy: &RestartPolicy{ 123 Mode: RestartPolicyModeFail, 124 Attempts: 3, 125 Interval: 10 * time.Minute, 126 Delay: 1 * time.Minute, 127 }, 128 Tasks: []*Task{ 129 &Task{ 130 Name: "web", 131 Driver: "exec", 132 Config: map[string]interface{}{ 133 "command": "/bin/date", 134 }, 135 Env: map[string]string{ 136 "FOO": "bar", 137 }, 138 Artifacts: []*TaskArtifact{ 139 { 140 GetterSource: "http://foo.com", 141 }, 142 }, 143 Services: []*Service{ 144 { 145 Name: "${TASK}-frontend", 146 PortLabel: "http", 147 }, 148 }, 149 Resources: &Resources{ 150 CPU: 500, 151 MemoryMB: 256, 152 Networks: []*NetworkResource{ 153 &NetworkResource{ 154 MBits: 50, 155 DynamicPorts: []Port{{Label: "http"}}, 156 }, 157 }, 158 }, 159 LogConfig: &LogConfig{ 160 MaxFiles: 10, 161 MaxFileSizeMB: 1, 162 }, 163 }, 164 }, 165 Meta: map[string]string{ 166 "elb_check_type": "http", 167 "elb_check_interval": "30s", 168 "elb_check_min": "3", 169 }, 170 }, 171 }, 172 Meta: map[string]string{ 173 "owner": "armon", 174 }, 175 } 176 } 177 178 func TestJob_Copy(t *testing.T) { 179 j := testJob() 180 c := j.Copy() 181 if !reflect.DeepEqual(j, c) { 182 t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j) 183 } 184 } 185 186 func TestJob_IsPeriodic(t *testing.T) { 187 j := &Job{ 188 Type: JobTypeService, 189 Periodic: &PeriodicConfig{ 190 Enabled: true, 191 }, 192 } 193 if !j.IsPeriodic() { 194 t.Fatalf("IsPeriodic() returned false on periodic job") 195 } 196 197 j = &Job{ 198 Type: JobTypeService, 199 } 200 if j.IsPeriodic() { 201 t.Fatalf("IsPeriodic() returned true on non-periodic job") 202 } 203 } 204 205 func TestJob_SystemJob_Validate(t *testing.T) { 206 j := testJob() 207 j.Type = JobTypeSystem 208 j.Canonicalize() 209 210 err := j.Validate() 211 if err == nil || !strings.Contains(err.Error(), "exceed") { 212 t.Fatalf("expect error due to count") 213 } 214 215 j.TaskGroups[0].Count = 0 216 if err := j.Validate(); err != nil { 217 t.Fatalf("unexpected err: %v", err) 218 } 219 220 j.TaskGroups[0].Count = 1 221 if err := j.Validate(); err != nil { 222 t.Fatalf("unexpected err: %v", err) 223 } 224 } 225 226 func TestJob_VaultPolicies(t *testing.T) { 227 j0 := &Job{} 228 e0 := make(map[string]map[string]*Vault, 0) 229 230 vj1 := &Vault{ 231 Policies: []string{ 232 "p1", 233 "p2", 234 }, 235 } 236 vj2 := &Vault{ 237 Policies: []string{ 238 "p3", 239 "p4", 240 }, 241 } 242 vj3 := &Vault{ 243 Policies: []string{ 244 "p5", 245 }, 246 } 247 j1 := &Job{ 248 TaskGroups: []*TaskGroup{ 249 &TaskGroup{ 250 Name: "foo", 251 Tasks: []*Task{ 252 &Task{ 253 Name: "t1", 254 }, 255 &Task{ 256 Name: "t2", 257 Vault: vj1, 258 }, 259 }, 260 }, 261 &TaskGroup{ 262 Name: "bar", 263 Tasks: []*Task{ 264 &Task{ 265 Name: "t3", 266 Vault: vj2, 267 }, 268 &Task{ 269 Name: "t4", 270 Vault: vj3, 271 }, 272 }, 273 }, 274 }, 275 } 276 277 e1 := map[string]map[string]*Vault{ 278 "foo": map[string]*Vault{ 279 "t2": vj1, 280 }, 281 "bar": map[string]*Vault{ 282 "t3": vj2, 283 "t4": vj3, 284 }, 285 } 286 287 cases := []struct { 288 Job *Job 289 Expected map[string]map[string]*Vault 290 }{ 291 { 292 Job: j0, 293 Expected: e0, 294 }, 295 { 296 Job: j1, 297 Expected: e1, 298 }, 299 } 300 301 for i, c := range cases { 302 got := c.Job.VaultPolicies() 303 if !reflect.DeepEqual(got, c.Expected) { 304 t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected) 305 } 306 } 307 } 308 309 func TestJob_RequiredSignals(t *testing.T) { 310 j0 := &Job{} 311 e0 := make(map[string]map[string][]string, 0) 312 313 vj1 := &Vault{ 314 Policies: []string{"p1"}, 315 ChangeMode: VaultChangeModeNoop, 316 } 317 vj2 := &Vault{ 318 Policies: []string{"p1"}, 319 ChangeMode: VaultChangeModeSignal, 320 ChangeSignal: "SIGUSR1", 321 } 322 tj1 := &Template{ 323 SourcePath: "foo", 324 DestPath: "bar", 325 ChangeMode: TemplateChangeModeNoop, 326 } 327 tj2 := &Template{ 328 SourcePath: "foo", 329 DestPath: "bar", 330 ChangeMode: TemplateChangeModeSignal, 331 ChangeSignal: "SIGUSR2", 332 } 333 j1 := &Job{ 334 TaskGroups: []*TaskGroup{ 335 &TaskGroup{ 336 Name: "foo", 337 Tasks: []*Task{ 338 &Task{ 339 Name: "t1", 340 }, 341 &Task{ 342 Name: "t2", 343 Vault: vj2, 344 Templates: []*Template{tj2}, 345 }, 346 }, 347 }, 348 &TaskGroup{ 349 Name: "bar", 350 Tasks: []*Task{ 351 &Task{ 352 Name: "t3", 353 Vault: vj1, 354 Templates: []*Template{tj1}, 355 }, 356 &Task{ 357 Name: "t4", 358 Vault: vj2, 359 }, 360 }, 361 }, 362 }, 363 } 364 365 e1 := map[string]map[string][]string{ 366 "foo": map[string][]string{ 367 "t2": []string{"SIGUSR1", "SIGUSR2"}, 368 }, 369 "bar": map[string][]string{ 370 "t4": []string{"SIGUSR1"}, 371 }, 372 } 373 374 cases := []struct { 375 Job *Job 376 Expected map[string]map[string][]string 377 }{ 378 { 379 Job: j0, 380 Expected: e0, 381 }, 382 { 383 Job: j1, 384 Expected: e1, 385 }, 386 } 387 388 for i, c := range cases { 389 got := c.Job.RequiredSignals() 390 if !reflect.DeepEqual(got, c.Expected) { 391 t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected) 392 } 393 } 394 } 395 396 func TestTaskGroup_Validate(t *testing.T) { 397 tg := &TaskGroup{ 398 Count: -1, 399 RestartPolicy: &RestartPolicy{ 400 Interval: 5 * time.Minute, 401 Delay: 10 * time.Second, 402 Attempts: 10, 403 Mode: RestartPolicyModeDelay, 404 }, 405 } 406 err := tg.Validate() 407 mErr := err.(*multierror.Error) 408 if !strings.Contains(mErr.Errors[0].Error(), "group name") { 409 t.Fatalf("err: %s", err) 410 } 411 if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") { 412 t.Fatalf("err: %s", err) 413 } 414 if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") { 415 t.Fatalf("err: %s", err) 416 } 417 418 tg = &TaskGroup{ 419 Name: "web", 420 Count: 1, 421 Tasks: []*Task{ 422 &Task{Name: "web", Leader: true}, 423 &Task{Name: "web", Leader: true}, 424 &Task{}, 425 }, 426 RestartPolicy: &RestartPolicy{ 427 Interval: 5 * time.Minute, 428 Delay: 10 * time.Second, 429 Attempts: 10, 430 Mode: RestartPolicyModeDelay, 431 }, 432 } 433 434 err = tg.Validate() 435 mErr = err.(*multierror.Error) 436 if !strings.Contains(mErr.Errors[0].Error(), "should have an ephemeral disk object") { 437 t.Fatalf("err: %s", err) 438 } 439 if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") { 440 t.Fatalf("err: %s", err) 441 } 442 if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") { 443 t.Fatalf("err: %s", err) 444 } 445 if !strings.Contains(mErr.Errors[3].Error(), "Only one task may be marked as leader") { 446 t.Fatalf("err: %s", err) 447 } 448 if !strings.Contains(mErr.Errors[4].Error(), "Task web validation failed") { 449 t.Fatalf("err: %s", err) 450 } 451 } 452 453 func TestTask_Validate(t *testing.T) { 454 task := &Task{} 455 ephemeralDisk := DefaultEphemeralDisk() 456 err := task.Validate(ephemeralDisk) 457 mErr := err.(*multierror.Error) 458 if !strings.Contains(mErr.Errors[0].Error(), "task name") { 459 t.Fatalf("err: %s", err) 460 } 461 if !strings.Contains(mErr.Errors[1].Error(), "task driver") { 462 t.Fatalf("err: %s", err) 463 } 464 if !strings.Contains(mErr.Errors[2].Error(), "task resources") { 465 t.Fatalf("err: %s", err) 466 } 467 468 task = &Task{Name: "web/foo"} 469 err = task.Validate(ephemeralDisk) 470 mErr = err.(*multierror.Error) 471 if !strings.Contains(mErr.Errors[0].Error(), "slashes") { 472 t.Fatalf("err: %s", err) 473 } 474 475 task = &Task{ 476 Name: "web", 477 Driver: "docker", 478 Resources: &Resources{ 479 CPU: 100, 480 MemoryMB: 100, 481 IOPS: 10, 482 }, 483 LogConfig: DefaultLogConfig(), 484 } 485 ephemeralDisk.SizeMB = 200 486 err = task.Validate(ephemeralDisk) 487 if err != nil { 488 t.Fatalf("err: %s", err) 489 } 490 491 task.Constraints = append(task.Constraints, 492 &Constraint{ 493 Operand: ConstraintDistinctHosts, 494 }, 495 &Constraint{ 496 Operand: ConstraintDistinctProperty, 497 LTarget: "${meta.rack}", 498 }) 499 500 err = task.Validate(ephemeralDisk) 501 mErr = err.(*multierror.Error) 502 if !strings.Contains(mErr.Errors[0].Error(), "task level: distinct_hosts") { 503 t.Fatalf("err: %s", err) 504 } 505 if !strings.Contains(mErr.Errors[1].Error(), "task level: distinct_property") { 506 t.Fatalf("err: %s", err) 507 } 508 } 509 510 func TestTask_Validate_Services(t *testing.T) { 511 s1 := &Service{ 512 Name: "service-name", 513 PortLabel: "bar", 514 Checks: []*ServiceCheck{ 515 { 516 Name: "check-name", 517 Type: ServiceCheckTCP, 518 Interval: 0 * time.Second, 519 }, 520 { 521 Name: "check-name", 522 Type: ServiceCheckTCP, 523 Timeout: 2 * time.Second, 524 }, 525 { 526 Name: "check-name", 527 Type: ServiceCheckTCP, 528 Interval: 1 * time.Second, 529 }, 530 }, 531 } 532 533 s2 := &Service{ 534 Name: "service-name", 535 PortLabel: "bar", 536 } 537 538 s3 := &Service{ 539 Name: "service-A", 540 PortLabel: "a", 541 } 542 s4 := &Service{ 543 Name: "service-A", 544 PortLabel: "b", 545 } 546 547 ephemeralDisk := DefaultEphemeralDisk() 548 ephemeralDisk.SizeMB = 200 549 task := &Task{ 550 Name: "web", 551 Driver: "docker", 552 Resources: &Resources{ 553 CPU: 100, 554 MemoryMB: 100, 555 IOPS: 10, 556 }, 557 Services: []*Service{s1, s2}, 558 } 559 560 task1 := &Task{ 561 Name: "web", 562 Driver: "docker", 563 Resources: DefaultResources(), 564 Services: []*Service{s3, s4}, 565 LogConfig: DefaultLogConfig(), 566 } 567 task1.Resources.Networks = []*NetworkResource{ 568 &NetworkResource{ 569 MBits: 10, 570 DynamicPorts: []Port{ 571 Port{ 572 Label: "a", 573 Value: 1000, 574 }, 575 Port{ 576 Label: "b", 577 Value: 2000, 578 }, 579 }, 580 }, 581 } 582 583 err := task.Validate(ephemeralDisk) 584 if err == nil { 585 t.Fatal("expected an error") 586 } 587 588 if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") { 589 t.Fatalf("err: %v", err) 590 } 591 592 if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") { 593 t.Fatalf("err: %v", err) 594 } 595 596 if !strings.Contains(err.Error(), "missing required value interval") { 597 t.Fatalf("err: %v", err) 598 } 599 600 if !strings.Contains(err.Error(), "cannot be less than") { 601 t.Fatalf("err: %v", err) 602 } 603 604 if err = task1.Validate(ephemeralDisk); err != nil { 605 t.Fatalf("err : %v", err) 606 } 607 } 608 609 func TestTask_Validate_Service_Check(t *testing.T) { 610 611 check1 := ServiceCheck{ 612 Name: "check-name", 613 Type: ServiceCheckTCP, 614 Interval: 10 * time.Second, 615 Timeout: 2 * time.Second, 616 } 617 618 err := check1.validate() 619 if err != nil { 620 t.Fatalf("err: %v", err) 621 } 622 623 check1.InitialStatus = "foo" 624 err = check1.validate() 625 if err == nil { 626 t.Fatal("Expected an error") 627 } 628 629 if !strings.Contains(err.Error(), "invalid initial check state (foo)") { 630 t.Fatalf("err: %v", err) 631 } 632 633 check1.InitialStatus = api.HealthCritical 634 err = check1.validate() 635 if err != nil { 636 t.Fatalf("err: %v", err) 637 } 638 639 check1.InitialStatus = api.HealthPassing 640 err = check1.validate() 641 if err != nil { 642 t.Fatalf("err: %v", err) 643 } 644 645 check1.InitialStatus = "" 646 err = check1.validate() 647 if err != nil { 648 t.Fatalf("err: %v", err) 649 } 650 } 651 652 func TestTask_Validate_LogConfig(t *testing.T) { 653 task := &Task{ 654 LogConfig: DefaultLogConfig(), 655 } 656 ephemeralDisk := &EphemeralDisk{ 657 SizeMB: 1, 658 } 659 660 err := task.Validate(ephemeralDisk) 661 mErr := err.(*multierror.Error) 662 if !strings.Contains(mErr.Errors[3].Error(), "log storage") { 663 t.Fatalf("err: %s", err) 664 } 665 } 666 667 func TestTask_Validate_Template(t *testing.T) { 668 669 bad := &Template{} 670 task := &Task{ 671 Templates: []*Template{bad}, 672 } 673 ephemeralDisk := &EphemeralDisk{ 674 SizeMB: 1, 675 } 676 677 err := task.Validate(ephemeralDisk) 678 if !strings.Contains(err.Error(), "Template 1 validation failed") { 679 t.Fatalf("err: %s", err) 680 } 681 682 // Have two templates that share the same destination 683 good := &Template{ 684 SourcePath: "foo", 685 DestPath: "local/foo", 686 ChangeMode: "noop", 687 } 688 689 task.Templates = []*Template{good, good} 690 err = task.Validate(ephemeralDisk) 691 if !strings.Contains(err.Error(), "same destination as") { 692 t.Fatalf("err: %s", err) 693 } 694 } 695 696 func TestTemplate_Validate(t *testing.T) { 697 cases := []struct { 698 Tmpl *Template 699 Fail bool 700 ContainsErrs []string 701 }{ 702 { 703 Tmpl: &Template{}, 704 Fail: true, 705 ContainsErrs: []string{ 706 "specify a source path", 707 "specify a destination", 708 TemplateChangeModeInvalidError.Error(), 709 }, 710 }, 711 { 712 Tmpl: &Template{ 713 Splay: -100, 714 }, 715 Fail: true, 716 ContainsErrs: []string{ 717 "positive splay", 718 }, 719 }, 720 { 721 Tmpl: &Template{ 722 ChangeMode: "foo", 723 }, 724 Fail: true, 725 ContainsErrs: []string{ 726 TemplateChangeModeInvalidError.Error(), 727 }, 728 }, 729 { 730 Tmpl: &Template{ 731 ChangeMode: "signal", 732 }, 733 Fail: true, 734 ContainsErrs: []string{ 735 "specify signal value", 736 }, 737 }, 738 { 739 Tmpl: &Template{ 740 SourcePath: "foo", 741 DestPath: "../../root", 742 ChangeMode: "noop", 743 }, 744 Fail: true, 745 ContainsErrs: []string{ 746 "destination escapes", 747 }, 748 }, 749 { 750 Tmpl: &Template{ 751 SourcePath: "foo", 752 DestPath: "local/foo", 753 ChangeMode: "noop", 754 }, 755 Fail: false, 756 }, 757 { 758 Tmpl: &Template{ 759 SourcePath: "foo", 760 DestPath: "local/foo", 761 ChangeMode: "noop", 762 Perms: "0444", 763 }, 764 Fail: false, 765 }, 766 { 767 Tmpl: &Template{ 768 SourcePath: "foo", 769 DestPath: "local/foo", 770 ChangeMode: "noop", 771 Perms: "zza", 772 }, 773 Fail: true, 774 ContainsErrs: []string{ 775 "as octal", 776 }, 777 }, 778 } 779 780 for i, c := range cases { 781 err := c.Tmpl.Validate() 782 if err != nil { 783 if !c.Fail { 784 t.Fatalf("Case %d: shouldn't have failed: %v", i+1, err) 785 } 786 787 e := err.Error() 788 for _, exp := range c.ContainsErrs { 789 if !strings.Contains(e, exp) { 790 t.Fatalf("Cased %d: should have contained error %q: %q", i+1, exp, e) 791 } 792 } 793 } else if c.Fail { 794 t.Fatalf("Case %d: should have failed: %v", i+1, err) 795 } 796 } 797 } 798 799 func TestConstraint_Validate(t *testing.T) { 800 c := &Constraint{} 801 err := c.Validate() 802 mErr := err.(*multierror.Error) 803 if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") { 804 t.Fatalf("err: %s", err) 805 } 806 807 c = &Constraint{ 808 LTarget: "$attr.kernel.name", 809 RTarget: "linux", 810 Operand: "=", 811 } 812 err = c.Validate() 813 if err != nil { 814 t.Fatalf("err: %v", err) 815 } 816 817 // Perform additional regexp validation 818 c.Operand = ConstraintRegex 819 c.RTarget = "(foo" 820 err = c.Validate() 821 mErr = err.(*multierror.Error) 822 if !strings.Contains(mErr.Errors[0].Error(), "missing closing") { 823 t.Fatalf("err: %s", err) 824 } 825 826 // Perform version validation 827 c.Operand = ConstraintVersion 828 c.RTarget = "~> foo" 829 err = c.Validate() 830 mErr = err.(*multierror.Error) 831 if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") { 832 t.Fatalf("err: %s", err) 833 } 834 } 835 836 func TestResource_NetIndex(t *testing.T) { 837 r := &Resources{ 838 Networks: []*NetworkResource{ 839 &NetworkResource{Device: "eth0"}, 840 &NetworkResource{Device: "lo0"}, 841 &NetworkResource{Device: ""}, 842 }, 843 } 844 if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 { 845 t.Fatalf("Bad: %d", idx) 846 } 847 if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 { 848 t.Fatalf("Bad: %d", idx) 849 } 850 if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 { 851 t.Fatalf("Bad: %d", idx) 852 } 853 } 854 855 func TestResource_Superset(t *testing.T) { 856 r1 := &Resources{ 857 CPU: 2000, 858 MemoryMB: 2048, 859 DiskMB: 10000, 860 IOPS: 100, 861 } 862 r2 := &Resources{ 863 CPU: 2000, 864 MemoryMB: 1024, 865 DiskMB: 5000, 866 IOPS: 50, 867 } 868 869 if s, _ := r1.Superset(r1); !s { 870 t.Fatalf("bad") 871 } 872 if s, _ := r1.Superset(r2); !s { 873 t.Fatalf("bad") 874 } 875 if s, _ := r2.Superset(r1); s { 876 t.Fatalf("bad") 877 } 878 if s, _ := r2.Superset(r2); !s { 879 t.Fatalf("bad") 880 } 881 } 882 883 func TestResource_Add(t *testing.T) { 884 r1 := &Resources{ 885 CPU: 2000, 886 MemoryMB: 2048, 887 DiskMB: 10000, 888 IOPS: 100, 889 Networks: []*NetworkResource{ 890 &NetworkResource{ 891 CIDR: "10.0.0.0/8", 892 MBits: 100, 893 ReservedPorts: []Port{{"ssh", 22}}, 894 }, 895 }, 896 } 897 r2 := &Resources{ 898 CPU: 2000, 899 MemoryMB: 1024, 900 DiskMB: 5000, 901 IOPS: 50, 902 Networks: []*NetworkResource{ 903 &NetworkResource{ 904 IP: "10.0.0.1", 905 MBits: 50, 906 ReservedPorts: []Port{{"web", 80}}, 907 }, 908 }, 909 } 910 911 err := r1.Add(r2) 912 if err != nil { 913 t.Fatalf("Err: %v", err) 914 } 915 916 expect := &Resources{ 917 CPU: 3000, 918 MemoryMB: 3072, 919 DiskMB: 15000, 920 IOPS: 150, 921 Networks: []*NetworkResource{ 922 &NetworkResource{ 923 CIDR: "10.0.0.0/8", 924 MBits: 150, 925 ReservedPorts: []Port{{"ssh", 22}, {"web", 80}}, 926 }, 927 }, 928 } 929 930 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 931 t.Fatalf("bad: %#v %#v", expect, r1) 932 } 933 } 934 935 func TestResource_Add_Network(t *testing.T) { 936 r1 := &Resources{} 937 r2 := &Resources{ 938 Networks: []*NetworkResource{ 939 &NetworkResource{ 940 MBits: 50, 941 DynamicPorts: []Port{{"http", 0}, {"https", 0}}, 942 }, 943 }, 944 } 945 r3 := &Resources{ 946 Networks: []*NetworkResource{ 947 &NetworkResource{ 948 MBits: 25, 949 DynamicPorts: []Port{{"admin", 0}}, 950 }, 951 }, 952 } 953 954 err := r1.Add(r2) 955 if err != nil { 956 t.Fatalf("Err: %v", err) 957 } 958 err = r1.Add(r3) 959 if err != nil { 960 t.Fatalf("Err: %v", err) 961 } 962 963 expect := &Resources{ 964 Networks: []*NetworkResource{ 965 &NetworkResource{ 966 MBits: 75, 967 DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}}, 968 }, 969 }, 970 } 971 972 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 973 t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0]) 974 } 975 } 976 977 func TestEncodeDecode(t *testing.T) { 978 type FooRequest struct { 979 Foo string 980 Bar int 981 Baz bool 982 } 983 arg := &FooRequest{ 984 Foo: "test", 985 Bar: 42, 986 Baz: true, 987 } 988 buf, err := Encode(1, arg) 989 if err != nil { 990 t.Fatalf("err: %v", err) 991 } 992 993 var out FooRequest 994 err = Decode(buf[1:], &out) 995 if err != nil { 996 t.Fatalf("err: %v", err) 997 } 998 999 if !reflect.DeepEqual(arg, &out) { 1000 t.Fatalf("bad: %#v %#v", arg, out) 1001 } 1002 } 1003 1004 func BenchmarkEncodeDecode(b *testing.B) { 1005 job := testJob() 1006 1007 for i := 0; i < b.N; i++ { 1008 buf, err := Encode(1, job) 1009 if err != nil { 1010 b.Fatalf("err: %v", err) 1011 } 1012 1013 var out Job 1014 err = Decode(buf[1:], &out) 1015 if err != nil { 1016 b.Fatalf("err: %v", err) 1017 } 1018 } 1019 } 1020 1021 func TestInvalidServiceCheck(t *testing.T) { 1022 s := Service{ 1023 Name: "service-name", 1024 PortLabel: "bar", 1025 Checks: []*ServiceCheck{ 1026 { 1027 Name: "check-name", 1028 Type: "lol", 1029 }, 1030 }, 1031 } 1032 if err := s.Validate(); err == nil { 1033 t.Fatalf("Service should be invalid (invalid type)") 1034 } 1035 1036 s = Service{ 1037 Name: "service.name", 1038 PortLabel: "bar", 1039 } 1040 if err := s.ValidateName(s.Name); err == nil { 1041 t.Fatalf("Service should be invalid (contains a dot): %v", err) 1042 } 1043 1044 s = Service{ 1045 Name: "-my-service", 1046 PortLabel: "bar", 1047 } 1048 if err := s.Validate(); err == nil { 1049 t.Fatalf("Service should be invalid (begins with a hyphen): %v", err) 1050 } 1051 1052 s = Service{ 1053 Name: "my-service-${NOMAD_META_FOO}", 1054 PortLabel: "bar", 1055 } 1056 if err := s.Validate(); err != nil { 1057 t.Fatalf("Service should be valid: %v", err) 1058 } 1059 1060 s = Service{ 1061 Name: "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456", 1062 PortLabel: "bar", 1063 } 1064 if err := s.ValidateName(s.Name); err == nil { 1065 t.Fatalf("Service should be invalid (too long): %v", err) 1066 } 1067 1068 s = Service{ 1069 Name: "service-name", 1070 Checks: []*ServiceCheck{ 1071 { 1072 Name: "check-tcp", 1073 Type: ServiceCheckTCP, 1074 Interval: 5 * time.Second, 1075 Timeout: 2 * time.Second, 1076 }, 1077 { 1078 Name: "check-http", 1079 Type: ServiceCheckHTTP, 1080 Path: "/foo", 1081 Interval: 5 * time.Second, 1082 Timeout: 2 * time.Second, 1083 }, 1084 }, 1085 } 1086 if err := s.Validate(); err == nil { 1087 t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err) 1088 } 1089 1090 s = Service{ 1091 Name: "service-name", 1092 Checks: []*ServiceCheck{ 1093 { 1094 Name: "check-script", 1095 Type: ServiceCheckScript, 1096 Command: "/bin/date", 1097 Interval: 5 * time.Second, 1098 Timeout: 2 * time.Second, 1099 }, 1100 }, 1101 } 1102 if err := s.Validate(); err != nil { 1103 t.Fatalf("un-expected error: %v", err) 1104 } 1105 } 1106 1107 func TestDistinctCheckID(t *testing.T) { 1108 c1 := ServiceCheck{ 1109 Name: "web-health", 1110 Type: "http", 1111 Path: "/health", 1112 Interval: 2 * time.Second, 1113 Timeout: 3 * time.Second, 1114 } 1115 c2 := ServiceCheck{ 1116 Name: "web-health", 1117 Type: "http", 1118 Path: "/health1", 1119 Interval: 2 * time.Second, 1120 Timeout: 3 * time.Second, 1121 } 1122 1123 c3 := ServiceCheck{ 1124 Name: "web-health", 1125 Type: "http", 1126 Path: "/health", 1127 Interval: 4 * time.Second, 1128 Timeout: 3 * time.Second, 1129 } 1130 serviceID := "123" 1131 c1Hash := c1.Hash(serviceID) 1132 c2Hash := c2.Hash(serviceID) 1133 c3Hash := c3.Hash(serviceID) 1134 1135 if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash { 1136 t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash) 1137 } 1138 1139 } 1140 1141 func TestService_Canonicalize(t *testing.T) { 1142 job := "example" 1143 taskGroup := "cache" 1144 task := "redis" 1145 1146 s := Service{ 1147 Name: "${TASK}-db", 1148 } 1149 1150 s.Canonicalize(job, taskGroup, task) 1151 if s.Name != "redis-db" { 1152 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 1153 } 1154 1155 s.Name = "db" 1156 s.Canonicalize(job, taskGroup, task) 1157 if s.Name != "db" { 1158 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 1159 } 1160 1161 s.Name = "${JOB}-${TASKGROUP}-${TASK}-db" 1162 s.Canonicalize(job, taskGroup, task) 1163 if s.Name != "example-cache-redis-db" { 1164 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 1165 } 1166 1167 s.Name = "${BASE}-db" 1168 s.Canonicalize(job, taskGroup, task) 1169 if s.Name != "example-cache-redis-db" { 1170 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 1171 } 1172 1173 } 1174 1175 func TestJob_ExpandServiceNames(t *testing.T) { 1176 j := &Job{ 1177 Name: "my-job", 1178 TaskGroups: []*TaskGroup{ 1179 &TaskGroup{ 1180 Name: "web", 1181 Tasks: []*Task{ 1182 { 1183 Name: "frontend", 1184 Services: []*Service{ 1185 { 1186 Name: "${BASE}-default", 1187 }, 1188 { 1189 Name: "jmx", 1190 }, 1191 }, 1192 }, 1193 }, 1194 }, 1195 &TaskGroup{ 1196 Name: "admin", 1197 Tasks: []*Task{ 1198 { 1199 Name: "admin-web", 1200 }, 1201 }, 1202 }, 1203 }, 1204 } 1205 1206 j.Canonicalize() 1207 1208 service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name 1209 if service1Name != "my-job-web-frontend-default" { 1210 t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name) 1211 } 1212 1213 service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name 1214 if service2Name != "jmx" { 1215 t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name) 1216 } 1217 1218 } 1219 1220 func TestPeriodicConfig_EnabledInvalid(t *testing.T) { 1221 // Create a config that is enabled but with no interval specified. 1222 p := &PeriodicConfig{Enabled: true} 1223 if err := p.Validate(); err == nil { 1224 t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid") 1225 } 1226 1227 // Create a config that is enabled, with a spec but no type specified. 1228 p = &PeriodicConfig{Enabled: true, Spec: "foo"} 1229 if err := p.Validate(); err == nil { 1230 t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid") 1231 } 1232 1233 // Create a config that is enabled, with a spec type but no spec specified. 1234 p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron} 1235 if err := p.Validate(); err == nil { 1236 t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid") 1237 } 1238 1239 // Create a config that is enabled, with a bad time zone. 1240 p = &PeriodicConfig{Enabled: true, TimeZone: "FOO"} 1241 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "time zone") { 1242 t.Fatalf("Enabled PeriodicConfig with bad time zone shouldn't be valid: %v", err) 1243 } 1244 } 1245 1246 func TestPeriodicConfig_InvalidCron(t *testing.T) { 1247 specs := []string{"foo", "* *", "@foo"} 1248 for _, spec := range specs { 1249 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 1250 p.Canonicalize() 1251 if err := p.Validate(); err == nil { 1252 t.Fatal("Invalid cron spec") 1253 } 1254 } 1255 } 1256 1257 func TestPeriodicConfig_ValidCron(t *testing.T) { 1258 specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"} 1259 for _, spec := range specs { 1260 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 1261 p.Canonicalize() 1262 if err := p.Validate(); err != nil { 1263 t.Fatal("Passed valid cron") 1264 } 1265 } 1266 } 1267 1268 func TestPeriodicConfig_NextCron(t *testing.T) { 1269 from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC) 1270 specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"} 1271 expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)} 1272 for i, spec := range specs { 1273 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 1274 p.Canonicalize() 1275 n := p.Next(from) 1276 if expected[i] != n { 1277 t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i]) 1278 } 1279 } 1280 } 1281 1282 func TestPeriodicConfig_ValidTimeZone(t *testing.T) { 1283 zones := []string{"Africa/Abidjan", "America/Chicago", "Europe/Minsk", "UTC"} 1284 for _, zone := range zones { 1285 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: "0 0 29 2 * 1980", TimeZone: zone} 1286 p.Canonicalize() 1287 if err := p.Validate(); err != nil { 1288 t.Fatalf("Valid tz errored: %v", err) 1289 } 1290 } 1291 } 1292 1293 func TestPeriodicConfig_DST(t *testing.T) { 1294 // On Sun, Mar 12, 2:00 am 2017: +1 hour UTC 1295 p := &PeriodicConfig{ 1296 Enabled: true, 1297 SpecType: PeriodicSpecCron, 1298 Spec: "0 2 11-12 3 * 2017", 1299 TimeZone: "America/Los_Angeles", 1300 } 1301 p.Canonicalize() 1302 1303 t1 := time.Date(2017, time.March, 11, 1, 0, 0, 0, p.location) 1304 t2 := time.Date(2017, time.March, 12, 1, 0, 0, 0, p.location) 1305 1306 // E1 is an 8 hour adjustment, E2 is a 7 hour adjustment 1307 e1 := time.Date(2017, time.March, 11, 10, 0, 0, 0, time.UTC) 1308 e2 := time.Date(2017, time.March, 12, 9, 0, 0, 0, time.UTC) 1309 1310 n1 := p.Next(t1).UTC() 1311 n2 := p.Next(t2).UTC() 1312 1313 if !reflect.DeepEqual(e1, n1) { 1314 t.Fatalf("Got %v; want %v", n1, e1) 1315 } 1316 if !reflect.DeepEqual(e2, n2) { 1317 t.Fatalf("Got %v; want %v", n1, e1) 1318 } 1319 } 1320 1321 func TestRestartPolicy_Validate(t *testing.T) { 1322 // Policy with acceptable restart options passes 1323 p := &RestartPolicy{ 1324 Mode: RestartPolicyModeFail, 1325 Attempts: 0, 1326 Interval: 5 * time.Second, 1327 } 1328 if err := p.Validate(); err != nil { 1329 t.Fatalf("err: %v", err) 1330 } 1331 1332 // Policy with ambiguous restart options fails 1333 p = &RestartPolicy{ 1334 Mode: RestartPolicyModeDelay, 1335 Attempts: 0, 1336 Interval: 5 * time.Second, 1337 } 1338 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") { 1339 t.Fatalf("expect ambiguity error, got: %v", err) 1340 } 1341 1342 // Bad policy mode fails 1343 p = &RestartPolicy{ 1344 Mode: "nope", 1345 Attempts: 1, 1346 Interval: 5 * time.Second, 1347 } 1348 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") { 1349 t.Fatalf("expect mode error, got: %v", err) 1350 } 1351 1352 // Fails when attempts*delay does not fit inside interval 1353 p = &RestartPolicy{ 1354 Mode: RestartPolicyModeDelay, 1355 Attempts: 3, 1356 Delay: 5 * time.Second, 1357 Interval: 5 * time.Second, 1358 } 1359 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") { 1360 t.Fatalf("expect restart interval error, got: %v", err) 1361 } 1362 1363 // Fails when interval is to small 1364 p = &RestartPolicy{ 1365 Mode: RestartPolicyModeDelay, 1366 Attempts: 3, 1367 Delay: 5 * time.Second, 1368 Interval: 2 * time.Second, 1369 } 1370 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "Interval can not be less than") { 1371 t.Fatalf("expect interval too small error, got: %v", err) 1372 } 1373 } 1374 1375 func TestAllocation_Index(t *testing.T) { 1376 a1 := Allocation{Name: "example.cache[0]"} 1377 e1 := 0 1378 a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"} 1379 e2 := 77 1380 1381 if a1.Index() != e1 || a2.Index() != e2 { 1382 t.Fatal() 1383 } 1384 } 1385 1386 func TestTaskArtifact_Validate_Source(t *testing.T) { 1387 valid := &TaskArtifact{GetterSource: "google.com"} 1388 if err := valid.Validate(); err != nil { 1389 t.Fatalf("unexpected error: %v", err) 1390 } 1391 } 1392 1393 func TestTaskArtifact_Validate_Dest(t *testing.T) { 1394 valid := &TaskArtifact{GetterSource: "google.com"} 1395 if err := valid.Validate(); err != nil { 1396 t.Fatalf("unexpected error: %v", err) 1397 } 1398 1399 valid.RelativeDest = "local/" 1400 if err := valid.Validate(); err != nil { 1401 t.Fatalf("unexpected error: %v", err) 1402 } 1403 1404 valid.RelativeDest = "local/.." 1405 if err := valid.Validate(); err != nil { 1406 t.Fatalf("unexpected error: %v", err) 1407 } 1408 1409 valid.RelativeDest = "local/../../.." 1410 if err := valid.Validate(); err == nil { 1411 t.Fatalf("expected error: %v", err) 1412 } 1413 } 1414 1415 func TestAllocation_ShouldMigrate(t *testing.T) { 1416 alloc := Allocation{ 1417 TaskGroup: "foo", 1418 Job: &Job{ 1419 TaskGroups: []*TaskGroup{ 1420 { 1421 Name: "foo", 1422 EphemeralDisk: &EphemeralDisk{ 1423 Migrate: true, 1424 Sticky: true, 1425 }, 1426 }, 1427 }, 1428 }, 1429 } 1430 1431 if !alloc.ShouldMigrate() { 1432 t.Fatalf("bad: %v", alloc) 1433 } 1434 1435 alloc1 := Allocation{ 1436 TaskGroup: "foo", 1437 Job: &Job{ 1438 TaskGroups: []*TaskGroup{ 1439 { 1440 Name: "foo", 1441 EphemeralDisk: &EphemeralDisk{}, 1442 }, 1443 }, 1444 }, 1445 } 1446 1447 if alloc1.ShouldMigrate() { 1448 t.Fatalf("bad: %v", alloc) 1449 } 1450 1451 alloc2 := Allocation{ 1452 TaskGroup: "foo", 1453 Job: &Job{ 1454 TaskGroups: []*TaskGroup{ 1455 { 1456 Name: "foo", 1457 EphemeralDisk: &EphemeralDisk{ 1458 Sticky: false, 1459 Migrate: true, 1460 }, 1461 }, 1462 }, 1463 }, 1464 } 1465 1466 if alloc2.ShouldMigrate() { 1467 t.Fatalf("bad: %v", alloc) 1468 } 1469 1470 alloc3 := Allocation{ 1471 TaskGroup: "foo", 1472 Job: &Job{ 1473 TaskGroups: []*TaskGroup{ 1474 { 1475 Name: "foo", 1476 }, 1477 }, 1478 }, 1479 } 1480 1481 if alloc3.ShouldMigrate() { 1482 t.Fatalf("bad: %v", alloc) 1483 } 1484 } 1485 1486 func TestTaskArtifact_Validate_Checksum(t *testing.T) { 1487 cases := []struct { 1488 Input *TaskArtifact 1489 Err bool 1490 }{ 1491 { 1492 &TaskArtifact{ 1493 GetterSource: "foo.com", 1494 GetterOptions: map[string]string{ 1495 "checksum": "no-type", 1496 }, 1497 }, 1498 true, 1499 }, 1500 { 1501 &TaskArtifact{ 1502 GetterSource: "foo.com", 1503 GetterOptions: map[string]string{ 1504 "checksum": "md5:toosmall", 1505 }, 1506 }, 1507 true, 1508 }, 1509 { 1510 &TaskArtifact{ 1511 GetterSource: "foo.com", 1512 GetterOptions: map[string]string{ 1513 "checksum": "invalid:type", 1514 }, 1515 }, 1516 true, 1517 }, 1518 } 1519 1520 for i, tc := range cases { 1521 err := tc.Input.Validate() 1522 if (err != nil) != tc.Err { 1523 t.Fatalf("case %d: %v", i, err) 1524 continue 1525 } 1526 } 1527 } 1528 1529 func TestAllocation_Terminated(t *testing.T) { 1530 type desiredState struct { 1531 ClientStatus string 1532 DesiredStatus string 1533 Terminated bool 1534 } 1535 1536 harness := []desiredState{ 1537 { 1538 ClientStatus: AllocClientStatusPending, 1539 DesiredStatus: AllocDesiredStatusStop, 1540 Terminated: false, 1541 }, 1542 { 1543 ClientStatus: AllocClientStatusRunning, 1544 DesiredStatus: AllocDesiredStatusStop, 1545 Terminated: false, 1546 }, 1547 { 1548 ClientStatus: AllocClientStatusFailed, 1549 DesiredStatus: AllocDesiredStatusStop, 1550 Terminated: true, 1551 }, 1552 { 1553 ClientStatus: AllocClientStatusFailed, 1554 DesiredStatus: AllocDesiredStatusRun, 1555 Terminated: true, 1556 }, 1557 } 1558 1559 for _, state := range harness { 1560 alloc := Allocation{} 1561 alloc.DesiredStatus = state.DesiredStatus 1562 alloc.ClientStatus = state.ClientStatus 1563 if alloc.Terminated() != state.Terminated { 1564 t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated()) 1565 } 1566 } 1567 } 1568 1569 func TestVault_Validate(t *testing.T) { 1570 v := &Vault{ 1571 Env: true, 1572 ChangeMode: VaultChangeModeNoop, 1573 } 1574 1575 if err := v.Validate(); err == nil || !strings.Contains(err.Error(), "Policy list") { 1576 t.Fatalf("Expected policy list empty error") 1577 } 1578 1579 v.Policies = []string{"foo", "root"} 1580 v.ChangeMode = VaultChangeModeSignal 1581 1582 err := v.Validate() 1583 if err == nil { 1584 t.Fatalf("Expected validation errors") 1585 } 1586 1587 if !strings.Contains(err.Error(), "Signal must") { 1588 t.Fatalf("Expected signal empty error") 1589 } 1590 if !strings.Contains(err.Error(), "root") { 1591 t.Fatalf("Expected root error") 1592 } 1593 } 1594 1595 func TestParameterizedJobConfig_Validate(t *testing.T) { 1596 d := &ParameterizedJobConfig{ 1597 Payload: "foo", 1598 } 1599 1600 if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "payload") { 1601 t.Fatalf("Expected unknown payload requirement: %v", err) 1602 } 1603 1604 d.Payload = DispatchPayloadOptional 1605 d.MetaOptional = []string{"foo", "bar"} 1606 d.MetaRequired = []string{"bar", "baz"} 1607 1608 if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "disjoint") { 1609 t.Fatalf("Expected meta not being disjoint error: %v", err) 1610 } 1611 } 1612 1613 func TestParameterizedJobConfig_Validate_NonBatch(t *testing.T) { 1614 job := testJob() 1615 job.ParameterizedJob = &ParameterizedJobConfig{ 1616 Payload: DispatchPayloadOptional, 1617 } 1618 job.Type = JobTypeSystem 1619 1620 if err := job.Validate(); err == nil || !strings.Contains(err.Error(), "only be used with") { 1621 t.Fatalf("Expected bad scheduler tpye: %v", err) 1622 } 1623 } 1624 1625 func TestParameterizedJobConfig_Canonicalize(t *testing.T) { 1626 d := &ParameterizedJobConfig{} 1627 d.Canonicalize() 1628 if d.Payload != DispatchPayloadOptional { 1629 t.Fatalf("Canonicalize failed") 1630 } 1631 } 1632 1633 func TestDispatchPayloadConfig_Validate(t *testing.T) { 1634 d := &DispatchPayloadConfig{ 1635 File: "foo", 1636 } 1637 1638 // task/local/haha 1639 if err := d.Validate(); err != nil { 1640 t.Fatalf("bad: %v", err) 1641 } 1642 1643 // task/haha 1644 d.File = "../haha" 1645 if err := d.Validate(); err != nil { 1646 t.Fatalf("bad: %v", err) 1647 } 1648 1649 // ../haha 1650 d.File = "../../../haha" 1651 if err := d.Validate(); err == nil { 1652 t.Fatalf("bad: %v", err) 1653 } 1654 } 1655 1656 func TestIsRecoverable(t *testing.T) { 1657 if IsRecoverable(nil) { 1658 t.Errorf("nil should not be recoverable") 1659 } 1660 if IsRecoverable(NewRecoverableError(nil, true)) { 1661 t.Errorf("NewRecoverableError(nil, true) should not be recoverable") 1662 } 1663 if IsRecoverable(fmt.Errorf("i promise im recoverable")) { 1664 t.Errorf("Custom errors should not be recoverable") 1665 } 1666 if IsRecoverable(NewRecoverableError(fmt.Errorf(""), false)) { 1667 t.Errorf("Explicitly unrecoverable errors should not be recoverable") 1668 } 1669 if !IsRecoverable(NewRecoverableError(fmt.Errorf(""), true)) { 1670 t.Errorf("Explicitly recoverable errors *should* be recoverable") 1671 } 1672 }