|
9 | 9 | from oar.kao.scheduling import schedule_id_jobs_ct, set_slots_with_prev_scheduled_jobs |
10 | 10 | from oar.kao.slot import Slot, SlotSet |
11 | 11 | from oar.lib.globals import init_oar |
12 | | -from oar.lib.job_handling import JobPseudo |
| 12 | +from oar.lib.job_handling import JobPseudo, set_jobs_cache_keys |
13 | 13 | from oar.lib.resource import ResourceSet |
14 | 14 |
|
15 | 15 | config, engine = init_oar(no_db=True) |
@@ -43,6 +43,37 @@ def compare_slots_val_ref(slots, v): |
43 | 43 | return True |
44 | 44 |
|
45 | 45 |
|
| 46 | +def sched_placement(nb_jobs, rules, alt_users=False): |
| 47 | + |
| 48 | + res = ProcSet((1, 32)) |
| 49 | + ResourceSet.default_itvs = ProcSet((1, 32)) |
| 50 | + |
| 51 | + Quotas.enabled = rules is not None |
| 52 | + if rules is not None: |
| 53 | + Quotas.default_rules = rules |
| 54 | + ss = SlotSet(Slot(1, 0, 0, ProcSet((1, 32)), 0, 2**31)) |
| 55 | + all_ss = {"default": ss} |
| 56 | + hy = {"node": [ProcSet((i, i)) for i in range(1, 32 + 1)]} |
| 57 | + jobs, jids = {}, list(range(1, nb_jobs + 1)) |
| 58 | + for i in jids: |
| 59 | + user = ("u0" if i % 2 == 0 else "u1") if alt_users else "u%d" % (i % 4) |
| 60 | + jobs[i] = JobPseudo( |
| 61 | + id=i, |
| 62 | + types={}, |
| 63 | + deps=[], |
| 64 | + key_cache={}, |
| 65 | + queue="default", |
| 66 | + user=user, |
| 67 | + project="", |
| 68 | + mld_res_rqts=[(i, 60, [([("node", 2)], res)])], |
| 69 | + ts=False, |
| 70 | + ph=0, |
| 71 | + ) |
| 72 | + set_jobs_cache_keys(None, jobs) # session arg is unused |
| 73 | + schedule_id_jobs_ct(all_ss, jobs, hy, jids, 10) |
| 74 | + return {i: jobs[i].start_time for i in jids} |
| 75 | + |
| 76 | + |
46 | 77 | @pytest.fixture(scope="module", autouse=True) |
47 | 78 | def oar_conf(request): |
48 | 79 | config["QUOTAS"] = "yes" |
@@ -376,3 +407,34 @@ def test_quotas_two_jobs_job_type_proc(): |
376 | 407 |
|
377 | 408 | assert j1.start_time == 0 |
378 | 409 | assert j2.start_time == 50 |
| 410 | + |
| 411 | + |
| 412 | +def test_quotas_nonblocking_placement_equals_no_quotas(): |
| 413 | + """ |
| 414 | + Non-blocking quotas reject nothing, so placement must match no-quotas. |
| 415 | + (regression test relatively to no quota resource research) |
| 416 | + """ |
| 417 | + big_quota = 10**9 |
| 418 | + nb = 48 |
| 419 | + off = sched_placement(nb, None) |
| 420 | + nonblocking = { |
| 421 | + ("*", "*", "*", "/"): [big_quota, -1, -1], |
| 422 | + ("*", "/", "*", "*"): [big_quota, -1, -1], |
| 423 | + ("*", "*", "*", "*"): [-1, -1, -1], |
| 424 | + } |
| 425 | + on = sched_placement(nb, nonblocking) |
| 426 | + assert on == off |
| 427 | + |
| 428 | + |
| 429 | +def test_quotas_cache_no_gap_under_blocking(): |
| 430 | + # Blocking per-user quota + mixed users: the cache must not leave a gap in |
| 431 | + # the first time layer (catches caching the quota-validated slot instead of |
| 432 | + # the resource frontier). Cluster 32 res, 2 res/job -> 16 jobs fill layer 0. |
| 433 | + nb = 64 # 32 u0 + 32 u1 |
| 434 | + rules = {("*", "*", "*", "u0"): [2, -1, -1], ("*", "*", "*", "*"): [-1, -1, -1]} |
| 435 | + st = sched_placement(nb, rules, alt_users=True) |
| 436 | + t0 = min(st.values()) |
| 437 | + nb_first_layer = sum(1 for t in st.values() if t == t0) |
| 438 | + assert nb_first_layer == 16, ( |
| 439 | + "gap in first layer: %d/16 jobs (poisoned cache?)" % nb_first_layer |
| 440 | + ) |
0 commit comments