github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/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 VaultToken: helper.StringToPtr("foo"), 35 36 Meta: map[string]string{ 37 "foo": "bar", 38 }, 39 40 Constraints: []*api.Constraint{ 41 &api.Constraint{ 42 LTarget: "kernel.os", 43 RTarget: "windows", 44 Operand: "=", 45 }, 46 }, 47 48 Update: &api.UpdateStrategy{ 49 Stagger: 60 * time.Second, 50 MaxParallel: 2, 51 }, 52 53 TaskGroups: []*api.TaskGroup{ 54 &api.TaskGroup{ 55 Name: helper.StringToPtr("outside"), 56 Tasks: []*api.Task{ 57 &api.Task{ 58 Name: "outside", 59 Driver: "java", 60 Config: map[string]interface{}{ 61 "jar_path": "s3://my-cool-store/foo.jar", 62 }, 63 Meta: map[string]string{ 64 "my-cool-key": "foobar", 65 }, 66 }, 67 }, 68 }, 69 70 &api.TaskGroup{ 71 Name: helper.StringToPtr("binsl"), 72 Count: helper.IntToPtr(5), 73 Constraints: []*api.Constraint{ 74 &api.Constraint{ 75 LTarget: "kernel.os", 76 RTarget: "linux", 77 Operand: "=", 78 }, 79 }, 80 Meta: map[string]string{ 81 "elb_mode": "tcp", 82 "elb_interval": "10", 83 "elb_checks": "3", 84 }, 85 RestartPolicy: &api.RestartPolicy{ 86 Interval: helper.TimeToPtr(10 * time.Minute), 87 Attempts: helper.IntToPtr(5), 88 Delay: helper.TimeToPtr(15 * time.Second), 89 Mode: helper.StringToPtr("delay"), 90 }, 91 EphemeralDisk: &api.EphemeralDisk{ 92 Sticky: helper.BoolToPtr(true), 93 SizeMB: helper.IntToPtr(150), 94 }, 95 Tasks: []*api.Task{ 96 &api.Task{ 97 Name: "binstore", 98 Driver: "docker", 99 User: "bob", 100 Config: map[string]interface{}{ 101 "image": "hashicorp/binstore", 102 "labels": []map[string]interface{}{ 103 map[string]interface{}{ 104 "FOO": "bar", 105 }, 106 }, 107 }, 108 Services: []*api.Service{ 109 { 110 Tags: []string{"foo", "bar"}, 111 PortLabel: "http", 112 Checks: []api.ServiceCheck{ 113 { 114 Name: "check-name", 115 Type: "tcp", 116 PortLabel: "admin", 117 Interval: 10 * time.Second, 118 Timeout: 2 * time.Second, 119 }, 120 }, 121 }, 122 }, 123 Env: map[string]string{ 124 "HELLO": "world", 125 "LOREM": "ipsum", 126 }, 127 Resources: &api.Resources{ 128 CPU: helper.IntToPtr(500), 129 MemoryMB: helper.IntToPtr(128), 130 Networks: []*api.NetworkResource{ 131 &api.NetworkResource{ 132 MBits: helper.IntToPtr(100), 133 ReservedPorts: []api.Port{{Label: "one", Value: 1}, {Label: "two", Value: 2}, {Label: "three", Value: 3}}, 134 DynamicPorts: []api.Port{{Label: "http", Value: 0}, {Label: "https", Value: 0}, {Label: "admin", Value: 0}}, 135 }, 136 }, 137 }, 138 KillTimeout: helper.TimeToPtr(22 * time.Second), 139 LogConfig: &api.LogConfig{ 140 MaxFiles: helper.IntToPtr(14), 141 MaxFileSizeMB: helper.IntToPtr(101), 142 }, 143 Artifacts: []*api.TaskArtifact{ 144 { 145 GetterSource: helper.StringToPtr("http://foo.com/artifact"), 146 GetterOptions: map[string]string{ 147 "checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f", 148 }, 149 }, 150 { 151 GetterSource: helper.StringToPtr("http://bar.com/artifact"), 152 RelativeDest: helper.StringToPtr("test/foo/"), 153 GetterOptions: map[string]string{ 154 "checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c", 155 }, 156 }, 157 }, 158 Vault: &api.Vault{ 159 Policies: []string{"foo", "bar"}, 160 Env: helper.BoolToPtr(true), 161 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 162 }, 163 Templates: []*api.Template{ 164 { 165 SourcePath: helper.StringToPtr("foo"), 166 DestPath: helper.StringToPtr("foo"), 167 ChangeMode: helper.StringToPtr("foo"), 168 ChangeSignal: helper.StringToPtr("foo"), 169 Splay: helper.TimeToPtr(10 * time.Second), 170 Perms: helper.StringToPtr("0644"), 171 }, 172 { 173 SourcePath: helper.StringToPtr("bar"), 174 DestPath: helper.StringToPtr("bar"), 175 ChangeMode: helper.StringToPtr(structs.TemplateChangeModeRestart), 176 Splay: helper.TimeToPtr(5 * time.Second), 177 Perms: helper.StringToPtr("777"), 178 LeftDelim: helper.StringToPtr("--"), 179 RightDelim: helper.StringToPtr("__"), 180 }, 181 }, 182 Leader: true, 183 }, 184 &api.Task{ 185 Name: "storagelocker", 186 Driver: "docker", 187 User: "", 188 Config: map[string]interface{}{ 189 "image": "hashicorp/storagelocker", 190 }, 191 Resources: &api.Resources{ 192 CPU: helper.IntToPtr(500), 193 MemoryMB: helper.IntToPtr(128), 194 IOPS: helper.IntToPtr(30), 195 }, 196 Constraints: []*api.Constraint{ 197 &api.Constraint{ 198 LTarget: "kernel.arch", 199 RTarget: "amd64", 200 Operand: "=", 201 }, 202 }, 203 Vault: &api.Vault{ 204 Policies: []string{"foo", "bar"}, 205 Env: helper.BoolToPtr(false), 206 ChangeMode: helper.StringToPtr(structs.VaultChangeModeSignal), 207 ChangeSignal: helper.StringToPtr("SIGUSR1"), 208 }, 209 }, 210 }, 211 }, 212 }, 213 }, 214 false, 215 }, 216 217 { 218 "multi-network.hcl", 219 nil, 220 true, 221 }, 222 223 { 224 "multi-resource.hcl", 225 nil, 226 true, 227 }, 228 229 { 230 "multi-vault.hcl", 231 nil, 232 true, 233 }, 234 235 { 236 "default-job.hcl", 237 &api.Job{ 238 ID: helper.StringToPtr("foo"), 239 Name: helper.StringToPtr("foo"), 240 }, 241 false, 242 }, 243 244 { 245 "version-constraint.hcl", 246 &api.Job{ 247 ID: helper.StringToPtr("foo"), 248 Name: helper.StringToPtr("foo"), 249 Constraints: []*api.Constraint{ 250 &api.Constraint{ 251 LTarget: "$attr.kernel.version", 252 RTarget: "~> 3.2", 253 Operand: structs.ConstraintVersion, 254 }, 255 }, 256 }, 257 false, 258 }, 259 260 { 261 "regexp-constraint.hcl", 262 &api.Job{ 263 ID: helper.StringToPtr("foo"), 264 Name: helper.StringToPtr("foo"), 265 Constraints: []*api.Constraint{ 266 &api.Constraint{ 267 LTarget: "$attr.kernel.version", 268 RTarget: "[0-9.]+", 269 Operand: structs.ConstraintRegex, 270 }, 271 }, 272 }, 273 false, 274 }, 275 276 { 277 "set-contains-constraint.hcl", 278 &api.Job{ 279 ID: helper.StringToPtr("foo"), 280 Name: helper.StringToPtr("foo"), 281 Constraints: []*api.Constraint{ 282 &api.Constraint{ 283 LTarget: "$meta.data", 284 RTarget: "foo,bar,baz", 285 Operand: structs.ConstraintSetContains, 286 }, 287 }, 288 }, 289 false, 290 }, 291 292 { 293 "distinctHosts-constraint.hcl", 294 &api.Job{ 295 ID: helper.StringToPtr("foo"), 296 Name: helper.StringToPtr("foo"), 297 Constraints: []*api.Constraint{ 298 &api.Constraint{ 299 Operand: structs.ConstraintDistinctHosts, 300 }, 301 }, 302 }, 303 false, 304 }, 305 306 { 307 "distinctProperty-constraint.hcl", 308 &api.Job{ 309 ID: helper.StringToPtr("foo"), 310 Name: helper.StringToPtr("foo"), 311 Constraints: []*api.Constraint{ 312 &api.Constraint{ 313 Operand: structs.ConstraintDistinctProperty, 314 LTarget: "${meta.rack}", 315 }, 316 }, 317 }, 318 false, 319 }, 320 321 { 322 "periodic-cron.hcl", 323 &api.Job{ 324 ID: helper.StringToPtr("foo"), 325 Name: helper.StringToPtr("foo"), 326 Periodic: &api.PeriodicConfig{ 327 SpecType: helper.StringToPtr(api.PeriodicSpecCron), 328 Spec: helper.StringToPtr("*/5 * * *"), 329 ProhibitOverlap: helper.BoolToPtr(true), 330 TimeZone: helper.StringToPtr("Europe/Minsk"), 331 }, 332 }, 333 false, 334 }, 335 336 { 337 "specify-job.hcl", 338 &api.Job{ 339 ID: helper.StringToPtr("job1"), 340 Name: helper.StringToPtr("My Job"), 341 }, 342 false, 343 }, 344 345 { 346 "task-nested-config.hcl", 347 &api.Job{ 348 ID: helper.StringToPtr("foo"), 349 Name: helper.StringToPtr("foo"), 350 TaskGroups: []*api.TaskGroup{ 351 &api.TaskGroup{ 352 Name: helper.StringToPtr("bar"), 353 Tasks: []*api.Task{ 354 &api.Task{ 355 Name: "bar", 356 Driver: "docker", 357 Config: map[string]interface{}{ 358 "image": "hashicorp/image", 359 "port_map": []map[string]interface{}{ 360 map[string]interface{}{ 361 "db": 1234, 362 }, 363 }, 364 }, 365 }, 366 }, 367 }, 368 }, 369 }, 370 false, 371 }, 372 373 { 374 "bad-artifact.hcl", 375 nil, 376 true, 377 }, 378 379 { 380 "artifacts.hcl", 381 &api.Job{ 382 ID: helper.StringToPtr("binstore-storagelocker"), 383 Name: helper.StringToPtr("binstore-storagelocker"), 384 TaskGroups: []*api.TaskGroup{ 385 &api.TaskGroup{ 386 Name: helper.StringToPtr("binsl"), 387 Tasks: []*api.Task{ 388 &api.Task{ 389 Name: "binstore", 390 Driver: "docker", 391 Artifacts: []*api.TaskArtifact{ 392 { 393 GetterSource: helper.StringToPtr("http://foo.com/bar"), 394 GetterOptions: map[string]string{"foo": "bar"}, 395 RelativeDest: helper.StringToPtr(""), 396 }, 397 { 398 GetterSource: helper.StringToPtr("http://foo.com/baz"), 399 GetterOptions: nil, 400 RelativeDest: nil, 401 }, 402 { 403 GetterSource: helper.StringToPtr("http://foo.com/bam"), 404 GetterOptions: nil, 405 RelativeDest: helper.StringToPtr("var/foo"), 406 }, 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 false, 414 }, 415 { 416 "service-check-initial-status.hcl", 417 &api.Job{ 418 ID: helper.StringToPtr("check_initial_status"), 419 Name: helper.StringToPtr("check_initial_status"), 420 Type: helper.StringToPtr("service"), 421 TaskGroups: []*api.TaskGroup{ 422 &api.TaskGroup{ 423 Name: helper.StringToPtr("group"), 424 Count: helper.IntToPtr(1), 425 Tasks: []*api.Task{ 426 &api.Task{ 427 Name: "task", 428 Services: []*api.Service{ 429 { 430 Tags: []string{"foo", "bar"}, 431 PortLabel: "http", 432 Checks: []api.ServiceCheck{ 433 { 434 Name: "check-name", 435 Type: "http", 436 Interval: 10 * time.Second, 437 Timeout: 2 * time.Second, 438 InitialStatus: capi.HealthPassing, 439 }, 440 }, 441 }, 442 }, 443 }, 444 }, 445 }, 446 }, 447 }, 448 false, 449 }, 450 { 451 // TODO This should be pushed into the API 452 "vault_inheritance.hcl", 453 &api.Job{ 454 ID: helper.StringToPtr("example"), 455 Name: helper.StringToPtr("example"), 456 TaskGroups: []*api.TaskGroup{ 457 &api.TaskGroup{ 458 Name: helper.StringToPtr("cache"), 459 Tasks: []*api.Task{ 460 &api.Task{ 461 Name: "redis", 462 Vault: &api.Vault{ 463 Policies: []string{"group"}, 464 Env: helper.BoolToPtr(true), 465 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 466 }, 467 }, 468 &api.Task{ 469 Name: "redis2", 470 Vault: &api.Vault{ 471 Policies: []string{"task"}, 472 Env: helper.BoolToPtr(false), 473 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 474 }, 475 }, 476 }, 477 }, 478 &api.TaskGroup{ 479 Name: helper.StringToPtr("cache2"), 480 Tasks: []*api.Task{ 481 &api.Task{ 482 Name: "redis", 483 Vault: &api.Vault{ 484 Policies: []string{"job"}, 485 Env: helper.BoolToPtr(true), 486 ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart), 487 }, 488 }, 489 }, 490 }, 491 }, 492 }, 493 false, 494 }, 495 { 496 "parameterized_job.hcl", 497 &api.Job{ 498 ID: helper.StringToPtr("parameterized_job"), 499 Name: helper.StringToPtr("parameterized_job"), 500 501 ParameterizedJob: &api.ParameterizedJobConfig{ 502 Payload: "required", 503 MetaRequired: []string{"foo", "bar"}, 504 MetaOptional: []string{"baz", "bam"}, 505 }, 506 507 TaskGroups: []*api.TaskGroup{ 508 { 509 Name: helper.StringToPtr("foo"), 510 Tasks: []*api.Task{ 511 { 512 Name: "bar", 513 Driver: "docker", 514 DispatchPayload: &api.DispatchPayloadConfig{ 515 File: "foo/bar", 516 }, 517 }, 518 }, 519 }, 520 }, 521 }, 522 false, 523 }, 524 } 525 526 for _, tc := range cases { 527 t.Logf("Testing parse: %s", tc.File) 528 529 path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File)) 530 if err != nil { 531 t.Fatalf("file: %s\n\n%s", tc.File, err) 532 continue 533 } 534 535 actual, err := ParseFile(path) 536 if (err != nil) != tc.Err { 537 t.Fatalf("file: %s\n\n%s", tc.File, err) 538 continue 539 } 540 541 if !reflect.DeepEqual(actual, tc.Result) { 542 for _, d := range pretty.Diff(actual, tc.Result) { 543 t.Logf(d) 544 } 545 t.Fatalf("file: %s", tc.File) 546 } 547 } 548 } 549 550 func TestBadPorts(t *testing.T) { 551 path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl")) 552 if err != nil { 553 t.Fatalf("Can't get absolute path for file: %s", err) 554 } 555 556 _, err = ParseFile(path) 557 558 if !strings.Contains(err.Error(), errPortLabel.Error()) { 559 t.Fatalf("\nExpected error\n %s\ngot\n %v", errPortLabel, err) 560 } 561 } 562 563 func TestOverlappingPorts(t *testing.T) { 564 path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl")) 565 if err != nil { 566 t.Fatalf("Can't get absolute path for file: %s", err) 567 } 568 569 _, err = ParseFile(path) 570 571 if err == nil { 572 t.Fatalf("Expected an error") 573 } 574 575 if !strings.Contains(err.Error(), "found a port label collision") { 576 t.Fatalf("Expected collision error; got %v", err) 577 } 578 } 579 580 func TestIncorrectKey(t *testing.T) { 581 path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl")) 582 if err != nil { 583 t.Fatalf("Can't get absolute path for file: %s", err) 584 } 585 586 _, err = ParseFile(path) 587 588 if err == nil { 589 t.Fatalf("Expected an error") 590 } 591 592 if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service: 'foo', check -> invalid key: nterval") { 593 t.Fatalf("Expected key error; got %v", err) 594 } 595 }