open-nomad/nomad/plan_apply_test.go

994 lines
26 KiB
Go
Raw Normal View History

2015-07-28 22:15:42 +00:00
package nomad
2015-08-05 01:10:57 +00:00
import (
"reflect"
2015-08-05 01:10:57 +00:00
"testing"
2019-03-05 21:41:41 +00:00
"time"
2015-07-28 22:15:42 +00:00
memdb "github.com/hashicorp/go-memdb"
2018-01-12 01:00:30 +00:00
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/mock"
2015-08-05 01:10:57 +00:00
"github.com/hashicorp/nomad/nomad/structs"
2015-08-05 01:18:44 +00:00
"github.com/hashicorp/nomad/testutil"
2015-10-11 21:57:36 +00:00
"github.com/hashicorp/raft"
"github.com/stretchr/testify/assert"
2018-11-02 01:06:32 +00:00
"github.com/stretchr/testify/require"
2015-08-05 01:10:57 +00:00
)
const (
// workerPoolSize is the size of the worker pool
workerPoolSize = 2
)
2015-10-11 21:57:36 +00:00
// planWaitFuture is used to wait for the Raft future to complete
func planWaitFuture(future raft.ApplyFuture) (uint64, error) {
if err := future.Error(); err != nil {
return 0, err
}
return future.Index(), nil
}
2015-08-05 01:18:44 +00:00
func testRegisterNode(t *testing.T, s *Server, n *structs.Node) {
// Create the register request
req := &structs.NodeRegisterRequest{
Node: n,
WriteRequest: structs.WriteRequest{Region: "global"},
2015-08-05 01:18:44 +00:00
}
// Fetch the response
2015-08-07 00:02:37 +00:00
var resp structs.NodeUpdateResponse
if err := s.RPC("Node.Register", req, &resp); err != nil {
2015-08-05 01:18:44 +00:00
t.Fatalf("err: %v", err)
}
if resp.Index == 0 {
t.Fatalf("bad index: %d", resp.Index)
}
}
2016-07-25 21:11:32 +00:00
func testRegisterJob(t *testing.T, s *Server, j *structs.Job) {
// Create the register request
req := &structs.JobRegisterRequest{
Job: j,
WriteRequest: structs.WriteRequest{Region: "global"},
}
// Fetch the response
var resp structs.JobRegisterResponse
if err := s.RPC("Job.Register", req, &resp); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Index == 0 {
t.Fatalf("bad index: %d", resp.Index)
}
}
2019-03-08 11:18:56 +00:00
// COMPAT 0.11: Tests the older unoptimized code path for applyPlan
2015-08-05 01:18:44 +00:00
func TestPlanApply_applyPlan(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
s1, cleanupS1 := TestServer(t, nil)
defer cleanupS1()
2015-08-05 01:18:44 +00:00
testutil.WaitForLeader(t, s1.RPC)
// Register node
node := mock.Node()
2015-08-05 01:18:44 +00:00
testRegisterNode(t, s1, node)
2017-07-06 16:28:08 +00:00
// Register a fake deployment
oldDeployment := mock.Deployment()
if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil {
t.Fatalf("UpsertDeployment failed: %v", err)
}
// Create a deployment
dnew := mock.Deployment()
// Create a deployment update for the old deployment id
desiredStatus, desiredStatusDescription := "foo", "bar"
updates := []*structs.DeploymentStatusUpdate{
{
DeploymentID: oldDeployment.ID,
Status: desiredStatus,
StatusDescription: desiredStatusDescription,
},
}
// Register alloc, deployment and deployment update
alloc := mock.Alloc()
2016-07-25 21:11:32 +00:00
s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))
// Create an eval
eval := mock.Eval()
eval.JobID = alloc.JobID
if err := s1.State().UpsertEvals(structs.MsgTypeTestSetup, 1, []*structs.Evaluation{eval}); err != nil {
t.Fatalf("err: %v", err)
}
planRes := &structs.PlanResult{
2015-08-05 01:18:44 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
2015-08-05 01:18:44 +00:00
},
2017-07-06 16:28:08 +00:00
Deployment: dnew,
DeploymentUpdates: updates,
2015-08-05 01:18:44 +00:00
}
2015-10-11 22:46:46 +00:00
// Snapshot the state
snap, err := s1.State().Snapshot()
if err != nil {
t.Fatalf("err: %v", err)
}
// Create the plan with a deployment
plan := &structs.Plan{
2017-07-06 16:28:08 +00:00
Job: alloc.Job,
Deployment: dnew,
DeploymentUpdates: updates,
EvalID: eval.ID,
}
2015-08-05 01:18:44 +00:00
// Apply the plan
future, err := s1.applyPlan(plan, planRes, snap)
assert := assert.New(t)
assert.Nil(err)
2015-10-11 22:46:46 +00:00
// Verify our optimistic snapshot is updated
2017-02-08 05:22:48 +00:00
ws := memdb.NewWatchSet()
allocOut, err := snap.AllocByID(ws, alloc.ID)
assert.Nil(err)
assert.NotNil(allocOut)
2015-10-11 22:46:46 +00:00
deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID)
assert.Nil(err)
assert.NotNil(deploymentOut)
2015-10-11 22:46:46 +00:00
// Check plan does apply cleanly
2015-10-11 21:57:36 +00:00
index, err := planWaitFuture(future)
assert.Nil(err)
assert.NotEqual(0, index)
2015-08-05 01:18:44 +00:00
// Lookup the allocation
fsmState := s1.fsm.State()
allocOut, err = fsmState.AllocByID(ws, alloc.ID)
assert.Nil(err)
assert.NotNil(allocOut)
assert.True(allocOut.CreateTime > 0)
assert.True(allocOut.ModifyTime > 0)
assert.Equal(allocOut.CreateTime, allocOut.ModifyTime)
2017-07-06 16:28:08 +00:00
// Lookup the new deployment
dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID)
assert.Nil(err)
assert.NotNil(dout)
2017-07-06 16:28:08 +00:00
// Lookup the updated deployment
dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID)
assert.Nil(err)
assert.NotNil(dout2)
assert.Equal(desiredStatus, dout2.Status)
assert.Equal(desiredStatusDescription, dout2.StatusDescription)
// Lookup updated eval
evalOut, err := fsmState.EvalByID(ws, eval.ID)
assert.Nil(err)
assert.NotNil(evalOut)
assert.Equal(index, evalOut.ModifyIndex)
2017-07-06 16:28:08 +00:00
2015-08-05 01:18:44 +00:00
// Evict alloc, Register alloc2
allocEvict := new(structs.Allocation)
*allocEvict = *alloc
allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
2016-02-21 19:42:54 +00:00
job := allocEvict.Job
allocEvict.Job = nil
alloc2 := mock.Alloc()
2016-07-26 22:11:48 +00:00
s1.State().UpsertJobSummary(1500, mock.JobSummary(alloc2.JobID))
planRes = &structs.PlanResult{
NodeUpdate: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {allocEvict},
2015-08-05 01:18:44 +00:00
},
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc2},
2015-08-05 01:18:44 +00:00
},
}
2015-10-11 22:46:46 +00:00
// Snapshot the state
snap, err = s1.State().Snapshot()
assert.Nil(err)
2015-10-11 22:46:46 +00:00
2015-08-05 01:18:44 +00:00
// Apply the plan
plan = &structs.Plan{
Job: job,
EvalID: eval.ID,
}
future, err = s1.applyPlan(plan, planRes, snap)
assert.Nil(err)
2015-10-11 22:46:46 +00:00
// Check that our optimistic view is updated
out, _ := snap.AllocByID(ws, allocEvict.ID)
if out.DesiredStatus != structs.AllocDesiredStatusEvict && out.DesiredStatus != structs.AllocDesiredStatusStop {
assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus)
}
2015-10-11 22:46:46 +00:00
// Verify plan applies cleanly
2015-10-11 21:57:36 +00:00
index, err = planWaitFuture(future)
assert.Nil(err)
assert.NotEqual(0, index)
2015-08-05 01:18:44 +00:00
// Lookup the allocation
allocOut, err = s1.fsm.State().AllocByID(ws, alloc.ID)
assert.Nil(err)
if allocOut.DesiredStatus != structs.AllocDesiredStatusEvict && allocOut.DesiredStatus != structs.AllocDesiredStatusStop {
assert.Equal(structs.AllocDesiredStatusEvict, allocOut.DesiredStatus)
}
assert.NotNil(allocOut.Job)
assert.True(allocOut.ModifyTime > 0)
2015-08-05 01:18:44 +00:00
// Lookup the allocation
allocOut, err = s1.fsm.State().AllocByID(ws, alloc2.ID)
assert.Nil(err)
assert.NotNil(allocOut)
assert.NotNil(allocOut.Job)
// Lookup updated eval
evalOut, err = fsmState.EvalByID(ws, eval.ID)
assert.Nil(err)
assert.NotNil(evalOut)
assert.Equal(index, evalOut.ModifyIndex)
2015-08-05 01:18:44 +00:00
}
// Verifies that applyPlan properly updates the constituent objects in MemDB,
// when the plan contains normalized allocs.
2019-03-05 21:41:41 +00:00
func TestPlanApply_applyPlanWithNormalizedAllocs(t *testing.T) {
t.Parallel()
s1, cleanupS1 := TestServer(t, func(c *Config) {
c.Build = "0.9.2"
2019-03-05 21:41:41 +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)
// Register a fake deployment
oldDeployment := mock.Deployment()
if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil {
t.Fatalf("UpsertDeployment failed: %v", err)
}
// Create a deployment
dnew := mock.Deployment()
// Create a deployment update for the old deployment id
desiredStatus, desiredStatusDescription := "foo", "bar"
updates := []*structs.DeploymentStatusUpdate{
{
DeploymentID: oldDeployment.ID,
Status: desiredStatus,
StatusDescription: desiredStatusDescription,
},
}
// Register allocs, deployment and deployment update
alloc := mock.Alloc()
stoppedAlloc := mock.Alloc()
stoppedAllocDiff := &structs.Allocation{
ID: stoppedAlloc.ID,
2019-03-05 21:41:41 +00:00
DesiredDescription: "Desired Description",
ClientStatus: structs.AllocClientStatusLost,
2019-03-05 21:41:41 +00:00
}
preemptedAlloc := mock.Alloc()
preemptedAllocDiff := &structs.Allocation{
ID: preemptedAlloc.ID,
2019-03-05 21:41:41 +00:00
PreemptedByAllocation: alloc.ID,
}
s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))
s1.State().UpsertAllocs(structs.MsgTypeTestSetup, 1100, []*structs.Allocation{stoppedAlloc, preemptedAlloc})
2019-03-05 21:41:41 +00:00
// Create an eval
eval := mock.Eval()
eval.JobID = alloc.JobID
if err := s1.State().UpsertEvals(structs.MsgTypeTestSetup, 1, []*structs.Evaluation{eval}); err != nil {
2019-03-05 21:41:41 +00:00
t.Fatalf("err: %v", err)
}
timestampBeforeCommit := time.Now().UTC().UnixNano()
planRes := &structs.PlanResult{
NodeAllocation: map[string][]*structs.Allocation{
node.ID: {alloc},
},
NodeUpdate: map[string][]*structs.Allocation{
stoppedAlloc.NodeID: {stoppedAllocDiff},
},
NodePreemptions: map[string][]*structs.Allocation{
preemptedAlloc.NodeID: {preemptedAllocDiff},
},
Deployment: dnew,
DeploymentUpdates: updates,
}
// Snapshot the state
snap, err := s1.State().Snapshot()
if err != nil {
t.Fatalf("err: %v", err)
}
// Create the plan with a deployment
plan := &structs.Plan{
Job: alloc.Job,
Deployment: dnew,
DeploymentUpdates: updates,
EvalID: eval.ID,
}
require := require.New(t)
assert := assert.New(t)
2019-03-05 21:41:41 +00:00
// Apply the plan
future, err := s1.applyPlan(plan, planRes, snap)
require.NoError(err)
2019-03-05 21:41:41 +00:00
// Verify our optimistic snapshot is updated
ws := memdb.NewWatchSet()
allocOut, err := snap.AllocByID(ws, alloc.ID)
require.NoError(err)
require.NotNil(allocOut)
2019-03-05 21:41:41 +00:00
deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID)
require.NoError(err)
require.NotNil(deploymentOut)
2019-03-05 21:41:41 +00:00
// Check plan does apply cleanly
index, err := planWaitFuture(future)
require.NoError(err)
2019-03-05 21:41:41 +00:00
assert.NotEqual(0, index)
// Lookup the allocation
fsmState := s1.fsm.State()
allocOut, err = fsmState.AllocByID(ws, alloc.ID)
require.NoError(err)
require.NotNil(allocOut)
2019-03-05 21:41:41 +00:00
assert.True(allocOut.CreateTime > 0)
assert.True(allocOut.ModifyTime > 0)
assert.Equal(allocOut.CreateTime, allocOut.ModifyTime)
// Verify stopped alloc diff applied cleanly
updatedStoppedAlloc, err := fsmState.AllocByID(ws, stoppedAlloc.ID)
require.NoError(err)
require.NotNil(updatedStoppedAlloc)
2019-03-05 21:41:41 +00:00
assert.True(updatedStoppedAlloc.ModifyTime > timestampBeforeCommit)
assert.Equal(updatedStoppedAlloc.DesiredDescription, stoppedAllocDiff.DesiredDescription)
assert.Equal(updatedStoppedAlloc.ClientStatus, stoppedAllocDiff.ClientStatus)
assert.Equal(updatedStoppedAlloc.DesiredStatus, structs.AllocDesiredStatusStop)
// Verify preempted alloc diff applied cleanly
updatedPreemptedAlloc, err := fsmState.AllocByID(ws, preemptedAlloc.ID)
require.NoError(err)
require.NotNil(updatedPreemptedAlloc)
2019-03-05 21:41:41 +00:00
assert.True(updatedPreemptedAlloc.ModifyTime > timestampBeforeCommit)
assert.Equal(updatedPreemptedAlloc.DesiredDescription,
"Preempted by alloc ID "+preemptedAllocDiff.PreemptedByAllocation)
2019-03-05 21:41:41 +00:00
assert.Equal(updatedPreemptedAlloc.DesiredStatus, structs.AllocDesiredStatusEvict)
// Lookup the new deployment
dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID)
require.NoError(err)
require.NotNil(dout)
2019-03-05 21:41:41 +00:00
// Lookup the updated deployment
dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID)
require.NoError(err)
require.NotNil(dout2)
2019-03-05 21:41:41 +00:00
assert.Equal(desiredStatus, dout2.Status)
assert.Equal(desiredStatusDescription, dout2.StatusDescription)
// Lookup updated eval
evalOut, err := fsmState.EvalByID(ws, eval.ID)
require.NoError(err)
require.NotNil(evalOut)
2019-03-05 21:41:41 +00:00
assert.Equal(index, evalOut.ModifyIndex)
}
2015-08-05 01:30:05 +00:00
func TestPlanApply_EvalPlan_Simple(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:30:05 +00:00
state := testStateStore(t)
node := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
2015-08-05 01:30:05 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
2015-08-05 01:30:05 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:30:05 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
2015-08-05 01:30:05 +00:00
},
2017-07-06 16:28:08 +00:00
Deployment: mock.Deployment(),
DeploymentUpdates: []*structs.DeploymentStatusUpdate{
{
DeploymentID: uuid.Generate(),
2017-07-06 16:28:08 +00:00
Status: "foo",
StatusDescription: "bar",
},
},
2015-08-05 01:30:05 +00:00
}
pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
defer pool.Shutdown()
2018-09-15 23:23:13 +00:00
result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
2015-08-05 01:30:05 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
if !reflect.DeepEqual(result.NodeAllocation, plan.NodeAllocation) {
t.Fatalf("incorrect node allocations")
}
2017-07-06 16:28:08 +00:00
if !reflect.DeepEqual(result.Deployment, plan.Deployment) {
t.Fatalf("incorrect deployment")
}
if !reflect.DeepEqual(result.DeploymentUpdates, plan.DeploymentUpdates) {
t.Fatalf("incorrect deployment updates")
}
2015-08-05 01:30:05 +00:00
}
2018-11-02 01:06:32 +00:00
func TestPlanApply_EvalPlan_Preemption(t *testing.T) {
t.Parallel()
state := testStateStore(t)
node := mock.Node()
node.NodeResources = &structs.NodeResources{
Cpu: structs.NodeCpuResources{
CpuShares: 2000,
},
Memory: structs.NodeMemoryResources{
MemoryMB: 4192,
},
Disk: structs.NodeDiskResources{
DiskMB: 30 * 1024,
},
Networks: []*structs.NetworkResource{
{
Device: "eth0",
CIDR: "192.168.0.100/32",
MBits: 1000,
},
},
}
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
2018-11-02 01:06:32 +00:00
preemptedAlloc := mock.Alloc()
preemptedAlloc.NodeID = node.ID
preemptedAlloc.AllocatedResources = &structs.AllocatedResources{
Shared: structs.AllocatedSharedResources{
DiskMB: 25 * 1024,
},
Tasks: map[string]*structs.AllocatedTaskResources{
"web": {
Cpu: structs.AllocatedCpuResources{
CpuShares: 1500,
},
Memory: structs.AllocatedMemoryResources{
MemoryMB: 4000,
},
Networks: []*structs.NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
MBits: 800,
DynamicPorts: []structs.Port{{Label: "http", Value: 9876}},
},
},
},
},
}
// Insert a preempted alloc such that the alloc will fit only after preemption
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{preemptedAlloc})
2018-11-02 01:06:32 +00:00
alloc := mock.Alloc()
alloc.AllocatedResources = &structs.AllocatedResources{
Shared: structs.AllocatedSharedResources{
DiskMB: 24 * 1024,
},
Tasks: map[string]*structs.AllocatedTaskResources{
"web": {
Cpu: structs.AllocatedCpuResources{
CpuShares: 1500,
},
Memory: structs.AllocatedMemoryResources{
MemoryMB: 3200,
},
Networks: []*structs.NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
MBits: 800,
DynamicPorts: []structs.Port{{Label: "http", Value: 9876}},
},
},
},
},
}
plan := &structs.Plan{
Job: alloc.Job,
NodeAllocation: map[string][]*structs.Allocation{
node.ID: {alloc},
},
NodePreemptions: map[string][]*structs.Allocation{
node.ID: {preemptedAlloc},
},
Deployment: mock.Deployment(),
DeploymentUpdates: []*structs.DeploymentStatusUpdate{
{
DeploymentID: uuid.Generate(),
Status: "foo",
StatusDescription: "bar",
},
},
}
snap, _ := state.Snapshot()
pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
defer pool.Shutdown()
result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
require := require.New(t)
require.NoError(err)
require.NotNil(result)
require.Equal(result.NodeAllocation, plan.NodeAllocation)
require.Equal(result.Deployment, plan.Deployment)
require.Equal(result.DeploymentUpdates, plan.DeploymentUpdates)
require.Equal(result.NodePreemptions, plan.NodePreemptions)
}
2015-08-05 01:30:05 +00:00
func TestPlanApply_EvalPlan_Partial(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:30:05 +00:00
state := testStateStore(t)
node := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
node2 := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1001, node2)
2015-08-05 01:30:05 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
alloc2 := mock.Alloc() // Ensure alloc2 does not fit
2018-10-03 16:47:18 +00:00
alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources)
2017-07-06 16:28:08 +00:00
// Create a deployment where the allocs are markeda as canaries
d := mock.Deployment()
d.TaskGroups["web"].PlacedCanaries = []string{alloc.ID, alloc2.ID}
2015-08-05 01:30:05 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:30:05 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
node2.ID: {alloc2},
2015-08-05 01:30:05 +00:00
},
2017-07-06 16:28:08 +00:00
Deployment: d,
2015-08-05 01:30:05 +00:00
}
pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
defer pool.Shutdown()
2018-09-15 23:23:13 +00:00
result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
2015-08-05 01:30:05 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
if _, ok := result.NodeAllocation[node.ID]; !ok {
t.Fatalf("should allow alloc")
}
if _, ok := result.NodeAllocation[node2.ID]; ok {
t.Fatalf("should not allow alloc2")
}
2017-07-06 16:28:08 +00:00
// Check the deployment was updated
if result.Deployment == nil || len(result.Deployment.TaskGroups) == 0 {
t.Fatalf("bad: %v", result.Deployment)
}
placedCanaries := result.Deployment.TaskGroups["web"].PlacedCanaries
if len(placedCanaries) != 1 || placedCanaries[0] != alloc.ID {
t.Fatalf("bad: %v", placedCanaries)
}
if result.RefreshIndex != 1001 {
t.Fatalf("bad: %d", result.RefreshIndex)
}
2015-08-05 01:30:05 +00:00
}
func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:30:05 +00:00
state := testStateStore(t)
node := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
node2 := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1001, node2)
2015-08-05 01:30:05 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
alloc2 := mock.Alloc() // Ensure alloc2 does not fit
2018-10-03 16:47:18 +00:00
alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources)
2015-08-05 01:30:05 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:30:05 +00:00
AllAtOnce: true, // Require all to make progress
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
node2.ID: {alloc2},
2015-08-05 01:30:05 +00:00
},
2017-07-06 16:28:08 +00:00
Deployment: mock.Deployment(),
DeploymentUpdates: []*structs.DeploymentStatusUpdate{
{
DeploymentID: uuid.Generate(),
2017-07-06 16:28:08 +00:00
Status: "foo",
StatusDescription: "bar",
},
},
2015-08-05 01:30:05 +00:00
}
pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
defer pool.Shutdown()
2018-09-15 23:23:13 +00:00
result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
2015-08-05 01:30:05 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
if len(result.NodeAllocation) != 0 {
t.Fatalf("should not alloc: %v", result.NodeAllocation)
}
if result.RefreshIndex != 1001 {
t.Fatalf("bad: %d", result.RefreshIndex)
}
2017-07-06 16:28:08 +00:00
if result.Deployment != nil || len(result.DeploymentUpdates) != 0 {
t.Fatalf("bad: %v", result)
}
2015-08-05 01:30:05 +00:00
}
2015-08-05 01:10:57 +00:00
func TestPlanApply_EvalNodePlan_Simple(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:10:57 +00:00
state := testStateStore(t)
node := mock.Node()
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
2015-08-05 01:10:57 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
2015-08-05 01:10:57 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:10:57 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
2015-08-05 01:10:57 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-08-05 01:10:57 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
if reason != "" {
t.Fatalf("bad")
}
2015-08-05 01:10:57 +00:00
}
func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:10:57 +00:00
state := testStateStore(t)
node := mock.Node()
2015-08-05 01:10:57 +00:00
node.Status = structs.NodeStatusInit
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
2015-08-05 01:10:57 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
2015-08-05 01:10:57 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:10:57 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
2015-08-05 01:10:57 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-08-05 01:10:57 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
if reason == "" {
t.Fatalf("bad")
}
2015-08-05 01:10:57 +00:00
}
2015-09-07 02:47:02 +00:00
func TestPlanApply_EvalNodePlan_NodeDrain(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-09-07 02:47:02 +00:00
state := testStateStore(t)
node := mock.DrainNode()
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
2015-09-07 02:47:02 +00:00
snap, _ := state.Snapshot()
alloc := mock.Alloc()
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-09-07 02:47:02 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
2015-09-07 02:47:02 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-09-07 02:47:02 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
if reason == "" {
t.Fatalf("bad")
}
2015-09-07 02:47:02 +00:00
}
2015-08-05 01:10:57 +00:00
func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-05 01:10:57 +00:00
state := testStateStore(t)
snap, _ := state.Snapshot()
nodeID := "12345678-abcd-efab-cdef-123456789abc"
alloc := mock.Alloc()
2015-08-05 01:10:57 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:10:57 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
nodeID: {alloc},
2015-08-05 01:10:57 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, nodeID)
2015-08-05 01:10:57 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
if reason == "" {
t.Fatalf("bad")
}
2015-08-05 01:10:57 +00:00
}
func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
alloc := mock.Alloc()
2015-08-05 01:10:57 +00:00
state := testStateStore(t)
node := mock.Node()
2018-10-03 16:47:18 +00:00
node.ReservedResources = nil
2015-08-05 01:10:57 +00:00
alloc.NodeID = node.ID
2018-10-03 16:47:18 +00:00
alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
2016-07-25 21:11:32 +00:00
state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
2015-08-05 01:10:57 +00:00
alloc2 := mock.Alloc()
alloc2.NodeID = node.ID
2016-07-25 21:11:32 +00:00
state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID))
snap, _ := state.Snapshot()
2015-08-05 01:10:57 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-05 01:10:57 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc2},
2015-08-05 01:10:57 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-08-05 01:10:57 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
if reason == "" {
t.Fatalf("bad")
}
2015-08-05 01:10:57 +00:00
}
// Test that we detect device oversubscription
func TestPlanApply_EvalNodePlan_NodeFull_Device(t *testing.T) {
t.Parallel()
2018-10-31 20:57:43 +00:00
require := require.New(t)
alloc := mock.Alloc()
state := testStateStore(t)
node := mock.NvidiaNode()
node.ReservedResources = nil
nvidia0 := node.NodeResources.Devices[0].Instances[0].ID
// Have the allocation use a Nvidia device
alloc.NodeID = node.ID
alloc.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{
{
Type: "gpu",
Vendor: "nvidia",
Name: "1080ti",
DeviceIDs: []string{nvidia0},
},
}
state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
// Alloc2 tries to use the same device
alloc2 := mock.Alloc()
alloc2.AllocatedResources.Tasks["web"].Networks = nil
alloc2.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{
{
Type: "gpu",
Vendor: "nvidia",
Name: "1080ti",
DeviceIDs: []string{nvidia0},
},
}
alloc2.NodeID = node.ID
state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID))
snap, _ := state.Snapshot()
plan := &structs.Plan{
Job: alloc.Job,
NodeAllocation: map[string][]*structs.Allocation{
node.ID: {alloc2},
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2018-10-31 20:57:43 +00:00
require.NoError(err)
require.False(fit)
require.Equal("device oversubscribed", reason)
}
func TestPlanApply_EvalNodePlan_UpdateExisting(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
alloc := mock.Alloc()
state := testStateStore(t)
node := mock.Node()
2018-10-03 16:47:18 +00:00
node.ReservedResources = nil
node.Reserved = nil
2018-10-03 16:47:18 +00:00
alloc.NodeID = node.ID
alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
snap, _ := state.Snapshot()
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc},
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
if reason != "" {
t.Fatalf("bad")
}
}
2015-08-05 01:10:57 +00:00
func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
alloc := mock.Alloc()
2015-08-05 01:10:57 +00:00
state := testStateStore(t)
node := mock.Node()
2018-10-03 16:47:18 +00:00
node.ReservedResources = nil
2015-08-05 01:10:57 +00:00
alloc.NodeID = node.ID
2018-10-03 16:47:18 +00:00
alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
2015-08-05 01:10:57 +00:00
snap, _ := state.Snapshot()
allocEvict := new(structs.Allocation)
*allocEvict = *alloc
allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
alloc2 := mock.Alloc()
2015-08-05 01:10:57 +00:00
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
NodeUpdate: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {allocEvict},
2015-08-05 01:10:57 +00:00
},
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc2},
2015-08-05 01:10:57 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-08-05 01:10:57 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
if reason != "" {
t.Fatalf("bad")
}
2015-07-28 22:15:42 +00:00
}
2015-08-23 01:30:49 +00:00
func TestPlanApply_EvalNodePlan_NodeFull_AllocEvict(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
2015-08-23 01:30:49 +00:00
alloc := mock.Alloc()
state := testStateStore(t)
node := mock.Node()
2018-10-03 16:47:18 +00:00
node.ReservedResources = nil
2015-08-23 01:30:49 +00:00
alloc.NodeID = node.ID
alloc.DesiredStatus = structs.AllocDesiredStatusEvict
2018-10-03 16:47:18 +00:00
alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
2015-08-23 01:30:49 +00:00
snap, _ := state.Snapshot()
alloc2 := mock.Alloc()
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
2015-08-23 01:30:49 +00:00
NodeAllocation: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {alloc2},
2015-08-23 01:30:49 +00:00
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
2015-08-23 01:30:49 +00:00
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
if reason != "" {
t.Fatalf("bad")
}
2015-08-23 01:30:49 +00:00
}
2015-09-07 02:47:02 +00:00
func TestPlanApply_EvalNodePlan_NodeDown_EvictOnly(t *testing.T) {
2017-07-23 22:04:38 +00:00
t.Parallel()
alloc := mock.Alloc()
state := testStateStore(t)
node := mock.Node()
alloc.NodeID = node.ID
2018-10-03 16:47:18 +00:00
alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
node.ReservedResources = nil
2015-09-07 02:47:02 +00:00
node.Status = structs.NodeStatusDown
state.UpsertNode(structs.MsgTypeTestSetup, 1000, node)
state.UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc})
snap, _ := state.Snapshot()
allocEvict := new(structs.Allocation)
*allocEvict = *alloc
allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
plan := &structs.Plan{
2017-10-13 21:36:02 +00:00
Job: alloc.Job,
NodeUpdate: map[string][]*structs.Allocation{
2017-09-26 22:26:33 +00:00
node.ID: {allocEvict},
},
}
fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
if reason != "" {
t.Fatalf("bad")
}
}