github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/nomad/structs/structs_test.go (about) 1 package structs 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/consul/api" 10 "github.com/hashicorp/go-multierror" 11 ) 12 13 func TestJob_Validate(t *testing.T) { 14 j := &Job{} 15 err := j.Validate() 16 mErr := err.(*multierror.Error) 17 if !strings.Contains(mErr.Errors[0].Error(), "job region") { 18 t.Fatalf("err: %s", err) 19 } 20 if !strings.Contains(mErr.Errors[1].Error(), "job ID") { 21 t.Fatalf("err: %s", err) 22 } 23 if !strings.Contains(mErr.Errors[2].Error(), "job name") { 24 t.Fatalf("err: %s", err) 25 } 26 if !strings.Contains(mErr.Errors[3].Error(), "job type") { 27 t.Fatalf("err: %s", err) 28 } 29 if !strings.Contains(mErr.Errors[4].Error(), "priority") { 30 t.Fatalf("err: %s", err) 31 } 32 if !strings.Contains(mErr.Errors[5].Error(), "datacenters") { 33 t.Fatalf("err: %s", err) 34 } 35 if !strings.Contains(mErr.Errors[6].Error(), "task groups") { 36 t.Fatalf("err: %s", err) 37 } 38 39 j = &Job{ 40 Type: JobTypeService, 41 Periodic: &PeriodicConfig{ 42 Enabled: true, 43 }, 44 } 45 err = j.Validate() 46 mErr = err.(*multierror.Error) 47 if !strings.Contains(mErr.Error(), "Periodic") { 48 t.Fatalf("err: %s", err) 49 } 50 51 j = &Job{ 52 Region: "global", 53 ID: GenerateUUID(), 54 Name: "my-job", 55 Type: JobTypeService, 56 Priority: 50, 57 Datacenters: []string{"dc1"}, 58 TaskGroups: []*TaskGroup{ 59 &TaskGroup{ 60 Name: "web", 61 RestartPolicy: &RestartPolicy{ 62 Interval: 5 * time.Minute, 63 Delay: 10 * time.Second, 64 Attempts: 10, 65 }, 66 }, 67 &TaskGroup{ 68 Name: "web", 69 RestartPolicy: &RestartPolicy{ 70 Interval: 5 * time.Minute, 71 Delay: 10 * time.Second, 72 Attempts: 10, 73 }, 74 }, 75 &TaskGroup{ 76 RestartPolicy: &RestartPolicy{ 77 Interval: 5 * time.Minute, 78 Delay: 10 * time.Second, 79 Attempts: 10, 80 }, 81 }, 82 }, 83 } 84 err = j.Validate() 85 mErr = err.(*multierror.Error) 86 if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") { 87 t.Fatalf("err: %s", err) 88 } 89 if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") { 90 t.Fatalf("err: %s", err) 91 } 92 if !strings.Contains(mErr.Errors[2].Error(), "Task group web validation failed") { 93 t.Fatalf("err: %s", err) 94 } 95 } 96 97 func testJob() *Job { 98 return &Job{ 99 Region: "global", 100 ID: GenerateUUID(), 101 Name: "my-job", 102 Type: JobTypeService, 103 Priority: 50, 104 AllAtOnce: false, 105 Datacenters: []string{"dc1"}, 106 Constraints: []*Constraint{ 107 &Constraint{ 108 LTarget: "$attr.kernel.name", 109 RTarget: "linux", 110 Operand: "=", 111 }, 112 }, 113 Periodic: &PeriodicConfig{ 114 Enabled: false, 115 }, 116 TaskGroups: []*TaskGroup{ 117 &TaskGroup{ 118 Name: "web", 119 Count: 10, 120 EphemeralDisk: DefaultEphemeralDisk(), 121 RestartPolicy: &RestartPolicy{ 122 Mode: RestartPolicyModeFail, 123 Attempts: 3, 124 Interval: 10 * time.Minute, 125 Delay: 1 * time.Minute, 126 }, 127 Tasks: []*Task{ 128 &Task{ 129 Name: "web", 130 Driver: "exec", 131 Config: map[string]interface{}{ 132 "command": "/bin/date", 133 }, 134 Env: map[string]string{ 135 "FOO": "bar", 136 }, 137 ExcludeNomadEnv: false, 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 TestTaskGroup_Validate(t *testing.T) { 310 tg := &TaskGroup{ 311 Count: -1, 312 RestartPolicy: &RestartPolicy{ 313 Interval: 5 * time.Minute, 314 Delay: 10 * time.Second, 315 Attempts: 10, 316 Mode: RestartPolicyModeDelay, 317 }, 318 } 319 err := tg.Validate() 320 mErr := err.(*multierror.Error) 321 if !strings.Contains(mErr.Errors[0].Error(), "group name") { 322 t.Fatalf("err: %s", err) 323 } 324 if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") { 325 t.Fatalf("err: %s", err) 326 } 327 if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") { 328 t.Fatalf("err: %s", err) 329 } 330 331 tg = &TaskGroup{ 332 Name: "web", 333 Count: 1, 334 Tasks: []*Task{ 335 &Task{Name: "web"}, 336 &Task{Name: "web"}, 337 &Task{}, 338 }, 339 RestartPolicy: &RestartPolicy{ 340 Interval: 5 * time.Minute, 341 Delay: 10 * time.Second, 342 Attempts: 10, 343 Mode: RestartPolicyModeDelay, 344 }, 345 } 346 347 err = tg.Validate() 348 mErr = err.(*multierror.Error) 349 if !strings.Contains(mErr.Errors[0].Error(), "should have a local disk object") { 350 t.Fatalf("err: %s", err) 351 } 352 if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") { 353 t.Fatalf("err: %s", err) 354 } 355 if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") { 356 t.Fatalf("err: %s", err) 357 } 358 if !strings.Contains(mErr.Errors[3].Error(), "Task web validation failed") { 359 t.Fatalf("err: %s", err) 360 } 361 } 362 363 func TestTask_Validate(t *testing.T) { 364 task := &Task{} 365 ephemeralDisk := DefaultEphemeralDisk() 366 err := task.Validate(ephemeralDisk) 367 mErr := err.(*multierror.Error) 368 if !strings.Contains(mErr.Errors[0].Error(), "task name") { 369 t.Fatalf("err: %s", err) 370 } 371 if !strings.Contains(mErr.Errors[1].Error(), "task driver") { 372 t.Fatalf("err: %s", err) 373 } 374 if !strings.Contains(mErr.Errors[2].Error(), "task resources") { 375 t.Fatalf("err: %s", err) 376 } 377 378 task = &Task{Name: "web/foo"} 379 err = task.Validate(ephemeralDisk) 380 mErr = err.(*multierror.Error) 381 if !strings.Contains(mErr.Errors[0].Error(), "slashes") { 382 t.Fatalf("err: %s", err) 383 } 384 385 task = &Task{ 386 Name: "web", 387 Driver: "docker", 388 Resources: &Resources{ 389 CPU: 100, 390 MemoryMB: 100, 391 IOPS: 10, 392 }, 393 LogConfig: DefaultLogConfig(), 394 } 395 ephemeralDisk.SizeMB = 200 396 err = task.Validate(ephemeralDisk) 397 if err != nil { 398 t.Fatalf("err: %s", err) 399 } 400 } 401 402 func TestTask_Validate_Services(t *testing.T) { 403 s1 := &Service{ 404 Name: "service-name", 405 PortLabel: "bar", 406 Checks: []*ServiceCheck{ 407 { 408 Name: "check-name", 409 Type: ServiceCheckTCP, 410 Interval: 0 * time.Second, 411 }, 412 { 413 Name: "check-name", 414 Type: ServiceCheckTCP, 415 Timeout: 2 * time.Second, 416 }, 417 }, 418 } 419 420 s2 := &Service{ 421 Name: "service-name", 422 } 423 424 ephemeralDisk := DefaultEphemeralDisk() 425 task := &Task{ 426 Name: "web", 427 Driver: "docker", 428 Resources: &Resources{ 429 CPU: 100, 430 MemoryMB: 100, 431 IOPS: 10, 432 }, 433 Services: []*Service{s1, s2}, 434 } 435 ephemeralDisk.SizeMB = 200 436 437 err := task.Validate(ephemeralDisk) 438 if err == nil { 439 t.Fatal("expected an error") 440 } 441 if !strings.Contains(err.Error(), "referenced by services service-name does not exist") { 442 t.Fatalf("err: %s", err) 443 } 444 445 if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") { 446 t.Fatalf("err: %v", err) 447 } 448 449 if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") { 450 t.Fatalf("err: %v", err) 451 } 452 453 if !strings.Contains(err.Error(), "interval (0s) can not be lower") { 454 t.Fatalf("err: %v", err) 455 } 456 } 457 458 func TestTask_Validate_Service_Check(t *testing.T) { 459 460 check1 := ServiceCheck{ 461 Name: "check-name", 462 Type: ServiceCheckTCP, 463 Interval: 10 * time.Second, 464 Timeout: 2 * time.Second, 465 } 466 467 err := check1.validate() 468 if err != nil { 469 t.Fatal("err: %v", err) 470 } 471 472 check1.InitialStatus = "foo" 473 err = check1.validate() 474 if err == nil { 475 t.Fatal("Expected an error") 476 } 477 478 if !strings.Contains(err.Error(), "invalid initial check state (foo)") { 479 t.Fatalf("err: %v", err) 480 } 481 482 check1.InitialStatus = api.HealthCritical 483 err = check1.validate() 484 if err != nil { 485 t.Fatalf("err: %v", err) 486 } 487 488 check1.InitialStatus = api.HealthPassing 489 err = check1.validate() 490 if err != nil { 491 t.Fatalf("err: %v", err) 492 } 493 494 check1.InitialStatus = "" 495 err = check1.validate() 496 if err != nil { 497 t.Fatalf("err: %v", err) 498 } 499 } 500 501 func TestTask_Validate_LogConfig(t *testing.T) { 502 task := &Task{ 503 LogConfig: DefaultLogConfig(), 504 } 505 ephemeralDisk := &EphemeralDisk{ 506 SizeMB: 1, 507 } 508 509 err := task.Validate(ephemeralDisk) 510 mErr := err.(*multierror.Error) 511 if !strings.Contains(mErr.Errors[3].Error(), "log storage") { 512 t.Fatalf("err: %s", err) 513 } 514 } 515 516 func TestConstraint_Validate(t *testing.T) { 517 c := &Constraint{} 518 err := c.Validate() 519 mErr := err.(*multierror.Error) 520 if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") { 521 t.Fatalf("err: %s", err) 522 } 523 524 c = &Constraint{ 525 LTarget: "$attr.kernel.name", 526 RTarget: "linux", 527 Operand: "=", 528 } 529 err = c.Validate() 530 if err != nil { 531 t.Fatalf("err: %v", err) 532 } 533 534 // Perform additional regexp validation 535 c.Operand = ConstraintRegex 536 c.RTarget = "(foo" 537 err = c.Validate() 538 mErr = err.(*multierror.Error) 539 if !strings.Contains(mErr.Errors[0].Error(), "missing closing") { 540 t.Fatalf("err: %s", err) 541 } 542 543 // Perform version validation 544 c.Operand = ConstraintVersion 545 c.RTarget = "~> foo" 546 err = c.Validate() 547 mErr = err.(*multierror.Error) 548 if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") { 549 t.Fatalf("err: %s", err) 550 } 551 } 552 553 func TestResource_NetIndex(t *testing.T) { 554 r := &Resources{ 555 Networks: []*NetworkResource{ 556 &NetworkResource{Device: "eth0"}, 557 &NetworkResource{Device: "lo0"}, 558 &NetworkResource{Device: ""}, 559 }, 560 } 561 if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 { 562 t.Fatalf("Bad: %d", idx) 563 } 564 if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 { 565 t.Fatalf("Bad: %d", idx) 566 } 567 if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 { 568 t.Fatalf("Bad: %d", idx) 569 } 570 } 571 572 func TestResource_Superset(t *testing.T) { 573 r1 := &Resources{ 574 CPU: 2000, 575 MemoryMB: 2048, 576 DiskMB: 10000, 577 IOPS: 100, 578 } 579 r2 := &Resources{ 580 CPU: 2000, 581 MemoryMB: 1024, 582 DiskMB: 5000, 583 IOPS: 50, 584 } 585 586 if s, _ := r1.Superset(r1); !s { 587 t.Fatalf("bad") 588 } 589 if s, _ := r1.Superset(r2); !s { 590 t.Fatalf("bad") 591 } 592 if s, _ := r2.Superset(r1); s { 593 t.Fatalf("bad") 594 } 595 if s, _ := r2.Superset(r2); !s { 596 t.Fatalf("bad") 597 } 598 } 599 600 func TestResource_Add(t *testing.T) { 601 r1 := &Resources{ 602 CPU: 2000, 603 MemoryMB: 2048, 604 DiskMB: 10000, 605 IOPS: 100, 606 Networks: []*NetworkResource{ 607 &NetworkResource{ 608 CIDR: "10.0.0.0/8", 609 MBits: 100, 610 ReservedPorts: []Port{{"ssh", 22}}, 611 }, 612 }, 613 } 614 r2 := &Resources{ 615 CPU: 2000, 616 MemoryMB: 1024, 617 DiskMB: 5000, 618 IOPS: 50, 619 Networks: []*NetworkResource{ 620 &NetworkResource{ 621 IP: "10.0.0.1", 622 MBits: 50, 623 ReservedPorts: []Port{{"web", 80}}, 624 }, 625 }, 626 } 627 628 err := r1.Add(r2) 629 if err != nil { 630 t.Fatalf("Err: %v", err) 631 } 632 633 expect := &Resources{ 634 CPU: 3000, 635 MemoryMB: 3072, 636 DiskMB: 15000, 637 IOPS: 150, 638 Networks: []*NetworkResource{ 639 &NetworkResource{ 640 CIDR: "10.0.0.0/8", 641 MBits: 150, 642 ReservedPorts: []Port{{"ssh", 22}, {"web", 80}}, 643 }, 644 }, 645 } 646 647 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 648 t.Fatalf("bad: %#v %#v", expect, r1) 649 } 650 } 651 652 func TestResource_Add_Network(t *testing.T) { 653 r1 := &Resources{} 654 r2 := &Resources{ 655 Networks: []*NetworkResource{ 656 &NetworkResource{ 657 MBits: 50, 658 DynamicPorts: []Port{{"http", 0}, {"https", 0}}, 659 }, 660 }, 661 } 662 r3 := &Resources{ 663 Networks: []*NetworkResource{ 664 &NetworkResource{ 665 MBits: 25, 666 DynamicPorts: []Port{{"admin", 0}}, 667 }, 668 }, 669 } 670 671 err := r1.Add(r2) 672 if err != nil { 673 t.Fatalf("Err: %v", err) 674 } 675 err = r1.Add(r3) 676 if err != nil { 677 t.Fatalf("Err: %v", err) 678 } 679 680 expect := &Resources{ 681 Networks: []*NetworkResource{ 682 &NetworkResource{ 683 MBits: 75, 684 DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}}, 685 }, 686 }, 687 } 688 689 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 690 t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0]) 691 } 692 } 693 694 func TestEncodeDecode(t *testing.T) { 695 type FooRequest struct { 696 Foo string 697 Bar int 698 Baz bool 699 } 700 arg := &FooRequest{ 701 Foo: "test", 702 Bar: 42, 703 Baz: true, 704 } 705 buf, err := Encode(1, arg) 706 if err != nil { 707 t.Fatalf("err: %v", err) 708 } 709 710 var out FooRequest 711 err = Decode(buf[1:], &out) 712 if err != nil { 713 t.Fatalf("err: %v", err) 714 } 715 716 if !reflect.DeepEqual(arg, &out) { 717 t.Fatalf("bad: %#v %#v", arg, out) 718 } 719 } 720 721 func BenchmarkEncodeDecode(b *testing.B) { 722 job := testJob() 723 724 for i := 0; i < b.N; i++ { 725 buf, err := Encode(1, job) 726 if err != nil { 727 b.Fatalf("err: %v", err) 728 } 729 730 var out Job 731 err = Decode(buf[1:], &out) 732 if err != nil { 733 b.Fatalf("err: %v", err) 734 } 735 } 736 } 737 738 func TestInvalidServiceCheck(t *testing.T) { 739 s := Service{ 740 Name: "service-name", 741 PortLabel: "bar", 742 Checks: []*ServiceCheck{ 743 { 744 Name: "check-name", 745 Type: "lol", 746 }, 747 }, 748 } 749 if err := s.Validate(); err == nil { 750 t.Fatalf("Service should be invalid (invalid type)") 751 } 752 753 s = Service{ 754 Name: "service.name", 755 PortLabel: "bar", 756 } 757 if err := s.Validate(); err == nil { 758 t.Fatalf("Service should be invalid (contains a dot): %v", err) 759 } 760 761 s = Service{ 762 Name: "-my-service", 763 PortLabel: "bar", 764 } 765 if err := s.Validate(); err == nil { 766 t.Fatalf("Service should be invalid (begins with a hyphen): %v", err) 767 } 768 769 s = Service{ 770 Name: "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456", 771 PortLabel: "bar", 772 } 773 if err := s.Validate(); err == nil { 774 t.Fatalf("Service should be invalid (too long): %v", err) 775 } 776 777 s = Service{ 778 Name: "service-name", 779 Checks: []*ServiceCheck{ 780 { 781 Name: "check-tcp", 782 Type: ServiceCheckTCP, 783 Interval: 5 * time.Second, 784 Timeout: 2 * time.Second, 785 }, 786 { 787 Name: "check-http", 788 Type: ServiceCheckHTTP, 789 Path: "/foo", 790 Interval: 5 * time.Second, 791 Timeout: 2 * time.Second, 792 }, 793 }, 794 } 795 if err := s.Validate(); err == nil { 796 t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err) 797 } 798 799 s = Service{ 800 Name: "service-name", 801 Checks: []*ServiceCheck{ 802 { 803 Name: "check-script", 804 Type: ServiceCheckScript, 805 Command: "/bin/date", 806 Interval: 5 * time.Second, 807 Timeout: 2 * time.Second, 808 }, 809 }, 810 } 811 if err := s.Validate(); err != nil { 812 t.Fatalf("un-expected error: %v", err) 813 } 814 } 815 816 func TestDistinctCheckID(t *testing.T) { 817 c1 := ServiceCheck{ 818 Name: "web-health", 819 Type: "http", 820 Path: "/health", 821 Interval: 2 * time.Second, 822 Timeout: 3 * time.Second, 823 } 824 c2 := ServiceCheck{ 825 Name: "web-health", 826 Type: "http", 827 Path: "/health1", 828 Interval: 2 * time.Second, 829 Timeout: 3 * time.Second, 830 } 831 832 c3 := ServiceCheck{ 833 Name: "web-health", 834 Type: "http", 835 Path: "/health", 836 Interval: 4 * time.Second, 837 Timeout: 3 * time.Second, 838 } 839 serviceID := "123" 840 c1Hash := c1.Hash(serviceID) 841 c2Hash := c2.Hash(serviceID) 842 c3Hash := c3.Hash(serviceID) 843 844 if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash { 845 t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash) 846 } 847 848 } 849 850 func TestService_Canonicalize(t *testing.T) { 851 job := "example" 852 taskGroup := "cache" 853 task := "redis" 854 855 s := Service{ 856 Name: "${TASK}-db", 857 } 858 859 s.Canonicalize(job, taskGroup, task) 860 if s.Name != "redis-db" { 861 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 862 } 863 864 s.Name = "db" 865 s.Canonicalize(job, taskGroup, task) 866 if s.Name != "db" { 867 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 868 } 869 870 s.Name = "${JOB}-${TASKGROUP}-${TASK}-db" 871 s.Canonicalize(job, taskGroup, task) 872 if s.Name != "example-cache-redis-db" { 873 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 874 } 875 876 s.Name = "${BASE}-db" 877 s.Canonicalize(job, taskGroup, task) 878 if s.Name != "example-cache-redis-db" { 879 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 880 } 881 882 } 883 884 func TestJob_ExpandServiceNames(t *testing.T) { 885 j := &Job{ 886 Name: "my-job", 887 TaskGroups: []*TaskGroup{ 888 &TaskGroup{ 889 Name: "web", 890 Tasks: []*Task{ 891 { 892 Name: "frontend", 893 Services: []*Service{ 894 { 895 Name: "${BASE}-default", 896 }, 897 { 898 Name: "jmx", 899 }, 900 }, 901 }, 902 }, 903 }, 904 &TaskGroup{ 905 Name: "admin", 906 Tasks: []*Task{ 907 { 908 Name: "admin-web", 909 }, 910 }, 911 }, 912 }, 913 } 914 915 j.Canonicalize() 916 917 service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name 918 if service1Name != "my-job-web-frontend-default" { 919 t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name) 920 } 921 922 service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name 923 if service2Name != "jmx" { 924 t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name) 925 } 926 927 } 928 929 func TestPeriodicConfig_EnabledInvalid(t *testing.T) { 930 // Create a config that is enabled but with no interval specified. 931 p := &PeriodicConfig{Enabled: true} 932 if err := p.Validate(); err == nil { 933 t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid") 934 } 935 936 // Create a config that is enabled, with a spec but no type specified. 937 p = &PeriodicConfig{Enabled: true, Spec: "foo"} 938 if err := p.Validate(); err == nil { 939 t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid") 940 } 941 942 // Create a config that is enabled, with a spec type but no spec specified. 943 p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron} 944 if err := p.Validate(); err == nil { 945 t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid") 946 } 947 } 948 949 func TestPeriodicConfig_InvalidCron(t *testing.T) { 950 specs := []string{"foo", "* *", "@foo"} 951 for _, spec := range specs { 952 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 953 if err := p.Validate(); err == nil { 954 t.Fatal("Invalid cron spec") 955 } 956 } 957 } 958 959 func TestPeriodicConfig_ValidCron(t *testing.T) { 960 specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"} 961 for _, spec := range specs { 962 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 963 if err := p.Validate(); err != nil { 964 t.Fatal("Passed valid cron") 965 } 966 } 967 } 968 969 func TestPeriodicConfig_NextCron(t *testing.T) { 970 from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC) 971 specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"} 972 expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)} 973 for i, spec := range specs { 974 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 975 n := p.Next(from) 976 if expected[i] != n { 977 t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i]) 978 } 979 } 980 } 981 982 func TestRestartPolicy_Validate(t *testing.T) { 983 // Policy with acceptable restart options passes 984 p := &RestartPolicy{ 985 Mode: RestartPolicyModeFail, 986 Attempts: 0, 987 } 988 if err := p.Validate(); err != nil { 989 t.Fatalf("err: %v", err) 990 } 991 992 // Policy with ambiguous restart options fails 993 p = &RestartPolicy{ 994 Mode: RestartPolicyModeDelay, 995 Attempts: 0, 996 } 997 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") { 998 t.Fatalf("expect ambiguity error, got: %v", err) 999 } 1000 1001 // Bad policy mode fails 1002 p = &RestartPolicy{ 1003 Mode: "nope", 1004 Attempts: 1, 1005 } 1006 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") { 1007 t.Fatalf("expect mode error, got: %v", err) 1008 } 1009 1010 // Fails when attempts*delay does not fit inside interval 1011 p = &RestartPolicy{ 1012 Mode: RestartPolicyModeDelay, 1013 Attempts: 3, 1014 Delay: 5 * time.Second, 1015 Interval: time.Second, 1016 } 1017 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") { 1018 t.Fatalf("expect restart interval error, got: %v", err) 1019 } 1020 } 1021 1022 func TestAllocation_Index(t *testing.T) { 1023 a1 := Allocation{Name: "example.cache[0]"} 1024 e1 := 0 1025 a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"} 1026 e2 := 77 1027 1028 if a1.Index() != e1 || a2.Index() != e2 { 1029 t.Fatal() 1030 } 1031 } 1032 1033 func TestTaskArtifact_Validate_Source(t *testing.T) { 1034 valid := &TaskArtifact{GetterSource: "google.com"} 1035 if err := valid.Validate(); err != nil { 1036 t.Fatalf("unexpected error: %v", err) 1037 } 1038 } 1039 1040 func TestTaskArtifact_Validate_Dest(t *testing.T) { 1041 valid := &TaskArtifact{GetterSource: "google.com"} 1042 if err := valid.Validate(); err != nil { 1043 t.Fatalf("unexpected error: %v", err) 1044 } 1045 1046 valid.RelativeDest = "local/" 1047 if err := valid.Validate(); err != nil { 1048 t.Fatalf("unexpected error: %v", err) 1049 } 1050 1051 valid.RelativeDest = "local/.." 1052 if err := valid.Validate(); err != nil { 1053 t.Fatalf("unexpected error: %v", err) 1054 } 1055 1056 valid.RelativeDest = "local/../.." 1057 if err := valid.Validate(); err == nil { 1058 t.Fatalf("expected error: %v", err) 1059 } 1060 } 1061 1062 func TestTaskArtifact_Validate_Checksum(t *testing.T) { 1063 cases := []struct { 1064 Input *TaskArtifact 1065 Err bool 1066 }{ 1067 { 1068 &TaskArtifact{ 1069 GetterSource: "foo.com", 1070 GetterOptions: map[string]string{ 1071 "checksum": "no-type", 1072 }, 1073 }, 1074 true, 1075 }, 1076 { 1077 &TaskArtifact{ 1078 GetterSource: "foo.com", 1079 GetterOptions: map[string]string{ 1080 "checksum": "md5:toosmall", 1081 }, 1082 }, 1083 true, 1084 }, 1085 { 1086 &TaskArtifact{ 1087 GetterSource: "foo.com", 1088 GetterOptions: map[string]string{ 1089 "checksum": "invalid:type", 1090 }, 1091 }, 1092 true, 1093 }, 1094 } 1095 1096 for i, tc := range cases { 1097 err := tc.Input.Validate() 1098 if (err != nil) != tc.Err { 1099 t.Fatalf("case %d: %v", i, err) 1100 continue 1101 } 1102 } 1103 } 1104 1105 func TestAllocation_Terminated(t *testing.T) { 1106 type desiredState struct { 1107 ClientStatus string 1108 DesiredStatus string 1109 Terminated bool 1110 } 1111 1112 harness := []desiredState{ 1113 { 1114 ClientStatus: AllocClientStatusPending, 1115 DesiredStatus: AllocDesiredStatusStop, 1116 Terminated: false, 1117 }, 1118 { 1119 ClientStatus: AllocClientStatusRunning, 1120 DesiredStatus: AllocDesiredStatusStop, 1121 Terminated: false, 1122 }, 1123 { 1124 ClientStatus: AllocClientStatusFailed, 1125 DesiredStatus: AllocDesiredStatusStop, 1126 Terminated: true, 1127 }, 1128 { 1129 ClientStatus: AllocClientStatusFailed, 1130 DesiredStatus: AllocDesiredStatusRun, 1131 Terminated: true, 1132 }, 1133 } 1134 1135 for _, state := range harness { 1136 alloc := Allocation{} 1137 alloc.DesiredStatus = state.DesiredStatus 1138 alloc.ClientStatus = state.ClientStatus 1139 if alloc.Terminated() != state.Terminated { 1140 t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated()) 1141 } 1142 } 1143 }