From d7e2d095774ff0416d46d58315fdb39600dd2b4d Mon Sep 17 00:00:00 2001 From: jean-baptiste-perez-bib Date: Wed, 26 Jun 2024 16:21:30 +0200 Subject: [PATCH 1/2] Creates a new operation per schedule When an object schedule is created, it embeds a complete operation template in its "task" field. On the first schedule time, it creates an operation from the template. Until now, this operation was used for each subsequent schedule. This commit prevent that and creates a new operation per schedule, with the start date added in its name. --- app/service/app_svc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/service/app_svc.py b/app/service/app_svc.py index 906a765e7..ec831a3f8 100644 --- a/app/service/app_svc.py +++ b/app/service/app_svc.py @@ -6,6 +6,7 @@ import os import re import time +import uuid from collections import namedtuple from datetime import datetime, timezone from importlib import import_module @@ -100,6 +101,8 @@ async def run_scheduler(self): if interval > diff.total_seconds() > 0: self.log.debug('Pulling %s off the scheduler' % s.id) sop = copy.deepcopy(s.task) + sop.id = str(uuid.uuid4()) + sop.name += f" ({datetime.now(timezone.utc).replace(microsecond=0).isoformat()})" sop.set_start_details() await sop.update_operation_agents(self.get_services()) await self._services.get('data_svc').store(sop) From 6898392f1c5540f2988d90cd0a14e8205ea07e4e Mon Sep 17 00:00:00 2001 From: samuel-sirven-bib Date: Thu, 4 Jul 2024 16:24:15 +0200 Subject: [PATCH 2/2] Update schedule Schemas - Add a validation step to ensure the schedule field has a valid cron syntax. - Update the tests --- app/objects/c_schedule.py | 8 +++++++- tests/api/v2/handlers/test_schedules_api.py | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/objects/c_schedule.py b/app/objects/c_schedule.py index 135d0c2bf..5fb68fe35 100644 --- a/app/objects/c_schedule.py +++ b/app/objects/c_schedule.py @@ -1,6 +1,7 @@ import uuid import marshmallow as ma +from croniter import croniter from app.objects.interfaces.i_object import FirstClassObjectInterface from app.objects.c_operation import OperationSchema @@ -13,9 +14,14 @@ class Meta: unknown = ma.EXCLUDE id = ma.fields.String() - schedule = ma.fields.String(required=True) + schedule = ma.fields.String(required=True, metadata={"example": "5 4 * * *"}) task = ma.fields.Nested(OperationSchema()) + @ma.validates('schedule') + def validate_schedule(self, value): + if not croniter.is_valid(value): + raise ma.ValidationError("Invalid cron syntax for schedule field.") + @ma.post_load def build_schedule(self, data, **kwargs): return None if kwargs.get('partial') is True else Schedule(**data) diff --git a/tests/api/v2/handlers/test_schedules_api.py b/tests/api/v2/handlers/test_schedules_api.py index 446a6f2dd..9f9410c4b 100644 --- a/tests/api/v2/handlers/test_schedules_api.py +++ b/tests/api/v2/handlers/test_schedules_api.py @@ -11,7 +11,7 @@ @pytest.fixture def updated_schedule_payload(): payload = { - 'schedule': '01:00:00.000000', + 'schedule': '0 1 * * *', 'task': { 'autonomous': 1, 'obfuscator': 'base64', @@ -40,7 +40,7 @@ def _merge_dictionaries(dict1, dict2): @pytest.fixture def replaced_schedule_payload(): payload = { - 'schedule': '12:12:00.000000', + 'schedule': '0 12 12 * *', 'task': { 'autonomous': 1, 'obfuscator': 'base64', @@ -57,7 +57,7 @@ def replaced_schedule_payload(): @pytest.fixture def new_schedule_payload(test_planner, test_adversary, test_source): - payload = dict(schedule='00:00:00.000000', + payload = dict(schedule='0 0 * * *', id='456', task={ 'name': 'new_scheduled_operation', @@ -80,7 +80,7 @@ def expected_new_schedule_dump(new_schedule_payload): def test_schedule(test_operation, event_loop): operation = OperationSchema().load(test_operation) schedule = ScheduleSchema().load(dict(id='123', - schedule='03:00:00.000000', + schedule='0 3 * * *', task=operation.schema.dump(operation))) event_loop.run_until_complete(BaseService.get_service('data_svc').store(schedule)) return schedule