github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/nomad/blocked_evals_test.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/nomad/mock" 10 "github.com/hashicorp/nomad/nomad/structs" 11 "github.com/hashicorp/nomad/testutil" 12 ) 13 14 func testBlockedEvals(t *testing.T) (*BlockedEvals, *EvalBroker) { 15 broker := testBroker(t, 0) 16 broker.SetEnabled(true) 17 blocked := NewBlockedEvals(broker) 18 blocked.SetEnabled(true) 19 return blocked, broker 20 } 21 22 func TestBlockedEvals_Block_Disabled(t *testing.T) { 23 t.Parallel() 24 blocked, _ := testBlockedEvals(t) 25 blocked.SetEnabled(false) 26 27 // Create an escaped eval and add it to the blocked tracker. 28 e := mock.Eval() 29 e.Status = structs.EvalStatusBlocked 30 e.EscapedComputedClass = true 31 blocked.Block(e) 32 33 // Verify block did nothing 34 bStats := blocked.Stats() 35 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 36 t.Fatalf("bad: %#v", bStats) 37 } 38 } 39 40 func TestBlockedEvals_Block_SameJob(t *testing.T) { 41 t.Parallel() 42 blocked, _ := testBlockedEvals(t) 43 44 // Create two blocked evals and add them to the blocked tracker. 45 e := mock.Eval() 46 e2 := mock.Eval() 47 e2.JobID = e.JobID 48 blocked.Block(e) 49 blocked.Block(e2) 50 51 // Verify block did track both 52 bStats := blocked.Stats() 53 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 { 54 t.Fatalf("bad: %#v", bStats) 55 } 56 } 57 58 func TestBlockedEvals_Block_PriorUnblocks(t *testing.T) { 59 t.Parallel() 60 blocked, _ := testBlockedEvals(t) 61 62 // Do unblocks prior to blocking 63 blocked.Unblock("v1:123", 1000) 64 blocked.Unblock("v1:123", 1001) 65 66 // Create two blocked evals and add them to the blocked tracker. 67 e := mock.Eval() 68 e.Status = structs.EvalStatusBlocked 69 e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false} 70 e.SnapshotIndex = 999 71 blocked.Block(e) 72 73 // Verify block did track both 74 bStats := blocked.Stats() 75 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 { 76 t.Fatalf("bad: %#v", bStats) 77 } 78 } 79 80 func TestBlockedEvals_GetDuplicates(t *testing.T) { 81 t.Parallel() 82 blocked, _ := testBlockedEvals(t) 83 84 // Create duplicate blocked evals and add them to the blocked tracker. 85 e := mock.Eval() 86 e2 := mock.Eval() 87 e2.JobID = e.JobID 88 e3 := mock.Eval() 89 e3.JobID = e.JobID 90 blocked.Block(e) 91 blocked.Block(e2) 92 93 // Verify block did track both 94 bStats := blocked.Stats() 95 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 { 96 t.Fatalf("bad: %#v", bStats) 97 } 98 99 // Get the duplicates. 100 out := blocked.GetDuplicates(0) 101 if len(out) != 1 || !reflect.DeepEqual(out[0], e2) { 102 t.Fatalf("bad: %#v %#v", out, e2) 103 } 104 105 // Call block again after a small sleep. 106 go func() { 107 time.Sleep(500 * time.Millisecond) 108 blocked.Block(e3) 109 }() 110 111 // Get the duplicates. 112 out = blocked.GetDuplicates(1 * time.Second) 113 if len(out) != 1 || !reflect.DeepEqual(out[0], e3) { 114 t.Fatalf("bad: %#v %#v", out, e2) 115 } 116 } 117 118 func TestBlockedEvals_UnblockEscaped(t *testing.T) { 119 t.Parallel() 120 blocked, broker := testBlockedEvals(t) 121 122 // Create an escaped eval and add it to the blocked tracker. 123 e := mock.Eval() 124 e.Status = structs.EvalStatusBlocked 125 e.EscapedComputedClass = true 126 blocked.Block(e) 127 128 // Verify block caused the eval to be tracked 129 bStats := blocked.Stats() 130 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 1 { 131 t.Fatalf("bad: %#v", bStats) 132 } 133 134 blocked.Unblock("v1:123", 1000) 135 136 testutil.WaitForResult(func() (bool, error) { 137 // Verify Unblock caused an enqueue 138 brokerStats := broker.Stats() 139 if brokerStats.TotalReady != 1 { 140 return false, fmt.Errorf("bad: %#v", brokerStats) 141 } 142 143 // Verify Unblock updates the stats 144 bStats := blocked.Stats() 145 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 146 return false, fmt.Errorf("bad: %#v", bStats) 147 } 148 return true, nil 149 }, func(err error) { 150 t.Fatalf("err: %s", err) 151 }) 152 } 153 154 func TestBlockedEvals_UnblockEligible(t *testing.T) { 155 t.Parallel() 156 blocked, broker := testBlockedEvals(t) 157 158 // Create a blocked eval that is eligible on a specific node class and add 159 // it to the blocked tracker. 160 e := mock.Eval() 161 e.Status = structs.EvalStatusBlocked 162 e.ClassEligibility = map[string]bool{"v1:123": true} 163 blocked.Block(e) 164 165 // Verify block caused the eval to be tracked 166 blockedStats := blocked.Stats() 167 if blockedStats.TotalBlocked != 1 { 168 t.Fatalf("bad: %#v", blockedStats) 169 } 170 171 blocked.Unblock("v1:123", 1000) 172 173 testutil.WaitForResult(func() (bool, error) { 174 // Verify Unblock caused an enqueue 175 brokerStats := broker.Stats() 176 if brokerStats.TotalReady != 1 { 177 return false, fmt.Errorf("bad: %#v", brokerStats) 178 } 179 180 // Verify Unblock updates the stats 181 bStats := blocked.Stats() 182 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 183 return false, fmt.Errorf("bad: %#v", bStats) 184 } 185 return true, nil 186 }, func(err error) { 187 t.Fatalf("err: %s", err) 188 }) 189 } 190 191 func TestBlockedEvals_UnblockIneligible(t *testing.T) { 192 t.Parallel() 193 blocked, broker := testBlockedEvals(t) 194 195 // Create a blocked eval that is ineligible on a specific node class and add 196 // it to the blocked tracker. 197 e := mock.Eval() 198 e.Status = structs.EvalStatusBlocked 199 e.ClassEligibility = map[string]bool{"v1:123": false} 200 blocked.Block(e) 201 202 // Verify block caused the eval to be tracked 203 blockedStats := blocked.Stats() 204 if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 { 205 t.Fatalf("bad: %#v", blockedStats) 206 } 207 208 // Should do nothing 209 blocked.Unblock("v1:123", 1000) 210 211 testutil.WaitForResult(func() (bool, error) { 212 // Verify Unblock didn't cause an enqueue 213 brokerStats := broker.Stats() 214 if brokerStats.TotalReady != 0 { 215 return false, fmt.Errorf("bad: %#v", brokerStats) 216 } 217 218 bStats := blocked.Stats() 219 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 { 220 return false, fmt.Errorf("bad: %#v", bStats) 221 } 222 return true, nil 223 }, func(err error) { 224 t.Fatalf("err: %s", err) 225 }) 226 } 227 228 func TestBlockedEvals_UnblockUnknown(t *testing.T) { 229 t.Parallel() 230 blocked, broker := testBlockedEvals(t) 231 232 // Create a blocked eval that is ineligible on a specific node class and add 233 // it to the blocked tracker. 234 e := mock.Eval() 235 e.Status = structs.EvalStatusBlocked 236 e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 237 blocked.Block(e) 238 239 // Verify block caused the eval to be tracked 240 blockedStats := blocked.Stats() 241 if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 { 242 t.Fatalf("bad: %#v", blockedStats) 243 } 244 245 // Should unblock because the eval hasn't seen this node class. 246 blocked.Unblock("v1:789", 1000) 247 248 testutil.WaitForResult(func() (bool, error) { 249 // Verify Unblock causes an enqueue 250 brokerStats := broker.Stats() 251 if brokerStats.TotalReady != 1 { 252 return false, fmt.Errorf("bad: %#v", brokerStats) 253 } 254 255 // Verify Unblock updates the stats 256 bStats := blocked.Stats() 257 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 258 return false, fmt.Errorf("bad: %#v", bStats) 259 } 260 return true, nil 261 }, func(err error) { 262 t.Fatalf("err: %s", err) 263 }) 264 } 265 266 func TestBlockedEvals_Reblock(t *testing.T) { 267 t.Parallel() 268 blocked, broker := testBlockedEvals(t) 269 270 // Create an evaluation, Enqueue/Dequeue it to get a token 271 e := mock.Eval() 272 e.SnapshotIndex = 500 273 e.Status = structs.EvalStatusBlocked 274 e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 275 broker.Enqueue(e) 276 277 _, token, err := broker.Dequeue([]string{e.Type}, time.Second) 278 if err != nil { 279 t.Fatalf("err: %v", err) 280 } 281 282 // Reblock the evaluation 283 blocked.Reblock(e, token) 284 285 // Verify block caused the eval to be tracked 286 blockedStats := blocked.Stats() 287 if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 { 288 t.Fatalf("bad: %#v", blockedStats) 289 } 290 291 // Should unblock because the eval 292 blocked.Unblock("v1:123", 1000) 293 294 brokerStats := broker.Stats() 295 if brokerStats.TotalReady != 0 && brokerStats.TotalUnacked != 1 { 296 t.Fatalf("bad: %#v", brokerStats) 297 } 298 299 // Ack the evaluation which should cause the reblocked eval to transistion 300 // to ready 301 if err := broker.Ack(e.ID, token); err != nil { 302 t.Fatalf("err: %v", err) 303 } 304 305 testutil.WaitForResult(func() (bool, error) { 306 // Verify Unblock causes an enqueue 307 brokerStats := broker.Stats() 308 if brokerStats.TotalReady != 1 { 309 return false, fmt.Errorf("bad: %#v", brokerStats) 310 } 311 312 // Verify Unblock updates the stats 313 bStats := blocked.Stats() 314 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 315 return false, fmt.Errorf("bad: %#v", bStats) 316 } 317 return true, nil 318 }, func(err error) { 319 t.Fatalf("err: %s", err) 320 }) 321 } 322 323 // Test the block case in which the eval should be immediately unblocked since 324 // it is escaped and old 325 func TestBlockedEvals_Block_ImmediateUnblock_Escaped(t *testing.T) { 326 t.Parallel() 327 blocked, broker := testBlockedEvals(t) 328 329 // Do an unblock prior to blocking 330 blocked.Unblock("v1:123", 1000) 331 332 // Create a blocked eval that is eligible on a specific node class and add 333 // it to the blocked tracker. 334 e := mock.Eval() 335 e.Status = structs.EvalStatusBlocked 336 e.EscapedComputedClass = true 337 e.SnapshotIndex = 900 338 blocked.Block(e) 339 340 // Verify block caused the eval to be immediately unblocked 341 blockedStats := blocked.Stats() 342 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 343 t.Fatalf("bad: %#v", blockedStats) 344 } 345 346 testutil.WaitForResult(func() (bool, error) { 347 // Verify Unblock caused an enqueue 348 brokerStats := broker.Stats() 349 if brokerStats.TotalReady != 1 { 350 return false, fmt.Errorf("bad: %#v", brokerStats) 351 } 352 353 return true, nil 354 }, func(err error) { 355 t.Fatalf("err: %s", err) 356 }) 357 } 358 359 // Test the block case in which the eval should be immediately unblocked since 360 // there is an unblock on an unseen class that occurred while it was in the 361 // scheduler 362 func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_After(t *testing.T) { 363 t.Parallel() 364 blocked, broker := testBlockedEvals(t) 365 366 // Do an unblock prior to blocking 367 blocked.Unblock("v1:123", 1000) 368 369 // Create a blocked eval that is eligible on a specific node class and add 370 // it to the blocked tracker. 371 e := mock.Eval() 372 e.Status = structs.EvalStatusBlocked 373 e.EscapedComputedClass = false 374 e.SnapshotIndex = 900 375 blocked.Block(e) 376 377 // Verify block caused the eval to be immediately unblocked 378 blockedStats := blocked.Stats() 379 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 380 t.Fatalf("bad: %#v", blockedStats) 381 } 382 383 testutil.WaitForResult(func() (bool, error) { 384 // Verify Unblock caused an enqueue 385 brokerStats := broker.Stats() 386 if brokerStats.TotalReady != 1 { 387 return false, fmt.Errorf("bad: %#v", brokerStats) 388 } 389 390 return true, nil 391 }, func(err error) { 392 t.Fatalf("err: %s", err) 393 }) 394 } 395 396 // Test the block case in which the eval should not immediately unblock since 397 // there is an unblock on an unseen class that occurred before it was in the 398 // scheduler 399 func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_Before(t *testing.T) { 400 t.Parallel() 401 blocked, _ := testBlockedEvals(t) 402 403 // Do an unblock prior to blocking 404 blocked.Unblock("v1:123", 500) 405 406 // Create a blocked eval that is eligible on a specific node class and add 407 // it to the blocked tracker. 408 e := mock.Eval() 409 e.Status = structs.EvalStatusBlocked 410 e.EscapedComputedClass = false 411 e.SnapshotIndex = 900 412 blocked.Block(e) 413 414 // Verify block caused the eval to be immediately unblocked 415 blockedStats := blocked.Stats() 416 if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 { 417 t.Fatalf("bad: %#v", blockedStats) 418 } 419 } 420 421 // Test the block case in which the eval should be immediately unblocked since 422 // it a class it is eligible for has been unblocked 423 func TestBlockedEvals_Block_ImmediateUnblock_SeenClass(t *testing.T) { 424 t.Parallel() 425 blocked, broker := testBlockedEvals(t) 426 427 // Do an unblock prior to blocking 428 blocked.Unblock("v1:123", 1000) 429 430 // Create a blocked eval that is eligible on a specific node class and add 431 // it to the blocked tracker. 432 e := mock.Eval() 433 e.Status = structs.EvalStatusBlocked 434 e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 435 e.SnapshotIndex = 900 436 blocked.Block(e) 437 438 // Verify block caused the eval to be immediately unblocked 439 blockedStats := blocked.Stats() 440 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 441 t.Fatalf("bad: %#v", blockedStats) 442 } 443 444 testutil.WaitForResult(func() (bool, error) { 445 // Verify Unblock caused an enqueue 446 brokerStats := broker.Stats() 447 if brokerStats.TotalReady != 1 { 448 return false, fmt.Errorf("bad: %#v", brokerStats) 449 } 450 451 return true, nil 452 }, func(err error) { 453 t.Fatalf("err: %s", err) 454 }) 455 } 456 457 func TestBlockedEvals_UnblockFailed(t *testing.T) { 458 t.Parallel() 459 blocked, broker := testBlockedEvals(t) 460 461 // Create blocked evals that are due to failures 462 e := mock.Eval() 463 e.Status = structs.EvalStatusBlocked 464 e.TriggeredBy = structs.EvalTriggerMaxPlans 465 e.EscapedComputedClass = true 466 blocked.Block(e) 467 468 e2 := mock.Eval() 469 e2.Status = structs.EvalStatusBlocked 470 e2.TriggeredBy = structs.EvalTriggerMaxPlans 471 e2.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 472 blocked.Block(e2) 473 474 // Trigger an unblock fail 475 blocked.UnblockFailed() 476 477 // Verify UnblockFailed caused the eval to be immediately unblocked 478 blockedStats := blocked.Stats() 479 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 480 t.Fatalf("bad: %#v", blockedStats) 481 } 482 483 testutil.WaitForResult(func() (bool, error) { 484 // Verify Unblock caused an enqueue 485 brokerStats := broker.Stats() 486 if brokerStats.TotalReady != 2 { 487 return false, fmt.Errorf("bad: %#v", brokerStats) 488 } 489 return true, nil 490 }, func(err error) { 491 t.Fatalf("err: %s", err) 492 }) 493 494 // Reblock an eval for the same job and check that it gets tracked. 495 blocked.Block(e) 496 blockedStats = blocked.Stats() 497 if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 1 { 498 t.Fatalf("bad: %#v", blockedStats) 499 } 500 } 501 502 func TestBlockedEvals_Untrack(t *testing.T) { 503 t.Parallel() 504 blocked, _ := testBlockedEvals(t) 505 506 // Create two blocked evals and add them to the blocked tracker. 507 e := mock.Eval() 508 e.Status = structs.EvalStatusBlocked 509 e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false} 510 e.SnapshotIndex = 1000 511 blocked.Block(e) 512 513 // Verify block did track 514 bStats := blocked.Stats() 515 if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 { 516 t.Fatalf("bad: %#v", bStats) 517 } 518 519 // Untrack and verify 520 blocked.Untrack(e.JobID) 521 bStats = blocked.Stats() 522 if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 { 523 t.Fatalf("bad: %#v", bStats) 524 } 525 }