Skip to content
Snippets Groups Projects
Commit 0f0578e5 authored by Jorrit Schaap's avatar Jorrit Schaap
Browse files

Merge branch 'TMSS-605' into 'master'

TMSS-605: Add check for stations in use when saving reservation, and fix...

Closes TMSS-605

See merge request !624
parents 156f7c96 43b8d9fb
No related branches found
No related tags found
2 merge requests!634WIP: COBALT commissioning delta,!624TMSS-605: Add check for stations in use when saving reservation, and fix...
...@@ -1691,7 +1691,29 @@ class Reservation(NamedCommon, TemplateSchemaMixin): ...@@ -1691,7 +1691,29 @@ class Reservation(NamedCommon, TemplateSchemaMixin):
else: else:
return None return None
def _prevent_reserving_stations_that_are_used_in_active_units(self):
# Determine active scheduling units that overlap in time with the reservation
# Note: we cannot filter for status and on sky times in SQL because these are properties
# todo: find a way to reduce the initial queryset reasonably
subs = SchedulingUnitBlueprint.objects.all()
active_subs = [x for x in subs if (x.status in [SchedulingUnitBlueprint.Status.OBSERVING.value, SchedulingUnitBlueprint.Status.SCHEDULED.value]
and x.scheduled_on_sky_stop_time >= self.start_time)]
if self.stop_time:
active_subs = [x for x in active_subs if x.scheduled_on_sky_start_time <= self.stop_time]
# Raise an exception if any of these scheduling units uses a station that shall be reserved
if "resources" in self.specifications_doc and "stations" in self.specifications_doc["resources"]:
stations_to_reserve = self.specifications_doc['resources']['stations']
for sub in active_subs:
# todo: not all the specified stations may actually be in use. Consider only those who were assigned in the end?
conflicting_stations = list(set(sub.flat_station_list).intersection(stations_to_reserve))
if len(conflicting_stations) != 0:
msg = "Station(s) %s cannot be reserved because they are currently in use by scheduling unit %s" % (conflicting_stations, sub)
logger.info(msg)
raise ValueError(msg)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self._prevent_reserving_stations_that_are_used_in_active_units()
self.annotate_validate_add_defaults_to_doc_using_template('specifications_doc', 'specifications_template') self.annotate_validate_add_defaults_to_doc_using_template('specifications_doc', 'specifications_template')
super().save(force_insert, force_update, using, update_fields) super().save(force_insert, force_update, using, update_fields)
......
...@@ -12,9 +12,11 @@ def get_active_station_reservations_in_timewindow(lower_bound, upper_bound): ...@@ -12,9 +12,11 @@ def get_active_station_reservations_in_timewindow(lower_bound, upper_bound):
queryset = models.Reservation.objects.all() queryset = models.Reservation.objects.all()
for res in queryset.filter(stop_time=None).values('specifications_doc'): for res in queryset.filter(stop_time=None).values('specifications_doc'):
lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"] if "stations" in res["specifications_doc"]["resources"]:
lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"]
if lower_bound is not None: if lower_bound is not None:
for res in queryset.filter(stop_time__gt=lower_bound).values('specifications_doc'): for res in queryset.filter(stop_time__gt=lower_bound).values('specifications_doc'):
lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"] if "stations" in res["specifications_doc"]["resources"]:
lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"]
return list(set(lst_active_station_reservations)) return list(set(lst_active_station_reservations))
...@@ -46,8 +46,11 @@ rest_data_creator = TMSSRESTTestDataCreator(BASE_URL, AUTH) ...@@ -46,8 +46,11 @@ rest_data_creator = TMSSRESTTestDataCreator(BASE_URL, AUTH)
from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp import models
from lofar.sas.tmss.tmss.tmssapp.reservations import get_active_station_reservations_in_timewindow from lofar.sas.tmss.tmss.tmssapp.reservations import get_active_station_reservations_in_timewindow
from lofar.sas.tmss.test.test_utils import set_subtask_state_following_allowed_transitions
from lofar.sas.tmss.tmss.exceptions import SchemaValidationException
from django.core.exceptions import ValidationError
class TestStationReservations(unittest.TestCase): class TestStationReservations(unittest.TestCase):
...@@ -65,7 +68,7 @@ class TestStationReservations(unittest.TestCase): ...@@ -65,7 +68,7 @@ class TestStationReservations(unittest.TestCase):
""" """
Create a station reservation with given list of stations, start_time and stop_time Create a station reservation with given list of stations, start_time and stop_time
""" """
reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") reservation_template = models.ReservationTemplate.objects.get(name="reservation")
reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema) reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema)
reservation_template_spec["resources"] = {"stations": lst_stations } reservation_template_spec["resources"] = {"stations": lst_stations }
res = models.Reservation.objects.create(name="Station Reservation %s" % additional_name, res = models.Reservation.objects.create(name="Station Reservation %s" % additional_name,
...@@ -80,7 +83,7 @@ class TestStationReservations(unittest.TestCase): ...@@ -80,7 +83,7 @@ class TestStationReservations(unittest.TestCase):
Check that creating 'default' reservation with no additional station reservation added, we still can Check that creating 'default' reservation with no additional station reservation added, we still can
call 'get_active_station_reservations_in_timewindow' and it will return an empty list call 'get_active_station_reservations_in_timewindow' and it will return an empty list
""" """
reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") reservation_template = models.ReservationTemplate.objects.get(name="reservation")
reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema) reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema)
res = models.Reservation.objects.create(name="AnyReservation", res = models.Reservation.objects.create(name="AnyReservation",
description="Reservation of something else", description="Reservation of something else",
...@@ -240,8 +243,62 @@ class TestStationReservations(unittest.TestCase): ...@@ -240,8 +243,62 @@ class TestStationReservations(unittest.TestCase):
get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=5))) get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=5)))
self.assertCountEqual(["CS001"], self.assertCountEqual(["CS001"],
get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=6))) get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=6)))
from lofar.sas.tmss.tmss.exceptions import SchemaValidationException
from django.core.exceptions import ValidationError def test_reservation_is_blocked_by_active_observation_using_same_station(self):
# create observation subtask in started state
subtask_template = models.SubtaskTemplate.objects.get(name='observation control')
spec = get_default_json_object_for_schema(subtask_template.schema)
spec['stations']['station_list'] = ['CS001', 'RS205']
task_template = models.TaskTemplate.objects.get(name='target observation')
task_spec = get_default_json_object_for_schema(task_template.schema)
task_spec['station_groups'] = [{'stations': ['CS001', 'RS205'], 'max_nr_missing': 0}]
scheduling_unit_blueprint = models.SchedulingUnitBlueprint.objects.create(**SchedulingUnitBlueprint_test_data(specifications_template=models.SchedulingUnitTemplate.objects.get(name='scheduling unit')))
task_blueprint = models.TaskBlueprint.objects.create(**TaskBlueprint_test_data(specifications_template=task_template, scheduling_unit_blueprint=scheduling_unit_blueprint, specifications_doc=task_spec))
subtask = models.Subtask.objects.create(**Subtask_test_data(subtask_template=subtask_template, specifications_doc=spec, task_blueprint=task_blueprint,
scheduled_on_sky_start_time=datetime(2020, 1, 1, 10, 0, 0), scheduled_on_sky_stop_time=datetime(2020, 1, 1, 14, 0, 0)))
set_subtask_state_following_allowed_transitions(subtask, "started")
# try to create a reservation that overlaps with the subtask and assert failure
#
# full overlap
with self.assertRaises(ValueError) as context:
self.create_station_reservation("my-reservation-long", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 2, 0, 0, 0))
self.assertIn("Station(s) ['RS205'] cannot be reserved", str(context.exception))
self.assertIn(scheduling_unit_blueprint.name, str(context.exception))
with self.assertRaises(ValueError) as context:
self.create_station_reservation("my-reservation-short", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 11, 0, 0), datetime(2020, 1, 1, 13, 0, 0))
self.assertIn("Station(s) ['RS205'] cannot be reserved", str(context.exception))
self.assertIn(scheduling_unit_blueprint.name, str(context.exception))
# partial overlap
with self.assertRaises(ValueError) as context:
self.create_station_reservation("my-reservation-early", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 1, 12, 0, 0))
self.assertIn("Station(s) ['RS205'] cannot be reserved", str(context.exception))
self.assertIn(scheduling_unit_blueprint.name, str(context.exception))
with self.assertRaises(ValueError) as context:
self.create_station_reservation("my-reservation-late", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 0, 0, 0))
self.assertIn("Station(s) ['RS205'] cannot be reserved", str(context.exception))
self.assertIn(scheduling_unit_blueprint.name, str(context.exception))
# open end
with self.assertRaises(ValueError) as context:
self.create_station_reservation("my-reservation-open-end", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 0, 0, 0))
self.assertIn("Station(s) ['RS205'] cannot be reserved", str(context.exception))
self.assertIn(scheduling_unit_blueprint.name, str(context.exception))
# assert that it works before or after the subtask
#
# before
self.create_station_reservation("my-reservation-before", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 1, 9, 0, 0))
# after
self.create_station_reservation("my-reservation-after", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 15, 0, 0), datetime(2020, 1, 2, 0, 0, 0))
# after, open end
self.create_station_reservation("my-reservation-open-end", ["CS002", "RS205", "DE609"], datetime(2020, 1, 1, 15, 0, 0))
class CreationFromReservationStrategyTemplate(unittest.TestCase): class CreationFromReservationStrategyTemplate(unittest.TestCase):
...@@ -271,7 +328,7 @@ class CreationFromReservationStrategyTemplate(unittest.TestCase): ...@@ -271,7 +328,7 @@ class CreationFromReservationStrategyTemplate(unittest.TestCase):
# Check that action call 'create_reservation' (no parameters) of strategy template creates a # Check that action call 'create_reservation' (no parameters) of strategy template creates a
# new reservation (with http result code 201) # new reservation (with http result code 201)
response = GET_and_assert_equal_expected_code(self, BASE_URL + '/reservation_strategy_template/%d/create_reservation' % strategy_template.pk, 201) response = POST_and_assert_expected_response(self, BASE_URL + '/reservation_strategy_template/%d/create_reservation' % strategy_template.pk, {}, 201, {})
self.assertNotEqual(response['id'], reservation.pk) # should be different id then previous one created self.assertNotEqual(response['id'], reservation.pk) # should be different id then previous one created
self.assertLess(response['start_time'], datetime.utcnow().isoformat()) # start_time created with now so that was some micro seconds ago self.assertLess(response['start_time'], datetime.utcnow().isoformat()) # start_time created with now so that was some micro seconds ago
self.assertEqual(response['stop_time'], None) self.assertEqual(response['stop_time'], None)
...@@ -316,3 +373,8 @@ class ReservationTest(unittest.TestCase): ...@@ -316,3 +373,8 @@ class ReservationTest(unittest.TestCase):
stop_time=None) stop_time=None)
self.assertIn('is not one of', str(context.exception)) self.assertIn('is not one of', str(context.exception))
if __name__ == "__main__":
os.environ['TZ'] = 'UTC'
unittest.main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment