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  }