diff --git a/Docker/lofar-ci/Dockerfile_ci_sas b/Docker/lofar-ci/Dockerfile_ci_sas index 527639e256c50c98b1ef0550b41a7cbf69b3e1c3..1aa8f6689b56f7529d3a0a17e0022128a9ab2bbc 100644 --- a/Docker/lofar-ci/Dockerfile_ci_sas +++ b/Docker/lofar-ci/Dockerfile_ci_sas @@ -16,7 +16,7 @@ RUN yum erase -y postgresql postgresql-server postgresql-devel && \ cd /bin && ln -s /usr/pgsql-9.6/bin/initdb && ln -s /usr/pgsql-9.6/bin/postgres ENV PATH /usr/pgsql-9.6/bin:$PATH -RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging +RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging django-debug-toolbar #Viewflow package RUN pip3 install django-material django-viewflow diff --git a/SAS/TMSS/docker-compose-ua.yml b/SAS/TMSS/docker-compose-ua.yml index 74752f8596f9daa35763a85b7f5e355288b38cbd..73b699d14c94f2d0606c3242d4b964f593d05b68 100644 --- a/SAS/TMSS/docker-compose-ua.yml +++ b/SAS/TMSS/docker-compose-ua.yml @@ -17,9 +17,10 @@ services: - "8088:8088" web: image: nexus.cep4.control.lofar:18080/tmss_django:latest + hostname: tmss-ua restart: on-failure env_file: - ./.env - command: bash -c 'source /opt/lofar/lofarinit.sh && python3 lib64/python3.6/site-packages/lofar/sas/tmss/manage.py runserver 0.0.0.0:8008' + command: bash -c 'source /opt/lofar/lofarinit.sh && ALLOWED_HOSTS=* tmss_test_environment -H 0.0.0.0 -P tmss-ua -p 8008 --data' ports: - "8008:8008" diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py index 26e8d2c27b480115014a9165d1feb824649a5885..a4dc3da81e336cc45ff82c57369e19ec93a81c28 100644 --- a/SAS/TMSS/src/tmss/settings.py +++ b/SAS/TMSS/src/tmss/settings.py @@ -96,8 +96,16 @@ INSTALLED_APPS = [ 'viewflow', 'viewflow.frontend', 'lofar.sas.tmss.tmss.workflowapp', + 'debug_toolbar', ] +def show_debug_toolbar(*args, **kwargs): + return os.environ.get('SHOW_DJANGO_DEBUG_TOOLBAR', False) + +DEBUG_TOOLBAR_CONFIG = { + 'SHOW_TOOLBAR_CALLBACK': show_debug_toolbar +} + MIDDLEWARE = [ 'django.middleware.gzip.GZipMiddleware', 'django.middleware.security.SecurityMiddleware', @@ -109,6 +117,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware' ] + ROOT_URLCONF = 'lofar.sas.tmss.tmss.urls' TEMPLATES = [ diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py index 9d8a1d48c82d0a81a12397985dab0c03097f0e8e..f250406f6e35263a43b47a21dbb436fe385815b0 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py @@ -150,7 +150,7 @@ class Subtask(BasicCommon): super().__init__(*args, **kwargs) # keep original state for logging - self.__original_state = self.state + self.__original_state_id = self.state_id @property def duration(self) -> timedelta: @@ -218,7 +218,7 @@ class Subtask(BasicCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') - if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state.value == SubtaskState.Choices.SCHEDULING.value: + if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state_id == SubtaskState.Choices.SCHEDULING.value: if self.start_time is None: if self.predecessors.all().count() == 0: raise SubtaskSchedulingException("Cannot schedule subtask id=%s when start time is 'None'." % (self.pk, )) @@ -231,12 +231,12 @@ class Subtask(BasicCommon): super().save(force_insert, force_update, using, update_fields) # log if either state update or new entry: - if self.state != self.__original_state or creating == True: + if self.state_id != self.__original_state_id or creating == True: if self.created_or_updated_by_user is None: identifier = None else: identifier = self.created_or_updated_by_user.email - log_entry = SubtaskStateLog(subtask=self, old_state=self.__original_state, new_state=self.state, + log_entry = SubtaskStateLog(subtask=self, old_state=SubtaskState.objects.get(value=self.__original_state_id), new_state=self.state, user=self.created_or_updated_by_user, user_identifier=identifier) log_entry.save() @@ -246,7 +246,7 @@ class Subtask(BasicCommon): logger.error("Could not send state change to messagebus: %s", e) # update the previous state value - self.__original_state = self.state + self.__original_state_id = self.state_id class SubtaskStateLog(BasicCommon): """ diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index f2dd9b2895028d88215609b534914d0ab644f076..cf8e50a3a664f8ac70aadb44a2d4748594727443 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -1,8 +1,6 @@ """ This file contains the database models """ -import logging -logger = logging.getLogger(__name__) import logging logger = logging.getLogger(__name__) @@ -21,6 +19,7 @@ import json import jsonschema from django.urls import reverse as revese_url from collections import Counter +from django.utils.functional import cached_property from lofar.messaging.messagebus import ToBus, DEFAULT_BROKER, DEFAULT_BUSNAME from lofar.messaging.messages import EventMessage @@ -48,7 +47,7 @@ class BasicCommon(Model): class NamedCommon(BasicCommon): name = CharField(max_length=128, help_text='Human-readable name of this object.', null=False) # todo: check if we want to have this primary_key=True - description = CharField(max_length=255, help_text='A longer description of this object.') + description = CharField(max_length=255, help_text='A longer description of this object.', blank=True) def __str__(self): return self.name @@ -418,7 +417,7 @@ class Cycle(NamedCommonPK): start = DateTimeField(help_text='Moment at which the cycle starts, that is, when its projects can run.') stop = DateTimeField(help_text='Moment at which the cycle officially ends.') - @property + @cached_property def duration(self) -> datetime.timedelta: '''the duration of the cycle (stop-start date)''' return self.stop - self.start @@ -560,19 +559,19 @@ class SchedulingUnitDraft(NamedCommon): validate_json_against_schema(self.observation_strategy_template.template, self.requirements_template.schema) if self.scheduling_constraints_doc is not None and self.scheduling_constraints_template_id and self.scheduling_constraints_template.schema is not None: - validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema) + validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema) annotate_validate_add_defaults_to_doc_using_template(self, 'requirements_doc', 'requirements_template') annotate_validate_add_defaults_to_doc_using_template(self, 'scheduling_constraints_doc', 'scheduling_constraints_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of all tasks of this scheduling unit ''' return self.relative_stop_time - self.relative_start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all tasks of this scheduling unit ''' @@ -582,7 +581,7 @@ class SchedulingUnitDraft(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all tasks of this scheduling unit ''' @@ -616,7 +615,7 @@ class SchedulingUnitBlueprint(NamedCommon): msg.subject, tobus.exchange, address[0], address[1], single_line_with_single_spaces(msg.content)) tobus.send(msg) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of all tasks of this scheduling unit ''' @@ -625,7 +624,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return self.stop_time - self.start_time # <- todo: do we ever want this? - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all tasks of this scheduling unit ''' @@ -635,7 +634,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all tasks of this scheduling unit ''' @@ -645,7 +644,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def start_time(self) -> datetime or None: '''return the earliest start time of all tasks of this scheduling unit ''' @@ -655,7 +654,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return None - @property + @cached_property def stop_time(self) -> datetime or None: '''return the latest stop time of all tasks of this scheduling unit ''' @@ -777,7 +776,7 @@ class TaskDraft(NamedCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def successors(self) -> QuerySet: '''return the connect successor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskdraft.successors.all() @@ -787,7 +786,7 @@ class TaskDraft(NamedCommon): "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.consumer_id = successor_task.id\n" "WHERE task_rel.producer_id = %s", params=[self.id])) - @property + @cached_property def predecessors(self) -> QuerySet: '''return the connect predecessor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskdraft.predecessors.all() @@ -797,26 +796,26 @@ class TaskDraft(NamedCommon): "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.producer_id = successor_task.id\n" "WHERE task_rel.consumer_id = %s", params=[self.id])) - @property + @cached_property def duration(self) -> datetime.timedelta: '''returns the overall duration of this task ''' return self.relative_stop_time - self.relative_start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all subtasks of this task ''' scheduling_relations = list(self.first_scheduling_relation.all()) + list(self.second_scheduling_relation.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after": + if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_draft.relative_stop_time: return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before": + if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations @@ -824,7 +823,7 @@ class TaskDraft(NamedCommon): return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset) return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all subtasks of this task ''' @@ -844,7 +843,7 @@ class TaskDraft(NamedCommon): # Only on the blueprints, we also aggregate start_stop times as they are in the system # I'll leave these code bits here for now, until we made up our minds about this, but this can probably be removed # - # @property + # @cached_property # def duration(self) -> datetime.timedelta: # '''returns the overall duration in seconds of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -855,7 +854,7 @@ class TaskDraft(NamedCommon): # else: # return self.stop_time - self.start_time # - # @property + # @cached_property # def start_time(self) -> datetime or None: # '''return the earliest start time of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -867,7 +866,7 @@ class TaskDraft(NamedCommon): # # todo: calculate? # return None # - # @property + # @cached_property # def stop_time(self) -> datetime or None: # '''return the latest stop time of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -891,7 +890,7 @@ class TaskBlueprint(NamedCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def successors(self) -> QuerySet: '''return the connect successor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskblueprint.successors.all() @@ -901,7 +900,7 @@ class TaskBlueprint(NamedCommon): "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.consumer_id = successor_task.id\n" "WHERE task_rel.producer_id = %s", params=[self.id])) - @property + @cached_property def predecessors(self) -> QuerySet: '''return the connect predecessor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskblueprint.predecessors.all() @@ -911,7 +910,7 @@ class TaskBlueprint(NamedCommon): "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.producer_id = predecessor_task.id\n" "WHERE task_rel.consumer_id = %s", params=[self.id])) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of this task ''' @@ -920,7 +919,7 @@ class TaskBlueprint(NamedCommon): else: return self.stop_time - self.start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''The relative start time is relative to the start time of the 'start' of the parent scheduling unit. It's based on the scheduling_relation's from the scheduling_unit's specification, @@ -928,14 +927,14 @@ class TaskBlueprint(NamedCommon): ''' scheduling_relations = list(self.first_scheduling_relation.all()) + list(self.second_scheduling_relation.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after": + if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_blueprint.relative_stop_time: return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before": + if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations @@ -943,7 +942,7 @@ class TaskBlueprint(NamedCommon): return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''The relative_stop_time is the relative_start_time+duration. See relative_start_time for an explanation of it's intended usage. @@ -956,7 +955,7 @@ class TaskBlueprint(NamedCommon): pass return self.relative_start_time - @property + @cached_property def start_time(self) -> datetime or None: '''return the earliest start time of all subtasks of this task ''' @@ -966,7 +965,7 @@ class TaskBlueprint(NamedCommon): else: return None - @property + @cached_property def stop_time(self) -> datetime or None: '''return the latest stop time of all subtasks of this task ''' diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index 00a1018c32c8fa4ccd3724329cf7e04bfea527b0..9d19d0c77460a5e99e5c2eb377f38b4adabbb0cb 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -55,6 +55,12 @@ def populate_test_data(): from lofar.sas.tmss.test.tmss_test_data_django_models import SchedulingSet_test_data, SchedulingUnitDraft_test_data from lofar.sas.tmss.tmss.tmssapp.tasks import create_task_blueprints_and_subtasks_from_scheduling_unit_draft, create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_draft from lofar.sas.tmss.tmss.tmssapp.subtasks import schedule_subtask + from lofar.common.json_utils import get_default_json_object_for_schema + + constraints_template = models.SchedulingConstraintsTemplate.objects.get(name="constraints") + constraints_spec = get_default_json_object_for_schema(constraints_template.schema) + + strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines") tmss_projects = models.Project.objects.filter(name__startswith="TMSS-Commissioning").order_by('priority_rank').all() for project_nr, tmss_project in enumerate(tmss_projects): @@ -69,18 +75,18 @@ def populate_test_data(): logger.info('created test scheduling_set: %s', scheduling_set.name) for unit_nr in range(2 if 'normal' in project_prio_name else 1): - strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines") - # the 'template' in the strategy_template is a predefined json-data blob which validates against the given scheduling_unit_template # a user might 'upload' a partial json-data blob, so add all the known defaults scheduling_unit_spec = add_defaults_to_json_object_for_schema(strategy_template.template, strategy_template.scheduling_unit_template.schema) # add the scheduling_unit_doc to a new SchedulingUnitDraft instance, and were ready to use it! - scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(name="UC1 unit p%s.s%s.u%s (%s)" % (project_nr+1, set_nr+1, unit_nr+1, project_prio_name), + scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(name="UC1 test scheduling unit %s.%s" % (set_nr+1, unit_nr+1), scheduling_set=scheduling_set, requirements_template=strategy_template.scheduling_unit_template, requirements_doc=scheduling_unit_spec, - observation_strategy_template=strategy_template) + observation_strategy_template=strategy_template, + scheduling_constraints_doc=constraints_spec, + scheduling_constraints_template=constraints_template) scheduling_unit_draft.tags = ["TEST", "UC1"] scheduling_unit_draft.save() diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json index 04bb208f0b4deff2d4a7d0491ef4108afe335922..77a916705c8df50c069f5929e11fc03d5586acf7 100644 --- a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json +++ b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json @@ -1,13 +1,32 @@ { + "$id":"http://tmss.lofar.org/api/schemas/schedulingconstraintstemplate/constraints/1#", "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Constraints", - "description": "This schema defines the constraints for a scheduling unit", + "title": "constraints", + "description": "This schema defines the scheduling constraints for a scheduling unit", "version": 1, "definitions": { "timestamp": { + "description": "A timestamp defined in UTC", "type": "string", - "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+Z", - "format": "datetime" + "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(\\.\\d+)?Z", + "format": "date-time" + }, + "timewindow": { + "type": "object", + "description": "A timewindow interval: [from, to)", + "properties": { + "from": { + "$ref": "#/definitions/timestamp" + }, + "to": { + "$ref": "#/definitions/timestamp" + } + }, + "additionalProperties": false, + "required": [ + "from", + "to" + ] }, "distance_on_sky": { "type": "number", @@ -24,7 +43,7 @@ "properties": { "scheduler": { "name": "Scheduler", - "description": "Which scheduling system wiil schedule this", + "description": "Which scheduling system will schedule this", "type": "string", "enum": [ "manual", @@ -34,6 +53,7 @@ }, "time": { "type": "object", + "default": {}, "properties": { "at": { "description": "Start at this moment", @@ -51,41 +71,28 @@ "description": "Run within one of these time windows", "type": "array", "items": { - "from": { - "$ref": "#/definitions/timestamp" - }, - "to": { - "$ref": "#/definitions/timestamp" - }, - "required": [ - "from", - "to" - ] + "$ref": "#/definitions/timewindow" }, - "additionalItems": false + "minItems":0, + "uniqueItems":true, + "default": [] }, "not_between": { - "description": "NOT run within one of these time windows", + "description": "Do NOT run within any of these time windows", "type": "array", "items": { - "from": { - "$ref": "#/definitions/timestamp" - }, - "to": { - "$ref": "#/definitions/timestamp" - }, - "required": [ - "from", - "to" - ] + "$ref": "#/definitions/timewindow" }, - "additionalItems": false + "minItems":0, + "uniqueItems":true, + "default": [] } }, "additionalProperties": false }, "daily": { "type": "object", + "default": {}, "properties": { "require_night": { "description": "Must run at night", @@ -107,6 +114,7 @@ }, "sky": { "type": "object", + "default": {}, "properties": { "min_calibrator_elevation": { "description": "Minimum elevation for all calibrator sources", @@ -123,14 +131,14 @@ "type": "object", "properties": { "from": { - "type": "integer", - "minimum": -43200, - "maximum": 43200 + "type": "number", + "minimum": -0.20943951, + "maximum": 0.20943951 }, "to": { - "type": "integer", - "minimum": -43200, - "maximum": 43200 + "type": "number", + "minimum": -0.20943951, + "maximum": 0.20943951 } }, "additionalProperties": false @@ -157,7 +165,6 @@ "additionalProperties": false } }, - "additionalProperties": false, "required": [ "scheduler" ] diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index 71b73ffd50ac52e00f9ccd7dee1311009d23c679..a82aae88bdd1ba43eec58561450f1d4c0aa2a0a7 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -288,6 +288,7 @@ class SchedulingSetSerializer(RelationalHyperlinkedModelSerializer): class SchedulingUnitDraftSerializer(RelationalHyperlinkedModelSerializer): requirements_doc = JSONEditorField(schema_source="requirements_template.schema") + scheduling_constraints_doc = JSONEditorField(schema_source="scheduling_constraints_template.schema") duration = FloatDurationField(read_only=True) class Meta: diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py index af49948d1615bac654f06ea03f55f8b09f679d6a..731dea891e03bf199867741111d93e58a53910ed 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py @@ -144,6 +144,8 @@ class SubtaskViewSet(LOFARViewSet): filter_class = SubTaskFilter ordering = ('start_time',) + queryset = queryset.prefetch_related('state') + @swagger_auto_schema(auto_schema=TextPlainAutoSchema, responses={200: 'A LOFAR parset for this subtask (as plain text)', 403: 'forbidden', diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index ce3fa163142398bbaf6ae6bf7e197b33b6311cd2..242f06d05a6084ad6f0aa64a46eec8ce75ac164d 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -307,6 +307,16 @@ class SchedulingUnitDraftViewSet(LOFARViewSet): queryset = models.SchedulingUnitDraft.objects.all() serializer_class = serializers.SchedulingUnitDraftSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('copied_from') \ + .prefetch_related('scheduling_unit_blueprints')\ + .prefetch_related('task_drafts') + + # preselect all references to other models to avoid even more duplicate queries + queryset = queryset.select_related('copies') \ + .select_related('copy_reason') \ + .select_related('scheduling_set') + @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header', 403: 'forbidden'}, operation_description="Carve SchedulingUnitDraft in stone, and make an (uneditable) blueprint out of it.") @@ -580,6 +590,9 @@ class SchedulingUnitBlueprintViewSet(LOFARViewSet): queryset = models.SchedulingUnitBlueprint.objects.all() serializer_class = serializers.SchedulingUnitBlueprintSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('task_blueprints') + @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints and (scheduled) Subtasks.", 403: 'forbidden'}, operation_description="Create TaskBlueprint(s) for this scheduling unit, create subtasks, and schedule the ones that are not dependend on predecessors.") @@ -633,6 +646,22 @@ class TaskDraftViewSet(LOFARViewSet): queryset = models.TaskDraft.objects.all() serializer_class = serializers.TaskDraftSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('first_to_connect') \ + .prefetch_related('second_to_connect')\ + .prefetch_related('produced_by')\ + .prefetch_related('consumed_by')\ + .prefetch_related('task_blueprints')\ + .prefetch_related('copied_from') + + # prefetch nested references in reverse models to avoid duplicate lookup queries + queryset = queryset.prefetch_related('first_to_connect__placement') \ + .prefetch_related('second_to_connect__placement') + + # select all references to other models to avoid even more duplicate queries + queryset = queryset.select_related('copies') \ + .select_related('copy_reason') + @swagger_auto_schema(responses={201: 'The created task blueprint, see Location in Response header', 403: 'forbidden'}, operation_description="Carve this draft task specification in stone, and make an (uneditable) blueprint out of it.") @@ -726,6 +755,17 @@ class TaskBlueprintViewSet(LOFARViewSet): queryset = models.TaskBlueprint.objects.all() serializer_class = serializers.TaskBlueprintSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('first_to_connect')\ + .prefetch_related('second_to_connect')\ + .prefetch_related('produced_by')\ + .prefetch_related('consumed_by')\ + .prefetch_related('subtasks') + + # prefetch nested references in reverse models to avoid duplicate lookup queries + queryset = queryset.prefetch_related('first_to_connect__placement') \ + .prefetch_related('second_to_connect__placement') + @swagger_auto_schema(responses={201: "This TaskBlueprint, with it is created subtasks", 403: 'forbidden'}, operation_description="Create subtasks.") diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py index af3411b868953413e5b87113d6beade2c26c6ff5..0776702860d8c041eb193e83f2ba72b78d1d8031 100644 --- a/SAS/TMSS/src/tmss/urls.py +++ b/SAS/TMSS/src/tmss/urls.py @@ -33,6 +33,8 @@ from datetime import datetime from material.frontend import urls as frontend_urls from viewflow.flow.viewset import FlowViewSet +import debug_toolbar + # # Django style patterns # @@ -63,7 +65,8 @@ urlpatterns = [ path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'), #TODO: how to make trailing slash optional? path('schemas/<str:template>/<str:name>/<str:version>/', views.get_template_json_schema, name='get_template_json_schema'), path(r'util/utc', views.utc, name="system-utc"), - path(r'util/lst', views.lst, name="conversion-lst") + path(r'util/lst', views.lst, name="conversion-lst"), + path('__debug__/', include(debug_toolbar.urls)), ] diff --git a/SAS/TMSS/test/test_utils.py b/SAS/TMSS/test/test_utils.py index 627713b67e2e8ae4cea367e680a5dd39f945defc..a07119df5a674abe88bf85414186e8b79b2c7227 100644 --- a/SAS/TMSS/test/test_utils.py +++ b/SAS/TMSS/test/test_utils.py @@ -214,7 +214,7 @@ class TMSSDjangoServerInstance(): # wait for server to be up and running.... # or exit via TimeoutError - self.check_running_server(timeout=30) + self.check_running_server(timeout=60) def stop(self): '''