github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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 // Test the block case in which the eval should be immediately unblocked since 259 // it is escaped and old 260 func TestBlockedEvals_Block_ImmediateUnblock_Escaped(t *testing.T) { 261 blocked, broker := testBlockedEvals(t) 262 263 // Do an unblock prior to blocking 264 blocked.Unblock("v1:123", 1000) 265 266 // Create a blocked eval that is eligible on a specific node class and add 267 // it to the blocked tracker. 268 e := mock.Eval() 269 e.Status = structs.EvalStatusBlocked 270 e.EscapedComputedClass = true 271 e.SnapshotIndex = 900 272 blocked.Block(e) 273 274 // Verify block caused the eval to be immediately unblocked 275 blockedStats := blocked.Stats() 276 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 277 t.Fatalf("bad: %#v", blockedStats) 278 } 279 280 testutil.WaitForResult(func() (bool, error) { 281 // Verify Unblock caused an enqueue 282 brokerStats := broker.Stats() 283 if brokerStats.TotalReady != 1 { 284 return false, fmt.Errorf("bad: %#v", brokerStats) 285 } 286 287 return true, nil 288 }, func(err error) { 289 t.Fatalf("err: %s", err) 290 }) 291 } 292 293 // Test the block case in which the eval should be immediately unblocked since 294 // it there is an unblock on an unseen class 295 func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass(t *testing.T) { 296 blocked, broker := testBlockedEvals(t) 297 298 // Do an unblock prior to blocking 299 blocked.Unblock("v1:123", 1000) 300 301 // Create a blocked eval that is eligible on a specific node class and add 302 // it to the blocked tracker. 303 e := mock.Eval() 304 e.Status = structs.EvalStatusBlocked 305 e.EscapedComputedClass = false 306 e.SnapshotIndex = 900 307 blocked.Block(e) 308 309 // Verify block caused the eval to be immediately unblocked 310 blockedStats := blocked.Stats() 311 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 312 t.Fatalf("bad: %#v", blockedStats) 313 } 314 315 testutil.WaitForResult(func() (bool, error) { 316 // Verify Unblock caused an enqueue 317 brokerStats := broker.Stats() 318 if brokerStats.TotalReady != 1 { 319 return false, fmt.Errorf("bad: %#v", brokerStats) 320 } 321 322 return true, nil 323 }, func(err error) { 324 t.Fatalf("err: %s", err) 325 }) 326 } 327 328 // Test the block case in which the eval should be immediately unblocked since 329 // it a class it is eligible for has been unblocked 330 func TestBlockedEvals_Block_ImmediateUnblock_SeenClass(t *testing.T) { 331 blocked, broker := testBlockedEvals(t) 332 333 // Do an unblock prior to blocking 334 blocked.Unblock("v1:123", 1000) 335 336 // Create a blocked eval that is eligible on a specific node class and add 337 // it to the blocked tracker. 338 e := mock.Eval() 339 e.Status = structs.EvalStatusBlocked 340 e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 341 e.SnapshotIndex = 900 342 blocked.Block(e) 343 344 // Verify block caused the eval to be immediately unblocked 345 blockedStats := blocked.Stats() 346 if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 { 347 t.Fatalf("bad: %#v", blockedStats) 348 } 349 350 testutil.WaitForResult(func() (bool, error) { 351 // Verify Unblock caused an enqueue 352 brokerStats := broker.Stats() 353 if brokerStats.TotalReady != 1 { 354 return false, fmt.Errorf("bad: %#v", brokerStats) 355 } 356 357 return true, nil 358 }, func(err error) { 359 t.Fatalf("err: %s", err) 360 }) 361 } 362 363 func TestBlockedEvals_UnblockFailed(t *testing.T) { 364 blocked, broker := testBlockedEvals(t) 365 366 // Create blocked evals that are due to failures 367 e := mock.Eval() 368 e.Status = structs.EvalStatusBlocked 369 e.TriggeredBy = structs.EvalTriggerMaxPlans 370 e.EscapedComputedClass = true 371 blocked.Block(e) 372 373 e2 := mock.Eval() 374 e2.Status = structs.EvalStatusBlocked 375 e2.TriggeredBy = structs.EvalTriggerMaxPlans 376 e2.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false} 377 blocked.Block(e2) 378 379 // Trigger an unblock fail 380 blocked.UnblockFailed() 381 382 testutil.WaitForResult(func() (bool, error) { 383 // Verify Unblock caused an enqueue 384 brokerStats := broker.Stats() 385 if brokerStats.TotalReady != 2 { 386 return false, fmt.Errorf("bad: %#v", brokerStats) 387 } 388 return true, nil 389 }, func(err error) { 390 t.Fatalf("err: %s", err) 391 }) 392 }