github.com/bigcommerce/nomad@v0.9.3-bc/nomad/periodic_test.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "math/rand" 6 "reflect" 7 "sort" 8 "strconv" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/nomad/helper/testlog" 15 "github.com/hashicorp/nomad/nomad/mock" 16 "github.com/hashicorp/nomad/nomad/structs" 17 "github.com/hashicorp/nomad/testutil" 18 "github.com/stretchr/testify/assert" 19 ) 20 21 type MockJobEvalDispatcher struct { 22 Jobs map[structs.NamespacedID]*structs.Job 23 lock sync.Mutex 24 } 25 26 func NewMockJobEvalDispatcher() *MockJobEvalDispatcher { 27 return &MockJobEvalDispatcher{Jobs: make(map[structs.NamespacedID]*structs.Job)} 28 } 29 30 func (m *MockJobEvalDispatcher) DispatchJob(job *structs.Job) (*structs.Evaluation, error) { 31 m.lock.Lock() 32 defer m.lock.Unlock() 33 tuple := structs.NamespacedID{ 34 ID: job.ID, 35 Namespace: job.Namespace, 36 } 37 m.Jobs[tuple] = job 38 return nil, nil 39 } 40 41 func (m *MockJobEvalDispatcher) RunningChildren(parent *structs.Job) (bool, error) { 42 m.lock.Lock() 43 defer m.lock.Unlock() 44 for _, job := range m.Jobs { 45 if job.ParentID == parent.ID && job.Namespace == parent.Namespace { 46 return true, nil 47 } 48 } 49 return false, nil 50 } 51 52 // LaunchTimes returns the launch times of child jobs in sorted order. 53 func (m *MockJobEvalDispatcher) LaunchTimes(p *PeriodicDispatch, namespace, parentID string) ([]time.Time, error) { 54 m.lock.Lock() 55 defer m.lock.Unlock() 56 var launches []time.Time 57 for _, job := range m.Jobs { 58 if job.ParentID != parentID || job.Namespace != namespace { 59 continue 60 } 61 62 t, err := p.LaunchTime(job.ID) 63 if err != nil { 64 return nil, err 65 } 66 launches = append(launches, t) 67 } 68 sort.Sort(times(launches)) 69 return launches, nil 70 } 71 72 type times []time.Time 73 74 func (t times) Len() int { return len(t) } 75 func (t times) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 76 func (t times) Less(i, j int) bool { return t[i].Before(t[j]) } 77 78 // testPeriodicDispatcher returns an enabled PeriodicDispatcher which uses the 79 // MockJobEvalDispatcher. 80 func testPeriodicDispatcher(t *testing.T) (*PeriodicDispatch, *MockJobEvalDispatcher) { 81 logger := testlog.HCLogger(t) 82 m := NewMockJobEvalDispatcher() 83 d := NewPeriodicDispatch(logger, m) 84 d.SetEnabled(true) 85 return d, m 86 } 87 88 // testPeriodicJob is a helper that creates a periodic job that launches at the 89 // passed times. 90 func testPeriodicJob(times ...time.Time) *structs.Job { 91 job := mock.PeriodicJob() 92 job.Periodic.SpecType = structs.PeriodicSpecTest 93 94 l := make([]string, len(times)) 95 for i, t := range times { 96 l[i] = strconv.Itoa(int(t.Round(1 * time.Second).Unix())) 97 } 98 99 job.Periodic.Spec = strings.Join(l, ",") 100 return job 101 } 102 103 // TestPeriodicDispatch_SetEnabled test that setting enabled twice is a no-op. 104 // This tests the reported issue: https://github.com/hashicorp/nomad/issues/2829 105 func TestPeriodicDispatch_SetEnabled(t *testing.T) { 106 t.Parallel() 107 p, _ := testPeriodicDispatcher(t) 108 109 // SetEnabled has been called once but do it again. 110 p.SetEnabled(true) 111 112 // Now disable and make sure everything is fine. 113 p.SetEnabled(false) 114 115 // Enable and track something 116 p.SetEnabled(true) 117 job := mock.PeriodicJob() 118 if err := p.Add(job); err != nil { 119 t.Fatalf("Add failed %v", err) 120 } 121 122 tracked := p.Tracked() 123 if len(tracked) != 1 { 124 t.Fatalf("Add didn't track the job: %v", tracked) 125 } 126 } 127 128 func TestPeriodicDispatch_Add_NonPeriodic(t *testing.T) { 129 t.Parallel() 130 p, _ := testPeriodicDispatcher(t) 131 job := mock.Job() 132 if err := p.Add(job); err != nil { 133 t.Fatalf("Add of non-periodic job failed: %v; expect no-op", err) 134 } 135 136 tracked := p.Tracked() 137 if len(tracked) != 0 { 138 t.Fatalf("Add of non-periodic job should be no-op: %v", tracked) 139 } 140 } 141 142 func TestPeriodicDispatch_Add_Periodic_Parameterized(t *testing.T) { 143 t.Parallel() 144 p, _ := testPeriodicDispatcher(t) 145 job := mock.PeriodicJob() 146 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 147 if err := p.Add(job); err != nil { 148 t.Fatalf("Add of periodic parameterized job failed: %v", err) 149 } 150 151 tracked := p.Tracked() 152 if len(tracked) != 0 { 153 t.Fatalf("Add of periodic parameterized job should be no-op: %v", tracked) 154 } 155 } 156 157 func TestPeriodicDispatch_Add_Periodic_Stopped(t *testing.T) { 158 t.Parallel() 159 p, _ := testPeriodicDispatcher(t) 160 job := mock.PeriodicJob() 161 job.Stop = true 162 if err := p.Add(job); err != nil { 163 t.Fatalf("Add of stopped periodic job failed: %v", err) 164 } 165 166 tracked := p.Tracked() 167 if len(tracked) != 0 { 168 t.Fatalf("Add of periodic parameterized job should be no-op: %v", tracked) 169 } 170 } 171 172 func TestPeriodicDispatch_Add_UpdateJob(t *testing.T) { 173 t.Parallel() 174 p, _ := testPeriodicDispatcher(t) 175 job := mock.PeriodicJob() 176 if err := p.Add(job); err != nil { 177 t.Fatalf("Add failed %v", err) 178 } 179 180 tracked := p.Tracked() 181 if len(tracked) != 1 { 182 t.Fatalf("Add didn't track the job: %v", tracked) 183 } 184 185 // Update the job and add it again. 186 job.Periodic.Spec = "foo" 187 if err := p.Add(job); err != nil { 188 t.Fatalf("Add failed %v", err) 189 } 190 191 tracked = p.Tracked() 192 if len(tracked) != 1 { 193 t.Fatalf("Add didn't update: %v", tracked) 194 } 195 196 if !reflect.DeepEqual(job, tracked[0]) { 197 t.Fatalf("Add didn't properly update: got %v; want %v", tracked[0], job) 198 } 199 } 200 201 func TestPeriodicDispatch_Add_Remove_Namespaced(t *testing.T) { 202 assert := assert.New(t) 203 t.Parallel() 204 p, _ := testPeriodicDispatcher(t) 205 job := mock.PeriodicJob() 206 job2 := mock.PeriodicJob() 207 job2.Namespace = "test" 208 assert.Nil(p.Add(job)) 209 210 assert.Nil(p.Add(job2)) 211 212 assert.Len(p.Tracked(), 2) 213 214 assert.Nil(p.Remove(job2.Namespace, job2.ID)) 215 assert.Len(p.Tracked(), 1) 216 assert.Equal(p.Tracked()[0], job) 217 } 218 219 func TestPeriodicDispatch_Add_RemoveJob(t *testing.T) { 220 t.Parallel() 221 p, _ := testPeriodicDispatcher(t) 222 job := mock.PeriodicJob() 223 if err := p.Add(job); err != nil { 224 t.Fatalf("Add failed %v", err) 225 } 226 227 tracked := p.Tracked() 228 if len(tracked) != 1 { 229 t.Fatalf("Add didn't track the job: %v", tracked) 230 } 231 232 // Update the job to be non-periodic and add it again. 233 job.Periodic = nil 234 if err := p.Add(job); err != nil { 235 t.Fatalf("Add failed %v", err) 236 } 237 238 tracked = p.Tracked() 239 if len(tracked) != 0 { 240 t.Fatalf("Add didn't remove: %v", tracked) 241 } 242 } 243 244 func TestPeriodicDispatch_Add_TriggersUpdate(t *testing.T) { 245 t.Parallel() 246 p, m := testPeriodicDispatcher(t) 247 248 // Create a job that won't be evaluated for a while. 249 job := testPeriodicJob(time.Now().Add(10 * time.Second)) 250 251 // Add it. 252 if err := p.Add(job); err != nil { 253 t.Fatalf("Add failed %v", err) 254 } 255 256 // Update it to be sooner and re-add. 257 expected := time.Now().Round(1 * time.Second).Add(1 * time.Second) 258 job.Periodic.Spec = fmt.Sprintf("%d", expected.Unix()) 259 if err := p.Add(job); err != nil { 260 t.Fatalf("Add failed %v", err) 261 } 262 263 // Check that nothing is created. 264 tuple := structs.NamespacedID{ 265 ID: job.ID, 266 Namespace: job.Namespace, 267 } 268 if _, ok := m.Jobs[tuple]; ok { 269 t.Fatalf("periodic dispatcher created eval at the wrong time") 270 } 271 272 time.Sleep(2 * time.Second) 273 274 // Check that job was launched correctly. 275 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 276 if err != nil { 277 t.Fatalf("failed to get launch times for job %q", job.ID) 278 } 279 if len(times) != 1 { 280 t.Fatalf("incorrect number of launch times for job %q", job.ID) 281 } 282 if times[0] != expected { 283 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], expected) 284 } 285 } 286 287 func TestPeriodicDispatch_Remove_Untracked(t *testing.T) { 288 t.Parallel() 289 p, _ := testPeriodicDispatcher(t) 290 if err := p.Remove("ns", "foo"); err != nil { 291 t.Fatalf("Remove failed %v; expected a no-op", err) 292 } 293 } 294 295 func TestPeriodicDispatch_Remove_Tracked(t *testing.T) { 296 t.Parallel() 297 p, _ := testPeriodicDispatcher(t) 298 299 job := mock.PeriodicJob() 300 if err := p.Add(job); err != nil { 301 t.Fatalf("Add failed %v", err) 302 } 303 304 tracked := p.Tracked() 305 if len(tracked) != 1 { 306 t.Fatalf("Add didn't track the job: %v", tracked) 307 } 308 309 if err := p.Remove(job.Namespace, job.ID); err != nil { 310 t.Fatalf("Remove failed %v", err) 311 } 312 313 tracked = p.Tracked() 314 if len(tracked) != 0 { 315 t.Fatalf("Remove didn't untrack the job: %v", tracked) 316 } 317 } 318 319 func TestPeriodicDispatch_Remove_TriggersUpdate(t *testing.T) { 320 t.Parallel() 321 p, _ := testPeriodicDispatcher(t) 322 323 // Create a job that will be evaluated soon. 324 job := testPeriodicJob(time.Now().Add(1 * time.Second)) 325 326 // Add it. 327 if err := p.Add(job); err != nil { 328 t.Fatalf("Add failed %v", err) 329 } 330 331 // Remove the job. 332 if err := p.Remove(job.Namespace, job.ID); err != nil { 333 t.Fatalf("Remove failed %v", err) 334 } 335 336 time.Sleep(2 * time.Second) 337 338 // Check that an eval wasn't created. 339 d := p.dispatcher.(*MockJobEvalDispatcher) 340 tuple := structs.NamespacedID{ 341 ID: job.ID, 342 Namespace: job.Namespace, 343 } 344 if _, ok := d.Jobs[tuple]; ok { 345 t.Fatalf("Remove didn't cancel creation of an eval") 346 } 347 } 348 349 func TestPeriodicDispatch_ForceRun_Untracked(t *testing.T) { 350 t.Parallel() 351 p, _ := testPeriodicDispatcher(t) 352 353 if _, err := p.ForceRun("ns", "foo"); err == nil { 354 t.Fatal("ForceRun of untracked job should fail") 355 } 356 } 357 358 func TestPeriodicDispatch_ForceRun_Tracked(t *testing.T) { 359 t.Parallel() 360 p, m := testPeriodicDispatcher(t) 361 362 // Create a job that won't be evaluated for a while. 363 job := testPeriodicJob(time.Now().Add(10 * time.Second)) 364 365 // Add it. 366 if err := p.Add(job); err != nil { 367 t.Fatalf("Add failed %v", err) 368 } 369 370 // ForceRun the job 371 if _, err := p.ForceRun(job.Namespace, job.ID); err != nil { 372 t.Fatalf("ForceRun failed %v", err) 373 } 374 375 // Check that job was launched correctly. 376 launches, err := m.LaunchTimes(p, job.Namespace, job.ID) 377 if err != nil { 378 t.Fatalf("failed to get launch times for job %q: %v", job.ID, err) 379 } 380 l := len(launches) 381 if l != 1 { 382 t.Fatalf("restorePeriodicDispatcher() created an unexpected"+ 383 " number of evals; got %d; want 1", l) 384 } 385 } 386 387 func TestPeriodicDispatch_Run_DisallowOverlaps(t *testing.T) { 388 t.Parallel() 389 p, m := testPeriodicDispatcher(t) 390 391 // Create a job that will trigger two launches but disallows overlapping. 392 launch1 := time.Now().Round(1 * time.Second).Add(1 * time.Second) 393 launch2 := time.Now().Round(1 * time.Second).Add(2 * time.Second) 394 job := testPeriodicJob(launch1, launch2) 395 job.Periodic.ProhibitOverlap = true 396 397 // Add it. 398 if err := p.Add(job); err != nil { 399 t.Fatalf("Add failed %v", err) 400 } 401 402 time.Sleep(3 * time.Second) 403 404 // Check that only one job was launched. 405 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 406 if err != nil { 407 t.Fatalf("failed to get launch times for job %q", job.ID) 408 } 409 if len(times) != 1 { 410 t.Fatalf("incorrect number of launch times for job %q; got %v", job.ID, times) 411 } 412 if times[0] != launch1 { 413 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch1) 414 } 415 } 416 417 func TestPeriodicDispatch_Run_Multiple(t *testing.T) { 418 t.Parallel() 419 p, m := testPeriodicDispatcher(t) 420 421 // Create a job that will be launched twice. 422 launch1 := time.Now().Round(1 * time.Second).Add(1 * time.Second) 423 launch2 := time.Now().Round(1 * time.Second).Add(2 * time.Second) 424 job := testPeriodicJob(launch1, launch2) 425 426 // Add it. 427 if err := p.Add(job); err != nil { 428 t.Fatalf("Add failed %v", err) 429 } 430 431 time.Sleep(3 * time.Second) 432 433 // Check that job was launched correctly. 434 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 435 if err != nil { 436 t.Fatalf("failed to get launch times for job %q", job.ID) 437 } 438 if len(times) != 2 { 439 t.Fatalf("incorrect number of launch times for job %q", job.ID) 440 } 441 if times[0] != launch1 { 442 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch1) 443 } 444 if times[1] != launch2 { 445 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[1], launch2) 446 } 447 } 448 449 func TestPeriodicDispatch_Run_SameTime(t *testing.T) { 450 t.Parallel() 451 p, m := testPeriodicDispatcher(t) 452 453 // Create two job that will be launched at the same time. 454 launch := time.Now().Round(1 * time.Second).Add(1 * time.Second) 455 job := testPeriodicJob(launch) 456 job2 := testPeriodicJob(launch) 457 458 // Add them. 459 if err := p.Add(job); err != nil { 460 t.Fatalf("Add failed %v", err) 461 } 462 if err := p.Add(job2); err != nil { 463 t.Fatalf("Add failed %v", err) 464 } 465 466 if l := len(p.Tracked()); l != 2 { 467 t.Fatalf("got %d tracked; want 2", l) 468 } 469 470 time.Sleep(2 * time.Second) 471 472 // Check that the jobs were launched correctly. 473 for _, job := range []*structs.Job{job, job2} { 474 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 475 if err != nil { 476 t.Fatalf("failed to get launch times for job %q", job.ID) 477 } 478 if len(times) != 1 { 479 t.Fatalf("incorrect number of launch times for job %q; got %d; want 1", job.ID, len(times)) 480 } 481 if times[0] != launch { 482 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch) 483 } 484 } 485 } 486 487 func TestPeriodicDispatch_Run_SameID_Different_Namespace(t *testing.T) { 488 t.Parallel() 489 p, m := testPeriodicDispatcher(t) 490 491 // Create two job that will be launched at the same time. 492 launch := time.Now().Round(1 * time.Second).Add(1 * time.Second) 493 job := testPeriodicJob(launch) 494 job2 := testPeriodicJob(launch) 495 job2.ID = job.ID 496 job2.Namespace = "test" 497 498 // Add them. 499 if err := p.Add(job); err != nil { 500 t.Fatalf("Add failed %v", err) 501 } 502 if err := p.Add(job2); err != nil { 503 t.Fatalf("Add failed %v", err) 504 } 505 506 if l := len(p.Tracked()); l != 2 { 507 t.Fatalf("got %d tracked; want 2", l) 508 } 509 510 if l := len(p.Tracked()); l != 2 { 511 t.Fatalf("got %d tracked; want 2", l) 512 } 513 514 time.Sleep(2 * time.Second) 515 516 // Check that the jobs were launched correctly. 517 for _, job := range []*structs.Job{job, job2} { 518 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 519 if err != nil { 520 t.Fatalf("failed to get launch times for job %q", job.ID) 521 } 522 if len(times) != 1 { 523 t.Fatalf("incorrect number of launch times for job %q; got %d; want 1", job.ID, len(times)) 524 } 525 if times[0] != launch { 526 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch) 527 } 528 } 529 } 530 531 // This test adds and removes a bunch of jobs, some launching at the same time, 532 // some after each other and some invalid times, and ensures the correct 533 // behavior. 534 func TestPeriodicDispatch_Complex(t *testing.T) { 535 t.Parallel() 536 p, m := testPeriodicDispatcher(t) 537 538 // Create some jobs launching at different times. 539 now := time.Now().Round(1 * time.Second) 540 same := now.Add(1 * time.Second) 541 launch1 := same.Add(1 * time.Second) 542 launch2 := same.Add(2 * time.Second) 543 launch3 := same.Add(3 * time.Second) 544 invalid := now.Add(-200 * time.Second) 545 546 // Create two jobs launching at the same time. 547 job1 := testPeriodicJob(same) 548 job2 := testPeriodicJob(same) 549 550 // Create a job that will never launch. 551 job3 := testPeriodicJob(invalid) 552 553 // Create a job that launches twice. 554 job4 := testPeriodicJob(launch1, launch3) 555 556 // Create a job that launches once. 557 job5 := testPeriodicJob(launch2) 558 559 // Create 3 jobs we will delete. 560 job6 := testPeriodicJob(same) 561 job7 := testPeriodicJob(launch1, launch3) 562 job8 := testPeriodicJob(launch2) 563 564 // Create a map of expected eval job ids. 565 expected := map[string][]time.Time{ 566 job1.ID: {same}, 567 job2.ID: {same}, 568 job3.ID: nil, 569 job4.ID: {launch1, launch3}, 570 job5.ID: {launch2}, 571 job6.ID: nil, 572 job7.ID: nil, 573 job8.ID: nil, 574 } 575 576 // Shuffle the jobs so they can be added randomly 577 jobs := []*structs.Job{job1, job2, job3, job4, job5, job6, job7, job8} 578 toDelete := []*structs.Job{job6, job7, job8} 579 shuffle(jobs) 580 shuffle(toDelete) 581 582 for _, job := range jobs { 583 if err := p.Add(job); err != nil { 584 t.Fatalf("Add failed %v", err) 585 } 586 } 587 588 for _, job := range toDelete { 589 if err := p.Remove(job.Namespace, job.ID); err != nil { 590 t.Fatalf("Remove failed %v", err) 591 } 592 } 593 594 time.Sleep(5 * time.Second) 595 actual := make(map[string][]time.Time, len(expected)) 596 for _, job := range jobs { 597 launches, err := m.LaunchTimes(p, job.Namespace, job.ID) 598 if err != nil { 599 t.Fatalf("LaunchTimes(%v, %v) failed %v", job.Namespace, job.ID, err) 600 } 601 602 actual[job.ID] = launches 603 } 604 605 if !reflect.DeepEqual(actual, expected) { 606 t.Fatalf("Unexpected launches; got %#v; want %#v", actual, expected) 607 } 608 } 609 610 func shuffle(jobs []*structs.Job) { 611 rand.Seed(time.Now().Unix()) 612 for i := range jobs { 613 j := rand.Intn(len(jobs)) 614 jobs[i], jobs[j] = jobs[j], jobs[i] 615 } 616 } 617 618 func TestPeriodicHeap_Order(t *testing.T) { 619 t.Parallel() 620 h := NewPeriodicHeap() 621 j1 := mock.PeriodicJob() 622 j2 := mock.PeriodicJob() 623 j3 := mock.PeriodicJob() 624 625 lookup := map[*structs.Job]string{ 626 j1: "j1", 627 j2: "j2", 628 j3: "j3", 629 } 630 631 h.Push(j1, time.Time{}) 632 h.Push(j2, time.Unix(10, 0)) 633 h.Push(j3, time.Unix(11, 0)) 634 635 exp := []string{"j2", "j3", "j1"} 636 var act []string 637 for i := 0; i < 3; i++ { 638 pJob := h.Pop() 639 act = append(act, lookup[pJob.job]) 640 } 641 642 if !reflect.DeepEqual(act, exp) { 643 t.Fatalf("Wrong ordering; got %v; want %v", act, exp) 644 } 645 } 646 647 // deriveChildJob takes a parent periodic job and returns a job with fields set 648 // such that it appears spawned from the parent. 649 func deriveChildJob(parent *structs.Job) *structs.Job { 650 childjob := mock.Job() 651 childjob.ParentID = parent.ID 652 childjob.ID = fmt.Sprintf("%s%s%v", parent.ID, structs.PeriodicLaunchSuffix, time.Now().Unix()) 653 return childjob 654 } 655 656 func TestPeriodicDispatch_RunningChildren_NoEvals(t *testing.T) { 657 t.Parallel() 658 s1 := TestServer(t, nil) 659 defer s1.Shutdown() 660 testutil.WaitForLeader(t, s1.RPC) 661 662 // Insert job. 663 state := s1.fsm.State() 664 job := mock.PeriodicJob() 665 if err := state.UpsertJob(1000, job); err != nil { 666 t.Fatalf("UpsertJob failed: %v", err) 667 } 668 669 running, err := s1.RunningChildren(job) 670 if err != nil { 671 t.Fatalf("RunningChildren failed: %v", err) 672 } 673 674 if running { 675 t.Fatalf("RunningChildren should return false") 676 } 677 } 678 679 func TestPeriodicDispatch_RunningChildren_ActiveEvals(t *testing.T) { 680 t.Parallel() 681 s1 := TestServer(t, nil) 682 defer s1.Shutdown() 683 testutil.WaitForLeader(t, s1.RPC) 684 685 // Insert periodic job and child. 686 state := s1.fsm.State() 687 job := mock.PeriodicJob() 688 if err := state.UpsertJob(1000, job); err != nil { 689 t.Fatalf("UpsertJob failed: %v", err) 690 } 691 692 childjob := deriveChildJob(job) 693 if err := state.UpsertJob(1001, childjob); err != nil { 694 t.Fatalf("UpsertJob failed: %v", err) 695 } 696 697 // Insert non-terminal eval 698 eval := mock.Eval() 699 eval.JobID = childjob.ID 700 eval.Status = structs.EvalStatusPending 701 if err := state.UpsertEvals(1002, []*structs.Evaluation{eval}); err != nil { 702 t.Fatalf("UpsertEvals failed: %v", err) 703 } 704 705 running, err := s1.RunningChildren(job) 706 if err != nil { 707 t.Fatalf("RunningChildren failed: %v", err) 708 } 709 710 if !running { 711 t.Fatalf("RunningChildren should return true") 712 } 713 } 714 715 func TestPeriodicDispatch_RunningChildren_ActiveAllocs(t *testing.T) { 716 t.Parallel() 717 s1 := TestServer(t, nil) 718 defer s1.Shutdown() 719 testutil.WaitForLeader(t, s1.RPC) 720 721 // Insert periodic job and child. 722 state := s1.fsm.State() 723 job := mock.PeriodicJob() 724 if err := state.UpsertJob(1000, job); err != nil { 725 t.Fatalf("UpsertJob failed: %v", err) 726 } 727 728 childjob := deriveChildJob(job) 729 if err := state.UpsertJob(1001, childjob); err != nil { 730 t.Fatalf("UpsertJob failed: %v", err) 731 } 732 733 // Insert terminal eval 734 eval := mock.Eval() 735 eval.JobID = childjob.ID 736 eval.Status = structs.EvalStatusPending 737 if err := state.UpsertEvals(1002, []*structs.Evaluation{eval}); err != nil { 738 t.Fatalf("UpsertEvals failed: %v", err) 739 } 740 741 // Insert active alloc 742 alloc := mock.Alloc() 743 alloc.JobID = childjob.ID 744 alloc.EvalID = eval.ID 745 alloc.DesiredStatus = structs.AllocDesiredStatusRun 746 if err := state.UpsertAllocs(1003, []*structs.Allocation{alloc}); err != nil { 747 t.Fatalf("UpsertAllocs failed: %v", err) 748 } 749 750 running, err := s1.RunningChildren(job) 751 if err != nil { 752 t.Fatalf("RunningChildren failed: %v", err) 753 } 754 755 if !running { 756 t.Fatalf("RunningChildren should return true") 757 } 758 }