diff --git a/README.rst b/README.rst index 9bb1f54c..60eab151 100644 --- a/README.rst +++ b/README.rst @@ -566,6 +566,71 @@ Finished job with result:: } +8.2 Actions default value +------------------------- + +It is possible to define properties that span accross multiple actions +This allow not to rewrite values that might be the same in multiple actions. +If properties are specificaly set in one action, then the specified value is the one used. + +Example:: + "job": { + "action_defaults": { + "log_file": "/tmp/freezer_tmp_log", + "container": "my_backup_container" + }, + "job_actions": + [ + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user1/file", + "backup_name" : "user1_backup"}}, + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user2/file", + "backup_name" : "user2_backup"}}, + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user3/file", + "backup_name" : "user2_backup", + "log_file" : "/home/user3/specific_log_file" }} + ] + "description": "scheduled one shot" + } + + +Is Equivalent to:: + "job": { + "job_actions": + [ + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user1/file", + "backup_name" : "user1_backup", + "log_file": "/tmp/freezer_tmp_log", + "container": "my_backup_container"}}, + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user2/file", + "backup_name" : "user2_backup", + "log_file": "/tmp/freezer_tmp_log", + "container": "my_backup_container"}}, + {"freezer_action":{ + "action" : "backup", + "mode" : "fs", + "src_file" : "/home/user3/file", + "backup_name" : "user2_backup", + "log_file" : "/home/user3/specific_log_file", + "container": "my_backup_container"}}, + ] + "description": "scheduled one shot" + } + 9 Actions ========= diff --git a/freezer_api/api/v1/jobs.py b/freezer_api/api/v1/jobs.py index 09f99448..8fc585c7 100644 --- a/freezer_api/api/v1/jobs.py +++ b/freezer_api/api/v1/jobs.py @@ -79,14 +79,14 @@ class JobsCollectionResource(JobsBaseResource): def on_post(self, req, resp): # POST /v1/jobs Creates job entry try: - doc = self.json_body(req) + job = Job(self.json_body(req)) except KeyError: raise freezer_api_exc.BadDataFormat( message='Missing request body') user_id = req.get_header('X-User-ID') - self.update_actions_in_job(user_id, doc) - job_id = self.db.add_job(user_id=user_id, doc=doc) + self.update_actions_in_job(user_id, job.doc) + job_id = self.db.add_job(user_id=user_id, doc=job.doc) resp.status = falcon.HTTP_201 resp.body = {'job_id': job_id} @@ -116,21 +116,21 @@ class JobsResource(JobsBaseResource): def on_patch(self, req, resp, job_id): # PATCH /v1/jobs/{job_id} updates the specified job user_id = req.get_header('X-User-ID') or '' - doc = self.json_body(req) - self.update_actions_in_job(user_id, doc) + job = Job(self.json_body(req)) + self.update_actions_in_job(user_id, job.doc) new_version = self.db.update_job(user_id=user_id, job_id=job_id, - patch_doc=doc) + patch_doc=job.doc) resp.body = {'job_id': job_id, 'version': new_version} def on_post(self, req, resp, job_id): # PUT /v1/jobs/{job_id} creates/replaces the specified job user_id = req.get_header('X-User-ID') or '' - doc = self.json_body(req) - self.update_actions_in_job(user_id, doc) + job = Job(self.json_body(req)) + self.update_actions_in_job(user_id, job.doc) new_version = self.db.replace_job(user_id=user_id, job_id=job_id, - doc=doc) + doc=job.doc) resp.status = falcon.HTTP_201 resp.body = {'job_id': job_id, 'version': new_version} @@ -215,6 +215,8 @@ class Job(object): """ def __init__(self, doc): self.doc = doc + if self.doc.get("action_defaults") is not None: + self.expand_default_properties() self.event_result = '' self.need_update = False if 'job_schedule' not in doc: @@ -282,3 +284,14 @@ class Job(object): """ for action_doc in self.doc.get('job_actions', []): yield Action(action_doc) + + def expand_default_properties(self): + action_defaults = self.doc.pop("action_defaults") + if isinstance(action_defaults, dict): + for key, val in action_defaults.items(): + for action in self.doc.get("job_actions"): + if action["freezer_action"].get(key) is None: + action["freezer_action"][key] = val + else: + raise freezer_api_exc.BadDataForma(message="action_defaults should" + "be a dictionary") diff --git a/freezer_api/common/json_schemas.py b/freezer_api/common/json_schemas.py index a53e29cf..fbca87d3 100644 --- a/freezer_api/common/json_schemas.py +++ b/freezer_api/common/json_schemas.py @@ -215,6 +215,9 @@ job_schema = { "_version": { "id": "_version", "type": "integer" + }, + "action_defaults": { + "$ref": "#/definitions/freezer_action" } }, "additionalProperties": False, @@ -304,6 +307,9 @@ job_patch_schema = { "_version": { "id": "_version", "type": "integer" + }, + "action_defaults": { + "$ref": "#/definitions/freezer_action" } }, "additionalProperties": False diff --git a/tests/test_jobs.py b/tests/test_jobs.py index 788f1d34..74468515 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -204,7 +204,8 @@ class TestJobsResource(unittest.TestCase): new_version = random.randint(0, 99) self.mock_db.update_job.return_value = new_version patch_doc = {'some_field': 'some_value', - 'because': 'size_matters'} + 'because': 'size_matters', + 'job_schedule': {}} self.mock_req.stream.read.return_value = json.dumps(patch_doc) expected_result = {'job_id': fake_job_0_job_id, 'version': new_version} @@ -472,3 +473,22 @@ class TestJobs(unittest.TestCase): def test_execute_raises_BadDataFormat_when_event_not_implemented(self): job = v1_jobs.Job({}) self.assertRaises(BadDataFormat, job.execute_event, 'smile', 'my_params') + + def test_expand_action_defaults(self): + job_doc = { + 'action_defaults': {'that_field': 'that_value'}, + 'job_actions': [ + {'freezer_action': {'not_that_field': 'some_value'}}, + {'freezer_action': {'that_field': 'another_value'}} + ] + } + expected_job_doc = { + 'job_actions': [ + {'freezer_action': {'not_that_field': 'some_value', + 'that_field': 'that_value'}}, + {'freezer_action': {'that_field': 'another_value'}} + ], + 'job_schedule': {} + } + job = v1_jobs.Job(job_doc) + self.assertEqual(job.doc, expected_job_doc)