Fix audit creation with named goal and strategy

This patch set fixes process of audit creation and
allows to create audit without Audit Template using only
names of Goal and Strategy. It also provides some additional
unit tests to improve tests covering.

Change-Id: I89a9c7661616f49639151869055d8f5ebe723d5f
Closes-Bug: #1794233
This commit is contained in:
Alexander Chadin 2018-09-25 13:44:51 +03:00
parent 925b971377
commit 7cb81ac6c5
3 changed files with 127 additions and 92 deletions

View File

@ -28,10 +28,6 @@ itself. In the first case, there also should be supplied
``audit_template_uuid``. If ``Audit`` is created without ``Audit Template``, ``audit_template_uuid``. If ``Audit`` is created without ``Audit Template``,
``goal`` should be provided. ``goal`` should be provided.
.. warning::
**Only ``audit_template_uuid`` can be used to create audit so far.**
It should be fixed during the ``Rocky`` cycle.
Normal response codes: 201 Normal response codes: 201
Error codes: 400,404 Error codes: 400,404

View File

@ -54,6 +54,13 @@ from watcher import objects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
def _get_object_by_value(context, class_name, value):
if utils.is_uuid_like(value) or utils.is_int_like(value):
return class_name.get(context, value)
else:
return class_name.get_by_name(context, value)
class AuditPostType(wtypes.Base): class AuditPostType(wtypes.Base):
name = wtypes.wsattr(wtypes.text, mandatory=False) name = wtypes.wsattr(wtypes.text, mandatory=False)
@ -93,6 +100,10 @@ class AuditPostType(wtypes.Base):
raise exception.AuditIntervalNotSpecified( raise exception.AuditIntervalNotSpecified(
audit_type=self.audit_type) audit_type=self.audit_type)
if self.audit_template_uuid and self.goal:
raise exception.Invalid('Either audit_template_uuid '
'or goal should be provided.')
# If audit_template_uuid was provided, we will provide any # If audit_template_uuid was provided, we will provide any
# variables not included in the request, but not override # variables not included in the request, but not override
# those variables that were included. # those variables that were included.
@ -123,7 +134,8 @@ class AuditPostType(wtypes.Base):
# Note: If audit name was not provided, used a default name # Note: If audit name was not provided, used a default name
if not self.name: if not self.name:
if self.strategy: if self.strategy:
strategy = objects.Strategy.get(context, self.strategy) strategy = _get_object_by_value(context, objects.Strategy,
self.strategy)
self.name = "%s-%s" % (strategy.name, self.name = "%s-%s" % (strategy.name,
datetime.datetime.utcnow().isoformat()) datetime.datetime.utcnow().isoformat())
elif self.audit_template_uuid: elif self.audit_template_uuid:
@ -132,7 +144,7 @@ class AuditPostType(wtypes.Base):
self.name = "%s-%s" % (audit_template.name, self.name = "%s-%s" % (audit_template.name,
datetime.datetime.utcnow().isoformat()) datetime.datetime.utcnow().isoformat())
else: else:
goal = objects.Goal.get(context, self.goal) goal = _get_object_by_value(context, objects.Goal, self.goal)
self.name = "%s-%s" % (goal.name, self.name = "%s-%s" % (goal.name,
datetime.datetime.utcnow().isoformat()) datetime.datetime.utcnow().isoformat())
# No more than 63 characters # No more than 63 characters

View File

