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