2015-07-28 22:15:42 +00:00
|
|
|
package nomad
|
|
|
|
|
2015-07-29 00:02:54 +00:00
|
|
|
import (
|
2019-05-17 21:37:42 +00:00
|
|
|
"fmt"
|
2015-07-29 00:02:54 +00:00
|
|
|
"reflect"
|
2015-08-23 17:52:31 +00:00
|
|
|
"sync"
|
2015-07-29 00:02:54 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
2015-07-28 22:15:42 +00:00
|
|
|
|
2018-09-15 23:23:13 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
2019-03-05 21:41:41 +00:00
|
|
|
"github.com/hashicorp/go-memdb"
|
2019-03-12 21:25:14 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2018-09-15 23:23:13 +00:00
|
|
|
|
2017-09-29 16:58:48 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
2015-08-11 21:27:14 +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"
|
2019-03-05 21:41:41 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2015-07-29 00:02:54 +00:00
|
|
|
)
|
|
|
|
|
2015-07-29 00:20:06 +00:00
|
|
|
type NoopScheduler struct {
|
2019-03-08 12:48:12 +00:00
|
|
|
state scheduler.State
|
|
|
|
planner scheduler.Planner
|
|
|
|
eval *structs.Evaluation
|
|
|
|
err error
|
2015-07-29 00:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2019-03-08 12:48:12 +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{
|
2019-03-08 12:48:12 +00:00
|
|
|
state: s,
|
|
|
|
planner: p,
|
2015-07-29 00:20:06 +00:00
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-29 00:02:54 +00:00
|
|
|
func TestWorker_dequeueEvaluation(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:02:54 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:02:54 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
2015-07-29 00:05:59 +00:00
|
|
|
// Create the evaluation
|
2015-08-11 21:27:14 +00:00
|
|
|
eval1 := mock.Eval()
|
2016-05-18 18:35:15 +00:00
|
|
|
s1.evalBroker.Enqueue(eval1)
|
2015-07-29 00:02:54 +00:00
|
|
|
|
|
|
|
// Create a worker
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
|
|
|
|
|
|
|
// Attempt dequeue
|
2017-09-13 20:47:01 +00:00
|
|
|
eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
|
2015-07-29 00:02:54 +00:00
|
|
|
if shutdown {
|
|
|
|
t.Fatalf("should not shutdown")
|
|
|
|
}
|
2015-08-12 22:25:31 +00:00
|
|
|
if token == "" {
|
|
|
|
t.Fatalf("should get token")
|
|
|
|
}
|
2017-09-13 20:47:01 +00:00
|
|
|
if waitIndex != eval1.ModifyIndex {
|
2017-09-26 22:26:33 +00:00
|
|
|
t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex)
|
2017-09-13 20:47:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we get a sane eval
|
|
|
|
if !reflect.DeepEqual(eval, eval1) {
|
|
|
|
t.Fatalf("bad: %#v %#v", eval, eval1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that the worker picks up the correct wait index when there are multiple
|
|
|
|
// evals for the same job.
|
|
|
|
func TestWorker_dequeueEvaluation_SerialJobs(t *testing.T) {
|
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2017-09-13 20:47:01 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2017-09-13 20:47:01 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Create the evaluation
|
|
|
|
eval1 := mock.Eval()
|
|
|
|
eval2 := mock.Eval()
|
|
|
|
eval2.JobID = eval1.JobID
|
|
|
|
|
|
|
|
// Insert the evals into the state store
|
2020-10-19 13:30:15 +00:00
|
|
|
if err := s1.fsm.State().UpsertEvals(structs.MsgTypeTestSetup, 1000, []*structs.Evaluation{eval1, eval2}); err != nil {
|
2017-09-13 20:47:01 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s1.evalBroker.Enqueue(eval1)
|
|
|
|
s1.evalBroker.Enqueue(eval2)
|
|
|
|
|
|
|
|
// Create a worker
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
|
|
|
|
|
|
|
// Attempt dequeue
|
|
|
|
eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
|
|
|
|
if shutdown {
|
|
|
|
t.Fatalf("should not shutdown")
|
|
|
|
}
|
|
|
|
if token == "" {
|
|
|
|
t.Fatalf("should get token")
|
|
|
|
}
|
|
|
|
if waitIndex != eval1.ModifyIndex {
|
2017-09-26 22:26:33 +00:00
|
|
|
t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex)
|
2017-09-13 20:47:01 +00:00
|
|
|
}
|
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)
|
|
|
|
}
|
2017-09-13 20:47:01 +00:00
|
|
|
|
|
|
|
// Update the modify index of the first eval
|
2020-10-19 13:30:15 +00:00
|
|
|
if err := s1.fsm.State().UpsertEvals(structs.MsgTypeTestSetup, 2000, []*structs.Evaluation{eval1}); err != nil {
|
2017-09-13 20:47:01 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the Ack
|
2021-11-17 15:49:55 +00:00
|
|
|
w.sendAck(eval1.ID, token)
|
2017-09-13 20:47:01 +00:00
|
|
|
|
|
|
|
// Attempt second dequeue
|
|
|
|
eval, token, waitIndex, shutdown = w.dequeueEvaluation(10 * time.Millisecond)
|
|
|
|
if shutdown {
|
|
|
|
t.Fatalf("should not shutdown")
|
|
|
|
}
|
|
|
|
if token == "" {
|
|
|
|
t.Fatalf("should get token")
|
|
|
|
}
|
|
|
|
if waitIndex != 2000 {
|
|
|
|
t.Fatalf("bad wait index; got %d; want 2000", eval2.ModifyIndex)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we get a sane eval
|
|
|
|
if !reflect.DeepEqual(eval, eval2) {
|
|
|
|
t.Fatalf("bad: %#v %#v", eval, eval2)
|
|
|
|
}
|
2015-07-29 00:02:54 +00:00
|
|
|
}
|
|
|
|
|
2015-08-23 17:52:31 +00:00
|
|
|
func TestWorker_dequeueEvaluation_paused(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-08-23 17:52:31 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-08-23 17:52:31 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Create the evaluation
|
|
|
|
eval1 := mock.Eval()
|
2016-05-18 18:35:15 +00:00
|
|
|
s1.evalBroker.Enqueue(eval1)
|
2015-08-23 17:52:31 +00:00
|
|
|
|
|
|
|
// 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()
|
2017-09-13 20:47:01 +00:00
|
|
|
eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
|
2015-08-23 17:52:31 +00:00
|
|
|
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")
|
|
|
|
}
|
2017-09-13 20:47:01 +00:00
|
|
|
if waitIndex != eval1.ModifyIndex {
|
2017-09-26 22:26:33 +00:00
|
|
|
t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex)
|
2017-09-13 20:47:01 +00:00
|
|
|
}
|
2015-08-23 17:52:31 +00:00
|
|
|
|
|
|
|
// 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) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:02:54 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:02:54 +00:00
|
|
|
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
|
2017-09-13 20:47:01 +00:00
|
|
|
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) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:05:59 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:05:59 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Create the evaluation
|
2015-08-11 21:27:14 +00:00
|
|
|
eval1 := mock.Eval()
|
2016-05-18 18:35:15 +00:00
|
|
|
s1.evalBroker.Enqueue(eval1)
|
2015-07-29 00:05:59 +00:00
|
|
|
|
|
|
|
// Create a worker
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
|
|
|
|
|
|
|
// Attempt dequeue
|
2017-09-13 20:47:01 +00:00
|
|
|
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
|
2021-11-17 15:49:55 +00:00
|
|
|
w.sendNack(eval.ID, token)
|
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
|
2017-09-13 20:47:01 +00:00
|
|
|
eval, token, _, _ = w.dequeueEvaluation(10 * time.Millisecond)
|
2015-07-29 00:05:59 +00:00
|
|
|
|
|
|
|
// Send the Ack
|
2021-11-17 15:49:55 +00:00
|
|
|
w.sendAck(eval.ID, token)
|
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) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:11:00 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:11:00 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Get the current index
|
|
|
|
index := s1.raft.AppliedIndex()
|
|
|
|
|
|
|
|
// Cause an increment
|
2019-03-12 21:25:14 +00:00
|
|
|
errCh := make(chan error, 1)
|
2015-07-29 00:11:00 +00:00
|
|
|
go func() {
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
2016-06-22 16:04:22 +00:00
|
|
|
n := mock.Node()
|
2020-10-19 13:30:15 +00:00
|
|
|
errCh <- s1.fsm.state.UpsertNode(structs.MsgTypeTestSetup, index+1, n)
|
2015-07-29 00:11:00 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for a future index
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
2019-06-24 18:59:44 +00:00
|
|
|
snap, err := w.snapshotMinIndex(index+1, time.Second)
|
2019-03-12 21:25:14 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, snap)
|
|
|
|
|
|
|
|
// No error from upserting
|
|
|
|
require.NoError(t, <-errCh)
|
2015-07-29 00:11:00 +00:00
|
|
|
|
|
|
|
// Cause a timeout
|
2019-05-17 21:37:42 +00:00
|
|
|
waitIndex := index + 100
|
|
|
|
timeout := 10 * time.Millisecond
|
2019-06-24 18:59:44 +00:00
|
|
|
snap, err = w.snapshotMinIndex(index+100, timeout)
|
2019-03-12 21:25:14 +00:00
|
|
|
require.Nil(t, snap)
|
2019-05-17 21:37:42 +00:00
|
|
|
require.EqualError(t, err,
|
|
|
|
fmt.Sprintf("timed out after %s waiting for index=%d", timeout, waitIndex))
|
2015-07-29 00:11:00 +00:00
|
|
|
}
|
2015-07-29 00:20:06 +00:00
|
|
|
|
|
|
|
func TestWorker_invokeScheduler(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:20:06 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:20:06 +00:00
|
|
|
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
2015-08-11 21:27:14 +00:00
|
|
|
eval := mock.Eval()
|
2015-07-29 00:20:06 +00:00
|
|
|
eval.Type = "noop"
|
|
|
|
|
2019-03-12 21:25:14 +00:00
|
|
|
snap, err := s1.fsm.state.Snapshot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = w.invokeScheduler(snap, eval, uuid.Generate())
|
|
|
|
require.NoError(t, err)
|
2015-07-29 00:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestWorker_SubmitPlan(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-07-29 00:20:06 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-07-29 00:20:06 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
2015-08-05 23:23:47 +00:00
|
|
|
// Register node
|
2015-08-11 21:27:14 +00:00
|
|
|
node := mock.Node()
|
2015-08-05 23:23:47 +00:00
|
|
|
testRegisterNode(t, s1, node)
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
job := mock.Job()
|
2015-08-12 22:44:36 +00:00
|
|
|
eval1 := mock.Eval()
|
2017-10-13 21:36:02 +00:00
|
|
|
eval1.JobID = job.ID
|
2020-10-19 13:30:15 +00:00
|
|
|
s1.fsm.State().UpsertJob(structs.MsgTypeTestSetup, 1000, job)
|
|
|
|
s1.fsm.State().UpsertEvals(structs.MsgTypeTestSetup, 1000, []*structs.Evaluation{eval1})
|
2016-07-25 21:11:32 +00:00
|
|
|
|
|
|
|
// Create the register request
|
2016-05-18 18:35:15 +00:00
|
|
|
s1.evalBroker.Enqueue(eval1)
|
|
|
|
|
2015-08-12 22:44:36 +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")
|
|
|
|
}
|
|
|
|
|
2015-08-05 23:23:47 +00:00
|
|
|
// Create an allocation plan
|
2015-08-11 21:27:14 +00:00
|
|
|
alloc := mock.Alloc()
|
2015-08-05 23:23:47 +00:00
|
|
|
plan := &structs.Plan{
|
2017-10-13 21:36:02 +00:00
|
|
|
Job: job,
|
2015-08-12 22:44:36 +00:00
|
|
|
EvalID: eval1.ID,
|
2015-08-05 23:23:47 +00:00
|
|
|
NodeAllocation: map[string][]*structs.Allocation{
|
2017-09-26 22:26:33 +00:00
|
|
|
node.ID: {alloc},
|
2015-08-05 23:23:47 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to submit a plan
|
2015-08-12 22:44:36 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 21:41:41 +00:00
|
|
|
func TestWorker_SubmitPlanNormalizedAllocations(t *testing.T) {
|
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2019-03-05 21:41:41 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
2019-04-24 18:01:59 +00:00
|
|
|
c.Build = "0.9.2"
|
2019-03-05 21:41:41 +00:00
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2019-03-05 21:41:41 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Register node
|
|
|
|
node := mock.Node()
|
|
|
|
testRegisterNode(t, s1, node)
|
|
|
|
|
|
|
|
job := mock.Job()
|
|
|
|
eval1 := mock.Eval()
|
|
|
|
eval1.JobID = job.ID
|
2020-10-19 13:30:15 +00:00
|
|
|
s1.fsm.State().UpsertJob(structs.MsgTypeTestSetup, 0, job)
|
|
|
|
s1.fsm.State().UpsertEvals(structs.MsgTypeTestSetup, 0, []*structs.Evaluation{eval1})
|
2019-03-05 21:41:41 +00:00
|
|
|
|
|
|
|
stoppedAlloc := mock.Alloc()
|
|
|
|
preemptedAlloc := mock.Alloc()
|
2020-10-19 13:30:15 +00:00
|
|
|
s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 5, []*structs.Allocation{stoppedAlloc, preemptedAlloc})
|
2019-03-05 21:41:41 +00:00
|
|
|
|
|
|
|
// Create an allocation plan
|
|
|
|
plan := &structs.Plan{
|
|
|
|
Job: job,
|
|
|
|
EvalID: eval1.ID,
|
|
|
|
NodeUpdate: make(map[string][]*structs.Allocation),
|
|
|
|
NodePreemptions: make(map[string][]*structs.Allocation),
|
|
|
|
}
|
|
|
|
desiredDescription := "desired desc"
|
2020-06-09 21:13:53 +00:00
|
|
|
plan.AppendStoppedAlloc(stoppedAlloc, desiredDescription, structs.AllocClientStatusLost, "")
|
2019-03-05 21:41:41 +00:00
|
|
|
preemptingAllocID := uuid.Generate()
|
|
|
|
plan.AppendPreemptedAlloc(preemptedAlloc, preemptingAllocID)
|
|
|
|
|
|
|
|
// Attempt to submit a plan
|
|
|
|
w := &Worker{srv: s1, logger: s1.logger}
|
|
|
|
w.SubmitPlan(plan)
|
|
|
|
|
|
|
|
assert.Equal(t, &structs.Allocation{
|
|
|
|
ID: preemptedAlloc.ID,
|
|
|
|
PreemptedByAllocation: preemptingAllocID,
|
|
|
|
}, plan.NodePreemptions[preemptedAlloc.NodeID][0])
|
|
|
|
assert.Equal(t, &structs.Allocation{
|
|
|
|
ID: stoppedAlloc.ID,
|
|
|
|
DesiredDescription: desiredDescription,
|
|
|
|
ClientStatus: structs.AllocClientStatusLost,
|
|
|
|
}, plan.NodeUpdate[stoppedAlloc.NodeID][0])
|
|
|
|
}
|
|
|
|
|
2015-08-05 23:23:47 +00:00
|
|
|
func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-08-05 23:23:47 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-08-05 23:23:47 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Register node
|
2015-08-11 21:27:14 +00:00
|
|
|
node := mock.Node()
|
2015-08-05 23:23:47 +00:00
|
|
|
testRegisterNode(t, s1, node)
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
// Create the job
|
|
|
|
job := mock.Job()
|
2020-10-19 13:30:15 +00:00
|
|
|
s1.fsm.State().UpsertJob(structs.MsgTypeTestSetup, 1000, job)
|
2017-10-13 21:36:02 +00:00
|
|
|
|
2015-08-12 22:44:36 +00:00
|
|
|
// Create the register request
|
|
|
|
eval1 := mock.Eval()
|
2017-10-13 21:36:02 +00:00
|
|
|
eval1.JobID = job.ID
|
2016-05-18 18:35:15 +00:00
|
|
|
s1.evalBroker.Enqueue(eval1)
|
|
|
|
|
2015-08-12 22:44:36 +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")
|
|
|
|
}
|
|
|
|
|
2015-08-05 23:23:47 +00:00
|
|
|
// Create an allocation plan, with unregistered node
|
2015-08-11 21:27:14 +00:00
|
|
|
node2 := mock.Node()
|
|
|
|
alloc := mock.Alloc()
|
2015-08-05 23:23:47 +00:00
|
|
|
plan := &structs.Plan{
|
2017-10-13 21:36:02 +00:00
|
|
|
Job: job,
|
2015-08-12 22:44:36 +00:00
|
|
|
EvalID: eval1.ID,
|
2015-08-05 23:23:47 +00:00
|
|
|
NodeAllocation: map[string][]*structs.Allocation{
|
2017-09-26 22:26:33 +00:00
|
|
|
node2.ID: {alloc},
|
2015-08-05 23:23:47 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to submit a plan
|
2015-08-12 22:44:36 +00:00
|
|
|
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) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-08-15 21:25:00 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-08-15 21:25:00 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Register node
|
|
|
|
node := mock.Node()
|
|
|
|
testRegisterNode(t, s1, node)
|
|
|
|
|
|
|
|
// Create the register request
|
|
|
|
eval1 := mock.Eval()
|
2016-05-18 18:35:15 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-02-08 05:22:48 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
out, err := s1.fsm.State().EvalByID(ws, 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)
|
|
|
|
}
|
2016-05-21 01:07:10 +00:00
|
|
|
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) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2015-09-07 21:23:48 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2015-09-07 21:23:48 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Register node
|
|
|
|
node := mock.Node()
|
|
|
|
testRegisterNode(t, s1, node)
|
|
|
|
|
|
|
|
// Create the register request
|
|
|
|
eval1 := mock.Eval()
|
2016-05-18 18:35:15 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-02-08 05:22:48 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
out, err := s1.fsm.State().EvalByID(ws, eval2.ID)
|
2015-09-07 21:23:48 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if out.PreviousEval != eval1.ID {
|
|
|
|
t.Fatalf("bad: %v", out)
|
|
|
|
}
|
2016-05-21 01:07:10 +00:00
|
|
|
if out.SnapshotIndex != w.snapshotIndex {
|
|
|
|
t.Fatalf("bad: %v", out)
|
|
|
|
}
|
2015-09-07 21:23:48 +00:00
|
|
|
}
|
2016-05-20 23:03:53 +00:00
|
|
|
|
|
|
|
func TestWorker_ReblockEval(t *testing.T) {
|
2017-07-23 22:04:38 +00:00
|
|
|
t.Parallel()
|
2019-12-04 00:15:11 +00:00
|
|
|
|
|
|
|
s1, cleanupS1 := TestServer(t, func(c *Config) {
|
2016-05-20 23:03:53 +00:00
|
|
|
c.NumSchedulers = 0
|
|
|
|
c.EnabledSchedulers = []string{structs.JobTypeService}
|
|
|
|
})
|
2019-12-04 00:15:11 +00:00
|
|
|
defer cleanupS1()
|
2016-05-20 23:03:53 +00:00
|
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
|
|
|
|
// Create the blocked eval
|
|
|
|
eval1 := mock.Eval()
|
|
|
|
eval1.Status = structs.EvalStatusBlocked
|
2016-07-20 21:09:03 +00:00
|
|
|
eval1.QueuedAllocations = map[string]int{"cache": 100}
|
2016-05-20 23:03:53 +00:00
|
|
|
|
|
|
|
// Insert it into the state store
|
2020-10-19 13:30:15 +00:00
|
|
|
if err := s1.fsm.State().UpsertEvals(structs.MsgTypeTestSetup, 1000, []*structs.Evaluation{eval1}); err != nil {
|
2016-05-20 23:03:53 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2016-07-20 21:09:03 +00:00
|
|
|
// Create the job summary
|
|
|
|
js := mock.JobSummary(eval1.JobID)
|
2016-07-21 21:43:21 +00:00
|
|
|
tg := js.Summary["web"]
|
2016-07-20 21:09:03 +00:00
|
|
|
tg.Queued = 100
|
2016-07-21 21:43:21 +00:00
|
|
|
js.Summary["web"] = tg
|
2016-07-20 21:09:03 +00:00
|
|
|
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)
|
2016-05-20 23:03:53 +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()
|
2016-07-21 21:43:21 +00:00
|
|
|
eval2.QueuedAllocations = map[string]int{"web": 50}
|
2016-05-20 23:03:53 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2016-05-21 01:07:10 +00:00
|
|
|
// Ack the eval
|
2021-11-17 15:49:55 +00:00
|
|
|
w.sendAck(evalOut.ID, token)
|
2016-05-21 01:07:10 +00:00
|
|
|
|
2016-05-20 23:03:53 +00:00
|
|
|
// Check that it is blocked
|
|
|
|
bStats := s1.blockedEvals.Stats()
|
2016-05-21 01:07:10 +00:00
|
|
|
if bStats.TotalBlocked+bStats.TotalEscaped != 1 {
|
|
|
|
t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats)
|
|
|
|
}
|
|
|
|
|
2016-07-20 21:09:03 +00:00
|
|
|
// Check that the eval was updated
|
2017-02-08 05:22:48 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
eval, err := s1.fsm.State().EvalByID(ws, eval2.ID)
|
2016-07-20 21:09:03 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(eval.QueuedAllocations, eval2.QueuedAllocations) {
|
|
|
|
t.Fatalf("expected: %#v, actual: %#v", eval2.QueuedAllocations, eval.QueuedAllocations)
|
|
|
|
}
|
|
|
|
|
2016-05-21 01:07:10 +00:00
|
|
|
// Check that the snapshot index was set properly by unblocking the eval and
|
|
|
|
// then dequeuing.
|
2016-05-23 22:24:31 +00:00
|
|
|
s1.blockedEvals.Unblock("foobar", 1000)
|
2016-05-21 01:07:10 +00:00
|
|
|
|
|
|
|
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)
|
2016-05-20 23:03:53 +00:00
|
|
|
}
|
|
|
|
}
|