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