diff --git a/SAS/ResourceAssignment/Common/lib/specification.py b/SAS/ResourceAssignment/Common/lib/specification.py index 5f953232fe009402f5cf5405fa2647c66b8ca07a..b164ab72e6e8a5a90b5493548b6fd0bcdf674a9c 100644 --- a/SAS/ResourceAssignment/Common/lib/specification.py +++ b/SAS/ResourceAssignment/Common/lib/specification.py @@ -724,7 +724,7 @@ class Specification: :returns 2-tuple (start_time, end_time) """ - # TODO: don't fix this crap here. Bad start/stop time has to go to error, like any other bad spec part. + # TODO? don't fix this crap here. Bad start/stop time has to go to error, like any other bad spec part. if not self.isUnmovable(): #maintenance and reservation shift = timedelta(seconds=0) starttime = self.starttime @@ -746,7 +746,7 @@ class Specification: self.min_starttime = self.starttime if self.max_endtime: self.endtime = self.max_endtime - #TODO Not happy with this min/maxDuration + #TODO Not happy with this min/maxDuration, what to do if duration is not None but they are set? if self.duration is None: if self.max_duration: self.duration = self.max_duration @@ -775,9 +775,9 @@ class Specification: self._store_changed_start_and_end_times_to_otdb(self.starttime, self.endtime, self.duration, self.otdb_id) #TODO self._store_changed_start_and_end_times_to_mom(self.starttime, self.endtime, self.duration, self.otdb_id) + #TODO maybe use min_duration, max_duration if specified? DwellScheduler can't use them right now? def calculate_dwell_values(self, start_time, end_time, min_starttime, max_endtime): - """ - Use any specified min_starttime/max_endtime to calculate the min_starttime/max_starttime. All timestamps are in + """ Use any specified min_starttime/max_endtime to calculate the min_starttime/max_starttime. All timestamps are in datetime format :param start_time: Task fixed start time @@ -867,8 +867,8 @@ class Specification: self.logger.warn('Task otdb_id=%s with status \'%s\' is not assignable. Allowed statuses are %s' % (self.otdb_id, self.status, assignable_task_states_str)) - message = "doAssignment: Unsupported status '%s' of task with OTDB ID: %s" % (self.status, self.otdb_id) - raise Exception(message) + message = "Unsupported status '%s' of task with OTDB ID: %s" % (self.status, self.otdb_id) + raise Exception(message) #TODO more specific exception type? def set_status(self, new_status): """ diff --git a/SAS/ResourceAssignment/Common/test/test_specification.py b/SAS/ResourceAssignment/Common/test/test_specification.py index 86c37c6364215448a63be9ffc9e3061ce9719bd6..474d8de471724947c24cdb903e7e73590da032f5 100755 --- a/SAS/ResourceAssignment/Common/test/test_specification.py +++ b/SAS/ResourceAssignment/Common/test/test_specification.py @@ -18,7 +18,7 @@ # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. # $Id: $ -import unittest, datetime, mock, os +import unittest, mock, os from lofar.parameterset import parameterset from datetime import datetime, timedelta @@ -49,8 +49,8 @@ class SpecificationTest(unittest.TestCase): #self.mom_id = 351557 #self.otdb_id = 1290494 #self.trigger_id = 2323 - #future_start_time = (datetime.datetime.utcnow() + datetime.timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S') - #future_stop_time = (datetime.datetime.utcnow() + datetime.timedelta(hours=2)).strftime('%Y-%m-%d %H:%M:%S') + #future_start_time = (datetime.utcnow() + datetime.timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S') + #future_stop_time = (datetime.utcnow() + datetime.timedelta(hours=2)).strftime('%Y-%m-%d %H:%M:%S') otdbrpc_patcher = mock.patch('lofar.sas.otdb.otdbrpc') self.addCleanup(otdbrpc_patcher.stop) @@ -84,8 +84,8 @@ class SpecificationTest(unittest.TestCase): def test_read_from_mom_misc(self): """ Verify that get_specification properly generates an RA parset subset for a preprocessing pipeline parset """ # Arrange - min_start_time = datetime.datetime(2017, 10, 2, 22, 43, 12) - max_end_time = datetime.datetime(2017, 10, 3, 22, 43, 12) + min_start_time = datetime(2017, 10, 2, 22, 43, 12) + max_end_time = datetime(2017, 10, 3, 22, 43, 12) min_duration = timedelta(seconds=200) max_duration = timedelta(seconds=3600) self.momrpc_mock.get_trigger_time_restrictions.return_value = {"minStartTime": min_start_time, @@ -379,8 +379,8 @@ class SpecificationTest(unittest.TestCase): start_time, end_time = Specification._get_start_and_end_times_from_parset(input_parset, INPUT_PREFIX) # Assert - self.assertEqual(start_time, datetime.datetime(2016, 12, 8, 23, 20, 25)) - self.assertEqual(end_time, datetime.datetime(2016, 12, 9, 7, 20, 25)) + self.assertEqual(start_time, datetime(2016, 12, 8, 23, 20, 25)) + self.assertEqual(end_time, datetime(2016, 12, 9, 7, 20, 25)) def test_get_no_start_and_end_times_from_parset(self): """ Verify that get_specification properly generates an RA parset subset for a reservation task """ @@ -432,9 +432,9 @@ class SpecificationTest(unittest.TestCase): input_parset = self.specification._get_parset_from_OTDB() # Assert - #TODO not sure what to assert here + #TODO not sure what to assert here, no easy comparison for parsets? #self.assertEqual(input_parset, None) - self.otdbrpc_mock.taskGetSpecification.assert() + self.otdbrpc_mock.taskGetSpecification.assert_any_call(otdb_id=562059) #TODO more tests for read_from_otdb? def test_read_from_otdb(self): @@ -525,11 +525,280 @@ class SpecificationTest(unittest.TestCase): # ------------------------------------------------------------------------------------------------------------------ # Tests of functions to change start/end times and durations + def test_update_start_end_times_value_error(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + self.specification.type = "maintenance" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertRaises(ValueError) + self.otdbrpc_mock.taskSetSpecification.assert_not_called() + + def test_update_start_end_times_maintenance(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() - timedelta(minutes=1) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.endtime = end_time + self.specification.type = "maintenance" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertGreater(self.specification.starttime, start_time) + self.assertGreater(self.specification.endtime, end_time) + self.assertEqual(self.specification.endtime - self.specification.starttime, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_no_times(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertGreater(self.specification.starttime, datetime.utcnow()) + self.assertGreater(self.specification.endtime, datetime.utcnow() + timedelta(hours=1)) + self.assertEqual(self.specification.endtime - self.specification.starttime, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_past(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() - timedelta(minutes=1) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.endtime = end_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertGreater(self.specification.starttime, datetime.utcnow()) + self.assertGreater(self.specification.endtime, datetime.utcnow() + timedelta(hours=1)) + self.assertEqual(self.specification.endtime - self.specification.starttime, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_1min(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=1) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.endtime = end_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, start_time) + self.assertEqual(self.specification.endtime, end_time) + self.assertEqual(self.specification.duration, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_min_start_time(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + min_start_time = datetime.utcnow() + timedelta(minutes=1) + start_time = datetime.utcnow() + timedelta(minutes=10) + end_time = start_time + timedelta(hours=1) + self.specification.min_starttime = min_start_time + self.specification.starttime = start_time + self.specification.endtime = end_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + # Assert + self.assertEqual(self.specification.starttime, min_start_time) + self.assertEqual(self.specification.endtime, end_time) + self.assertGreater(self.specification.duration, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_max_end_time(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=1) + end_time = start_time + timedelta(hours=1) + max_end_time = datetime.utcnow() + timedelta(hours=2) + self.specification.max_endtime = max_end_time + self.specification.starttime = start_time + self.specification.endtime = end_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, start_time) + self.assertEqual(self.specification.endtime, max_end_time) + self.assertGreater(self.specification.duration, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_duration(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=10) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.duration = end_time - start_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, start_time) + self.assertEqual(self.specification.endtime, end_time) + self.assertEqual(self.specification.duration, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_min_duration(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=10) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.min_duration = end_time - start_time + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, start_time) + self.assertEqual(self.specification.endtime, end_time) + self.assertEqual(self.specification.duration, timedelta(hours=1)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_predecessor_shift(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=10) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.endtime = end_time + spec = Specification(self.logger_mock, self.otdbrpc_mock, self.momrpc_mock, self.radbrpc_mock) + spec.starttime = start_time + timedelta(hours=2) + spec.endtime = end_time + timedelta(hours=2) + self.specification.predecessors.append(spec) + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, spec.endtime + timedelta(minutes=3)) + self.assertEqual(self.specification.endtime, spec.endtime + timedelta(hours=1) + timedelta(minutes=3)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_update_start_end_times_predecessor_no_shift(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime.utcnow() + timedelta(minutes=10) + end_time = start_time + timedelta(hours=1) + self.specification.starttime = start_time + self.specification.endtime = end_time + spec = Specification(self.logger_mock, self.otdbrpc_mock, self.momrpc_mock, self.radbrpc_mock) + spec.starttime = start_time - timedelta(hours=4) + spec.endtime = end_time - timedelta(hours=4) + self.specification.predecessors.append(spec) + self.specification.type = "observation" + + # Act + self.specification.update_start_end_times() + + # Assert + self.assertEqual(self.specification.starttime, spec.endtime + timedelta(minutes=3)) + self.assertEqual(self.specification.endtime, spec.endtime + timedelta(hours=1) + timedelta(minutes=3)) + self.otdbrpc_mock.taskSetSpecification.assert_called() + + def test_calculate_dwell_values_with_misc(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + min_start_time = datetime(2017, 10, 2, 22, 43, 12) + max_end_time = datetime(2017, 10, 3, 22, 43, 12) + start_time = datetime(2017, 10, 2, 22, 00, 00) + end_time = datetime(2017, 10, 2, 23, 00, 00) + self.specification.type = "observation" + + # Act + test_min_starttime, test_max_starttime, test_duration = \ + self.specification.calculate_dwell_values(start_time, end_time, min_start_time, max_end_time) + + # Assert + self.assertEqual(test_min_starttime, start_time) + self.assertEqual(test_max_starttime, max_end_time - timedelta(hours=1)) + self.assertEqual(test_duration, timedelta(hours=1)) + + def test_calculate_dwell_values_without_misc(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + start_time = datetime(2017, 10, 3, 22, 00, 00) + end_time = datetime(2017, 10, 3, 23, 00, 00) + self.specification.type = "observation" + + # Act + test_min_starttime, test_max_starttime, test_duration = \ + self.specification.calculate_dwell_values(start_time, end_time, None, None) + + # Assert + self.assertEqual(start_time, start_time) + self.assertEqual(test_max_starttime, start_time) + self.assertEqual(test_duration, timedelta(hours=1)) # ------------------------------------------------------------------------------------------------------------------ # Tests of functions to communicate with RADB + def test_read_from_radb(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + task = {"id": 1, "mom_id": 2, "otdb_id": 3, "status": "scheduled", + "type": "observation", "duration":100, "cluster": "CEP4"} + self.radbrpc_mock.getTask.return_value = task + + # Act + self.specification.read_from_radb(1) + + # Assert + self.assertEqual(self.specification.radb_id, task["id"]) + self.assertEqual(self.specification.mom_id, task["mom_id"]) + self.assertEqual(self.specification.otdb_id, task["otdb_id"]) + self.assertEqual(self.specification.status, task["status"]) + self.assertEqual(self.specification.type, task["type"]) + self.assertEqual(self.specification.duration, timedelta(seconds=task["duration"])) + self.assertEqual(self.specification.cluster, task["cluster"]) + + def test_insert_into_radb(self): + """ Verify that get_specification properly generates an RA parset subset for a reservation task """ + # Arrange + task = {"id": 1, "mom_id": 2, "otdb_id": 3, "status": "scheduled", + "type": "observation", "duration":100, "cluster": "CEP4"} + self.radbrpc_mock.getTask.return_value = task + + # Act + self.specification.insert_into_radb() + + # Assert + self.assertEqual(self.specification.radb_id, task["id"]) + self.assertEqual(self.specification.mom_id, task["mom_id"]) + self.assertEqual(self.specification.otdb_id, task["otdb_id"]) + self.assertEqual(self.specification.status, task["status"]) + self.assertEqual(self.specification.type, task["type"]) + self.assertEqual(self.specification.duration, timedelta(seconds=task["duration"])) + self.assertEqual(self.specification.cluster, task["cluster"]) + + # self.momrpc_mock.getPredecessorIds.return_value = {str(self.task_mom_id): self.predecessor_task_mom_ids}