github.com/djenriquez/nomad-1@v0.8.1/api/jobs_test.go (about) 1 package api 2 3 import ( 4 "reflect" 5 "sort" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/helper" 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/testutil" 13 "github.com/kr/pretty" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestJobs_Register(t *testing.T) { 19 t.Parallel() 20 require := require.New(t) 21 22 c, s := makeClient(t, nil, nil) 23 defer s.Stop() 24 jobs := c.Jobs() 25 26 // Listing jobs before registering returns nothing 27 resp, _, err := jobs.List(nil) 28 require.Nil(err) 29 require.Emptyf(resp, "expected 0 jobs, got: %d", len(resp)) 30 31 // Create a job and attempt to register it 32 job := testJob() 33 resp2, wm, err := jobs.Register(job, nil) 34 require.Nil(err) 35 require.NotNil(resp2) 36 require.NotEmpty(resp2.EvalID) 37 assertWriteMeta(t, wm) 38 39 // Query the jobs back out again 40 resp, qm, err := jobs.List(nil) 41 assertQueryMeta(t, qm) 42 require.Nil(err) 43 44 // Check that we got the expected response 45 if len(resp) != 1 || resp[0].ID != *job.ID { 46 t.Fatalf("bad: %#v", resp[0]) 47 } 48 } 49 50 func TestJobs_Validate(t *testing.T) { 51 t.Parallel() 52 c, s := makeClient(t, nil, nil) 53 defer s.Stop() 54 jobs := c.Jobs() 55 56 // Create a job and attempt to register it 57 job := testJob() 58 resp, _, err := jobs.Validate(job, nil) 59 if err != nil { 60 t.Fatalf("err: %s", err) 61 } 62 63 if len(resp.ValidationErrors) != 0 { 64 t.Fatalf("bad %v", resp) 65 } 66 67 job.ID = nil 68 resp1, _, err := jobs.Validate(job, nil) 69 if err != nil { 70 t.Fatalf("err: %v", err) 71 } 72 73 if len(resp1.ValidationErrors) == 0 { 74 t.Fatalf("bad %v", resp1) 75 } 76 } 77 78 func TestJobs_Canonicalize(t *testing.T) { 79 t.Parallel() 80 testCases := []struct { 81 name string 82 expected *Job 83 input *Job 84 }{ 85 { 86 name: "empty", 87 input: &Job{ 88 TaskGroups: []*TaskGroup{ 89 { 90 Tasks: []*Task{ 91 {}, 92 }, 93 }, 94 }, 95 }, 96 expected: &Job{ 97 ID: helper.StringToPtr(""), 98 Name: helper.StringToPtr(""), 99 Region: helper.StringToPtr("global"), 100 Namespace: helper.StringToPtr(DefaultNamespace), 101 Type: helper.StringToPtr("service"), 102 ParentID: helper.StringToPtr(""), 103 Priority: helper.IntToPtr(50), 104 AllAtOnce: helper.BoolToPtr(false), 105 VaultToken: helper.StringToPtr(""), 106 Status: helper.StringToPtr(""), 107 StatusDescription: helper.StringToPtr(""), 108 Stop: helper.BoolToPtr(false), 109 Stable: helper.BoolToPtr(false), 110 Version: helper.Uint64ToPtr(0), 111 CreateIndex: helper.Uint64ToPtr(0), 112 ModifyIndex: helper.Uint64ToPtr(0), 113 JobModifyIndex: helper.Uint64ToPtr(0), 114 TaskGroups: []*TaskGroup{ 115 { 116 Name: helper.StringToPtr(""), 117 Count: helper.IntToPtr(1), 118 EphemeralDisk: &EphemeralDisk{ 119 Sticky: helper.BoolToPtr(false), 120 Migrate: helper.BoolToPtr(false), 121 SizeMB: helper.IntToPtr(300), 122 }, 123 RestartPolicy: &RestartPolicy{ 124 Delay: helper.TimeToPtr(15 * time.Second), 125 Attempts: helper.IntToPtr(2), 126 Interval: helper.TimeToPtr(30 * time.Minute), 127 Mode: helper.StringToPtr("fail"), 128 }, 129 ReschedulePolicy: &ReschedulePolicy{ 130 Attempts: helper.IntToPtr(0), 131 Interval: helper.TimeToPtr(0), 132 DelayFunction: helper.StringToPtr("exponential"), 133 Delay: helper.TimeToPtr(30 * time.Second), 134 MaxDelay: helper.TimeToPtr(1 * time.Hour), 135 Unlimited: helper.BoolToPtr(true), 136 }, 137 Migrate: DefaultMigrateStrategy(), 138 Tasks: []*Task{ 139 { 140 KillTimeout: helper.TimeToPtr(5 * time.Second), 141 LogConfig: DefaultLogConfig(), 142 Resources: DefaultResources(), 143 }, 144 }, 145 }, 146 }, 147 }, 148 }, 149 { 150 name: "partial", 151 input: &Job{ 152 Name: helper.StringToPtr("foo"), 153 Namespace: helper.StringToPtr("bar"), 154 ID: helper.StringToPtr("bar"), 155 ParentID: helper.StringToPtr("lol"), 156 TaskGroups: []*TaskGroup{ 157 { 158 Name: helper.StringToPtr("bar"), 159 Tasks: []*Task{ 160 { 161 Name: "task1", 162 }, 163 }, 164 }, 165 }, 166 }, 167 expected: &Job{ 168 Namespace: helper.StringToPtr("bar"), 169 ID: helper.StringToPtr("bar"), 170 Name: helper.StringToPtr("foo"), 171 Region: helper.StringToPtr("global"), 172 Type: helper.StringToPtr("service"), 173 ParentID: helper.StringToPtr("lol"), 174 Priority: helper.IntToPtr(50), 175 AllAtOnce: helper.BoolToPtr(false), 176 VaultToken: helper.StringToPtr(""), 177 Stop: helper.BoolToPtr(false), 178 Stable: helper.BoolToPtr(false), 179 Version: helper.Uint64ToPtr(0), 180 Status: helper.StringToPtr(""), 181 StatusDescription: helper.StringToPtr(""), 182 CreateIndex: helper.Uint64ToPtr(0), 183 ModifyIndex: helper.Uint64ToPtr(0), 184 JobModifyIndex: helper.Uint64ToPtr(0), 185 TaskGroups: []*TaskGroup{ 186 { 187 Name: helper.StringToPtr("bar"), 188 Count: helper.IntToPtr(1), 189 EphemeralDisk: &EphemeralDisk{ 190 Sticky: helper.BoolToPtr(false), 191 Migrate: helper.BoolToPtr(false), 192 SizeMB: helper.IntToPtr(300), 193 }, 194 RestartPolicy: &RestartPolicy{ 195 Delay: helper.TimeToPtr(15 * time.Second), 196 Attempts: helper.IntToPtr(2), 197 Interval: helper.TimeToPtr(30 * time.Minute), 198 Mode: helper.StringToPtr("fail"), 199 }, 200 ReschedulePolicy: &ReschedulePolicy{ 201 Attempts: helper.IntToPtr(0), 202 Interval: helper.TimeToPtr(0), 203 DelayFunction: helper.StringToPtr("exponential"), 204 Delay: helper.TimeToPtr(30 * time.Second), 205 MaxDelay: helper.TimeToPtr(1 * time.Hour), 206 Unlimited: helper.BoolToPtr(true), 207 }, 208 Migrate: DefaultMigrateStrategy(), 209 Tasks: []*Task{ 210 { 211 Name: "task1", 212 LogConfig: DefaultLogConfig(), 213 Resources: DefaultResources(), 214 KillTimeout: helper.TimeToPtr(5 * time.Second), 215 }, 216 }, 217 }, 218 }, 219 }, 220 }, 221 { 222 name: "example_template", 223 input: &Job{ 224 ID: helper.StringToPtr("example_template"), 225 Name: helper.StringToPtr("example_template"), 226 Datacenters: []string{"dc1"}, 227 Type: helper.StringToPtr("service"), 228 Update: &UpdateStrategy{ 229 MaxParallel: helper.IntToPtr(1), 230 }, 231 TaskGroups: []*TaskGroup{ 232 { 233 Name: helper.StringToPtr("cache"), 234 Count: helper.IntToPtr(1), 235 RestartPolicy: &RestartPolicy{ 236 Interval: helper.TimeToPtr(5 * time.Minute), 237 Attempts: helper.IntToPtr(10), 238 Delay: helper.TimeToPtr(25 * time.Second), 239 Mode: helper.StringToPtr("delay"), 240 }, 241 EphemeralDisk: &EphemeralDisk{ 242 SizeMB: helper.IntToPtr(300), 243 }, 244 Tasks: []*Task{ 245 { 246 Name: "redis", 247 Driver: "docker", 248 Config: map[string]interface{}{ 249 "image": "redis:3.2", 250 "port_map": []map[string]int{{ 251 "db": 6379, 252 }}, 253 }, 254 Resources: &Resources{ 255 CPU: helper.IntToPtr(500), 256 MemoryMB: helper.IntToPtr(256), 257 Networks: []*NetworkResource{ 258 { 259 MBits: helper.IntToPtr(10), 260 DynamicPorts: []Port{ 261 { 262 Label: "db", 263 }, 264 }, 265 }, 266 }, 267 }, 268 Services: []*Service{ 269 { 270 Name: "redis-cache", 271 Tags: []string{"global", "cache"}, 272 PortLabel: "db", 273 Checks: []ServiceCheck{ 274 { 275 Name: "alive", 276 Type: "tcp", 277 Interval: 10 * time.Second, 278 Timeout: 2 * time.Second, 279 }, 280 }, 281 }, 282 }, 283 Templates: []*Template{ 284 { 285 EmbeddedTmpl: helper.StringToPtr("---"), 286 DestPath: helper.StringToPtr("local/file.yml"), 287 }, 288 { 289 EmbeddedTmpl: helper.StringToPtr("FOO=bar\n"), 290 DestPath: helper.StringToPtr("local/file.env"), 291 Envvars: helper.BoolToPtr(true), 292 VaultGrace: helper.TimeToPtr(3 * time.Second), 293 }, 294 }, 295 }, 296 }, 297 }, 298 }, 299 }, 300 expected: &Job{ 301 Namespace: helper.StringToPtr(DefaultNamespace), 302 ID: helper.StringToPtr("example_template"), 303 Name: helper.StringToPtr("example_template"), 304 ParentID: helper.StringToPtr(""), 305 Priority: helper.IntToPtr(50), 306 Region: helper.StringToPtr("global"), 307 Type: helper.StringToPtr("service"), 308 AllAtOnce: helper.BoolToPtr(false), 309 VaultToken: helper.StringToPtr(""), 310 Stop: helper.BoolToPtr(false), 311 Stable: helper.BoolToPtr(false), 312 Version: helper.Uint64ToPtr(0), 313 Status: helper.StringToPtr(""), 314 StatusDescription: helper.StringToPtr(""), 315 CreateIndex: helper.Uint64ToPtr(0), 316 ModifyIndex: helper.Uint64ToPtr(0), 317 JobModifyIndex: helper.Uint64ToPtr(0), 318 Datacenters: []string{"dc1"}, 319 Update: &UpdateStrategy{ 320 Stagger: helper.TimeToPtr(30 * time.Second), 321 MaxParallel: helper.IntToPtr(1), 322 HealthCheck: helper.StringToPtr("checks"), 323 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 324 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 325 AutoRevert: helper.BoolToPtr(false), 326 Canary: helper.IntToPtr(0), 327 }, 328 TaskGroups: []*TaskGroup{ 329 { 330 Name: helper.StringToPtr("cache"), 331 Count: helper.IntToPtr(1), 332 RestartPolicy: &RestartPolicy{ 333 Interval: helper.TimeToPtr(5 * time.Minute), 334 Attempts: helper.IntToPtr(10), 335 Delay: helper.TimeToPtr(25 * time.Second), 336 Mode: helper.StringToPtr("delay"), 337 }, 338 ReschedulePolicy: &ReschedulePolicy{ 339 Attempts: helper.IntToPtr(0), 340 Interval: helper.TimeToPtr(0), 341 DelayFunction: helper.StringToPtr("exponential"), 342 Delay: helper.TimeToPtr(30 * time.Second), 343 MaxDelay: helper.TimeToPtr(1 * time.Hour), 344 Unlimited: helper.BoolToPtr(true), 345 }, 346 EphemeralDisk: &EphemeralDisk{ 347 Sticky: helper.BoolToPtr(false), 348 Migrate: helper.BoolToPtr(false), 349 SizeMB: helper.IntToPtr(300), 350 }, 351 352 Update: &UpdateStrategy{ 353 Stagger: helper.TimeToPtr(30 * time.Second), 354 MaxParallel: helper.IntToPtr(1), 355 HealthCheck: helper.StringToPtr("checks"), 356 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 357 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 358 AutoRevert: helper.BoolToPtr(false), 359 Canary: helper.IntToPtr(0), 360 }, 361 Migrate: DefaultMigrateStrategy(), 362 Tasks: []*Task{ 363 { 364 Name: "redis", 365 Driver: "docker", 366 Config: map[string]interface{}{ 367 "image": "redis:3.2", 368 "port_map": []map[string]int{{ 369 "db": 6379, 370 }}, 371 }, 372 Resources: &Resources{ 373 CPU: helper.IntToPtr(500), 374 MemoryMB: helper.IntToPtr(256), 375 IOPS: helper.IntToPtr(0), 376 Networks: []*NetworkResource{ 377 { 378 MBits: helper.IntToPtr(10), 379 DynamicPorts: []Port{ 380 { 381 Label: "db", 382 }, 383 }, 384 }, 385 }, 386 }, 387 Services: []*Service{ 388 { 389 Name: "redis-cache", 390 Tags: []string{"global", "cache"}, 391 PortLabel: "db", 392 AddressMode: "auto", 393 Checks: []ServiceCheck{ 394 { 395 Name: "alive", 396 Type: "tcp", 397 Interval: 10 * time.Second, 398 Timeout: 2 * time.Second, 399 }, 400 }, 401 }, 402 }, 403 KillTimeout: helper.TimeToPtr(5 * time.Second), 404 LogConfig: DefaultLogConfig(), 405 Templates: []*Template{ 406 { 407 SourcePath: helper.StringToPtr(""), 408 DestPath: helper.StringToPtr("local/file.yml"), 409 EmbeddedTmpl: helper.StringToPtr("---"), 410 ChangeMode: helper.StringToPtr("restart"), 411 ChangeSignal: helper.StringToPtr(""), 412 Splay: helper.TimeToPtr(5 * time.Second), 413 Perms: helper.StringToPtr("0644"), 414 LeftDelim: helper.StringToPtr("{{"), 415 RightDelim: helper.StringToPtr("}}"), 416 Envvars: helper.BoolToPtr(false), 417 VaultGrace: helper.TimeToPtr(15 * time.Second), 418 }, 419 { 420 SourcePath: helper.StringToPtr(""), 421 DestPath: helper.StringToPtr("local/file.env"), 422 EmbeddedTmpl: helper.StringToPtr("FOO=bar\n"), 423 ChangeMode: helper.StringToPtr("restart"), 424 ChangeSignal: helper.StringToPtr(""), 425 Splay: helper.TimeToPtr(5 * time.Second), 426 Perms: helper.StringToPtr("0644"), 427 LeftDelim: helper.StringToPtr("{{"), 428 RightDelim: helper.StringToPtr("}}"), 429 Envvars: helper.BoolToPtr(true), 430 VaultGrace: helper.TimeToPtr(3 * time.Second), 431 }, 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 }, 439 440 { 441 name: "periodic", 442 input: &Job{ 443 ID: helper.StringToPtr("bar"), 444 Periodic: &PeriodicConfig{}, 445 }, 446 expected: &Job{ 447 Namespace: helper.StringToPtr(DefaultNamespace), 448 ID: helper.StringToPtr("bar"), 449 ParentID: helper.StringToPtr(""), 450 Name: helper.StringToPtr("bar"), 451 Region: helper.StringToPtr("global"), 452 Type: helper.StringToPtr("service"), 453 Priority: helper.IntToPtr(50), 454 AllAtOnce: helper.BoolToPtr(false), 455 VaultToken: helper.StringToPtr(""), 456 Stop: helper.BoolToPtr(false), 457 Stable: helper.BoolToPtr(false), 458 Version: helper.Uint64ToPtr(0), 459 Status: helper.StringToPtr(""), 460 StatusDescription: helper.StringToPtr(""), 461 CreateIndex: helper.Uint64ToPtr(0), 462 ModifyIndex: helper.Uint64ToPtr(0), 463 JobModifyIndex: helper.Uint64ToPtr(0), 464 Periodic: &PeriodicConfig{ 465 Enabled: helper.BoolToPtr(true), 466 Spec: helper.StringToPtr(""), 467 SpecType: helper.StringToPtr(PeriodicSpecCron), 468 ProhibitOverlap: helper.BoolToPtr(false), 469 TimeZone: helper.StringToPtr("UTC"), 470 }, 471 }, 472 }, 473 474 { 475 name: "update_merge", 476 input: &Job{ 477 Name: helper.StringToPtr("foo"), 478 ID: helper.StringToPtr("bar"), 479 ParentID: helper.StringToPtr("lol"), 480 Update: &UpdateStrategy{ 481 Stagger: helper.TimeToPtr(1 * time.Second), 482 MaxParallel: helper.IntToPtr(1), 483 HealthCheck: helper.StringToPtr("checks"), 484 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 485 HealthyDeadline: helper.TimeToPtr(6 * time.Minute), 486 AutoRevert: helper.BoolToPtr(false), 487 Canary: helper.IntToPtr(0), 488 }, 489 TaskGroups: []*TaskGroup{ 490 { 491 Name: helper.StringToPtr("bar"), 492 Update: &UpdateStrategy{ 493 Stagger: helper.TimeToPtr(2 * time.Second), 494 MaxParallel: helper.IntToPtr(2), 495 HealthCheck: helper.StringToPtr("manual"), 496 MinHealthyTime: helper.TimeToPtr(1 * time.Second), 497 AutoRevert: helper.BoolToPtr(true), 498 Canary: helper.IntToPtr(1), 499 }, 500 Tasks: []*Task{ 501 { 502 Name: "task1", 503 }, 504 }, 505 }, 506 { 507 Name: helper.StringToPtr("baz"), 508 Tasks: []*Task{ 509 { 510 Name: "task1", 511 }, 512 }, 513 }, 514 }, 515 }, 516 expected: &Job{ 517 Namespace: helper.StringToPtr(DefaultNamespace), 518 ID: helper.StringToPtr("bar"), 519 Name: helper.StringToPtr("foo"), 520 Region: helper.StringToPtr("global"), 521 Type: helper.StringToPtr("service"), 522 ParentID: helper.StringToPtr("lol"), 523 Priority: helper.IntToPtr(50), 524 AllAtOnce: helper.BoolToPtr(false), 525 VaultToken: helper.StringToPtr(""), 526 Stop: helper.BoolToPtr(false), 527 Stable: helper.BoolToPtr(false), 528 Version: helper.Uint64ToPtr(0), 529 Status: helper.StringToPtr(""), 530 StatusDescription: helper.StringToPtr(""), 531 CreateIndex: helper.Uint64ToPtr(0), 532 ModifyIndex: helper.Uint64ToPtr(0), 533 JobModifyIndex: helper.Uint64ToPtr(0), 534 Update: &UpdateStrategy{ 535 Stagger: helper.TimeToPtr(1 * time.Second), 536 MaxParallel: helper.IntToPtr(1), 537 HealthCheck: helper.StringToPtr("checks"), 538 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 539 HealthyDeadline: helper.TimeToPtr(6 * time.Minute), 540 AutoRevert: helper.BoolToPtr(false), 541 Canary: helper.IntToPtr(0), 542 }, 543 TaskGroups: []*TaskGroup{ 544 { 545 Name: helper.StringToPtr("bar"), 546 Count: helper.IntToPtr(1), 547 EphemeralDisk: &EphemeralDisk{ 548 Sticky: helper.BoolToPtr(false), 549 Migrate: helper.BoolToPtr(false), 550 SizeMB: helper.IntToPtr(300), 551 }, 552 RestartPolicy: &RestartPolicy{ 553 Delay: helper.TimeToPtr(15 * time.Second), 554 Attempts: helper.IntToPtr(2), 555 Interval: helper.TimeToPtr(30 * time.Minute), 556 Mode: helper.StringToPtr("fail"), 557 }, 558 ReschedulePolicy: &ReschedulePolicy{ 559 Attempts: helper.IntToPtr(0), 560 Interval: helper.TimeToPtr(0), 561 DelayFunction: helper.StringToPtr("exponential"), 562 Delay: helper.TimeToPtr(30 * time.Second), 563 MaxDelay: helper.TimeToPtr(1 * time.Hour), 564 Unlimited: helper.BoolToPtr(true), 565 }, 566 Update: &UpdateStrategy{ 567 Stagger: helper.TimeToPtr(2 * time.Second), 568 MaxParallel: helper.IntToPtr(2), 569 HealthCheck: helper.StringToPtr("manual"), 570 MinHealthyTime: helper.TimeToPtr(1 * time.Second), 571 HealthyDeadline: helper.TimeToPtr(6 * time.Minute), 572 AutoRevert: helper.BoolToPtr(true), 573 Canary: helper.IntToPtr(1), 574 }, 575 Migrate: DefaultMigrateStrategy(), 576 Tasks: []*Task{ 577 { 578 Name: "task1", 579 LogConfig: DefaultLogConfig(), 580 Resources: DefaultResources(), 581 KillTimeout: helper.TimeToPtr(5 * time.Second), 582 }, 583 }, 584 }, 585 { 586 Name: helper.StringToPtr("baz"), 587 Count: helper.IntToPtr(1), 588 EphemeralDisk: &EphemeralDisk{ 589 Sticky: helper.BoolToPtr(false), 590 Migrate: helper.BoolToPtr(false), 591 SizeMB: helper.IntToPtr(300), 592 }, 593 RestartPolicy: &RestartPolicy{ 594 Delay: helper.TimeToPtr(15 * time.Second), 595 Attempts: helper.IntToPtr(2), 596 Interval: helper.TimeToPtr(30 * time.Minute), 597 Mode: helper.StringToPtr("fail"), 598 }, 599 ReschedulePolicy: &ReschedulePolicy{ 600 Attempts: helper.IntToPtr(0), 601 Interval: helper.TimeToPtr(0), 602 DelayFunction: helper.StringToPtr("exponential"), 603 Delay: helper.TimeToPtr(30 * time.Second), 604 MaxDelay: helper.TimeToPtr(1 * time.Hour), 605 Unlimited: helper.BoolToPtr(true), 606 }, 607 Update: &UpdateStrategy{ 608 Stagger: helper.TimeToPtr(1 * time.Second), 609 MaxParallel: helper.IntToPtr(1), 610 HealthCheck: helper.StringToPtr("checks"), 611 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 612 HealthyDeadline: helper.TimeToPtr(6 * time.Minute), 613 AutoRevert: helper.BoolToPtr(false), 614 Canary: helper.IntToPtr(0), 615 }, 616 Migrate: DefaultMigrateStrategy(), 617 Tasks: []*Task{ 618 { 619 Name: "task1", 620 LogConfig: DefaultLogConfig(), 621 Resources: DefaultResources(), 622 KillTimeout: helper.TimeToPtr(5 * time.Second), 623 }, 624 }, 625 }, 626 }, 627 }, 628 }, 629 } 630 631 for _, tc := range testCases { 632 t.Run(tc.name, func(t *testing.T) { 633 tc.input.Canonicalize() 634 if !reflect.DeepEqual(tc.input, tc.expected) { 635 t.Fatalf("Name: %v, Diffs:\n%v", tc.name, pretty.Diff(tc.expected, tc.input)) 636 } 637 }) 638 } 639 } 640 641 func TestJobs_EnforceRegister(t *testing.T) { 642 t.Parallel() 643 require := require.New(t) 644 c, s := makeClient(t, nil, nil) 645 defer s.Stop() 646 jobs := c.Jobs() 647 648 // Listing jobs before registering returns nothing 649 resp, _, err := jobs.List(nil) 650 require.Nil(err) 651 require.Empty(resp) 652 653 // Create a job and attempt to register it with an incorrect index. 654 job := testJob() 655 resp2, _, err := jobs.EnforceRegister(job, 10, nil) 656 require.NotNil(err) 657 require.Contains(err.Error(), RegisterEnforceIndexErrPrefix) 658 659 // Register 660 resp2, wm, err := jobs.EnforceRegister(job, 0, nil) 661 require.Nil(err) 662 require.NotNil(resp2) 663 require.NotZero(resp2.EvalID) 664 assertWriteMeta(t, wm) 665 666 // Query the jobs back out again 667 resp, qm, err := jobs.List(nil) 668 require.Nil(err) 669 require.Len(resp, 1) 670 require.Equal(*job.ID, resp[0].ID) 671 assertQueryMeta(t, qm) 672 673 // Fail at incorrect index 674 curIndex := resp[0].JobModifyIndex 675 resp2, _, err = jobs.EnforceRegister(job, 123456, nil) 676 require.NotNil(err) 677 require.Contains(err.Error(), RegisterEnforceIndexErrPrefix) 678 679 // Works at correct index 680 resp3, wm, err := jobs.EnforceRegister(job, curIndex, nil) 681 require.Nil(err) 682 require.NotNil(resp3) 683 require.NotZero(resp3.EvalID) 684 assertWriteMeta(t, wm) 685 } 686 687 func TestJobs_Revert(t *testing.T) { 688 t.Parallel() 689 c, s := makeClient(t, nil, nil) 690 defer s.Stop() 691 jobs := c.Jobs() 692 693 // Register twice 694 job := testJob() 695 resp, wm, err := jobs.Register(job, nil) 696 if err != nil { 697 t.Fatalf("err: %s", err) 698 } 699 if resp == nil || resp.EvalID == "" { 700 t.Fatalf("missing eval id") 701 } 702 assertWriteMeta(t, wm) 703 704 job.Meta = map[string]string{"foo": "new"} 705 resp, wm, err = jobs.Register(job, nil) 706 if err != nil { 707 t.Fatalf("err: %s", err) 708 } 709 if resp == nil || resp.EvalID == "" { 710 t.Fatalf("missing eval id") 711 } 712 assertWriteMeta(t, wm) 713 714 // Fail revert at incorrect enforce 715 _, _, err = jobs.Revert(*job.ID, 0, helper.Uint64ToPtr(10), nil) 716 if err == nil || !strings.Contains(err.Error(), "enforcing version") { 717 t.Fatalf("expected enforcement error: %v", err) 718 } 719 720 // Works at correct index 721 revertResp, wm, err := jobs.Revert(*job.ID, 0, helper.Uint64ToPtr(1), nil) 722 if err != nil { 723 t.Fatalf("err: %s", err) 724 } 725 if revertResp.EvalID == "" { 726 t.Fatalf("missing eval id") 727 } 728 if revertResp.EvalCreateIndex == 0 { 729 t.Fatalf("bad eval create index") 730 } 731 if revertResp.JobModifyIndex == 0 { 732 t.Fatalf("bad job modify index") 733 } 734 assertWriteMeta(t, wm) 735 } 736 737 func TestJobs_Info(t *testing.T) { 738 t.Parallel() 739 c, s := makeClient(t, nil, nil) 740 defer s.Stop() 741 jobs := c.Jobs() 742 743 // Trying to retrieve a job by ID before it exists 744 // returns an error 745 _, _, err := jobs.Info("job1", nil) 746 if err == nil || !strings.Contains(err.Error(), "not found") { 747 t.Fatalf("expected not found error, got: %#v", err) 748 } 749 750 // Register the job 751 job := testJob() 752 _, wm, err := jobs.Register(job, nil) 753 if err != nil { 754 t.Fatalf("err: %s", err) 755 } 756 assertWriteMeta(t, wm) 757 758 // Query the job again and ensure it exists 759 result, qm, err := jobs.Info("job1", nil) 760 if err != nil { 761 t.Fatalf("err: %s", err) 762 } 763 assertQueryMeta(t, qm) 764 765 // Check that the result is what we expect 766 if result == nil || *result.ID != *job.ID { 767 t.Fatalf("expect: %#v, got: %#v", job, result) 768 } 769 } 770 771 func TestJobs_Versions(t *testing.T) { 772 t.Parallel() 773 c, s := makeClient(t, nil, nil) 774 defer s.Stop() 775 jobs := c.Jobs() 776 777 // Trying to retrieve a job by ID before it exists returns an error 778 _, _, _, err := jobs.Versions("job1", false, nil) 779 if err == nil || !strings.Contains(err.Error(), "not found") { 780 t.Fatalf("expected not found error, got: %#v", err) 781 } 782 783 // Register the job 784 job := testJob() 785 _, wm, err := jobs.Register(job, nil) 786 if err != nil { 787 t.Fatalf("err: %s", err) 788 } 789 assertWriteMeta(t, wm) 790 791 // Query the job again and ensure it exists 792 result, _, qm, err := jobs.Versions("job1", false, nil) 793 if err != nil { 794 t.Fatalf("err: %s", err) 795 } 796 assertQueryMeta(t, qm) 797 798 // Check that the result is what we expect 799 if len(result) == 0 || *result[0].ID != *job.ID { 800 t.Fatalf("expect: %#v, got: %#v", job, result) 801 } 802 } 803 804 func TestJobs_PrefixList(t *testing.T) { 805 t.Parallel() 806 c, s := makeClient(t, nil, nil) 807 defer s.Stop() 808 jobs := c.Jobs() 809 810 // Listing when nothing exists returns empty 811 results, _, err := jobs.PrefixList("dummy") 812 if err != nil { 813 t.Fatalf("err: %s", err) 814 } 815 if n := len(results); n != 0 { 816 t.Fatalf("expected 0 jobs, got: %d", n) 817 } 818 819 // Register the job 820 job := testJob() 821 _, wm, err := jobs.Register(job, nil) 822 if err != nil { 823 t.Fatalf("err: %s", err) 824 } 825 assertWriteMeta(t, wm) 826 827 // Query the job again and ensure it exists 828 // Listing when nothing exists returns empty 829 results, _, err = jobs.PrefixList((*job.ID)[:1]) 830 if err != nil { 831 t.Fatalf("err: %s", err) 832 } 833 834 // Check if we have the right list 835 if len(results) != 1 || results[0].ID != *job.ID { 836 t.Fatalf("bad: %#v", results) 837 } 838 } 839 840 func TestJobs_List(t *testing.T) { 841 t.Parallel() 842 c, s := makeClient(t, nil, nil) 843 defer s.Stop() 844 jobs := c.Jobs() 845 846 // Listing when nothing exists returns empty 847 results, _, err := jobs.List(nil) 848 if err != nil { 849 t.Fatalf("err: %s", err) 850 } 851 if n := len(results); n != 0 { 852 t.Fatalf("expected 0 jobs, got: %d", n) 853 } 854 855 // Register the job 856 job := testJob() 857 _, wm, err := jobs.Register(job, nil) 858 if err != nil { 859 t.Fatalf("err: %s", err) 860 } 861 assertWriteMeta(t, wm) 862 863 // Query the job again and ensure it exists 864 // Listing when nothing exists returns empty 865 results, _, err = jobs.List(nil) 866 if err != nil { 867 t.Fatalf("err: %s", err) 868 } 869 870 // Check if we have the right list 871 if len(results) != 1 || results[0].ID != *job.ID { 872 t.Fatalf("bad: %#v", results) 873 } 874 } 875 876 func TestJobs_Allocations(t *testing.T) { 877 t.Parallel() 878 c, s := makeClient(t, nil, nil) 879 defer s.Stop() 880 jobs := c.Jobs() 881 882 // Looking up by a nonexistent job returns nothing 883 allocs, qm, err := jobs.Allocations("job1", true, nil) 884 if err != nil { 885 t.Fatalf("err: %s", err) 886 } 887 if qm.LastIndex != 0 { 888 t.Fatalf("bad index: %d", qm.LastIndex) 889 } 890 if n := len(allocs); n != 0 { 891 t.Fatalf("expected 0 allocs, got: %d", n) 892 } 893 894 // TODO: do something here to create some allocations for 895 // an existing job, lookup again. 896 } 897 898 func TestJobs_Evaluations(t *testing.T) { 899 t.Parallel() 900 c, s := makeClient(t, nil, nil) 901 defer s.Stop() 902 jobs := c.Jobs() 903 904 // Looking up by a nonexistent job ID returns nothing 905 evals, qm, err := jobs.Evaluations("job1", nil) 906 if err != nil { 907 t.Fatalf("err: %s", err) 908 } 909 if qm.LastIndex != 0 { 910 t.Fatalf("bad index: %d", qm.LastIndex) 911 } 912 if n := len(evals); n != 0 { 913 t.Fatalf("expected 0 evals, got: %d", n) 914 } 915 916 // Insert a job. This also creates an evaluation so we should 917 // be able to query that out after. 918 job := testJob() 919 resp, wm, err := jobs.Register(job, nil) 920 if err != nil { 921 t.Fatalf("err: %s", err) 922 } 923 assertWriteMeta(t, wm) 924 925 // Look up the evaluations again. 926 evals, qm, err = jobs.Evaluations("job1", nil) 927 if err != nil { 928 t.Fatalf("err: %s", err) 929 } 930 assertQueryMeta(t, qm) 931 932 // Check that we got the evals back, evals are in order most recent to least recent 933 // so the last eval is the original registered eval 934 idx := len(evals) - 1 935 if n := len(evals); n == 0 || evals[idx].ID != resp.EvalID { 936 t.Fatalf("expected >= 1 eval (%s), got: %#v", resp.EvalID, evals[idx]) 937 } 938 } 939 940 func TestJobs_Deregister(t *testing.T) { 941 t.Parallel() 942 c, s := makeClient(t, nil, nil) 943 defer s.Stop() 944 jobs := c.Jobs() 945 946 // Register a new job 947 job := testJob() 948 _, wm, err := jobs.Register(job, nil) 949 if err != nil { 950 t.Fatalf("err: %s", err) 951 } 952 assertWriteMeta(t, wm) 953 954 // Attempting delete on non-existing job returns an error 955 if _, _, err = jobs.Deregister("nope", false, nil); err != nil { 956 t.Fatalf("unexpected error deregistering job: %v", err) 957 } 958 959 // Do a soft deregister of an existing job 960 evalID, wm3, err := jobs.Deregister("job1", false, nil) 961 if err != nil { 962 t.Fatalf("err: %s", err) 963 } 964 assertWriteMeta(t, wm3) 965 if evalID == "" { 966 t.Fatalf("missing eval ID") 967 } 968 969 // Check that the job is still queryable 970 out, qm1, err := jobs.Info("job1", nil) 971 if err != nil { 972 t.Fatalf("err: %s", err) 973 } 974 assertQueryMeta(t, qm1) 975 if out == nil { 976 t.Fatalf("missing job") 977 } 978 979 // Do a purge deregister of an existing job 980 evalID, wm4, err := jobs.Deregister("job1", true, nil) 981 if err != nil { 982 t.Fatalf("err: %s", err) 983 } 984 assertWriteMeta(t, wm4) 985 if evalID == "" { 986 t.Fatalf("missing eval ID") 987 } 988 989 // Check that the job is really gone 990 result, qm, err := jobs.List(nil) 991 if err != nil { 992 t.Fatalf("err: %s", err) 993 } 994 assertQueryMeta(t, qm) 995 if n := len(result); n != 0 { 996 t.Fatalf("expected 0 jobs, got: %d", n) 997 } 998 } 999 1000 func TestJobs_ForceEvaluate(t *testing.T) { 1001 t.Parallel() 1002 c, s := makeClient(t, nil, nil) 1003 defer s.Stop() 1004 jobs := c.Jobs() 1005 1006 // Force-eval on a non-existent job fails 1007 _, _, err := jobs.ForceEvaluate("job1", nil) 1008 if err == nil || !strings.Contains(err.Error(), "not found") { 1009 t.Fatalf("expected not found error, got: %#v", err) 1010 } 1011 1012 // Create a new job 1013 _, wm, err := jobs.Register(testJob(), nil) 1014 if err != nil { 1015 t.Fatalf("err: %s", err) 1016 } 1017 assertWriteMeta(t, wm) 1018 1019 // Try force-eval again 1020 evalID, wm, err := jobs.ForceEvaluate("job1", nil) 1021 if err != nil { 1022 t.Fatalf("err: %s", err) 1023 } 1024 assertWriteMeta(t, wm) 1025 1026 // Retrieve the evals and see if we get a matching one 1027 evals, qm, err := jobs.Evaluations("job1", nil) 1028 if err != nil { 1029 t.Fatalf("err: %s", err) 1030 } 1031 assertQueryMeta(t, qm) 1032 for _, eval := range evals { 1033 if eval.ID == evalID { 1034 return 1035 } 1036 } 1037 t.Fatalf("evaluation %q missing", evalID) 1038 } 1039 1040 func TestJobs_PeriodicForce(t *testing.T) { 1041 t.Parallel() 1042 c, s := makeClient(t, nil, nil) 1043 defer s.Stop() 1044 jobs := c.Jobs() 1045 1046 // Force-eval on a nonexistent job fails 1047 _, _, err := jobs.PeriodicForce("job1", nil) 1048 if err == nil || !strings.Contains(err.Error(), "not found") { 1049 t.Fatalf("expected not found error, got: %#v", err) 1050 } 1051 1052 // Create a new job 1053 job := testPeriodicJob() 1054 _, _, err = jobs.Register(job, nil) 1055 if err != nil { 1056 t.Fatalf("err: %s", err) 1057 } 1058 1059 testutil.WaitForResult(func() (bool, error) { 1060 out, _, err := jobs.Info(*job.ID, nil) 1061 if err != nil || out == nil || *out.ID != *job.ID { 1062 return false, err 1063 } 1064 return true, nil 1065 }, func(err error) { 1066 t.Fatalf("err: %s", err) 1067 }) 1068 1069 // Try force again 1070 evalID, wm, err := jobs.PeriodicForce(*job.ID, nil) 1071 if err != nil { 1072 t.Fatalf("err: %s", err) 1073 } 1074 assertWriteMeta(t, wm) 1075 1076 if evalID == "" { 1077 t.Fatalf("empty evalID") 1078 } 1079 1080 // Retrieve the eval 1081 evals := c.Evaluations() 1082 eval, qm, err := evals.Info(evalID, nil) 1083 if err != nil { 1084 t.Fatalf("err: %s", err) 1085 } 1086 assertQueryMeta(t, qm) 1087 if eval.ID == evalID { 1088 return 1089 } 1090 t.Fatalf("evaluation %q missing", evalID) 1091 } 1092 1093 func TestJobs_Plan(t *testing.T) { 1094 t.Parallel() 1095 c, s := makeClient(t, nil, nil) 1096 defer s.Stop() 1097 jobs := c.Jobs() 1098 1099 // Create a job and attempt to register it 1100 job := testJob() 1101 resp, wm, err := jobs.Register(job, nil) 1102 if err != nil { 1103 t.Fatalf("err: %s", err) 1104 } 1105 if resp == nil || resp.EvalID == "" { 1106 t.Fatalf("missing eval id") 1107 } 1108 assertWriteMeta(t, wm) 1109 1110 // Check that passing a nil job fails 1111 if _, _, err := jobs.Plan(nil, true, nil); err == nil { 1112 t.Fatalf("expect an error when job isn't provided") 1113 } 1114 1115 // Make a plan request 1116 planResp, wm, err := jobs.Plan(job, true, nil) 1117 if err != nil { 1118 t.Fatalf("err: %s", err) 1119 } 1120 if planResp == nil { 1121 t.Fatalf("nil response") 1122 } 1123 1124 if planResp.JobModifyIndex == 0 { 1125 t.Fatalf("bad JobModifyIndex value: %#v", planResp) 1126 } 1127 if planResp.Diff == nil { 1128 t.Fatalf("got nil diff: %#v", planResp) 1129 } 1130 if planResp.Annotations == nil { 1131 t.Fatalf("got nil annotations: %#v", planResp) 1132 } 1133 // Can make this assertion because there are no clients. 1134 if len(planResp.CreatedEvals) == 0 { 1135 t.Fatalf("got no CreatedEvals: %#v", planResp) 1136 } 1137 assertWriteMeta(t, wm) 1138 1139 // Make a plan request w/o the diff 1140 planResp, wm, err = jobs.Plan(job, false, nil) 1141 if err != nil { 1142 t.Fatalf("err: %s", err) 1143 } 1144 assertWriteMeta(t, wm) 1145 1146 if planResp == nil { 1147 t.Fatalf("nil response") 1148 } 1149 1150 if planResp.JobModifyIndex == 0 { 1151 t.Fatalf("bad JobModifyIndex value: %d", planResp.JobModifyIndex) 1152 } 1153 if planResp.Diff != nil { 1154 t.Fatalf("got non-nil diff: %#v", planResp) 1155 } 1156 if planResp.Annotations == nil { 1157 t.Fatalf("got nil annotations: %#v", planResp) 1158 } 1159 // Can make this assertion because there are no clients. 1160 if len(planResp.CreatedEvals) == 0 { 1161 t.Fatalf("got no CreatedEvals: %#v", planResp) 1162 } 1163 } 1164 1165 func TestJobs_JobSummary(t *testing.T) { 1166 t.Parallel() 1167 c, s := makeClient(t, nil, nil) 1168 defer s.Stop() 1169 jobs := c.Jobs() 1170 1171 // Trying to retrieve a job summary before the job exists 1172 // returns an error 1173 _, _, err := jobs.Summary("job1", nil) 1174 if err == nil || !strings.Contains(err.Error(), "not found") { 1175 t.Fatalf("expected not found error, got: %#v", err) 1176 } 1177 1178 // Register the job 1179 job := testJob() 1180 taskName := job.TaskGroups[0].Name 1181 _, wm, err := jobs.Register(job, nil) 1182 if err != nil { 1183 t.Fatalf("err: %s", err) 1184 } 1185 assertWriteMeta(t, wm) 1186 1187 // Query the job summary again and ensure it exists 1188 result, qm, err := jobs.Summary("job1", nil) 1189 if err != nil { 1190 t.Fatalf("err: %s", err) 1191 } 1192 assertQueryMeta(t, qm) 1193 1194 // Check that the result is what we expect 1195 if *job.ID != result.JobID { 1196 t.Fatalf("err: expected job id of %s saw %s", *job.ID, result.JobID) 1197 } 1198 if _, ok := result.Summary[*taskName]; !ok { 1199 t.Fatalf("err: unable to find %s key in job summary", *taskName) 1200 } 1201 } 1202 1203 func TestJobs_NewBatchJob(t *testing.T) { 1204 t.Parallel() 1205 job := NewBatchJob("job1", "myjob", "region1", 5) 1206 expect := &Job{ 1207 Region: helper.StringToPtr("region1"), 1208 ID: helper.StringToPtr("job1"), 1209 Name: helper.StringToPtr("myjob"), 1210 Type: helper.StringToPtr(JobTypeBatch), 1211 Priority: helper.IntToPtr(5), 1212 } 1213 if !reflect.DeepEqual(job, expect) { 1214 t.Fatalf("expect: %#v, got: %#v", expect, job) 1215 } 1216 } 1217 1218 func TestJobs_NewServiceJob(t *testing.T) { 1219 t.Parallel() 1220 job := NewServiceJob("job1", "myjob", "region1", 5) 1221 expect := &Job{ 1222 Region: helper.StringToPtr("region1"), 1223 ID: helper.StringToPtr("job1"), 1224 Name: helper.StringToPtr("myjob"), 1225 Type: helper.StringToPtr(JobTypeService), 1226 Priority: helper.IntToPtr(5), 1227 } 1228 if !reflect.DeepEqual(job, expect) { 1229 t.Fatalf("expect: %#v, got: %#v", expect, job) 1230 } 1231 } 1232 1233 func TestJobs_SetMeta(t *testing.T) { 1234 t.Parallel() 1235 job := &Job{Meta: nil} 1236 1237 // Initializes a nil map 1238 out := job.SetMeta("foo", "bar") 1239 if job.Meta == nil { 1240 t.Fatalf("should initialize metadata") 1241 } 1242 1243 // Check that the job was returned 1244 if job != out { 1245 t.Fatalf("expect: %#v, got: %#v", job, out) 1246 } 1247 1248 // Setting another pair is additive 1249 job.SetMeta("baz", "zip") 1250 expect := map[string]string{"foo": "bar", "baz": "zip"} 1251 if !reflect.DeepEqual(job.Meta, expect) { 1252 t.Fatalf("expect: %#v, got: %#v", expect, job.Meta) 1253 } 1254 } 1255 1256 func TestJobs_Constrain(t *testing.T) { 1257 t.Parallel() 1258 job := &Job{Constraints: nil} 1259 1260 // Create and add a constraint 1261 out := job.Constrain(NewConstraint("kernel.name", "=", "darwin")) 1262 if n := len(job.Constraints); n != 1 { 1263 t.Fatalf("expected 1 constraint, got: %d", n) 1264 } 1265 1266 // Check that the job was returned 1267 if job != out { 1268 t.Fatalf("expect: %#v, got: %#v", job, out) 1269 } 1270 1271 // Adding another constraint preserves the original 1272 job.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000")) 1273 expect := []*Constraint{ 1274 { 1275 LTarget: "kernel.name", 1276 RTarget: "darwin", 1277 Operand: "=", 1278 }, 1279 { 1280 LTarget: "memory.totalbytes", 1281 RTarget: "128000000", 1282 Operand: ">=", 1283 }, 1284 } 1285 if !reflect.DeepEqual(job.Constraints, expect) { 1286 t.Fatalf("expect: %#v, got: %#v", expect, job.Constraints) 1287 } 1288 } 1289 1290 func TestJobs_Sort(t *testing.T) { 1291 t.Parallel() 1292 jobs := []*JobListStub{ 1293 {ID: "job2"}, 1294 {ID: "job0"}, 1295 {ID: "job1"}, 1296 } 1297 sort.Sort(JobIDSort(jobs)) 1298 1299 expect := []*JobListStub{ 1300 {ID: "job0"}, 1301 {ID: "job1"}, 1302 {ID: "job2"}, 1303 } 1304 if !reflect.DeepEqual(jobs, expect) { 1305 t.Fatalf("\n\n%#v\n\n%#v", jobs, expect) 1306 } 1307 } 1308 1309 func TestJobs_Summary_WithACL(t *testing.T) { 1310 t.Parallel() 1311 assert := assert.New(t) 1312 1313 c, s, root := makeACLClient(t, nil, nil) 1314 defer s.Stop() 1315 jobs := c.Jobs() 1316 1317 invalidToken := mock.ACLToken() 1318 1319 // Registering with an invalid token should fail 1320 c.SetSecretID(invalidToken.SecretID) 1321 job := testJob() 1322 _, _, err := jobs.Register(job, nil) 1323 assert.NotNil(err) 1324 1325 // Register with token should succeed 1326 c.SetSecretID(root.SecretID) 1327 resp2, wm, err := jobs.Register(job, nil) 1328 assert.Nil(err) 1329 assert.NotNil(resp2) 1330 assert.NotEqual("", resp2.EvalID) 1331 assertWriteMeta(t, wm) 1332 1333 // Query the job summary with an invalid token should fail 1334 c.SetSecretID(invalidToken.SecretID) 1335 result, _, err := jobs.Summary(*job.ID, nil) 1336 assert.NotNil(err) 1337 1338 // Query the job summary with a valid token should succeed 1339 c.SetSecretID(root.SecretID) 1340 result, qm, err := jobs.Summary(*job.ID, nil) 1341 assert.Nil(err) 1342 assertQueryMeta(t, qm) 1343 1344 // Check that the result is what we expect 1345 assert.Equal(*job.ID, result.JobID) 1346 }