open-nomad/nomad/worker_test.go

544 lines
12 KiB
Go
Raw Normal View History

2015-07-28 22:15:42 +00:00
package nomad
2015-07-29 00:02:54 +00:00
import (
2015-08-07 00:36:10 +00:00
"log"
2015-07-29 00:02:54 +00:00
"reflect"
2015-07-29 00:11:00 +00:00
"strings"
"sync"
2015-07-29 00:02:54 +00:00
"testing"
"time"
2015-07-28 22:15:42 +00:00
"github.com/hashicorp/nomad/nomad/mock"
2015-07-29 00:02:54 +00:00
"github.com/hashicorp/nomad/nomad/structs"
2015-07-29 00:20:06 +00:00
"github.com/hashicorp/nomad/scheduler"
2015-07-29 00:02:54 +00:00
"github.com/hashicorp/nomad/testutil"
)
2015-07-29 00:20:06 +00:00
type NoopScheduler struct {
state scheduler.State
planner scheduler.Planner
eval *structs.Evaluation
err error
}
func (n *NoopScheduler) Process(eval *structs.Evaluation) error {
if n.state == nil {
panic("missing state")
}
if n.planner == nil {
panic("missing planner")
}
n.eval = eval
return n.err
}
func init() {
2015-08-07 00:36:10 +00:00
scheduler.BuiltinSchedulers["noop"] = func(logger *log.Logger, s scheduler.State, p scheduler.Planner) scheduler.Scheduler {
2015-07-29 00:20:06 +00:00
n := &NoopScheduler{
state: s,
planner: p,
}
return n
}
}
2015-07-29 00:02:54 +00:00
func TestWorker_dequeueEvaluation(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
2015-07-29 00:05:59 +00:00
// Create the evaluation
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
2015-07-29 00:02:54 +00:00
// Create a worker
w := &Worker{srv: s1, logger: s1.logger}
// Attempt dequeue
eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
2015-07-29 00:02:54 +00:00
if shutdown {
t.Fatalf("should not shutdown")
}
if token == "" {
t.Fatalf("should get token")
}
2015-07-29 00:02:54 +00:00
// Ensure we get a sane eval
if !reflect.DeepEqual(eval, eval1) {
t.Fatalf("bad: %#v %#v", eval, eval1)
}
}
func TestWorker_dequeueEvaluation_paused(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Create the evaluation
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
// Create a worker
w := &Worker{srv: s1, logger: s1.logger}
w.pauseCond = sync.NewCond(&w.pauseLock)
// PAUSE the worker
w.SetPause(true)
go func() {
time.Sleep(100 * time.Millisecond)
w.SetPause(false)
}()
// Attempt dequeue
start := time.Now()
eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
if diff := time.Since(start); diff < 100*time.Millisecond {
t.Fatalf("should have paused: %v", diff)
}
if shutdown {
t.Fatalf("should not shutdown")
}
if token == "" {
t.Fatalf("should get token")
}
// Ensure we get a sane eval
if !reflect.DeepEqual(eval, eval1) {
t.Fatalf("bad: %#v %#v", eval, eval1)
}
}
2015-07-29 00:02:54 +00:00
func TestWorker_dequeueEvaluation_shutdown(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Create a worker
w := &Worker{srv: s1, logger: s1.logger}
go func() {
time.Sleep(10 * time.Millisecond)
s1.Shutdown()
}()
// Attempt dequeue
eval, _, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
2015-07-29 00:02:54 +00:00
if !shutdown {
t.Fatalf("should not shutdown")
}
// Ensure we get a sane eval
if eval != nil {
t.Fatalf("bad: %#v", eval)
}
2015-07-28 22:15:42 +00:00
}
2015-07-29 00:05:59 +00:00
func TestWorker_sendAck(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Create the evaluation
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
2015-07-29 00:05:59 +00:00
// Create a worker
w := &Worker{srv: s1, logger: s1.logger}
// Attempt dequeue
eval, token, _ := w.dequeueEvaluation(10 * time.Millisecond)
2015-07-29 00:05:59 +00:00
// Check the depth is 0, 1 unacked
stats := s1.evalBroker.Stats()
if stats.TotalReady != 0 && stats.TotalUnacked != 1 {
t.Fatalf("bad: %#v", stats)
}
// Send the Nack
w.sendAck(eval.ID, token, false)
2015-07-29 00:05:59 +00:00
// Check the depth is 1, nothing unacked
stats = s1.evalBroker.Stats()
if stats.TotalReady != 1 && stats.TotalUnacked != 0 {
t.Fatalf("bad: %#v", stats)
}
// Attempt dequeue
eval, token, _ = w.dequeueEvaluation(10 * time.Millisecond)
2015-07-29 00:05:59 +00:00
// Send the Ack
w.sendAck(eval.ID, token, true)
2015-07-29 00:05:59 +00:00
// Check the depth is 0
stats = s1.evalBroker.Stats()
if stats.TotalReady != 0 && stats.TotalUnacked != 0 {
t.Fatalf("bad: %#v", stats)
}
}
2015-07-29 00:11:00 +00:00
func TestWorker_waitForIndex(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Get the current index
index := s1.raft.AppliedIndex()
// Cause an increment
go func() {
time.Sleep(10 * time.Millisecond)
n := mock.Node()
if err := s1.fsm.state.UpsertNode(index+1, n); err != nil {
t.Fatalf("failed to upsert node: %v", err)
}
2015-07-29 00:11:00 +00:00
}()
// Wait for a future index
w := &Worker{srv: s1, logger: s1.logger}
err := w.waitForIndex(index+1, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
// Cause a timeout
err = w.waitForIndex(index+100, 10*time.Millisecond)
if err == nil || !strings.Contains(err.Error(), "timeout") {
t.Fatalf("err: %v", err)
}
}
2015-07-29 00:20:06 +00:00
func TestWorker_invokeScheduler(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
w := &Worker{srv: s1, logger: s1.logger}
eval := mock.Eval()
2015-07-29 00:20:06 +00:00
eval.Type = "noop"
err := w.invokeScheduler(eval, structs.GenerateUUID())
2015-07-29 00:20:06 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
}
func TestWorker_SubmitPlan(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
2015-08-05 23:23:47 +00:00
// Register node
node := mock.Node()
2015-08-05 23:23:47 +00:00
testRegisterNode(t, s1, node)
eval1 := mock.Eval()
2016-07-25 21:11:32 +00:00
s1.fsm.State().UpsertJobSummary(1000, mock.JobSummary(eval1.JobID))
// Create the register request
s1.evalBroker.Enqueue(eval1)
evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if evalOut != eval1 {
t.Fatalf("Bad eval")
}
2015-08-05 23:23:47 +00:00
// Create an allocation plan
alloc := mock.Alloc()
2016-07-25 21:11:32 +00:00
s1.fsm.State().UpsertJobSummary(1200, mock.JobSummary(alloc.JobID))
2015-08-05 23:23:47 +00:00
plan := &structs.Plan{
EvalID: eval1.ID,
2015-08-05 23:23:47 +00:00
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
},
}
// Attempt to submit a plan
w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
2015-08-05 23:23:47 +00:00
result, state, err := w.SubmitPlan(plan)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should have no update
if state != nil {
t.Fatalf("unexpected state update")
}
// Result should have allocated
if result == nil {
t.Fatalf("missing result")
}
if result.AllocIndex == 0 {
t.Fatalf("Bad: %#v", result)
}
if len(result.NodeAllocation) != 1 {
t.Fatalf("Bad: %#v", result)
}
}
func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Register node
node := mock.Node()
2015-08-05 23:23:47 +00:00
testRegisterNode(t, s1, node)
// Create the register request
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if evalOut != eval1 {
t.Fatalf("Bad eval")
}
2015-08-05 23:23:47 +00:00
// Create an allocation plan, with unregistered node
node2 := mock.Node()
alloc := mock.Alloc()
2015-08-05 23:23:47 +00:00
plan := &structs.Plan{
EvalID: eval1.ID,
2015-08-05 23:23:47 +00:00
NodeAllocation: map[string][]*structs.Allocation{
node2.ID: []*structs.Allocation{alloc},
},
}
// Attempt to submit a plan
w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
2015-08-05 23:23:47 +00:00
result, state, err := w.SubmitPlan(plan)
if err != nil {
t.Fatalf("err: %v", err)
}
// Result should have allocated
if result == nil {
t.Fatalf("missing result")
}
// Expect no allocation and forced refresh
if result.AllocIndex != 0 {
t.Fatalf("Bad: %#v", result)
}
if result.RefreshIndex == 0 {
t.Fatalf("Bad: %#v", result)
}
if len(result.NodeAllocation) != 0 {
t.Fatalf("Bad: %#v", result)
}
// Should have an update
if state == nil {
t.Fatalf("expected state update")
}
2015-07-29 00:20:06 +00:00
}
2015-08-15 21:25:00 +00:00
func TestWorker_UpdateEval(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Register node
node := mock.Node()
testRegisterNode(t, s1, node)
// Create the register request
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
2015-08-15 21:25:00 +00:00
evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if evalOut != eval1 {
t.Fatalf("Bad eval")
}
eval2 := evalOut.Copy()
eval2.Status = structs.EvalStatusComplete
// Attempt to update eval
w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
err = w.UpdateEval(eval2)
if err != nil {
t.Fatalf("err: %v", err)
}
2015-09-07 03:56:38 +00:00
out, err := s1.fsm.State().EvalByID(eval2.ID)
2015-08-15 21:25:00 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if out.Status != structs.EvalStatusComplete {
t.Fatalf("bad: %v", out)
}
if out.SnapshotIndex != w.snapshotIndex {
t.Fatalf("bad: %v", out)
}
2015-08-15 21:25:00 +00:00
}
2015-09-07 21:23:48 +00:00
func TestWorker_CreateEval(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Register node
node := mock.Node()
testRegisterNode(t, s1, node)
// Create the register request
eval1 := mock.Eval()
s1.evalBroker.Enqueue(eval1)
2015-09-07 21:23:48 +00:00
evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if evalOut != eval1 {
t.Fatalf("Bad eval")
}
eval2 := mock.Eval()
eval2.PreviousEval = eval1.ID
// Attempt to create eval
w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
err = w.CreateEval(eval2)
if err != nil {
t.Fatalf("err: %v", err)
}
out, err := s1.fsm.State().EvalByID(eval2.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out.PreviousEval != eval1.ID {
t.Fatalf("bad: %v", out)
}
if out.SnapshotIndex != w.snapshotIndex {
t.Fatalf("bad: %v", out)
}
2015-09-07 21:23:48 +00:00
}
func TestWorker_ReblockEval(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0
c.EnabledSchedulers = []string{structs.JobTypeService}
})
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
// Create the blocked eval
eval1 := mock.Eval()
eval1.Status = structs.EvalStatusBlocked
eval1.QueuedAllocations = map[string]int{"cache": 100}
// Insert it into the state store
if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil {
t.Fatal(err)
}
// Create the job summary
js := mock.JobSummary(eval1.JobID)
2016-07-21 21:43:21 +00:00
tg := js.Summary["web"]
tg.Queued = 100
2016-07-21 21:43:21 +00:00
js.Summary["web"] = tg
if err := s1.fsm.State().UpsertJobSummary(1001, js); err != nil {
t.Fatal(err)
}
2016-05-25 03:20:06 +00:00
// Enqueue the eval and then dequeue
s1.evalBroker.Enqueue(eval1)
evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if evalOut != eval1 {
t.Fatalf("Bad eval")
}
eval2 := evalOut.Copy()
2016-07-21 21:43:21 +00:00
eval2.QueuedAllocations = map[string]int{"web": 50}
// Attempt to reblock eval
w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
err = w.ReblockEval(eval2)
if err != nil {
t.Fatalf("err: %v", err)
}
// Ack the eval
w.sendAck(evalOut.ID, token, true)
// Check that it is blocked
bStats := s1.blockedEvals.Stats()
if bStats.TotalBlocked+bStats.TotalEscaped != 1 {
t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats)
}
// Check that the eval was updated
eval, err := s1.fsm.State().EvalByID(eval2.ID)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eval.QueuedAllocations, eval2.QueuedAllocations) {
t.Fatalf("expected: %#v, actual: %#v", eval2.QueuedAllocations, eval.QueuedAllocations)
}
// Check that the snapshot index was set properly by unblocking the eval and
// then dequeuing.
s1.blockedEvals.Unblock("foobar", 1000)
reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second)
if err != nil {
t.Fatalf("err: %v", err)
}
if reblockedEval == nil {
t.Fatalf("Nil eval")
}
if reblockedEval.ID != eval1.ID {
t.Fatalf("Bad eval")
}
// Check that the SnapshotIndex is set
if reblockedEval.SnapshotIndex != w.snapshotIndex {
t.Fatalf("incorrect snapshot index; got %d; want %d",
reblockedEval.SnapshotIndex, w.snapshotIndex)
}
}