func TestPeriodicHeap_Order(t *testing.T) { h := NewPeriodicHeap() j1 := mock.PeriodicJob() j2 := mock.PeriodicJob() j3 := mock.PeriodicJob() lookup := map[*structs.Job]string{ j1: "j1", j2: "j2", j3: "j3", } h.Push(j1, time.Time{}) h.Push(j2, time.Unix(10, 0)) h.Push(j3, time.Unix(11, 0)) exp := []string{"j2", "j3", "j1"} var act []string for i := 0; i < 3; i++ { pJob := h.Pop() act = append(act, lookup[pJob.job]) } if !reflect.DeepEqual(act, exp) { t.Fatalf("Wrong ordering; got %v; want %v", act, exp) } }
func TestPeriodicDispatch_Add_UpdateJob(t *testing.T) { p, _ := testPeriodicDispatcher() job := mock.PeriodicJob() if err := p.Add(job); err != nil { t.Fatalf("Add failed %v", err) } tracked := p.Tracked() if len(tracked) != 1 { t.Fatalf("Add didn't track the job: %v", tracked) } // Update the job and add it again. job.Periodic.Spec = "foo" if err := p.Add(job); err != nil { t.Fatalf("Add failed %v", err) } tracked = p.Tracked() if len(tracked) != 1 { t.Fatalf("Add didn't update: %v", tracked) } if !reflect.DeepEqual(job, tracked[0]) { t.Fatalf("Add didn't properly update: got %v; want %v", tracked[0], job) } }
func TestPeriodicDispatch_RunningChildren_ActiveEvals(t *testing.T) { s1 := testServer(t, nil) defer s1.Shutdown() testutil.WaitForLeader(t, s1.RPC) // Insert periodic job and child. state := s1.fsm.State() job := mock.PeriodicJob() if err := state.UpsertJob(1000, job); err != nil { t.Fatalf("UpsertJob failed: %v", err) } childjob := deriveChildJob(job) if err := state.UpsertJob(1001, childjob); err != nil { t.Fatalf("UpsertJob failed: %v", err) } // Insert non-terminal eval eval := mock.Eval() eval.JobID = childjob.ID eval.Status = structs.EvalStatusPending if err := state.UpsertEvals(1002, []*structs.Evaluation{eval}); err != nil { t.Fatalf("UpsertEvals failed: %v", err) } running, err := s1.RunningChildren(job) if err != nil { t.Fatalf("RunningChildren failed: %v", err) } if !running { t.Fatalf("RunningChildren should return true") } }
func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { s1 := testServer(t, func(c *Config) { c.NumSchedulers = 0 // Prevent automatic dequeue }) defer s1.Shutdown() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request job := mock.PeriodicJob() req := &structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } // Fetch the response var resp structs.JobRegisterResponse if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { t.Fatalf("err: %v", err) } if resp.JobModifyIndex == 0 { t.Fatalf("bad index: %d", resp.Index) } // Force a re-evaluation reEval := &structs.JobEvaluateRequest{ JobID: job.ID, WriteRequest: structs.WriteRequest{Region: "global"}, } // Fetch the response if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { t.Fatal("expect an err") } }
// testPeriodicJob is a helper that creates a periodic job that launches at the // passed times. func testPeriodicJob(times ...time.Time) *structs.Job { job := mock.PeriodicJob() job.Periodic.SpecType = structs.PeriodicSpecTest l := make([]string, len(times)) for i, t := range times { l[i] = strconv.Itoa(int(t.Round(1 * time.Second).Unix())) } job.Periodic.Spec = strings.Join(l, ",") return job }
func TestFSM_DeregisterJob(t *testing.T) { fsm := testFSM(t) job := mock.PeriodicJob() req := structs.JobRegisterRequest{ Job: job, } buf, err := structs.Encode(structs.JobRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } req2 := structs.JobDeregisterRequest{ JobID: job.ID, } buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are NOT registered jobOut, err := fsm.State().JobByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if jobOut != nil { t.Fatalf("job found!") } // Verify it was removed from the periodic runner. if _, ok := fsm.periodicDispatcher.tracked[job.ID]; ok { t.Fatal("job not removed from periodic runner") } // Verify it was removed from the periodic launch table. launchOut, err := fsm.State().PeriodicLaunchByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if launchOut != nil { t.Fatalf("launch found!") } }
func TestStateStore_GetJobStatus_NoEvalsOrAllocs_Periodic(t *testing.T) { job := mock.PeriodicJob() state := testStateStore(t) txn := state.db.Txn(false) status, err := state.getJobStatus(txn, job, false) if err != nil { t.Fatalf("getJobStatus() failed: %v", err) } if status != structs.JobStatusRunning { t.Fatalf("getJobStatus() returned %v; expected %v", status, structs.JobStatusRunning) } }
func TestJobEndpoint_Deregister_Periodic(t *testing.T) { s1 := testServer(t, func(c *Config) { c.NumSchedulers = 0 // Prevent automatic dequeue }) defer s1.Shutdown() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request job := mock.PeriodicJob() reg := &structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } // Fetch the response var resp structs.JobRegisterResponse if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { t.Fatalf("err: %v", err) } // Deregister dereg := &structs.JobDeregisterRequest{ JobID: job.ID, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp2 structs.JobDeregisterResponse if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { t.Fatalf("err: %v", err) } if resp2.JobModifyIndex == 0 { t.Fatalf("bad index: %d", resp2.Index) } // Check for the node in the FSM state := s1.fsm.State() out, err := state.JobByID(job.ID) if err != nil { t.Fatalf("err: %v", err) } if out != nil { t.Fatalf("unexpected job") } if resp.EvalID != "" { t.Fatalf("Deregister created an eval for a periodic job") } }
func TestFSM_RegisterJob(t *testing.T) { fsm := testFSM(t) job := mock.PeriodicJob() req := structs.JobRegisterRequest{ Job: job, } buf, err := structs.Encode(structs.JobRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered jobOut, err := fsm.State().JobByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if jobOut == nil { t.Fatalf("not found!") } if jobOut.CreateIndex != 1 { t.Fatalf("bad index: %d", jobOut.CreateIndex) } // Verify it was added to the periodic runner. if _, ok := fsm.periodicDispatcher.tracked[job.ID]; !ok { t.Fatal("job not added to periodic runner") } // Verify the launch time was tracked. launchOut, err := fsm.State().PeriodicLaunchByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if launchOut == nil { t.Fatalf("not found!") } if launchOut.Launch.IsZero() { t.Fatalf("bad launch time: %v", launchOut.Launch) } }
func TestJobEndpoint_Register_Periodic(t *testing.T) { s1 := testServer(t, func(c *Config) { c.NumSchedulers = 0 // Prevent automatic dequeue }) defer s1.Shutdown() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request for a periodic job. job := mock.PeriodicJob() req := &structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } // Fetch the response var resp structs.JobRegisterResponse if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { t.Fatalf("err: %v", err) } if resp.JobModifyIndex == 0 { t.Fatalf("bad index: %d", resp.Index) } // Check for the node in the FSM state := s1.fsm.State() out, err := state.JobByID(job.ID) if err != nil { t.Fatalf("err: %v", err) } if out == nil { t.Fatalf("expected job") } if out.CreateIndex != resp.JobModifyIndex { t.Fatalf("index mis-match") } serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name expectedServiceName := "web-frontend" if serviceName != expectedServiceName { t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) } if resp.EvalID != "" { t.Fatalf("Register created an eval for a periodic job") } }
func TestPeriodicEndpoint_Force(t *testing.T) { s1 := testServer(t, func(c *Config) { c.NumSchedulers = 0 // Prevent automatic dequeue }) state := s1.fsm.State() defer s1.Shutdown() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create and insert a periodic job. job := mock.PeriodicJob() job.Periodic.ProhibitOverlap = true // Shouldn't affect anything. if err := state.UpsertJob(100, job); err != nil { t.Fatalf("err: %v", err) } s1.periodicDispatcher.Add(job) // Force launch it. req := &structs.PeriodicForceRequest{ JobID: job.ID, WriteRequest: structs.WriteRequest{Region: "global"}, } // Fetch the response var resp structs.PeriodicForceResponse if err := msgpackrpc.CallWithCodec(codec, "Periodic.Force", req, &resp); err != nil { t.Fatalf("err: %v", err) } if resp.Index == 0 { t.Fatalf("bad index: %d", resp.Index) } // Lookup the evaluation eval, err := state.EvalByID(resp.EvalID) if err != nil { t.Fatalf("err: %v", err) } if eval == nil { t.Fatalf("expected eval") } if eval.CreateIndex != resp.EvalCreateIndex { t.Fatalf("index mis-match") } }
func TestPeriodicDispatch_RunningChildren_NoEvals(t *testing.T) { s1 := testServer(t, nil) defer s1.Shutdown() testutil.WaitForLeader(t, s1.RPC) // Insert job. state := s1.fsm.State() job := mock.PeriodicJob() if err := state.UpsertJob(1000, job); err != nil { t.Fatalf("UpsertJob failed: %v", err) } running, err := s1.RunningChildren(job) if err != nil { t.Fatalf("RunningChildren failed: %v", err) } if running { t.Fatalf("RunningChildren should return false") } }
func TestPeriodicDispatch_Remove_Tracked(t *testing.T) { p, _ := testPeriodicDispatcher() job := mock.PeriodicJob() if err := p.Add(job); err != nil { t.Fatalf("Add failed %v", err) } tracked := p.Tracked() if len(tracked) != 1 { t.Fatalf("Add didn't track the job: %v", tracked) } if err := p.Remove(job.ID); err != nil { t.Fatalf("Remove failed %v", err) } tracked = p.Tracked() if len(tracked) != 0 { t.Fatalf("Remove didn't untrack the job: %v", tracked) } }
func TestHTTP_PeriodicForce(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create and register a periodic job. job := mock.PeriodicJob() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the response r := obj.(structs.PeriodicForceResponse) if r.EvalID == "" { t.Fatalf("bad: %#v", r) } }) }
func TestPeriodicDispatch_Add_RemoveJob(t *testing.T) { p, _ := testPeriodicDispatcher() job := mock.PeriodicJob() if err := p.Add(job); err != nil { t.Fatalf("Add failed %v", err) } tracked := p.Tracked() if len(tracked) != 1 { t.Fatalf("Add didn't track the job: %v", tracked) } // Update the job to be non-periodic and add it again. job.Periodic = nil if err := p.Add(job); err != nil { t.Fatalf("Add failed %v", err) } tracked = p.Tracked() if len(tracked) != 0 { t.Fatalf("Add didn't remove: %v", tracked) } }
func TestStateStore_JobsByGC(t *testing.T) { state := testStateStore(t) var gc, nonGc []*structs.Job for i := 0; i < 20; i++ { var job *structs.Job if i%2 == 0 { job = mock.Job() } else { job = mock.PeriodicJob() } nonGc = append(nonGc, job) if err := state.UpsertJob(1000+uint64(i), job); err != nil { t.Fatalf("err: %v", err) } } for i := 0; i < 10; i++ { job := mock.Job() job.Type = structs.JobTypeBatch gc = append(gc, job) if err := state.UpsertJob(2000+uint64(i), job); err != nil { t.Fatalf("err: %v", err) } } iter, err := state.JobsByGC(true) if err != nil { t.Fatalf("err: %v", err) } var outGc []*structs.Job for i := iter.Next(); i != nil; i = iter.Next() { outGc = append(outGc, i.(*structs.Job)) } iter, err = state.JobsByGC(false) if err != nil { t.Fatalf("err: %v", err) } var outNonGc []*structs.Job for i := iter.Next(); i != nil; i = iter.Next() { outNonGc = append(outNonGc, i.(*structs.Job)) } sort.Sort(JobIDSort(gc)) sort.Sort(JobIDSort(nonGc)) sort.Sort(JobIDSort(outGc)) sort.Sort(JobIDSort(outNonGc)) if !reflect.DeepEqual(gc, outGc) { t.Fatalf("bad: %#v %#v", gc, outGc) } if !reflect.DeepEqual(nonGc, outNonGc) { t.Fatalf("bad: %#v %#v", nonGc, outNonGc) } }
func TestStateStore_JobsByPeriodic(t *testing.T) { state := testStateStore(t) var periodic, nonPeriodic []*structs.Job for i := 0; i < 10; i++ { job := mock.Job() nonPeriodic = append(nonPeriodic, job) err := state.UpsertJob(1000+uint64(i), job) if err != nil { t.Fatalf("err: %v", err) } } for i := 0; i < 10; i++ { job := mock.PeriodicJob() periodic = append(periodic, job) err := state.UpsertJob(2000+uint64(i), job) if err != nil { t.Fatalf("err: %v", err) } } iter, err := state.JobsByPeriodic(true) if err != nil { t.Fatalf("err: %v", err) } var outPeriodic []*structs.Job for { raw := iter.Next() if raw == nil { break } outPeriodic = append(outPeriodic, raw.(*structs.Job)) } iter, err = state.JobsByPeriodic(false) var outNonPeriodic []*structs.Job for { raw := iter.Next() if raw == nil { break } outNonPeriodic = append(outNonPeriodic, raw.(*structs.Job)) } sort.Sort(JobIDSort(periodic)) sort.Sort(JobIDSort(nonPeriodic)) sort.Sort(JobIDSort(outPeriodic)) sort.Sort(JobIDSort(outNonPeriodic)) if !reflect.DeepEqual(periodic, outPeriodic) { t.Fatalf("bad: %#v %#v", periodic, outPeriodic) } if !reflect.DeepEqual(nonPeriodic, outNonPeriodic) { t.Fatalf("bad: %#v %#v", nonPeriodic, outNonPeriodic) } }
func TestLeader_PeriodicDispatcher_Restore_Adds(t *testing.T) { s1 := testServer(t, func(c *Config) { c.NumSchedulers = 0 }) defer s1.Shutdown() s2 := testServer(t, func(c *Config) { c.NumSchedulers = 0 c.DevDisableBootstrap = true }) defer s2.Shutdown() s3 := testServer(t, func(c *Config) { c.NumSchedulers = 0 c.DevDisableBootstrap = true }) defer s3.Shutdown() servers := []*Server{s1, s2, s3} testJoin(t, s1, s2, s3) testutil.WaitForLeader(t, s1.RPC) for _, s := range servers { testutil.WaitForResult(func() (bool, error) { peers, _ := s.raftPeers.Peers() return len(peers) == 3, nil }, func(err error) { t.Fatalf("should have 3 peers") }) } var leader *Server for _, s := range servers { if s.IsLeader() { leader = s break } } if leader == nil { t.Fatalf("Should have a leader") } // Inject a periodic job and non-periodic job periodic := mock.PeriodicJob() nonPeriodic := mock.Job() for _, job := range []*structs.Job{nonPeriodic, periodic} { req := structs.JobRegisterRequest{ Job: job, } _, _, err := leader.raftApply(structs.JobRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } } // Kill the leader leader.Shutdown() time.Sleep(100 * time.Millisecond) // Wait for a new leader leader = nil testutil.WaitForResult(func() (bool, error) { for _, s := range servers { if s.IsLeader() { leader = s return true, nil } } return false, nil }, func(err error) { t.Fatalf("should have leader") }) // Check that the new leader is tracking the periodic job. testutil.WaitForResult(func() (bool, error) { _, tracked := leader.periodicDispatcher.tracked[periodic.ID] return tracked, nil }, func(err error) { t.Fatalf("periodic job not tracked") }) }