github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/scheduler/context_test.go (about) 1 package scheduler 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/nomad/helper/testlog" 7 "github.com/hashicorp/nomad/helper/uuid" 8 "github.com/hashicorp/nomad/nomad/mock" 9 "github.com/hashicorp/nomad/nomad/state" 10 "github.com/hashicorp/nomad/nomad/structs" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func testContext(t testing.TB) (*state.StateStore, *EvalContext) { 15 state := state.TestStateStore(t) 16 plan := &structs.Plan{ 17 NodeUpdate: make(map[string][]*structs.Allocation), 18 NodeAllocation: make(map[string][]*structs.Allocation), 19 NodePreemptions: make(map[string][]*structs.Allocation), 20 } 21 22 logger := testlog.HCLogger(t) 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 NodeResources: &structs.NodeResources{ 36 Cpu: structs.NodeCpuResources{ 37 CpuShares: 2048, 38 }, 39 Memory: structs.NodeMemoryResources{ 40 MemoryMB: 2048, 41 }, 42 }, 43 }, 44 }, 45 { 46 Node: &structs.Node{ 47 // Perfect fit 48 ID: uuid.Generate(), 49 NodeResources: &structs.NodeResources{ 50 Cpu: structs.NodeCpuResources{ 51 CpuShares: 2048, 52 }, 53 Memory: structs.NodeMemoryResources{ 54 MemoryMB: 2048, 55 }, 56 }, 57 }, 58 }, 59 } 60 61 // Add existing allocations 62 j1, j2 := mock.Job(), mock.Job() 63 alloc1 := &structs.Allocation{ 64 ID: uuid.Generate(), 65 Namespace: structs.DefaultNamespace, 66 EvalID: uuid.Generate(), 67 NodeID: nodes[0].Node.ID, 68 JobID: j1.ID, 69 Job: j1, 70 AllocatedResources: &structs.AllocatedResources{ 71 Tasks: map[string]*structs.AllocatedTaskResources{ 72 "web": { 73 Cpu: structs.AllocatedCpuResources{ 74 CpuShares: 2048, 75 }, 76 Memory: structs.AllocatedMemoryResources{ 77 MemoryMB: 2048, 78 }, 79 }, 80 }, 81 }, 82 DesiredStatus: structs.AllocDesiredStatusRun, 83 ClientStatus: structs.AllocClientStatusPending, 84 TaskGroup: "web", 85 } 86 alloc2 := &structs.Allocation{ 87 ID: uuid.Generate(), 88 Namespace: structs.DefaultNamespace, 89 EvalID: uuid.Generate(), 90 NodeID: nodes[1].Node.ID, 91 JobID: j2.ID, 92 Job: j2, 93 AllocatedResources: &structs.AllocatedResources{ 94 Tasks: map[string]*structs.AllocatedTaskResources{ 95 "web": { 96 Cpu: structs.AllocatedCpuResources{ 97 CpuShares: 1024, 98 }, 99 Memory: structs.AllocatedMemoryResources{ 100 MemoryMB: 1024, 101 }, 102 }, 103 }, 104 }, 105 DesiredStatus: structs.AllocDesiredStatusRun, 106 ClientStatus: structs.AllocClientStatusPending, 107 TaskGroup: "web", 108 } 109 require.NoError(t, state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))) 110 require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))) 111 require.NoError(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2})) 112 113 // Add a planned eviction to alloc1 114 plan := ctx.Plan() 115 plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{alloc1} 116 117 // Add a planned placement to node1 118 plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{ 119 { 120 AllocatedResources: &structs.AllocatedResources{ 121 Tasks: map[string]*structs.AllocatedTaskResources{ 122 "web": { 123 Cpu: structs.AllocatedCpuResources{ 124 CpuShares: 1024, 125 }, 126 Memory: structs.AllocatedMemoryResources{ 127 MemoryMB: 1024, 128 }, 129 }, 130 }, 131 }, 132 }, 133 } 134 135 proposed, err := ctx.ProposedAllocs(nodes[0].Node.ID) 136 if err != nil { 137 t.Fatalf("err: %v", err) 138 } 139 if len(proposed) != 0 { 140 t.Fatalf("bad: %#v", proposed) 141 } 142 143 proposed, err = ctx.ProposedAllocs(nodes[1].Node.ID) 144 if err != nil { 145 t.Fatalf("err: %v", err) 146 } 147 if len(proposed) != 2 { 148 t.Fatalf("bad: %#v", proposed) 149 } 150 } 151 152 // TestEvalContext_ProposedAlloc_EvictPreempt asserts both Evicted and 153 // Preempted allocs are removed from the allocs propsed for a node. 154 // 155 // See https://github.com/hashicorp/nomad/issues/6787 156 // 157 func TestEvalContext_ProposedAlloc_EvictPreempt(t *testing.T) { 158 t.Parallel() 159 state, ctx := testContext(t) 160 nodes := []*RankedNode{ 161 { 162 Node: &structs.Node{ 163 ID: uuid.Generate(), 164 NodeResources: &structs.NodeResources{ 165 Cpu: structs.NodeCpuResources{ 166 CpuShares: 1024 * 3, 167 }, 168 Memory: structs.NodeMemoryResources{ 169 MemoryMB: 1024 * 3, 170 }, 171 }, 172 }, 173 }, 174 } 175 176 // Add existing allocations 177 j1, j2, j3 := mock.Job(), mock.Job(), mock.Job() 178 allocEvict := &structs.Allocation{ 179 ID: uuid.Generate(), 180 Namespace: structs.DefaultNamespace, 181 EvalID: uuid.Generate(), 182 NodeID: nodes[0].Node.ID, 183 JobID: j1.ID, 184 Job: j1, 185 AllocatedResources: &structs.AllocatedResources{ 186 Tasks: map[string]*structs.AllocatedTaskResources{ 187 "web": { 188 Cpu: structs.AllocatedCpuResources{ 189 CpuShares: 1024, 190 }, 191 Memory: structs.AllocatedMemoryResources{ 192 MemoryMB: 1024, 193 }, 194 }, 195 }, 196 }, 197 DesiredStatus: structs.AllocDesiredStatusRun, 198 ClientStatus: structs.AllocClientStatusPending, 199 TaskGroup: "web", 200 } 201 allocPreempt := &structs.Allocation{ 202 ID: uuid.Generate(), 203 Namespace: structs.DefaultNamespace, 204 EvalID: uuid.Generate(), 205 NodeID: nodes[0].Node.ID, 206 JobID: j2.ID, 207 Job: j2, 208 AllocatedResources: &structs.AllocatedResources{ 209 Tasks: map[string]*structs.AllocatedTaskResources{ 210 "web": { 211 Cpu: structs.AllocatedCpuResources{ 212 CpuShares: 1024, 213 }, 214 Memory: structs.AllocatedMemoryResources{ 215 MemoryMB: 1024, 216 }, 217 }, 218 }, 219 }, 220 DesiredStatus: structs.AllocDesiredStatusRun, 221 ClientStatus: structs.AllocClientStatusPending, 222 TaskGroup: "web", 223 } 224 allocPropose := &structs.Allocation{ 225 ID: uuid.Generate(), 226 Namespace: structs.DefaultNamespace, 227 EvalID: uuid.Generate(), 228 NodeID: nodes[0].Node.ID, 229 JobID: j3.ID, 230 Job: j3, 231 AllocatedResources: &structs.AllocatedResources{ 232 Tasks: map[string]*structs.AllocatedTaskResources{ 233 "web": { 234 Cpu: structs.AllocatedCpuResources{ 235 CpuShares: 1024, 236 }, 237 Memory: structs.AllocatedMemoryResources{ 238 MemoryMB: 1024, 239 }, 240 }, 241 }, 242 }, 243 DesiredStatus: structs.AllocDesiredStatusRun, 244 ClientStatus: structs.AllocClientStatusPending, 245 TaskGroup: "web", 246 } 247 require.NoError(t, state.UpsertJobSummary(998, mock.JobSummary(allocEvict.JobID))) 248 require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(allocPreempt.JobID))) 249 require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(allocPropose.JobID))) 250 require.NoError(t, state.UpsertAllocs(1000, []*structs.Allocation{allocEvict, allocPreempt, allocPropose})) 251 252 // Plan to evict one alloc and preempt another 253 plan := ctx.Plan() 254 plan.NodePreemptions[nodes[0].Node.ID] = []*structs.Allocation{allocEvict} 255 plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{allocPreempt} 256 257 proposed, err := ctx.ProposedAllocs(nodes[0].Node.ID) 258 require.NoError(t, err) 259 require.Len(t, proposed, 1) 260 } 261 262 func TestEvalEligibility_JobStatus(t *testing.T) { 263 e := NewEvalEligibility() 264 cc := "v1:100" 265 266 // Get the job before its been set. 267 if status := e.JobStatus(cc); status != EvalComputedClassUnknown { 268 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassUnknown) 269 } 270 271 // Set the job and get its status. 272 e.SetJobEligibility(false, cc) 273 if status := e.JobStatus(cc); status != EvalComputedClassIneligible { 274 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassIneligible) 275 } 276 277 e.SetJobEligibility(true, cc) 278 if status := e.JobStatus(cc); status != EvalComputedClassEligible { 279 t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassEligible) 280 } 281 } 282 283 func TestEvalEligibility_TaskGroupStatus(t *testing.T) { 284 e := NewEvalEligibility() 285 cc := "v1:100" 286 tg := "foo" 287 288 // Get the tg before its been set. 289 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassUnknown { 290 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassUnknown) 291 } 292 293 // Set the tg and get its status. 294 e.SetTaskGroupEligibility(false, tg, cc) 295 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassIneligible { 296 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassIneligible) 297 } 298 299 e.SetTaskGroupEligibility(true, tg, cc) 300 if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassEligible { 301 t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassEligible) 302 } 303 } 304 305 func TestEvalEligibility_SetJob(t *testing.T) { 306 e := NewEvalEligibility() 307 ne1 := &structs.Constraint{ 308 LTarget: "${attr.kernel.name}", 309 RTarget: "linux", 310 Operand: "=", 311 } 312 e1 := &structs.Constraint{ 313 LTarget: "${attr.unique.kernel.name}", 314 RTarget: "linux", 315 Operand: "=", 316 } 317 e2 := &structs.Constraint{ 318 LTarget: "${meta.unique.key_foo}", 319 RTarget: "linux", 320 Operand: "<", 321 } 322 e3 := &structs.Constraint{ 323 LTarget: "${meta.unique.key_foo}", 324 RTarget: "Windows", 325 Operand: "<", 326 } 327 328 job := mock.Job() 329 jobCon := []*structs.Constraint{ne1, e1, e2} 330 job.Constraints = jobCon 331 332 // Set the task constraints 333 tg := job.TaskGroups[0] 334 tg.Constraints = []*structs.Constraint{e1} 335 tg.Tasks[0].Constraints = []*structs.Constraint{e3} 336 337 e.SetJob(job) 338 if !e.HasEscaped() { 339 t.Fatalf("HasEscaped() should be true") 340 } 341 342 if !e.jobEscaped { 343 t.Fatalf("SetJob() should mark job as escaped") 344 } 345 if escaped, ok := e.tgEscapedConstraints[tg.Name]; !ok || !escaped { 346 t.Fatalf("SetJob() should mark task group as escaped") 347 } 348 } 349 350 func TestEvalEligibility_GetClasses(t *testing.T) { 351 e := NewEvalEligibility() 352 e.SetJobEligibility(true, "v1:1") 353 e.SetJobEligibility(false, "v1:2") 354 e.SetTaskGroupEligibility(true, "foo", "v1:3") 355 e.SetTaskGroupEligibility(false, "bar", "v1:4") 356 e.SetTaskGroupEligibility(true, "bar", "v1:5") 357 358 // Mark an existing eligible class as ineligible in the TG. 359 e.SetTaskGroupEligibility(false, "fizz", "v1:1") 360 e.SetTaskGroupEligibility(false, "fizz", "v1:3") 361 362 expClasses := map[string]bool{ 363 "v1:1": false, 364 "v1:2": false, 365 "v1:3": true, 366 "v1:4": false, 367 "v1:5": true, 368 } 369 370 actClasses := e.GetClasses() 371 require.Equal(t, expClasses, actClasses) 372 } 373 func TestEvalEligibility_GetClasses_JobEligible_TaskGroupIneligible(t *testing.T) { 374 e := NewEvalEligibility() 375 e.SetJobEligibility(true, "v1:1") 376 e.SetTaskGroupEligibility(false, "foo", "v1:1") 377 378 e.SetJobEligibility(true, "v1:2") 379 e.SetTaskGroupEligibility(false, "foo", "v1:2") 380 e.SetTaskGroupEligibility(true, "bar", "v1:2") 381 382 e.SetJobEligibility(true, "v1:3") 383 e.SetTaskGroupEligibility(false, "foo", "v1:3") 384 e.SetTaskGroupEligibility(false, "bar", "v1:3") 385 386 expClasses := map[string]bool{ 387 "v1:1": false, 388 "v1:2": true, 389 "v1:3": false, 390 } 391 392 actClasses := e.GetClasses() 393 require.Equal(t, expClasses, actClasses) 394 }