Merge "Added suspended audit state"

This commit is contained in:
Jenkins
2017-04-12 05:24:01 +00:00
committed by Gerrit Code Review
14 changed files with 114 additions and 73 deletions

View File

@@ -50,21 +50,6 @@ from watcher.decision_engine import rpcapi
from watcher import objects
ALLOWED_AUDIT_TRANSITIONS = {
objects.audit.State.PENDING:
[objects.audit.State.ONGOING, objects.audit.State.CANCELLED],
objects.audit.State.ONGOING:
[objects.audit.State.FAILED, objects.audit.State.SUCCEEDED,
objects.audit.State.CANCELLED],
objects.audit.State.FAILED:
[objects.audit.State.DELETED],
objects.audit.State.SUCCEEDED:
[objects.audit.State.DELETED],
objects.audit.State.CANCELLED:
[objects.audit.State.DELETED]
}
class AuditPostType(wtypes.Base):
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
@@ -144,8 +129,15 @@ class AuditPatchType(types.JsonPatchType):
@staticmethod
def validate(patch):
serialized_patch = {'path': patch.path, 'op': patch.op}
if patch.path in AuditPatchType.mandatory_attrs():
def is_new_state_none(p):
return p.path == '/state' and p.op == 'replace' and p.value is None
serialized_patch = {'path': patch.path,
'op': patch.op,
'value': patch.value}
if (patch.path in AuditPatchType.mandatory_attrs() or
is_new_state_none(patch)):
msg = _("%(field)s can't be updated.")
raise exception.PatchError(
patch=serialized_patch,
@@ -572,21 +564,22 @@ class AuditsController(rest.RestController):
try:
audit_dict = audit_to_update.as_dict()
initial_state = audit_dict['state']
new_state = api_utils.get_patch_value(patch, 'state')
if not api_utils.check_audit_state_transition(
patch, initial_state):
error_message = _("State transition not allowed: "
"(%(initial_state)s -> %(new_state)s)")
raise exception.PatchError(
patch=patch,
reason=error_message % dict(
initial_state=initial_state, new_state=new_state))
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
initial_state = audit_dict['state']
new_state = api_utils.get_patch_value(patch, 'state')
allowed_states = ALLOWED_AUDIT_TRANSITIONS.get(initial_state, [])
if new_state is not None and new_state not in allowed_states:
error_message = _("State transition not allowed: "
"(%(initial_state)s -> %(new_state)s)")
raise exception.PatchError(
patch=patch,
reason=error_message % dict(
initial_state=initial_state, new_state=new_state))
# Update only the fields that have changed
for field in objects.Audit.fields:
try:

View File

@@ -79,6 +79,15 @@ def get_patch_value(patch, key):
return p['value']
def check_audit_state_transition(patch, initial):
is_transition_valid = True
state_value = get_patch_value(patch, "state")
if state_value is not None:
is_transition_valid = objects.audit.AuditStateTransitionManager(
).check_transition(initial, state_value)
return is_transition_valid
def as_filters_dict(**filters):
filters_dict = {}
for filter_name, filter_value in filters.items():

View File

@@ -49,9 +49,7 @@ class ContinuousAuditHandler(base.AuditHandler):
def _is_audit_inactive(self, audit):
audit = objects.Audit.get_by_uuid(
self.context_show_deleted, audit.uuid)
if audit.state in (objects.audit.State.CANCELLED,
objects.audit.State.DELETED,
objects.audit.State.FAILED):
if objects.audit.AuditStateTransitionManager().is_inactive(audit):
# if audit isn't in active states, audit's job must be removed to
# prevent using of inactive audit in future.
job_to_delete = [job for job in self.jobs

View File

@@ -46,6 +46,9 @@ be one of the following:
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
**ONGOING** state and was cancelled by the
:ref:`Administrator <administrator_definition>`
- **SUSPENDED** : the :ref:`Audit <audit_definition>` was in **ONGOING**
state and was suspended by the
:ref:`Administrator <administrator_definition>`
"""
import enum
@@ -66,6 +69,7 @@ class State(object):
CANCELLED = 'CANCELLED'
DELETED = 'DELETED'
PENDING = 'PENDING'
SUSPENDED = 'SUSPENDED'
class AuditType(enum.Enum):
@@ -296,3 +300,25 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
notifications.audit.send_delete(self._context, self)
_notify()
class AuditStateTransitionManager(object):
TRANSITIONS = {
State.PENDING: [State.ONGOING, State.CANCELLED],
State.ONGOING: [State.FAILED, State.SUCCEEDED,
State.CANCELLED, State.SUSPENDED],
State.FAILED: [State.DELETED],
State.SUCCEEDED: [State.DELETED],
State.CANCELLED: [State.DELETED],
State.SUSPENDED: [State.ONGOING, State.DELETED],
}
INACTIVE_STATES = (State.CANCELLED, State.DELETED,
State.FAILED, State.SUSPENDED)
def check_transition(self, initial, new):
return new in self.TRANSITIONS.get(initial, [])
def is_inactive(self, audit):
return audit.state in self.INACTIVE_STATES

View File

@@ -345,23 +345,10 @@ class TestPatch(api_base.FunctionalTest):
ALLOWED_TRANSITIONS = [
{"original_state": objects.audit.State.PENDING,
"new_state": objects.audit.State.ONGOING},
{"original_state": objects.audit.State.PENDING,
"new_state": objects.audit.State.CANCELLED},
{"original_state": objects.audit.State.ONGOING,
"new_state": objects.audit.State.FAILED},
{"original_state": objects.audit.State.ONGOING,
"new_state": objects.audit.State.SUCCEEDED},
{"original_state": objects.audit.State.ONGOING,
"new_state": objects.audit.State.CANCELLED},
{"original_state": objects.audit.State.FAILED,
"new_state": objects.audit.State.DELETED},
{"original_state": objects.audit.State.SUCCEEDED,
"new_state": objects.audit.State.DELETED},
{"original_state": objects.audit.State.CANCELLED,
"new_state": objects.audit.State.DELETED},
]
{"original_state": key, "new_state": value}
for key, values in (
objects.audit.AuditStateTransitionManager.TRANSITIONS.items())
for value in values]
class TestPatchStateTransitionDenied(api_base.FunctionalTest):

View File

@@ -53,6 +53,10 @@ class TestDbAuditFilters(base.DbTestCase):
self.audit3 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=3, uuid=None,
state=objects.audit.State.CANCELLED)
with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.audit4 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=4, uuid=None,
state=objects.audit.State.SUSPENDED)
def _soft_delete_audits(self):
with freezegun.freeze_time(self.FAKE_TODAY):
@@ -92,8 +96,9 @@ class TestDbAuditFilters(base.DbTestCase):
res = self.dbapi.get_audit_list(
self.context, filters={'deleted': False})
self.assertEqual([self.audit2['id'], self.audit3['id']],
[r.id for r in res])
self.assertEqual(
[self.audit2['id'], self.audit3['id'], self.audit4['id']],
[r.id for r in res])
def test_get_audit_list_filter_deleted_at_eq(self):
self._soft_delete_audits()
@@ -154,7 +159,7 @@ class TestDbAuditFilters(base.DbTestCase):
self.context, filters={'created_at__lt': self.FAKE_TODAY})
self.assertEqual(
[self.audit2['id'], self.audit3['id']],
[self.audit2['id'], self.audit3['id'], self.audit4['id']],
[r.id for r in res])
def test_get_audit_list_filter_created_at_lte(self):
@@ -162,7 +167,7 @@ class TestDbAuditFilters(base.DbTestCase):
self.context, filters={'created_at__lte': self.FAKE_OLD_DATE})
self.assertEqual(
[self.audit2['id'], self.audit3['id']],
[self.audit2['id'], self.audit3['id'], self.audit4['id']],
[r.id for r in res])
def test_get_audit_list_filter_created_at_gt(self):
@@ -230,18 +235,22 @@ class TestDbAuditFilters(base.DbTestCase):
def test_get_audit_list_filter_state_in(self):
res = self.dbapi.get_audit_list(
self.context,
filters={'state__in': (objects.audit.State.FAILED,
objects.audit.State.CANCELLED)})
filters={
'state__in':
objects.audit.AuditStateTransitionManager.INACTIVE_STATES
})
self.assertEqual(
[self.audit2['id'], self.audit3['id']],
[self.audit2['id'], self.audit3['id'], self.audit4['id']],
[r.id for r in res])
def test_get_audit_list_filter_state_notin(self):
res = self.dbapi.get_audit_list(
self.context,
filters={'state__notin': (objects.audit.State.FAILED,
objects.audit.State.CANCELLED)})
filters={
'state__notin':
objects.audit.AuditStateTransitionManager.INACTIVE_STATES
})
self.assertEqual(
[self.audit1['id']],

View File

@@ -274,16 +274,18 @@ class TestContinuousAuditHandler(base.DbTestCase):
audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock())
mock_list.return_value = self.audits
mock_jobs.return_value = mock.MagicMock()
self.audits[1].state = objects.audit.State.CANCELLED
calls = [mock.call(audit_handler.execute_audit, 'interval',
args=[mock.ANY, mock.ANY],
seconds=3600,
name='execute_audit',
next_run_time=mock.ANY)]
audit_handler.launch_audits_periodically()
m_add_job.assert_has_calls(calls)
audit_handler.update_audit_state(self.audits[1],
objects.audit.State.CANCELLED)
is_inactive = audit_handler._is_audit_inactive(self.audits[1])
for state in [objects.audit.State.CANCELLED,
objects.audit.State.SUSPENDED]:
self.audits[1].state = state
calls = [mock.call(audit_handler.execute_audit, 'interval',
args=[mock.ANY, mock.ANY],
seconds=3600,
name='execute_audit',
next_run_time=mock.ANY)]
audit_handler.launch_audits_periodically()
m_add_job.assert_has_calls(calls)
audit_handler.update_audit_state(self.audits[1], state)
is_inactive = audit_handler._is_audit_inactive(self.audits[1])
self.assertTrue(is_inactive)