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