github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/jobspec/parse_test.go (about) 1 package jobspec 2 3 import ( 4 "path/filepath" 5 "reflect" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/api" 11 "github.com/hashicorp/nomad/helper" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/kr/pretty" 14 15 capi "github.com/hashicorp/consul/api" 16 ) 17 18 func TestParse(t *testing.T) { 19 cases := []struct { 20 File string 21 Result *api.Job 22 Err bool 23 }{ 24 { 25 "basic.hcl", 26 &api.Job{ 27 ID: helper.StringToPtr("binstore-storagelocker"), 28 Name: helper.StringToPtr("binstore-storagelocker"), 29 Type: helper.StringToPtr("batch"), 30 Priority: helper.IntToPtr(52), 31 AllAtOnce: helper.BoolToPtr(true), 32 Datacenters: []string{"us2", "eu1"}, 33 Region: helper.StringToPtr("fooregion"), 34 Namespace: helper.StringToPtr("foonamespace"), 35 VaultToken: helper.StringToPtr("foo"), 36 37 Meta: map[string]string{ 38 "foo": "bar", 39 }, 40 41 Constraints: []*api.Constraint{ 42 { 43 LTarget: "kernel.os", 44 RTarget: "windows", 45 Operand: "=", 46 }, 47 }, 48 49 Update: &api.UpdateStrategy{ 50 Stagger: helper.TimeToPtr(60 * time.Second), 51 MaxParallel: helper.IntToPtr(2), 52 HealthCheck: helper.StringToPtr("manual"), 53 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 54 HealthyDeadline: helper.TimeToPtr(10 * time.Minute), 55 AutoRevert: helper.BoolToPtr(true), 56 Canary: helper.IntToPtr(1), 57 }, 58 59 TaskGroups: []*api.TaskGroup{ 60 { 61 Name: helper.StringToPtr("outside"), 62 Tasks: []*api.Task{ 63 { 64 Name: "outside", 65 Driver: "java", 66 Config: map[string]interface{}{ 67 "jar_path": "s3://my-cool-store/foo.jar", 68 }, 69 Meta: map[string]string{ 70 "my-cool-key": "foobar", 71 }, 72 }, 73 }, 74 }, 75 76 { 77 Name: helper.StringToPtr("binsl"), 78 Count: helper.IntToPtr(5), 79 Constraints: []*api.Constraint{ 80 { 81 LTarget: "kernel.os", 82 RTarget: "linux", 83 Operand: "=", 84 }, 85 }, 86 Meta: map[string]string{ 87 "elb_mode": "tcp", 88 "elb_interval": "10", 89 "elb_checks": "3", 90 }, 91 RestartPolicy: &api.RestartPolicy{ 92 Interval: helper.TimeToPtr(10 * time.Minute), 93 Attempts: helper.IntToPtr(5), 94 Delay: helper.TimeToPtr(15 * time.Second), 95 Mode: helper.StringToPtr("delay"), 96 }, 97 ReschedulePolicy: &api.ReschedulePolicy{ 98 Interval: helper.TimeToPtr(12 * time.Hour), 99 Attempts: helper.IntToPtr(5), 100 }, 101 EphemeralDisk: &api.EphemeralDisk{ 102 Sticky: helper.BoolToPtr(true), 103 SizeMB: helper.IntToPtr(150), 104 }, 105 Update: &api.UpdateStrategy{ 106 MaxParallel: helper.IntToPtr(3), 107 HealthCheck: helper.StringToPtr("checks"), 108 MinHealthyTime: helper.TimeToPtr(1 * time.Second), 109 HealthyDeadline: helper.TimeToPtr(1 * time.Minute), 110 AutoRevert: helper.BoolToPtr(false), 111 Canary: helper.IntToPtr(2), 112 }, 113 Tasks: []*api.Task{ 114 { 115 Name: "binstore", 116 Driver: "docker", 117 User: "bob", 118 Config: map[string]interface{}{ 119 "image": "hashicorp/binstore", 120 "labels": []map[string]interface{}{ 121 { 122 "FOO": "bar", 123 }, 124 }, 125 }, 126 Services: []*api.Service{ 127 { 128 Tags: []string{"foo", "bar"}, 129 PortLabel: "http", 130 Checks: []api.ServiceCheck{ 131 { 132 Name: "check-name", 133 Type: "tcp", 134 PortLabel: "admin", 135 Interval: 10 * time.Second, 136 Timeout: 2 * time.Second, 137 CheckRestart: &api.CheckRestart{ 138 Limit: 3, 139 Grace: helper.TimeToPtr(10 * time.Second), 140 IgnoreWarnings: true, 141 }, 142 }, 143 }, 144 }, 145 }, 146 Env: map[string]string{ 147 "HELLO": "world", 148 "LOREM": "ipsum", 149 }, 150 Resources: &api.Resources{ 151 CPU: helper.IntToPtr(500), 152 MemoryMB: helper.IntToPtr(128), 153 Networks: []*api.NetworkResource{ 154 { 155 MBits: helper.IntToPtr(100), 156 ReservedPorts: []api.Port{{Label: "one", Value: 1}, {Label: "two", Value: 2}, {Label: "three", Value: 3}}, 157 DynamicPorts: []api.Port{{Label: "http", Value: 0}, {Label: "https", Value: 0}, {Label: "admin", Value: 0}}, 158 }, 159 }, 160 }, 161 KillTimeout: helper.TimeToPtr(22 * time.Second), 162 ShutdownDelay: 11 * time.Second, 163 LogConfig: &api.LogConfig{ 164 MaxFiles: helper.IntToPtr(14), 165 MaxFileSizeMB: helper.IntToPtr(101), 166 }, 167 Artifacts: []*api.TaskArtifact{ 168 { 169 GetterSource: helper.StringToPtr("http://foo.com/artifact"), 170 GetterOptions: map[string]string{ 171 "checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f", 172 }, 173 }, 174 { 175 GetterSource: helper.StringToPtr("http://bar.com/artifact"), 176 RelativeDest: helper.StringToPtr("test/foo/"), 177 GetterOptions: map[string]string{ 178 "checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c", 179 }, 180 GetterMode: helper.StringToPtr("file"), 181 }, 182 }, 183 Vault: &api.Vault{ 184 Policies: []string{"foo", "bar"}, 185 Env: helper.BoolToPtr(true), 186 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 187 }, 188 Templates: []*api.Template{ 189 { 190 SourcePath: helper.StringToPtr("foo"), 191 DestPath: helper.StringToPtr("foo"), 192 ChangeMode: helper.StringToPtr("foo"), 193 ChangeSignal: helper.StringToPtr("foo"), 194 Splay: helper.TimeToPtr(10 * time.Second), 195 Perms: helper.StringToPtr("0644"), 196 Envvars: helper.BoolToPtr(true), 197 VaultGrace: helper.TimeToPtr(33 * time.Second), 198 }, 199 { 200 SourcePath: helper.StringToPtr("bar"), 201 DestPath: helper.StringToPtr("bar"), 202 ChangeMode: helper.StringToPtr(structs.TemplateChangeModeRestart), 203 Splay: helper.TimeToPtr(5 * time.Second), 204 Perms: helper.StringToPtr("777"), 205 LeftDelim: helper.StringToPtr("--"), 206 RightDelim: helper.StringToPtr("__"), 207 }, 208 }, 209 Leader: true, 210 KillSignal: "", 211 }, 212 { 213 Name: "storagelocker", 214 Driver: "docker", 215 User: "", 216 Config: map[string]interface{}{ 217 "image": "hashicorp/storagelocker", 218 }, 219 Resources: &api.Resources{ 220 CPU: helper.IntToPtr(500), 221 MemoryMB: helper.IntToPtr(128), 222 IOPS: helper.IntToPtr(30), 223 }, 224 Constraints: []*api.Constraint{ 225 { 226 LTarget: "kernel.arch", 227 RTarget: "amd64", 228 Operand: "=", 229 }, 230 }, 231 Vault: &api.Vault{ 232 Policies: []string{"foo", "bar"}, 233 Env: helper.BoolToPtr(false), 234 ChangeMode: helper.StringToPtr(structs.VaultChangeModeSignal), 235 ChangeSignal: helper.StringToPtr("SIGUSR1"), 236 }, 237 }, 238 }, 239 }, 240 }, 241 }, 242 false, 243 }, 244 245 { 246 "multi-network.hcl", 247 nil, 248 true, 249 }, 250 251 { 252 "multi-resource.hcl", 253 nil, 254 true, 255 }, 256 257 { 258 "multi-vault.hcl", 259 nil, 260 true, 261 }, 262 263 { 264 "default-job.hcl", 265 &api.Job{ 266 ID: helper.StringToPtr("foo"), 267 Name: helper.StringToPtr("foo"), 268 }, 269 false, 270 }, 271 272 { 273 "version-constraint.hcl", 274 &api.Job{ 275 ID: helper.StringToPtr("foo"), 276 Name: helper.StringToPtr("foo"), 277 Constraints: []*api.Constraint{ 278 { 279 LTarget: "$attr.kernel.version", 280 RTarget: "~> 3.2", 281 Operand: structs.ConstraintVersion, 282 }, 283 }, 284 }, 285 false, 286 }, 287 288 { 289 "regexp-constraint.hcl", 290 &api.Job{ 291 ID: helper.StringToPtr("foo"), 292 Name: helper.StringToPtr("foo"), 293 Constraints: []*api.Constraint{ 294 { 295 LTarget: "$attr.kernel.version", 296 RTarget: "[0-9.]+", 297 Operand: structs.ConstraintRegex, 298 }, 299 }, 300 }, 301 false, 302 }, 303 304 { 305 "set-contains-constraint.hcl", 306 &api.Job{ 307 ID: helper.StringToPtr("foo"), 308 Name: helper.StringToPtr("foo"), 309 Constraints: []*api.Constraint{ 310 { 311 LTarget: "$meta.data", 312 RTarget: "foo,bar,baz", 313 Operand: structs.ConstraintSetContains, 314 }, 315 }, 316 }, 317 false, 318 }, 319 320 { 321 "distinctHosts-constraint.hcl", 322 &api.Job{ 323 ID: helper.StringToPtr("foo"), 324 Name: helper.StringToPtr("foo"), 325 Constraints: []*api.Constraint{ 326 { 327 Operand: structs.ConstraintDistinctHosts, 328 }, 329 }, 330 }, 331 false, 332 }, 333 334 { 335 "distinctProperty-constraint.hcl", 336 &api.Job{ 337 ID: helper.StringToPtr("foo"), 338 Name: helper.StringToPtr("foo"), 339 Constraints: []*api.Constraint{ 340 { 341 Operand: structs.ConstraintDistinctProperty, 342 LTarget: "${meta.rack}", 343 }, 344 }, 345 }, 346 false, 347 }, 348 349 { 350 "periodic-cron.hcl", 351 &api.Job{ 352 ID: helper.StringToPtr("foo"), 353 Name: helper.StringToPtr("foo"), 354 Periodic: &api.PeriodicConfig{ 355 SpecType: helper.StringToPtr(api.PeriodicSpecCron), 356 Spec: helper.StringToPtr("*/5 * * *"), 357 ProhibitOverlap: helper.BoolToPtr(true), 358 TimeZone: helper.StringToPtr("Europe/Minsk"), 359 }, 360 }, 361 false, 362 }, 363 364 { 365 "specify-job.hcl", 366 &api.Job{ 367 ID: helper.StringToPtr("job1"), 368 Name: helper.StringToPtr("My Job"), 369 }, 370 false, 371 }, 372 373 { 374 "task-nested-config.hcl", 375 &api.Job{ 376 ID: helper.StringToPtr("foo"), 377 Name: helper.StringToPtr("foo"), 378 TaskGroups: []*api.TaskGroup{ 379 { 380 Name: helper.StringToPtr("bar"), 381 Tasks: []*api.Task{ 382 { 383 Name: "bar", 384 Driver: "docker", 385 Config: map[string]interface{}{ 386 "image": "hashicorp/image", 387 "port_map": []map[string]interface{}{ 388 { 389 "db": 1234, 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 false, 399 }, 400 401 { 402 "bad-artifact.hcl", 403 nil, 404 true, 405 }, 406 407 { 408 "artifacts.hcl", 409 &api.Job{ 410 ID: helper.StringToPtr("binstore-storagelocker"), 411 Name: helper.StringToPtr("binstore-storagelocker"), 412 TaskGroups: []*api.TaskGroup{ 413 { 414 Name: helper.StringToPtr("binsl"), 415 Tasks: []*api.Task{ 416 { 417 Name: "binstore", 418 Driver: "docker", 419 Artifacts: []*api.TaskArtifact{ 420 { 421 GetterSource: helper.StringToPtr("http://foo.com/bar"), 422 GetterOptions: map[string]string{"foo": "bar"}, 423 RelativeDest: helper.StringToPtr(""), 424 }, 425 { 426 GetterSource: helper.StringToPtr("http://foo.com/baz"), 427 GetterOptions: nil, 428 RelativeDest: nil, 429 }, 430 { 431 GetterSource: helper.StringToPtr("http://foo.com/bam"), 432 GetterOptions: nil, 433 RelativeDest: helper.StringToPtr("var/foo"), 434 }, 435 }, 436 }, 437 }, 438 }, 439 }, 440 }, 441 false, 442 }, 443 { 444 "service-check-initial-status.hcl", 445 &api.Job{ 446 ID: helper.StringToPtr("check_initial_status"), 447 Name: helper.StringToPtr("check_initial_status"), 448 Type: helper.StringToPtr("service"), 449 TaskGroups: []*api.TaskGroup{ 450 { 451 Name: helper.StringToPtr("group"), 452 Count: helper.IntToPtr(1), 453 Tasks: []*api.Task{ 454 { 455 Name: "task", 456 Services: []*api.Service{ 457 { 458 Tags: []string{"foo", "bar"}, 459 PortLabel: "http", 460 Checks: []api.ServiceCheck{ 461 { 462 Name: "check-name", 463 Type: "http", 464 Path: "/", 465 Interval: 10 * time.Second, 466 Timeout: 2 * time.Second, 467 InitialStatus: capi.HealthPassing, 468 Method: "POST", 469 Header: map[string][]string{ 470 "Authorization": {"Basic ZWxhc3RpYzpjaGFuZ2VtZQ=="}, 471 }, 472 }, 473 }, 474 }, 475 }, 476 }, 477 }, 478 }, 479 }, 480 }, 481 false, 482 }, 483 { 484 "service-check-bad-header.hcl", 485 nil, 486 true, 487 }, 488 { 489 "service-check-bad-header-2.hcl", 490 nil, 491 true, 492 }, 493 { 494 // TODO This should be pushed into the API 495 "vault_inheritance.hcl", 496 &api.Job{ 497 ID: helper.StringToPtr("example"), 498 Name: helper.StringToPtr("example"), 499 TaskGroups: []*api.TaskGroup{ 500 { 501 Name: helper.StringToPtr("cache"), 502 Tasks: []*api.Task{ 503 { 504 Name: "redis", 505 Vault: &api.Vault{ 506 Policies: []string{"group"}, 507 Env: helper.BoolToPtr(true), 508 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 509 }, 510 }, 511 { 512 Name: "redis2", 513 Vault: &api.Vault{ 514 Policies: []string{"task"}, 515 Env: helper.BoolToPtr(false), 516 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 517 }, 518 }, 519 }, 520 }, 521 { 522 Name: helper.StringToPtr("cache2"), 523 Tasks: []*api.Task{ 524 { 525 Name: "redis", 526 Vault: &api.Vault{ 527 Policies: []string{"job"}, 528 Env: helper.BoolToPtr(true), 529 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 530 }, 531 }, 532 }, 533 }, 534 }, 535 }, 536 false, 537 }, 538 { 539 "parameterized_job.hcl", 540 &api.Job{ 541 ID: helper.StringToPtr("parameterized_job"), 542 Name: helper.StringToPtr("parameterized_job"), 543 544 ParameterizedJob: &api.ParameterizedJobConfig{ 545 Payload: "required", 546 MetaRequired: []string{"foo", "bar"}, 547 MetaOptional: []string{"baz", "bam"}, 548 }, 549 550 TaskGroups: []*api.TaskGroup{ 551 { 552 Name: helper.StringToPtr("foo"), 553 Tasks: []*api.Task{ 554 { 555 Name: "bar", 556 Driver: "docker", 557 DispatchPayload: &api.DispatchPayloadConfig{ 558 File: "foo/bar", 559 }, 560 }, 561 }, 562 }, 563 }, 564 }, 565 false, 566 }, 567 { 568 "job-with-kill-signal.hcl", 569 &api.Job{ 570 ID: helper.StringToPtr("foo"), 571 Name: helper.StringToPtr("foo"), 572 TaskGroups: []*api.TaskGroup{ 573 { 574 Name: helper.StringToPtr("bar"), 575 Tasks: []*api.Task{ 576 { 577 Name: "bar", 578 Driver: "docker", 579 KillSignal: "SIGQUIT", 580 Config: map[string]interface{}{ 581 "image": "hashicorp/image", 582 }, 583 }, 584 }, 585 }, 586 }, 587 }, 588 false, 589 }, 590 { 591 "service-check-driver-address.hcl", 592 &api.Job{ 593 ID: helper.StringToPtr("address_mode_driver"), 594 Name: helper.StringToPtr("address_mode_driver"), 595 Type: helper.StringToPtr("service"), 596 TaskGroups: []*api.TaskGroup{ 597 { 598 Name: helper.StringToPtr("group"), 599 Tasks: []*api.Task{ 600 { 601 Name: "task", 602 Services: []*api.Service{ 603 { 604 Name: "http-service", 605 PortLabel: "http", 606 AddressMode: "auto", 607 Checks: []api.ServiceCheck{ 608 { 609 Name: "http-check", 610 Type: "http", 611 Path: "/", 612 PortLabel: "http", 613 AddressMode: "driver", 614 }, 615 }, 616 }, 617 { 618 Name: "random-service", 619 PortLabel: "9000", 620 AddressMode: "driver", 621 Checks: []api.ServiceCheck{ 622 { 623 Name: "random-check", 624 Type: "tcp", 625 PortLabel: "9001", 626 AddressMode: "driver", 627 }, 628 }, 629 }, 630 }, 631 }, 632 }, 633 }, 634 }, 635 }, 636 false, 637 }, 638 { 639 "service-check-restart.hcl", 640 &api.Job{ 641 ID: helper.StringToPtr("service_check_restart"), 642 Name: helper.StringToPtr("service_check_restart"), 643 Type: helper.StringToPtr("service"), 644 TaskGroups: []*api.TaskGroup{ 645 { 646 Name: helper.StringToPtr("group"), 647 Tasks: []*api.Task{ 648 { 649 Name: "task", 650 Services: []*api.Service{ 651 { 652 Name: "http-service", 653 CheckRestart: &api.CheckRestart{ 654 Limit: 3, 655 Grace: helper.TimeToPtr(10 * time.Second), 656 IgnoreWarnings: true, 657 }, 658 Checks: []api.ServiceCheck{ 659 { 660 Name: "random-check", 661 Type: "tcp", 662 PortLabel: "9001", 663 }, 664 }, 665 }, 666 }, 667 }, 668 }, 669 }, 670 }, 671 }, 672 false, 673 }, 674 { 675 "reschedule-job.hcl", 676 &api.Job{ 677 ID: helper.StringToPtr("foo"), 678 Name: helper.StringToPtr("foo"), 679 Type: helper.StringToPtr("batch"), 680 Datacenters: []string{"dc1"}, 681 Reschedule: &api.ReschedulePolicy{ 682 Attempts: helper.IntToPtr(15), 683 Interval: helper.TimeToPtr(30 * time.Minute), 684 }, 685 TaskGroups: []*api.TaskGroup{ 686 { 687 Name: helper.StringToPtr("bar"), 688 Count: helper.IntToPtr(3), 689 Tasks: []*api.Task{ 690 { 691 Name: "bar", 692 Driver: "raw_exec", 693 Config: map[string]interface{}{ 694 "command": "bash", 695 "args": []interface{}{"-c", "echo hi"}, 696 }, 697 }, 698 }, 699 }, 700 }, 701 }, 702 false, 703 }, 704 } 705 706 for _, tc := range cases { 707 t.Logf("Testing parse: %s", tc.File) 708 709 path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File)) 710 if err != nil { 711 t.Fatalf("file: %s\n\n%s", tc.File, err) 712 continue 713 } 714 715 actual, err := ParseFile(path) 716 if (err != nil) != tc.Err { 717 t.Fatalf("file: %s\n\n%s", tc.File, err) 718 continue 719 } 720 721 if !reflect.DeepEqual(actual, tc.Result) { 722 for _, d := range pretty.Diff(actual, tc.Result) { 723 t.Logf(d) 724 } 725 t.Fatalf("file: %s", tc.File) 726 } 727 } 728 } 729 730 func TestBadPorts(t *testing.T) { 731 path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl")) 732 if err != nil { 733 t.Fatalf("Can't get absolute path for file: %s", err) 734 } 735 736 _, err = ParseFile(path) 737 738 if !strings.Contains(err.Error(), errPortLabel.Error()) { 739 t.Fatalf("\nExpected error\n %s\ngot\n %v", errPortLabel, err) 740 } 741 } 742 743 func TestOverlappingPorts(t *testing.T) { 744 path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl")) 745 if err != nil { 746 t.Fatalf("Can't get absolute path for file: %s", err) 747 } 748 749 _, err = ParseFile(path) 750 751 if err == nil { 752 t.Fatalf("Expected an error") 753 } 754 755 if !strings.Contains(err.Error(), "found a port label collision") { 756 t.Fatalf("Expected collision error; got %v", err) 757 } 758 } 759 760 func TestIncorrectKey(t *testing.T) { 761 path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl")) 762 if err != nil { 763 t.Fatalf("Can't get absolute path for file: %s", err) 764 } 765 766 _, err = ParseFile(path) 767 768 if err == nil { 769 t.Fatalf("Expected an error") 770 } 771 772 if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service: 'foo', check -> invalid key: nterval") { 773 t.Fatalf("Expected key error; got %v", err) 774 } 775 }