blob: 6cde6329713e076d13d59fc08ba85e89de361eba [file]
############################################################################
# Copyright (C) SchedMD LLC.
############################################################################
import datetime
import logging
import time
import pytest
import atf
pytestmark = pytest.mark.slow
# atf.get_version() shells out through sudo, so resolve them once.
CTLD_VERSION = atf.get_version("sbin/slurmctld")
SCONTROL_VERSION = atf.get_version("bin/scontrol")
DAY = 24 * 3600
WEEK = 7 * DAY
# Ticket 24758 was fixed in two parts, each in a different component
REASON_2605 = (
"Ticket 24758: DST-aware reoccurring reservation overlap check added in 26.05"
)
REASON_2611 = "Ticket 24758: Relative days/weeks time specs ignore DST. Fixed in 26.11."
def xfail_dst_t24758(fixed_version, component_version, start, end, reason, strict=True):
"""Return an xfail mark for Ticket 24758: the component is older than
fixed_version and a DST change falls between start and end (epoch)."""
dst_shift = time.localtime(start).tm_gmtoff != time.localtime(end).tm_gmtoff
return pytest.mark.xfail(
component_version < fixed_version and dst_shift,
strict=strict,
reason=reason,
)
def xfail_dst_t24758_before_2605(reocurring_flag=None, week_flag=None):
"""Return an xfail mark for the overlap check before 26.05 when a DST
change falls in the window of the case. Pass only one of:
- reocurring_flag: for test_overlap_replacing, the window is from now to
the next occurrence of resv1.
- week_flag: for test_overlap_weekdays, the window is from resv2 to resv3,
as resv1 lands an hour off them.
The overlap check ignored DST until 26.05 (see test_overlap_DST)."""
assert (reocurring_flag is None) != (
week_flag is None
), "Exactly one of reocurring_flag or week_flag must be set"
if reocurring_flag:
start = time.time()
end = start + {"DAILY": DAY, "WEEKLY": WEEK}[reocurring_flag]
else:
_, resv2_start, resv3_start = overlap_weekdays_starts(week_flag)
start, end = (
datetime.datetime.combine(date, datetime.time(17)).timestamp()
for date in (resv2_start, resv3_start)
)
return xfail_dst_t24758((26, 5), CTLD_VERSION, start, end, REASON_2605)
# The cases use NOW+1week..NOW+5weeks. Any DST change within 5 weeks is only an
# approximation of whether their start times cross it, so it can't be strict.
_now = time.time()
xfail_dst_t24758_before_2611 = xfail_dst_t24758(
(26, 11), SCONTROL_VERSION, _now, _now + 5 * WEEK, REASON_2611, strict=False
)
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_nodes(3)
atf.require_slurm_running()
@pytest.fixture(scope="function", autouse=True)
def get_and_down_nodes():
logging.info("Getting the necessary 3 nodes:")
nodes = list(atf.get_nodes().keys())
logging.info("Setting ALL nodes Down:")
atf.run_command(
"scontrol update nodename=ALL state=down reason=test_resv_overlap",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.run_command_output(
"sinfo --states=idle,mixed --Format=NodeList -h"
),
lambda not_down: not_down == "",
fatal=True,
)
yield nodes
atf.run_command(
"scontrol delete reservationname=resv1",
user=atf.properties["slurm-user"],
quiet=True,
)
atf.run_command(
"scontrol delete reservationname=resv2",
user=atf.properties["slurm-user"],
quiet=True,
)
atf.run_command(
"scontrol delete reservationname=resv3",
user=atf.properties["slurm-user"],
quiet=True,
)
atf.run_command(
"scontrol update nodename=ALL state=resume",
user=atf.properties["slurm-user"],
quiet=True,
)
@pytest.mark.xfail(
CTLD_VERSION < (26, 5),
reason=REASON_2605,
)
def test_overlap_DST(get_and_down_nodes):
"""Verify that DST changes are respected when checking for overlapping"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info(f"Creating resv1: {atf.get_node_parameter(nodes[0], 'state')}")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime={datetime.date.today().year + 1}-04-22T15:00:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags=DAILY",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime={datetime.date.today().year + 1}-11-15T15:00:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created after resv1 because {nodes[0]} should be used only by resv1"
@pytest.mark.parametrize("reocurring_flag", ["HOURLY", "DAILY", "WEEKLY"])
@xfail_dst_t24758_before_2611
def test_overlap_weeks(get_and_down_nodes, reocurring_flag):
"""Verify that reservations don't overlap nodes if they are have a start time difference greater than a week"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info(f"Creating resv1: {atf.get_node_parameter(nodes[0], 'state')}")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime=NOW+3weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 can be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW+1week nodecnt=1 duration=00:15:00 user={atf.properties['test-user']}",
user=atf.properties["slurm-user"],
)
assert (
rc["exit_code"] == 0
), f"resv2 should be created before resv1 because {nodes[0]} should is not yet reserved by resv1"
logging.info("Ensuring that resv3 cannot be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime=NOW+5weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv3 should NOT be created after resv1 because {nodes[0]} should be used only by resv1"
logging.info(
"Double-checking that that there is only 2 reservations with the expected node:"
)
assert (
len(atf.get_reservations()) == 2
), "only resv1 and resv2 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv2", "Nodes") == nodes[0]
), f"resv2 should use node {nodes[0]}"
@pytest.mark.parametrize("reocurring_flag", ["HOURLY", "DAILY", "WEEKLY"])
@xfail_dst_t24758_before_2611
def test_overlap_weeks_reverse(get_and_down_nodes, reocurring_flag):
"""Verify that recurring reservations don't overlap nodes if they are have a start time difference greater than a week"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime=NOW+3weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW+1week nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created before resv1 because {nodes[0]} should be reserved by resv1"
logging.info("Ensuring that resv3 can be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime=NOW+5weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
)
assert (
rc["exit_code"] == 0
), f"resv3 should be created after resv1 because {nodes[0]} should is not anymore reserved by resv1"
logging.info(
"Double-checking that that there is only 2 reservations with the expected node:"
)
assert (
len(atf.get_reservations()) == 2
), "only resv1 and resv2 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv3", "Nodes") == nodes[0]
), f"resv3 should use node {nodes[0]}"
@pytest.mark.parametrize("reocurring_flag", ["HOURLY", "DAILY", "WEEKLY"])
@xfail_dst_t24758_before_2611
def test_overlap_reocurring(get_and_down_nodes, reocurring_flag):
"""Verify that recurring reservations don't overlap when the start at 1 recurring period"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
if reocurring_flag == "HOURLY":
resv1_start = "2hours"
resv2_start = "1hour"
resv3_start = "3hours"
elif reocurring_flag == "DAILY":
resv1_start = "2days"
resv2_start = "1day"
resv3_start = "3days"
elif reocurring_flag == "WEEKLY":
resv1_start = "2weeks"
resv2_start = "1week"
resv3_start = "3weeks"
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime=NOW+{resv1_start} nodecnt=1 duration=00:10:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW+{resv2_start} nodecnt=1 duration=00:10:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created before resv1 because {nodes[0]} should be used only by resv1"
logging.info("Ensuring that resv3 cannot be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime=NOW+{resv3_start} nodecnt=1 duration=00:10:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv3 should NOT be created after resv1 because {nodes[0]} should be used only by resv1"
logging.info(
"Double-checking that that there is only 1 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 1, "only resv1 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
@pytest.mark.parametrize("reocurring_flag", ["HOURLY", "DAILY", "WEEKLY"])
@xfail_dst_t24758_before_2611
def test_overlap_reocurring_week(get_and_down_nodes, reocurring_flag):
"""Verify that recurring reservations don't overlap if they are have a start time difference greater than a week"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime=NOW+3weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW+1weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created before resv1 because {nodes[0]} should be used only by resv1"
logging.info("Ensuring that resv3 cannot be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime=NOW+5weeks nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv3 should NOT be created after resv1 because {nodes[0]} should be used only by resv1"
logging.info(
"Double-checking that that there is only 1 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 1, "only resv1 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
@pytest.mark.parametrize(
"reocurring_flag",
[
"HOURLY",
pytest.param(
"DAILY", marks=xfail_dst_t24758_before_2605(reocurring_flag="DAILY")
),
pytest.param(
"WEEKLY", marks=xfail_dst_t24758_before_2605(reocurring_flag="WEEKLY")
),
],
)
def test_overlap_replacing(get_and_down_nodes, reocurring_flag):
"""Verify that reservations don't overlap when replacing nodes"""
nodes = get_and_down_nodes
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime=NOW nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag},REPLACE_DOWN,PURGE_COMP=1",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created yet:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag},REPLACE_DOWN,PURGE_COMP=3",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created because {nodes[0]} should be used only by resv1"
logging.info(
"Double-checking that that there is only 1 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 1, "only resv1 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
logging.info(f"Resuming node {nodes[1]}:")
atf.run_command(
f"scontrol update nodename={nodes[1]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[1], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv2:")
rc2 = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime=NOW nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag},REPLACE_DOWN,PURGE_COMP=3",
user=atf.properties["slurm-user"],
)
assert rc2["exit_code"] == 0, "resv2 should be created"
logging.info(
"Double-checking that there are only 2 reservation and they are using different nodes:"
)
assert len(atf.get_reservations()) == 2, "resv1 and resv2 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv2", "Nodes") == nodes[1]
), f"resv2 should use node {nodes[1]}"
logging.info(
"Waiting until resv1 and resv2 are ACTIVE (should be started in 1 minute)..."
)
atf.repeat_until(
lambda: atf.get_reservation_parameter("resv1", "State"),
lambda state: state == "ACTIVE",
fatal=True,
timeout=90,
poll_interval=5,
)
atf.repeat_until(
lambda: atf.get_reservation_parameter("resv2", "State"),
lambda state: state == "ACTIVE",
fatal=True,
timeout=90,
poll_interval=5,
)
logging.info(
f"Setting node {nodes[1]} down, so resv2 won't have any valid node and will try to look for a new one:"
)
atf.run_command(
f"scontrol update nodename={nodes[1]} state=down reason=test_resv_overlap",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[1], "state"),
lambda state: "DOWN" in state,
fatal=True,
)
logging.info("Forcing resv1 to INACTIVE state by skipping to next occurrence")
atf.run_command(
"scontrol update ReservationName=resv1 skip",
user=atf.properties["slurm-user"],
fatal=True,
)
logging.info("Waiting until resv1 is INACTIVE...")
atf.repeat_until(
lambda: atf.get_reservation_parameter("resv1", "State"),
lambda state: state == "INACTIVE",
fatal=True,
timeout=165,
poll_interval=5,
)
logging.info(
"Ensuring that there resv2 won't steal any node (it will try it every PERIODIC_TIMEOUT=30s):"
)
assert not atf.repeat_until(
lambda: atf.get_reservation_parameter("resv2", "Nodes"),
lambda rnodes: rnodes != nodes[1],
timeout=31,
)
logging.info(f"Resuming node {nodes[2]}:")
atf.run_command(
f"scontrol update nodename={nodes[2]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[2], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info(
"Checking that resv2 picks the new available node, and that not overlapping reservations happen:"
)
assert atf.repeat_until(
lambda: atf.get_reservation_parameter("resv2", "Nodes"),
lambda rnodes: rnodes != nodes[1],
timeout=60,
)
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should still use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv2", "Nodes") == nodes[2]
), f"resv2 should use node {nodes[2]} now"
def overlap_weekdays_starts(week_flag):
"""Return the resv1, resv2 and resv3 start dates of test_overlap_weekdays.
A WEEKDAY or WEEKEND reservation must start on a day its flag covers, so
NOW cannot be used: it makes the test depend on the day it runs on.
HOURLY and DAILY accept any day, and no parameter of the test mixes
WEEKDAY with WEEKEND, so week_flag alone picks the anchor day.
"""
today = datetime.date.today()
anchor = 5 if week_flag == "WEEKEND" else 0
base = today + datetime.timedelta(days=(anchor - today.weekday()) % 7 or 7)
return [base + datetime.timedelta(weeks=weeks) for weeks in (3, 1, 5)]
# An HOURLY resv1 still overlaps an hour off, so it needs no xfail_dst_t24758_before_2605().
@pytest.mark.parametrize(
"reocurring_flag,week_flag",
[
("HOURLY", "WEEKDAY"),
("HOURLY", "WEEKEND"),
pytest.param(
"DAILY", "WEEKDAY", marks=xfail_dst_t24758_before_2605(week_flag="WEEKDAY")
),
pytest.param(
"DAILY", "WEEKEND", marks=xfail_dst_t24758_before_2605(week_flag="WEEKEND")
),
pytest.param(
"WEEKDAY",
"WEEKDAY",
marks=xfail_dst_t24758_before_2605(week_flag="WEEKDAY"),
),
pytest.param(
"WEEKEND",
"WEEKEND",
marks=xfail_dst_t24758_before_2605(week_flag="WEEKEND"),
),
],
)
def test_overlap_weekdays(get_and_down_nodes, reocurring_flag, week_flag):
"""Verify that recurring reservations with weekdays and weekends handle overlapping properly with other reocurrings"""
nodes = get_and_down_nodes
resv1_start, resv2_start, resv3_start = overlap_weekdays_starts(week_flag)
logging.info(f"resv1_start = {resv1_start}")
logging.info(f"resv2_start = {resv2_start}")
logging.info(f"resv3_start = {resv3_start}")
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime={resv1_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={reocurring_flag}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 cannot be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime={resv2_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={week_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv2 should NOT be created before resv1 because {nodes[0]} should be used only by resv1"
logging.info("Ensuring that resv3 cannot be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime={resv3_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={week_flag}",
user=atf.properties["slurm-user"],
xfail=True,
)
assert (
rc["exit_code"] != 0
), f"resv3 should NOT be created after resv1 because {nodes[0]} should be used only by resv1"
logging.info(
"Double-checking that that there is only 1 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 1, "only resv1 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
@pytest.mark.parametrize(
"resv1_flag,resv2_flag", [("WEEKEND", "WEEKDAY"), ("WEEKDAY", "WEEKEND")]
)
def test_no_overlap_weekday_weekend(get_and_down_nodes, resv1_flag, resv2_flag):
"""Verify that recurring reservations with weekdays and weekends handle overlapping properly between them"""
nodes = get_and_down_nodes
# A WEEKDAY or WEEKEND reservation must start on a day its flag covers,
# so specify the right start times/dates for this test.
today = datetime.date.today()
if resv1_flag == "WEEKEND":
# resv1: next saturday
# resv2: next thursday
# resv3: next next thursday
resv1_start = today + datetime.timedelta(weeks=1, days=5 - today.weekday())
resv2_start = today + datetime.timedelta(weeks=1, days=3 - today.weekday())
resv3_start = today + datetime.timedelta(weeks=2, days=3 - today.weekday())
else:
# resv1: next next thursday
# resv2: next saturday
# resv3: next next saturday
resv1_start = today + datetime.timedelta(weeks=2, days=3 - today.weekday())
resv2_start = today + datetime.timedelta(weeks=1, days=5 - today.weekday())
resv3_start = today + datetime.timedelta(weeks=2, days=5 - today.weekday())
logging.info(f"resv1_start = {resv1_start}")
logging.info(f"resv2_start = {resv2_start}")
logging.info(f"resv3_start = {resv3_start}")
logging.info(f"Resuming only node {nodes[0]}:")
atf.run_command(
f"scontrol update nodename={nodes[0]} state=resume",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.repeat_until(
lambda: atf.get_node_parameter(nodes[0], "state"),
lambda state: state == ["IDLE"],
fatal=True,
)
logging.info("Creating resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv1 StartTime={resv1_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={resv1_flag}",
user=atf.properties["slurm-user"],
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv2 can be created before resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv2 StartTime={resv2_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={resv2_flag}",
user=atf.properties["slurm-user"],
)
assert (
rc["exit_code"] == 0
), "resv2 should be created before resv1 because WEEKDAYS and WEEKENDS won't overlap"
logging.info(
"Double-checking that that there are 2 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 2, "resv1 and resv2 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv2", "Nodes") == nodes[0]
), f"resv2 should use node {nodes[0]}"
logging.info("Deleting resv2:")
rc = atf.run_command(
"scontrol delete ReservationName=resv2", user=atf.properties["slurm-user"]
)
assert rc["exit_code"] == 0, "resv1 should be created"
logging.info("Ensuring that resv3 can be created after resv1:")
rc = atf.run_command(
f"scontrol create reservation ReservationName=resv3 StartTime={resv3_start}T17:00 nodecnt=1 duration=00:15:00 user={atf.properties['test-user']} flags={resv2_flag}",
user=atf.properties["slurm-user"],
)
assert (
rc["exit_code"] == 0
), "resv3 should be created after resv1 because WEEKDAYS and WEEKENDS won't overlap"
logging.info(
"Double-checking that that there are 2 reservation with the expected node:"
)
assert len(atf.get_reservations()) == 2, "resv1 and resv3 should be in the system"
assert (
atf.get_reservation_parameter("resv1", "Nodes") == nodes[0]
), f"resv1 should use node {nodes[0]}"
assert (
atf.get_reservation_parameter("resv3", "Nodes") == nodes[0]
), f"resv3 should use node {nodes[0]}"