Add parameters verification when Audit is being created
We have to check Audit Type and Audit State to make sure these parameters are in valid status. Also, we provide default states for the next attributes: - 'audit_template' is required and should be either UUID or text field - 'state' is readonly so it raises an error if submitted in POST and is set by default to PENDING - 'deadline' is optional and should be a datetime - 'type' is a required text field Change-Id: I2a7e0deec0ee2040e86400b500bb0efd8eade564 Closes-Bug: #1532843 Closes-Bug: #1533210
This commit is contained in:
parent
0f14b7635d
commit
e52dc4f8aa
|
@ -49,6 +49,31 @@ from watcher.decision_engine import rpcapi
|
|||
from watcher import objects
|
||||
|
||||
|
||||
class AuditPostType(wtypes.Base):
|
||||
|
||||
uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||
|
||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=True)
|
||||
|
||||
type = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
|
||||
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
|
||||
|
||||
state = wsme.wsattr(wtypes.text, readonly=True,
|
||||
default=objects.audit.State.PENDING)
|
||||
|
||||
def as_audit(self):
|
||||
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||
if self.type not in audit_type_values:
|
||||
raise exception.AuditTypeNotFound(audit_type=self.type)
|
||||
|
||||
return Audit(
|
||||
uuid=self.uuid or utils.generate_uuid(),
|
||||
audit_template_id=self.audit_template_uuid,
|
||||
type=self.type,
|
||||
deadline=self.deadline)
|
||||
|
||||
|
||||
class AuditPatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
|
@ -325,12 +350,13 @@ class AuditsController(rest.RestController):
|
|||
audit_uuid)
|
||||
return Audit.convert_with_links(rpc_audit)
|
||||
|
||||
@wsme_pecan.wsexpose(Audit, body=Audit, status_code=201)
|
||||
def post(self, audit):
|
||||
@wsme_pecan.wsexpose(Audit, body=AuditPostType, status_code=201)
|
||||
def post(self, audit_p):
|
||||
"""Create a new audit.
|
||||
|
||||
:param audit: a audit within the request body.
|
||||
:param audit_p: a audit within the request body.
|
||||
"""
|
||||
audit = audit_p.as_audit()
|
||||
if self.from_audits:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
|
|
|
@ -31,11 +31,6 @@ class UuidOrNameType(wtypes.UserType):
|
|||
|
||||
basetype = wtypes.text
|
||||
name = 'uuid_or_name'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
|
@ -55,11 +50,6 @@ class NameType(wtypes.UserType):
|
|||
|
||||
basetype = wtypes.text
|
||||
name = 'name'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
|
@ -79,11 +69,6 @@ class UuidType(wtypes.UserType):
|
|||
|
||||
basetype = wtypes.text
|
||||
name = 'uuid'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
|
@ -103,11 +88,6 @@ class BooleanType(wtypes.UserType):
|
|||
|
||||
basetype = wtypes.text
|
||||
name = 'boolean'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
|
@ -129,11 +109,6 @@ class JsonType(wtypes.UserType):
|
|||
|
||||
basetype = wtypes.text
|
||||
name = 'json'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
def __str__(self):
|
||||
# These are the json serializable native types
|
||||
|
|
|
@ -180,6 +180,10 @@ class AuditTemplateReferenced(Invalid):
|
|||
"multiple audit")
|
||||
|
||||
|
||||
class AuditTypeNotFound(Invalid):
|
||||
msg_fmt = _("Audit type %(audit_type)s could not be found")
|
||||
|
||||
|
||||
class AuditNotFound(ResourceNotFound):
|
||||
msg_fmt = _("Audit %(audit)s could not be found")
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ be one of the following:
|
|||
:ref:`Administrator <administrator_definition>`
|
||||
"""
|
||||
|
||||
import enum
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
|
@ -65,7 +67,7 @@ class State(object):
|
|||
PENDING = 'PENDING'
|
||||
|
||||
|
||||
class AuditType(object):
|
||||
class AuditType(enum.Enum):
|
||||
ONESHOT = 'ONESHOT'
|
||||
CONTINUOUS = 'CONTINUOUS'
|
||||
|
||||
|
|
|
@ -432,7 +432,8 @@ class TestPost(api_base.FunctionalTest):
|
|||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
|
||||
audit_dict = post_get_test_audit()
|
||||
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING)
|
||||
del audit_dict['state']
|
||||
|
||||
response = self.post_json('/audits', audit_dict)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
@ -451,12 +452,28 @@ class TestPost(api_base.FunctionalTest):
|
|||
response.json['created_at']).replace(tzinfo=None)
|
||||
self.assertEqual(test_time, return_created_at)
|
||||
|
||||
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
|
||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||
def test_create_audit_with_state_not_allowed(self, mock_utcnow,
|
||||
mock_trigger_audit):
|
||||
mock_trigger_audit.return_value = mock.ANY
|
||||
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.SUCCEEDED)
|
||||
|
||||
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('oslo_utils.timeutils.utcnow')
|
||||
def test_create_audit_invalid_audit_template_uuid(self, mock_utcnow):
|
||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
|
||||
audit_dict = post_get_test_audit()
|
||||
del audit_dict['state']
|
||||
# Make the audit template UUID some garbage value
|
||||
audit_dict['audit_template_uuid'] = (
|
||||
'01234567-8910-1112-1314-151617181920')
|
||||
|
@ -473,11 +490,13 @@ class TestPost(api_base.FunctionalTest):
|
|||
def test_create_audit_doesnt_contain_id(self, mock_trigger_audit):
|
||||
mock_trigger_audit.return_value = mock.ANY
|
||||
|
||||
audit_dict = post_get_test_audit(state='ONGOING')
|
||||
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING)
|
||||
state = audit_dict['state']
|
||||
del audit_dict['state']
|
||||
with mock.patch.object(self.dbapi, 'create_audit',
|
||||
wraps=self.dbapi.create_audit) as cn_mock:
|
||||
response = self.post_json('/audits', audit_dict)
|
||||
self.assertEqual(audit_dict['state'], response.json['state'])
|
||||
self.assertEqual(state, response.json['state'])
|
||||
cn_mock.assert_called_once_with(mock.ANY)
|
||||
# Check that 'id' is not in first arg of positional args
|
||||
self.assertNotIn('id', cn_mock.call_args[0][0])
|
||||
|
@ -488,6 +507,7 @@ class TestPost(api_base.FunctionalTest):
|
|||
|
||||
audit_dict = post_get_test_audit()
|
||||
del audit_dict['uuid']
|
||||
del audit_dict['state']
|
||||
|
||||
response = self.post_json('/audits', audit_dict)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
@ -499,7 +519,8 @@ class TestPost(api_base.FunctionalTest):
|
|||
def test_create_audit_trigger_decision_engine(self):
|
||||
with mock.patch.object(deapi.DecisionEngineAPI,
|
||||
'trigger_audit') as de_mock:
|
||||
audit_dict = post_get_test_audit(state='ONGOING')
|
||||
audit_dict = post_get_test_audit(state=objects.audit.State.PENDING)
|
||||
del audit_dict['state']
|
||||
self.post_json('/audits', audit_dict)
|
||||
de_mock.assert_called_once_with(mock.ANY, audit_dict['uuid'])
|
||||
|
||||
|
|
|
@ -138,6 +138,8 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
|||
"""
|
||||
audit = {'audit_template_uuid': audit_template_uuid}
|
||||
audit.update(kwargs)
|
||||
if not audit['state']:
|
||||
del audit['state']
|
||||
|
||||
return self._create_request('audits', audit)
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest import test
|
||||
from tempest_lib.common.utils import data_utils
|
||||
|
||||
from watcher_tempest_plugin import infra_optim_clients as clients
|
||||
|
||||
|
@ -157,7 +157,7 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||
|
||||
@classmethod
|
||||
def create_audit(cls, audit_template_uuid, type='ONESHOT',
|
||||
state='PENDING', deadline=None):
|
||||
state=None, deadline=None):
|
||||
"""Wrapper utility for creating a test audit
|
||||
|
||||
:param audit_template_uuid: Audit Template UUID this audit will use
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
from tempest import test
|
||||
|
||||
|
@ -72,7 +71,6 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||
self.assertRaises(
|
||||
exceptions.BadRequest, self.create_audit, **audit_params)
|
||||
|
||||
@decorators.skip_because(bug="1532843")
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_with_invalid_state(self):
|
||||
_, audit_template = self.create_audit_template()
|
||||
|
@ -85,7 +83,6 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||
self.assertRaises(
|
||||
exceptions.BadRequest, self.create_audit, **audit_params)
|
||||
|
||||
@decorators.skip_because(bug="1533210")
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_with_no_state(self):
|
||||
_, audit_template = self.create_audit_template()
|
||||
|
|
|
@ -24,8 +24,8 @@ from oslo_log import log
|
|||
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.scenario import manager
|
||||
from tempest_lib.common.utils import data_utils
|
||||
|
||||
from watcher_tempest_plugin import infra_optim_clients as clients
|
||||
|
||||
|
@ -114,7 +114,7 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
|||
# ### AUDITS ### #
|
||||
|
||||
def create_audit(self, audit_template_uuid, type='ONESHOT',
|
||||
state='PENDING', deadline=None):
|
||||
state=None, deadline=None):
|
||||
"""Wrapper utility for creating a test audit
|
||||
|
||||
:param audit_template_uuid: Audit Template UUID this audit will use
|
||||
|
|
Loading…
Reference in New Issue