github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 "github.com/stretchr/testify/require" 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(t *testing.T) (*PeriodicDispatch, *MockJobEvalDispatcher) { 82 logger := testlog.HCLogger(t) 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(t) 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(t) 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(t) 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(t) 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(t) 176 job := mock.PeriodicJob() 177 err := p.Add(job) 178 require.NoError(t, err) 179 180 tracked := p.Tracked() 181 require.Lenf(t, tracked, 1, "did not track the job") 182 183 // Update the job and add it again. 184 job.Periodic.Spec = "foo" 185 err = p.Add(job) 186 require.Error(t, err) 187 require.Contains(t, err.Error(), "failed parsing cron expression") 188 189 tracked = p.Tracked() 190 require.Lenf(t, tracked, 1, "did not update") 191 192 require.Equalf(t, job, tracked[0], "add did not properly update") 193 } 194 195 func TestPeriodicDispatch_Add_Remove_Namespaced(t *testing.T) { 196 assert := assert.New(t) 197 t.Parallel() 198 p, _ := testPeriodicDispatcher(t) 199 job := mock.PeriodicJob() 200 job2 := mock.PeriodicJob() 201 job2.Namespace = "test" 202 assert.Nil(p.Add(job)) 203 204 assert.Nil(p.Add(job2)) 205 206 assert.Len(p.Tracked(), 2) 207 208 assert.Nil(p.Remove(job2.Namespace, job2.ID)) 209 assert.Len(p.Tracked(), 1) 210 assert.Equal(p.Tracked()[0], job) 211 } 212 213 func TestPeriodicDispatch_Add_RemoveJob(t *testing.T) { 214 t.Parallel() 215 p, _ := testPeriodicDispatcher(t) 216 job := mock.PeriodicJob() 217 if err := p.Add(job); err != nil { 218 t.Fatalf("Add failed %v", err) 219 } 220 221 tracked := p.Tracked() 222 if len(tracked) != 1 { 223 t.Fatalf("Add didn't track the job: %v", tracked) 224 } 225 226 // Update the job to be non-periodic and add it again. 227 job.Periodic = nil 228 if err := p.Add(job); err != nil { 229 t.Fatalf("Add failed %v", err) 230 } 231 232 tracked = p.Tracked() 233 if len(tracked) != 0 { 234 t.Fatalf("Add didn't remove: %v", tracked) 235 } 236 } 237 238 func TestPeriodicDispatch_Add_TriggersUpdate(t *testing.T) { 239 t.Parallel() 240 p, m := testPeriodicDispatcher(t) 241 242 // Create a job that won't be evaluated for a while. 243 job := testPeriodicJob(time.Now().Add(10 * time.Second)) 244 245 // Add it. 246 if err := p.Add(job); err != nil { 247 t.Fatalf("Add failed %v", err) 248 } 249 250 // Update it to be sooner and re-add. 251 expected := time.Now().Round(1 * time.Second).Add(1 * time.Second) 252 job.Periodic.Spec = fmt.Sprintf("%d", expected.Unix()) 253 if err := p.Add(job); err != nil { 254 t.Fatalf("Add failed %v", err) 255 } 256 257 // Check that nothing is created. 258 tuple := structs.NamespacedID{ 259 ID: job.ID, 260 Namespace: job.Namespace, 261 } 262 if _, ok := m.Jobs[tuple]; ok { 263 t.Fatalf("periodic dispatcher created eval at the wrong time") 264 } 265 266 time.Sleep(2 * time.Second) 267 268 // Check that job was launched correctly. 269 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 270 if err != nil { 271 t.Fatalf("failed to get launch times for job %q", job.ID) 272 } 273 if len(times) != 1 { 274 t.Fatalf("incorrect number of launch times for job %q", job.ID) 275 } 276 if times[0] != expected { 277 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], expected) 278 } 279 } 280 281 func TestPeriodicDispatch_Remove_Untracked(t *testing.T) { 282 t.Parallel() 283 p, _ := testPeriodicDispatcher(t) 284 if err := p.Remove("ns", "foo"); err != nil { 285 t.Fatalf("Remove failed %v; expected a no-op", err) 286 } 287 } 288 289 func TestPeriodicDispatch_Remove_Tracked(t *testing.T) { 290 t.Parallel() 291 p, _ := testPeriodicDispatcher(t) 292 293 job := mock.PeriodicJob() 294 if err := p.Add(job); err != nil { 295 t.Fatalf("Add failed %v", err) 296 } 297 298 tracked := p.Tracked() 299 if len(tracked) != 1 { 300 t.Fatalf("Add didn't track the job: %v", tracked) 301 } 302 303 if err := p.Remove(job.Namespace, job.ID); err != nil { 304 t.Fatalf("Remove failed %v", err) 305 } 306 307 tracked = p.Tracked() 308 if len(tracked) != 0 { 309 t.Fatalf("Remove didn't untrack the job: %v", tracked) 310 } 311 } 312 313 func TestPeriodicDispatch_Remove_TriggersUpdate(t *testing.T) { 314 t.Parallel() 315 p, _ := testPeriodicDispatcher(t) 316 317 // Create a job that will be evaluated soon. 318 job := testPeriodicJob(time.Now().Add(1 * time.Second)) 319 320 // Add it. 321 if err := p.Add(job); err != nil { 322 t.Fatalf("Add failed %v", err) 323 } 324 325 // Remove the job. 326 if err := p.Remove(job.Namespace, job.ID); err != nil { 327 t.Fatalf("Remove failed %v", err) 328 } 329 330 time.Sleep(2 * time.Second) 331 332 // Check that an eval wasn't created. 333 d := p.dispatcher.(*MockJobEvalDispatcher) 334 tuple := structs.NamespacedID{ 335 ID: job.ID, 336 Namespace: job.Namespace, 337 } 338 if _, ok := d.Jobs[tuple]; ok { 339 t.Fatalf("Remove didn't cancel creation of an eval") 340 } 341 } 342 343 func TestPeriodicDispatch_ForceRun_Untracked(t *testing.T) { 344 t.Parallel() 345 p, _ := testPeriodicDispatcher(t) 346 347 if _, err := p.ForceRun("ns", "foo"); err == nil { 348 t.Fatal("ForceRun of untracked job should fail") 349 } 350 } 351 352 func TestPeriodicDispatch_ForceRun_Tracked(t *testing.T) { 353 t.Parallel() 354 p, m := testPeriodicDispatcher(t) 355 356 // Create a job that won't be evaluated for a while. 357 job := testPeriodicJob(time.Now().Add(10 * time.Second)) 358 359 // Add it. 360 if err := p.Add(job); err != nil { 361 t.Fatalf("Add failed %v", err) 362 } 363 364 // ForceRun the job 365 if _, err := p.ForceRun(job.Namespace, job.ID); err != nil { 366 t.Fatalf("ForceRun failed %v", err) 367 } 368 369 // Check that job was launched correctly. 370 launches, err := m.LaunchTimes(p, job.Namespace, job.ID) 371 if err != nil { 372 t.Fatalf("failed to get launch times for job %q: %v", job.ID, err) 373 } 374 l := len(launches) 375 if l != 1 { 376 t.Fatalf("restorePeriodicDispatcher() created an unexpected"+ 377 " number of evals; got %d; want 1", l) 378 } 379 } 380 381 func TestPeriodicDispatch_Run_DisallowOverlaps(t *testing.T) { 382 t.Parallel() 383 p, m := testPeriodicDispatcher(t) 384 385 // Create a job that will trigger two launches but disallows overlapping. 386 launch1 := time.Now().Round(1 * time.Second).Add(1 * time.Second) 387 launch2 := time.Now().Round(1 * time.Second).Add(2 * time.Second) 388 job := testPeriodicJob(launch1, launch2) 389 job.Periodic.ProhibitOverlap = true 390 391 // Add it. 392 if err := p.Add(job); err != nil { 393 t.Fatalf("Add failed %v", err) 394 } 395 396 time.Sleep(3 * time.Second) 397 398 // Check that only one job was launched. 399 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 400 if err != nil { 401 t.Fatalf("failed to get launch times for job %q", job.ID) 402 } 403 if len(times) != 1 { 404 t.Fatalf("incorrect number of launch times for job %q; got %v", job.ID, times) 405 } 406 if times[0] != launch1 { 407 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch1) 408 } 409 } 410 411 func TestPeriodicDispatch_Run_Multiple(t *testing.T) { 412 t.Parallel() 413 p, m := testPeriodicDispatcher(t) 414 415 // Create a job that will be launched twice. 416 launch1 := time.Now().Round(1 * time.Second).Add(1 * time.Second) 417 launch2 := time.Now().Round(1 * time.Second).Add(2 * time.Second) 418 job := testPeriodicJob(launch1, launch2) 419 420 // Add it. 421 if err := p.Add(job); err != nil { 422 t.Fatalf("Add failed %v", err) 423 } 424 425 time.Sleep(3 * time.Second) 426 427 // Check that job was launched correctly. 428 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 429 if err != nil { 430 t.Fatalf("failed to get launch times for job %q", job.ID) 431 } 432 if len(times) != 2 { 433 t.Fatalf("incorrect number of launch times for job %q", job.ID) 434 } 435 if times[0] != launch1 { 436 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch1) 437 } 438 if times[1] != launch2 { 439 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[1], launch2) 440 } 441 } 442 443 func TestPeriodicDispatch_Run_SameTime(t *testing.T) { 444 t.Parallel() 445 p, m := testPeriodicDispatcher(t) 446 447 // Create two job that will be launched at the same time. 448 launch := time.Now().Round(1 * time.Second).Add(1 * time.Second) 449 job := testPeriodicJob(launch) 450 job2 := testPeriodicJob(launch) 451 452 // Add them. 453 if err := p.Add(job); err != nil { 454 t.Fatalf("Add failed %v", err) 455 } 456 if err := p.Add(job2); err != nil { 457 t.Fatalf("Add failed %v", err) 458 } 459 460 if l := len(p.Tracked()); l != 2 { 461 t.Fatalf("got %d tracked; want 2", l) 462 } 463 464 time.Sleep(2 * time.Second) 465 466 // Check that the jobs were launched correctly. 467 for _, job := range []*structs.Job{job, job2} { 468 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 469 if err != nil { 470 t.Fatalf("failed to get launch times for job %q", job.ID) 471 } 472 if len(times) != 1 { 473 t.Fatalf("incorrect number of launch times for job %q; got %d; want 1", job.ID, len(times)) 474 } 475 if times[0] != launch { 476 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch) 477 } 478 } 479 } 480 481 func TestPeriodicDispatch_Run_SameID_Different_Namespace(t *testing.T) { 482 t.Parallel() 483 p, m := testPeriodicDispatcher(t) 484 485 // Create two job that will be launched at the same time. 486 launch := time.Now().Round(1 * time.Second).Add(1 * time.Second) 487 job := testPeriodicJob(launch) 488 job2 := testPeriodicJob(launch) 489 job2.ID = job.ID 490 job2.Namespace = "test" 491 492 // Add them. 493 if err := p.Add(job); err != nil { 494 t.Fatalf("Add failed %v", err) 495 } 496 if err := p.Add(job2); err != nil { 497 t.Fatalf("Add failed %v", err) 498 } 499 500 if l := len(p.Tracked()); l != 2 { 501 t.Fatalf("got %d tracked; want 2", l) 502 } 503 504 if l := len(p.Tracked()); l != 2 { 505 t.Fatalf("got %d tracked; want 2", l) 506 } 507 508 time.Sleep(2 * time.Second) 509 510 // Check that the jobs were launched correctly. 511 for _, job := range []*structs.Job{job, job2} { 512 times, err := m.LaunchTimes(p, job.Namespace, job.ID) 513 if err != nil { 514 t.Fatalf("failed to get launch times for job %q", job.ID) 515 } 516 if len(times) != 1 { 517 t.Fatalf("incorrect number of launch times for job %q; got %d; want 1", job.ID, len(times)) 518 } 519 if times[0] != launch { 520 t.Fatalf("periodic dispatcher created eval for time %v; want %v", times[0], launch) 521 } 522 } 523 } 524 525 // This test adds and removes a bunch of jobs, some launching at the same time, 526 // some after each other and some invalid times, and ensures the correct 527 // behavior. 528 func TestPeriodicDispatch_Complex(t *testing.T) { 529 t.Parallel() 530 p, m := testPeriodicDispatcher(t) 531 532 // Create some jobs launching at different times. 533 now := time.Now().Round(1 * time.Second) 534 same := now.Add(1 * time.Second) 535 launch1 := same.Add(1 * time.Second) 536 launch2 := same.Add(2 * time.Second) 537 launch3 := same.Add(3 * time.Second) 538 invalid := now.Add(-200 * time.Second) 539 540 // Create two jobs launching at the same time. 541 job1 := testPeriodicJob(same) 542 job2 := testPeriodicJob(same) 543 544 // Create a job that will never launch. 545 job3 := testPeriodicJob(invalid) 546 547 // Create a job that launches twice. 548 job4 := testPeriodicJob(launch1, launch3) 549 550 // Create a job that launches once. 551 job5 := testPeriodicJob(launch2) 552 553 // Create 3 jobs we will delete. 554 job6 := testPeriodicJob(same) 555 job7 := testPeriodicJob(launch1, launch3) 556 job8 := testPeriodicJob(launch2) 557 558 // Create a map of expected eval job ids. 559 expected := map[string][]time.Time{ 560 job1.ID: {same}, 561 job2.ID: {same}, 562 job3.ID: nil, 563 job4.ID: {launch1, launch3}, 564 job5.ID: {launch2}, 565 job6.ID: nil, 566 job7.ID: nil, 567 job8.ID: nil, 568 } 569 570 // Shuffle the jobs so they can be added randomly 571 jobs := []*structs.Job{job1, job2, job3, job4, job5, job6, job7, job8} 572 toDelete := []*structs.Job{job6, job7, job8} 573 shuffle(jobs) 574 shuffle(toDelete) 575 576 for _, job := range jobs { 577 if err := p.Add(job); err != nil { 578 t.Fatalf("Add failed %v", err) 579 } 580 } 581 582 for _, job := range toDelete { 583 if err := p.Remove(job.Namespace, job.ID); err != nil { 584 t.Fatalf("Remove failed %v", err) 585 } 586 } 587 588 time.Sleep(5 * time.Second) 589 actual := make(map[string][]time.Time, len(expected)) 590 for _, job := range jobs { 591 launches, err := m.LaunchTimes(p, job.Namespace, job.ID) 592 if err != nil { 593 t.Fatalf("LaunchTimes(%v, %v) failed %v", job.Namespace, job.ID, err) 594 } 595 596 actual[job.ID] = launches 597 } 598 599 if !reflect.DeepEqual(actual, expected) { 600 t.Fatalf("Unexpected launches; got %#v; want %#v", actual, expected) 601 } 602 } 603 604 func shuffle(jobs []*structs.Job) { 605 rand.Seed(time.Now().Unix()) 606 for i := range jobs { 607 j := rand.Intn(len(jobs)) 608 jobs[i], jobs[j] = jobs[j], jobs[i] 609 } 610 } 611 612 func TestPeriodicHeap_Order(t *testing.T) { 613 t.Parallel() 614 h := NewPeriodicHeap() 615 j1 := mock.PeriodicJob() 616 j2 := mock.PeriodicJob() 617 j3 := mock.PeriodicJob() 618 619 lookup := map[*structs.Job]string{ 620 j1: "j1", 621 j2: "j2", 622 j3: "j3", 623 } 624 625 h.Push(j1, time.Time{}) 626 h.Push(j2, time.Unix(10, 0)) 627 h.Push(j3, time.Unix(11, 0)) 628 629 exp := []string{"j2", "j3", "j1"} 630 var act []string 631 for i := 0; i < 3; i++ { 632 pJob := h.Pop() 633 act = append(act, lookup[pJob.job]) 634 } 635 636 if !reflect.DeepEqual(act, exp) { 637 t.Fatalf("Wrong ordering; got %v; want %v", act, exp) 638 } 639 } 640 641 // deriveChildJob takes a parent periodic job and returns a job with fields set 642 // such that it appears spawned from the parent. 643 func deriveChildJob(parent *structs.Job) *structs.Job { 644 childjob := mock.Job() 645 childjob.ParentID = parent.ID 646 childjob.ID = fmt.Sprintf("%s%s%v", parent.ID, structs.PeriodicLaunchSuffix, time.Now().Unix()) 647 return childjob 648 } 649 650 func TestPeriodicDispatch_RunningChildren_NoEvals(t *testing.T) { 651 t.Parallel() 652 653 s1, cleanupS1 := TestServer(t, nil) 654 defer cleanupS1() 655 testutil.WaitForLeader(t, s1.RPC) 656 657 // Insert job. 658 state := s1.fsm.State() 659 job := mock.PeriodicJob() 660 if err := state.UpsertJob(1000, job); err != nil { 661 t.Fatalf("UpsertJob failed: %v", err) 662 } 663 664 running, err := s1.RunningChildren(job) 665 if err != nil { 666 t.Fatalf("RunningChildren failed: %v", err) 667 } 668 669 if running { 670 t.Fatalf("RunningChildren should return false") 671 } 672 } 673 674 func TestPeriodicDispatch_RunningChildren_ActiveEvals(t *testing.T) { 675 t.Parallel() 676 677 s1, cleanupS1 := TestServer(t, nil) 678 defer cleanupS1() 679 testutil.WaitForLeader(t, s1.RPC) 680 681 // Insert periodic job and child. 682 state := s1.fsm.State() 683 job := mock.PeriodicJob() 684 if err := state.UpsertJob(1000, job); err != nil { 685 t.Fatalf("UpsertJob failed: %v", err) 686 } 687 688 childjob := deriveChildJob(job) 689 if err := state.UpsertJob(1001, childjob); err != nil { 690 t.Fatalf("UpsertJob failed: %v", err) 691 } 692 693 // Insert non-terminal eval 694 eval := mock.Eval() 695 eval.JobID = childjob.ID 696 eval.Status = structs.EvalStatusPending 697 if err := state.UpsertEvals(1002, []*structs.Evaluation{eval}); err != nil { 698 t.Fatalf("UpsertEvals failed: %v", err) 699 } 700 701 running, err := s1.RunningChildren(job) 702 if err != nil { 703 t.Fatalf("RunningChildren failed: %v", err) 704 } 705 706 if !running { 707 t.Fatalf("RunningChildren should return true") 708 } 709 } 710 711 func TestPeriodicDispatch_RunningChildren_ActiveAllocs(t *testing.T) { 712 t.Parallel() 713 714 s1, cleanupS1 := TestServer(t, nil) 715 defer cleanupS1() 716 testutil.WaitForLeader(t, s1.RPC) 717 718 // Insert periodic job and child. 719 state := s1.fsm.State() 720 job := mock.PeriodicJob() 721 if err := state.UpsertJob(1000, job); err != nil { 722 t.Fatalf("UpsertJob failed: %v", err) 723 } 724 725 childjob := deriveChildJob(job) 726 if err := state.UpsertJob(1001, childjob); err != nil { 727 t.Fatalf("UpsertJob failed: %v", err) 728 } 729 730 // Insert terminal eval 731 eval := mock.Eval() 732 eval.JobID = childjob.ID 733 eval.Status = structs.EvalStatusPending 734 if err := state.UpsertEvals(1002, []*structs.Evaluation{eval}); err != nil { 735 t.Fatalf("UpsertEvals failed: %v", err) 736 } 737 738 // Insert active alloc 739 alloc := mock.Alloc() 740 alloc.JobID = childjob.ID 741 alloc.EvalID = eval.ID 742 alloc.DesiredStatus = structs.AllocDesiredStatusRun 743 if err := state.UpsertAllocs(1003, []*structs.Allocation{alloc}); err != nil { 744 t.Fatalf("UpsertAllocs failed: %v", err) 745 } 746 747 running, err := s1.RunningChildren(job) 748 if err != nil { 749 t.Fatalf("RunningChildren failed: %v", err) 750 } 751 752 if !running { 753 t.Fatalf("RunningChildren should return true") 754 } 755 }