From 2afd0dfcf5b0113110462331f73c0c843c728a03 Mon Sep 17 00:00:00 2001
From: licanwei
Date: Thu, 9 May 2019 14:48:25 +0800
Subject: [PATCH] Audit API supports new force option
Depends-on:Ia08694d2fb76907ea14e64116af2e722fe930063
Change-Id: Ib2d221ea9c994dea396c54cc8d2d32237025a1d4
Implements: blueprint add-force-field-to-audit
---
...force-field-to-audit-4bcaeedfe27233ad.yaml | 7 ++++
watcher/api/controllers/v1/audit.py | 5 ++-
watcher/api/controllers/v1/utils.py | 9 +++++
watcher/api/controllers/v1/versions.py | 4 ++-
watcher/decision_engine/audit/base.py | 5 ++-
watcher/tests/api/v1/test_audits.py | 33 +++++++++++++++++++
.../audit/test_audit_handlers.py | 7 ++++
7 files changed, 67 insertions(+), 3 deletions(-)
create mode 100644 releasenotes/notes/add-force-field-to-audit-4bcaeedfe27233ad.yaml
diff --git a/releasenotes/notes/add-force-field-to-audit-4bcaeedfe27233ad.yaml b/releasenotes/notes/add-force-field-to-audit-4bcaeedfe27233ad.yaml
new file mode 100644
index 000000000..5360beef7
--- /dev/null
+++ b/releasenotes/notes/add-force-field-to-audit-4bcaeedfe27233ad.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Add force field to Audit. User can set --force to enable the new option when
+ launching audit. If force is True, audit will be executed despite of ongoing
+ actionplan. The new audit may create a wrong actionplan if they use the same
+ data model.
diff --git a/watcher/api/controllers/v1/audit.py b/watcher/api/controllers/v1/audit.py
index 3f67f4164..74df4a4f0 100644
--- a/watcher/api/controllers/v1/audit.py
+++ b/watcher/api/controllers/v1/audit.py
@@ -73,6 +73,8 @@ def hide_fields_in_newer_versions(obj):
if not api_utils.allow_start_end_audit_time():
obj.start_time = wtypes.Unset
obj.end_time = wtypes.Unset
+ if not api_utils.allow_force():
+ obj.force = wtypes.Unset
class AuditPostType(wtypes.Base):
@@ -194,7 +196,8 @@ class AuditPostType(wtypes.Base):
scope=self.scope,
auto_trigger=self.auto_trigger,
start_time=self.start_time,
- end_time=self.end_time)
+ end_time=self.end_time,
+ force=self.force)
class AuditPatchType(types.JsonPatchType):
diff --git a/watcher/api/controllers/v1/utils.py b/watcher/api/controllers/v1/utils.py
index 55e83d001..123c8e6e9 100644
--- a/watcher/api/controllers/v1/utils.py
+++ b/watcher/api/controllers/v1/utils.py
@@ -165,3 +165,12 @@ def allow_start_end_audit_time():
audits.
"""
return pecan.request.version.minor >= versions.MINOR_1_START_END_TIMING
+
+
+def allow_force():
+ """Check if we should support optional force attribute for Audit.
+
+ Version 1.2 of the API added support for forced audits that allows to
+ launch audit when other action plan is ongoing.
+ """
+ return pecan.request.version.minor >= versions.MINOR_2_FORCE
diff --git a/watcher/api/controllers/v1/versions.py b/watcher/api/controllers/v1/versions.py
index eec13f9fe..6de00d196 100644
--- a/watcher/api/controllers/v1/versions.py
+++ b/watcher/api/controllers/v1/versions.py
@@ -22,11 +22,13 @@ BASE_VERSION = 1
#
# v1.0: corresponds to Rocky API
# v1.1: Add start/end time for continuous audit
+# v1.2: Add force field to audit
MINOR_0_ROCKY = 0
MINOR_1_START_END_TIMING = 1
+MINOR_2_FORCE = 2
-MINOR_MAX_VERSION = MINOR_1_START_END_TIMING
+MINOR_MAX_VERSION = MINOR_2_FORCE
# String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_0_ROCKY)
diff --git a/watcher/decision_engine/audit/base.py b/watcher/decision_engine/audit/base.py
index 1d4e25adc..229c90290 100644
--- a/watcher/decision_engine/audit/base.py
+++ b/watcher/decision_engine/audit/base.py
@@ -121,7 +121,10 @@ class AuditHandler(BaseAuditHandler):
def pre_execute(self, audit, request_context):
LOG.debug("Trigger audit %s", audit.uuid)
- self.check_ongoing_action_plans(request_context)
+ # If audit.force is true, audit will be executed
+ # despite of ongoing actionplan
+ if not audit.force:
+ self.check_ongoing_action_plans(request_context)
# Write hostname that will execute this audit.
audit.hostname = CONF.host
# change state of the audit to ONGOING
diff --git a/watcher/tests/api/v1/test_audits.py b/watcher/tests/api/v1/test_audits.py
index c40d16dfb..14137f62a 100644
--- a/watcher/tests/api/v1/test_audits.py
+++ b/watcher/tests/api/v1/test_audits.py
@@ -950,6 +950,39 @@ class TestPost(api_base.FunctionalTest):
self.assertIn(expected_error_msg, response.json['error_message'])
assert not mock_trigger_audit.called
+ @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
+ def test_create_audit_with_force_false(self, mock_trigger_audit):
+ mock_trigger_audit.return_value = mock.ANY
+
+ audit_dict = post_get_test_audit(
+ params_to_exclude=['uuid', 'state', 'interval', 'scope',
+ 'next_run_time', 'hostname', 'goal'])
+
+ response = self.post_json(
+ '/audits',
+ audit_dict,
+ headers={'OpenStack-API-Version': 'infra-optim 1.2'})
+ self.assertEqual('application/json', response.content_type)
+ self.assertEqual(201, response.status_int)
+ self.assertFalse(response.json['force'])
+
+ @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
+ def test_create_audit_with_force_true(self, mock_trigger_audit):
+ mock_trigger_audit.return_value = mock.ANY
+
+ audit_dict = post_get_test_audit(
+ params_to_exclude=['uuid', 'state', 'interval', 'scope',
+ 'next_run_time', 'hostname', 'goal'])
+
+ audit_dict['force'] = True
+ response = self.post_json(
+ '/audits',
+ audit_dict,
+ headers={'OpenStack-API-Version': 'infra-optim 1.2'})
+ self.assertEqual('application/json', response.content_type)
+ self.assertEqual(201, response.status_int)
+ self.assertTrue(response.json['force'])
+
class TestDelete(api_base.FunctionalTest):
diff --git a/watcher/tests/decision_engine/audit/test_audit_handlers.py b/watcher/tests/decision_engine/audit/test_audit_handlers.py
index de1000116..5acacfc9f 100644
--- a/watcher/tests/decision_engine/audit/test_audit_handlers.py
+++ b/watcher/tests/decision_engine/audit/test_audit_handlers.py
@@ -227,6 +227,13 @@ class TestAutoTriggerActionPlan(base.DbTestCase):
mock_applier.assert_called_once_with(self.context,
self.recommended_action_plan.uuid)
+ @mock.patch.object(oneshot.OneShotAuditHandler, 'do_execute')
+ def test_trigger_audit_with_force(self, mock_do_execute):
+ audit_handler = oneshot.OneShotAuditHandler()
+ self.audit.force = True
+ audit_handler.execute(self.audit, self.context)
+ self.assertTrue(mock_do_execute.called)
+
class TestContinuousAuditHandler(base.DbTestCase):