github.com/djenriquez/nomad-1@v0.8.1/scheduler/context_test.go (about) 1 package scheduler 2 3 import ( 4 "log" 5 "os" 6 "reflect" 7 "testing" 8 9 "github.com/hashicorp/nomad/helper/uuid" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/state" 12 "github.com/hashicorp/nomad/nomad/structs" 13 ) 14 15 func testContext(t testing.TB) (*state.StateStore, *EvalContext) { 16 state := state.TestStateStore(t) 17 plan := &structs.Plan{ 18 NodeUpdate: make(map[string][]*structs.Allocation), 19 NodeAllocation: make(map[string][]*structs.Allocation), 20 } 21 22 logger := log.New(os.Stderr, "", log.LstdFlags) 23 24 ctx := NewEvalContext(state, plan, logger) 25 return state, ctx 26 } 27 28 func TestEvalContext_ProposedAlloc(t *testing.T) { 29 state, ctx := testContext(t) 30 nodes := []*RankedNode{ 31 { 32 Node: &structs.Node{ 33 // Perfect fit 34 ID: uuid.Generate(), 35 Resources: &structs.Resources{ 36 CPU: 2048, 37 MemoryMB: 2048, 38 }, 39 }, 40 }, 41 { 42 Node: &structs.Node{ 43 // Perfect fit 44 ID: uuid.Generate(), 45 Resources: &structs.Resources{ 46 CPU: 2048, 47 MemoryMB: 2048, 48 }, 49 }, 50 }, 51 } 52 53 // Add existing allocations 54 j1, j2 := mock.Job(), mock.Job() 55 alloc1 := &structs.Allocation{ 56 ID: uuid.Generate(), 57 Namespace: structs.DefaultNamespace, 58 EvalID: uuid.Generate(), 59 NodeID: nodes[0].Node.ID, 60 JobID: j1.ID, 61 Job: j1, 62 Resources: &structs.Resources{ 63 CPU: 2048, 64 MemoryMB: 2048, 65 }, 66 DesiredStatus: structs.AllocDesiredStatusRun, 67 ClientStatus: structs.AllocClientStatusPending, 68 TaskGroup: "web", 69 } 70 alloc2 := &structs.Allocation{ 71 ID: uuid.Generate(), 72 Namespace: structs.DefaultNamespace, 73 EvalID: uuid.Generate(), 74 NodeID: nodes[1].Node.ID, 75 JobID: j2.ID, 76 Job: j2, 77 Resources: &structs.Resources{ 78 CPU: 1024, 79 MemoryMB: 1024, 80 }, 81 DesiredStatus: structs.AllocDesiredStatusRun, 82 ClientStatus: structs.AllocClientStatusPending, 83 TaskGroup: "web", 84 } 85 noErr(t, state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))) 86 noErr(t, state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))) 87 noErr(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2})) 88 89 // Add a planned eviction to alloc1 90 plan := ctx.Plan() 91 plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{alloc1} 92 93 // Add a planned placement to node1 94 plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{ 95 { 96 Resources: &structs.Resources{ 97 CPU: 1024, 98 MemoryMB: 1024, 99 }, 100 }, 101 } 102 103 proposed, err := ctx.ProposedAllocs(nodes[0].Node.ID) 104 if err != nil { 105 t.Fatalf("err: %v", err) 106 } 107 if len(proposed) != 0 { 108 t.Fatalf("bad: %#v", proposed) 109 } 110 111 proposed, err = ctx.ProposedAllocs(nodes[1].Node.ID) 112 if err != nil { 113 t.Fatalf("err: %v", err) 114 } 115 if len(proposed) != 2 { 116 t.Fatalf("bad: %#v", proposed) 117 } 118 } 119 120 func TestEvalEligibility_JobStatus(t *testing.T) { 121 e := NewEvalEligibility() 122 cc := "v1:100" 123 124 // Get the job before its been set. 125 if status := e.JobStatus(cc); status != EvalComputedClassUnknown { 126 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassUnknown) 127 } 128 129 // Set the job and get its status. 130 e.SetJobEligibility(false, cc) 131 if status := e.JobStatus(cc); status != EvalComputedClassIneligible { 132 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassIneligible) 133 } 134 135 e.SetJobEligibility(true, cc) 136 if status := e.JobStatus(cc); status != EvalComputedClassEligible { 137 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassEligible) 138 } 139 140 // Check that if I pass an empty class it returns escaped 141 if status := e.JobStatus(""); status != EvalComputedClassEscaped { 142 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassEscaped) 143 } 144 } 145 146 func TestEvalEligibility_TaskGroupStatus(t *testing.T) { 147 e := NewEvalEligibility() 148 cc := "v1:100" 149 tg := "foo" 150 151 // Get the tg before its been set. 152 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassUnknown { 153 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassUnknown) 154 } 155 156 // Set the tg and get its status. 157 e.SetTaskGroupEligibility(false, tg, cc) 158 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassIneligible { 159 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassIneligible) 160 } 161 162 e.SetTaskGroupEligibility(true, tg, cc) 163 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassEligible { 164 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassEligible) 165 } 166 167 // Check that if I pass an empty class it returns escaped 168 if status := e.TaskGroupStatus(tg, ""); status != EvalComputedClassEscaped { 169 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassEscaped) 170 } 171 } 172 173 func TestEvalEligibility_SetJob(t *testing.T) { 174 e := NewEvalEligibility() 175 ne1 := &structs.Constraint{ 176 LTarget: "${attr.kernel.name}", 177 RTarget: "linux", 178 Operand: "=", 179 } 180 e1 := &structs.Constraint{ 181 LTarget: "${attr.unique.kernel.name}", 182 RTarget: "linux", 183 Operand: "=", 184 } 185 e2 := &structs.Constraint{ 186 LTarget: "${meta.unique.key_foo}", 187 RTarget: "linux", 188 Operand: "<", 189 } 190 e3 := &structs.Constraint{ 191 LTarget: "${meta.unique.key_foo}", 192 RTarget: "Windows", 193 Operand: "<", 194 } 195 196 job := mock.Job() 197 jobCon := []*structs.Constraint{ne1, e1, e2} 198 job.Constraints = jobCon 199 200 // Set the task constraints 201 tg := job.TaskGroups[0] 202 tg.Constraints = []*structs.Constraint{e1} 203 tg.Tasks[0].Constraints = []*structs.Constraint{e3} 204 205 e.SetJob(job) 206 if !e.HasEscaped() { 207 t.Fatalf("HasEscaped() should be true") 208 } 209 210 if !e.jobEscaped { 211 t.Fatalf("SetJob() should mark job as escaped") 212 } 213 if escaped, ok := e.tgEscapedConstraints[tg.Name]; !ok || !escaped { 214 t.Fatalf("SetJob() should mark task group as escaped") 215 } 216 } 217 218 func TestEvalEligibility_GetClasses(t *testing.T) { 219 e := NewEvalEligibility() 220 e.SetJobEligibility(true, "v1:1") 221 e.SetJobEligibility(false, "v1:2") 222 e.SetTaskGroupEligibility(true, "foo", "v1:3") 223 e.SetTaskGroupEligibility(false, "bar", "v1:4") 224 e.SetTaskGroupEligibility(true, "bar", "v1:5") 225 226 // Mark an existing eligible class as ineligible in the TG. 227 e.SetTaskGroupEligibility(false, "fizz", "v1:1") 228 e.SetTaskGroupEligibility(false, "fizz", "v1:3") 229 230 expClasses := map[string]bool{ 231 "v1:1": true, 232 "v1:2": false, 233 "v1:3": true, 234 "v1:4": false, 235 "v1:5": true, 236 } 237 238 actClasses := e.GetClasses() 239 if !reflect.DeepEqual(actClasses, expClasses) { 240 t.Fatalf("GetClasses() returned %#v; want %#v", actClasses, expClasses) 241 } 242 }