@ -37,13 +37,16 @@ def post_get_test_audit(**kw):
audit_template = db_utils.get_test_audit_template() audit_template = db_utils.get_test_audit_template()
goal = db_utils.get_test_goal() goal = db_utils.get_test_goal()
del_keys = ['goal_id', 'strategy_id'] del_keys = ['goal_id', 'strategy_id']
del_keys.extend(kw.get('params_to_exclude', []))
add_keys = {'audit_template_uuid': audit_template['uuid'], add_keys = {'audit_template_uuid': audit_template['uuid'],
'goal': goal['uuid'], 'goal': goal['uuid'],
} }
for k in del_keys: if kw.get('use_named_goal'):
del audit[k] add_keys['goal'] = 'TEST'
for k in add_keys: for k in add_keys:
audit[k] = kw.get(k, add_keys[k]) audit[k] = kw.get(k, add_keys[k])
for k in del_keys:
del audit[k]
return audit return audit
@ -491,13 +494,10 @@ class TestPost(api_base.FunctionalTest):
test_time = datetime.datetime(2000, 1, 1, 0, 0) test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time mock_utcnow.return_value = test_time
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) audit_dict = post_get_test_audit(
del audit_dict['uuid'] state=objects.audit.State.PENDING,
del audit_dict['state'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['interval'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -530,18 +530,78 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message']) self.assertTrue(response.json['error_message'])
@mock.patch('oslo_utils.timeutils.utcnow')
def test_create_audit_with_at_uuid_and_goal_specified(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
audit_dict = post_get_test_audit(
state=objects.audit.State.PENDING,
params_to_exclude=['uuid', 'state', 'interval', 'scope',
'next_run_time', 'hostname'])
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
def test_create_audit_with_goal(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',
'audit_template_uuid'])
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(objects.audit.State.PENDING,
response.json['state'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
def test_create_audit_with_goal_without_strategy(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',
'audit_template_uuid', 'strategy'])
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(objects.audit.State.PENDING,
response.json['state'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
def test_create_audit_with_named_goal(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',
'audit_template_uuid'],
use_named_goal=True)
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(objects.audit.State.PENDING,
response.json['state'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
@mock.patch('oslo_utils.timeutils.utcnow') @mock.patch('oslo_utils.timeutils.utcnow')
def test_create_audit_invalid_audit_template_uuid(self, mock_utcnow): def test_create_audit_invalid_audit_template_uuid(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0) test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time mock_utcnow.return_value = test_time
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
# Make the audit template UUID some garbage value # Make the audit template UUID some garbage value
audit_dict['audit_template_uuid'] = ( audit_dict['audit_template_uuid'] = (
'01234567-8910-1112-1314-151617181920') '01234567-8910-1112-1314-151617181920')
@ -558,14 +618,12 @@ class TestPost(api_base.FunctionalTest):
def test_create_audit_doesnt_contain_id(self, mock_trigger_audit): def test_create_audit_doesnt_contain_id(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) audit_dict = post_get_test_audit(
state=objects.audit.State.PENDING,
params_to_exclude=['uuid', 'interval', 'scope',
'next_run_time', 'hostname', 'goal'])
state = audit_dict['state'] state = audit_dict['state']
del audit_dict['uuid']
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
with mock.patch.object(self.dbapi, 'create_audit', with mock.patch.object(self.dbapi, 'create_audit',
wraps=self.dbapi.create_audit) as cn_mock: wraps=self.dbapi.create_audit) as cn_mock:
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
@ -578,13 +636,9 @@ class TestPost(api_base.FunctionalTest):
def test_create_audit_generate_uuid(self, mock_trigger_audit): def test_create_audit_generate_uuid(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -597,12 +651,9 @@ class TestPost(api_base.FunctionalTest):
def test_create_continuous_audit_with_interval(self, mock_trigger_audit): def test_create_continuous_audit_with_interval(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = '1200' audit_dict['interval'] = '1200'
@ -619,12 +670,9 @@ class TestPost(api_base.FunctionalTest):
mock_trigger_audit): mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = '* * * * *' audit_dict['interval'] = '* * * * *'
@ -641,12 +689,9 @@ class TestPost(api_base.FunctionalTest):
mock_trigger_audit): mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = 'zxc' audit_dict['interval'] = 'zxc'
@ -662,14 +707,10 @@ class TestPost(api_base.FunctionalTest):
def test_create_continuous_audit_without_period(self, mock_trigger_audit): def test_create_continuous_audit_without_period(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)
@ -683,13 +724,10 @@ class TestPost(api_base.FunctionalTest):
def test_create_oneshot_audit_with_period(self, mock_trigger_audit): def test_create_oneshot_audit_with_period(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
del audit_dict['uuid'] params_to_exclude=['uuid', 'state', 'scope',
del audit_dict['state'] 'next_run_time', 'hostname', 'goal'])
audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)
@ -701,13 +739,10 @@ class TestPost(api_base.FunctionalTest):
def test_create_audit_trigger_decision_engine(self): def test_create_audit_trigger_decision_engine(self):
with mock.patch.object(deapi.DecisionEngineAPI, with mock.patch.object(deapi.DecisionEngineAPI,
'trigger_audit') as de_mock: 'trigger_audit') as de_mock:
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) audit_dict = post_get_test_audit(
del audit_dict['uuid'] state=objects.audit.State.PENDING,
del audit_dict['state'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['interval'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
de_mock.assert_called_once_with(mock.ANY, response.json['uuid']) de_mock.assert_called_once_with(mock.ANY, response.json['uuid'])
@ -726,13 +761,10 @@ class TestPost(api_base.FunctionalTest):
def test_create_audit_parameters_no_predefined_strategy( def test_create_audit_parameters_no_predefined_strategy(
self, mock_trigger_audit): self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit(parameters={'name': 'Tom'}) audit_dict = post_get_test_audit(
del audit_dict['uuid'] parameters={'name': 'Tom'},
del audit_dict['state'] params_to_exclude=['uuid', 'state', 'interval', 'scope',
del audit_dict['interval'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -831,16 +863,13 @@ class TestPost(api_base.FunctionalTest):
test_time = datetime.datetime(2000, 1, 1, 0, 0) test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time mock_utcnow.return_value = test_time
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit(
params_to_exclude=['state', 'interval', 'scope',
'next_run_time', 'hostname', 'goal'])
normal_name = 'this audit name is just for test' normal_name = 'this audit name is just for test'
# long_name length exceeds 63 characters # long_name length exceeds 63 characters
long_name = normal_name + audit_dict['uuid'] long_name = normal_name + audit_dict['uuid']
del audit_dict['uuid'] del audit_dict['uuid']
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['name'] = normal_name audit_dict['name'] = normal_name
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
@ -962,12 +991,10 @@ class TestAuditPolicyEnforcement(api_base.FunctionalTest):
'op': 'replace'}], expect_errors=True) 'op': 'replace'}], expect_errors=True)
def test_policy_disallow_create(self): def test_policy_disallow_create(self):
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) audit_dict = post_get_test_audit(
del audit_dict['uuid'] state=objects.audit.State.PENDING,
del audit_dict['state'] params_to_exclude=['uuid', 'state', 'scope',
del audit_dict['scope'] 'next_run_time', 'hostname', 'goal'])
del audit_dict['next_run_time']
del audit_dict['hostname']
self._common_policy_check( self._common_policy_check(
"audit:create", self.post_json, '/audits', audit_dict, "audit:create", self.post_json, '/audits', audit_dict,
expect_errors=True) expect_errors=True)