diff --git a/watcher/api/app.py b/watcher/api/app.py index c660a3da7..fde9f8be6 100644 --- a/watcher/api/app.py +++ b/watcher/api/app.py @@ -23,8 +23,6 @@ from watcher._i18n import _ from watcher.api import acl from watcher.api import config as api_config from watcher.api import middleware -from watcher.decision_engine.strategy.selection import default \ - as strategy_selector # Register options for the service API_SERVICE_OPTS = [ @@ -61,7 +59,6 @@ opt_group = cfg.OptGroup(name='api', CONF.register_group(opt_group) CONF.register_opts(API_SERVICE_OPTS, opt_group) -CONF.register_opts(strategy_selector.WATCHER_GOALS_OPTS) def get_pecan_config(): diff --git a/watcher/api/controllers/v1/audit_template.py b/watcher/api/controllers/v1/audit_template.py index 00dc9d492..c6f104e3f 100644 --- a/watcher/api/controllers/v1/audit_template.py +++ b/watcher/api/controllers/v1/audit_template.py @@ -50,42 +50,146 @@ provided as a list of key-value pairs. import datetime -from oslo_config import cfg import pecan from pecan import rest import wsme from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from watcher._i18n import _ from watcher.api.controllers import base from watcher.api.controllers import link from watcher.api.controllers.v1 import collection from watcher.api.controllers.v1 import types from watcher.api.controllers.v1 import utils as api_utils +from watcher.common import context as context_utils from watcher.common import exception from watcher.common import utils as common_utils from watcher import objects +class AuditTemplatePostType(wtypes.Base): + _ctx = context_utils.make_context() + + name = wtypes.wsattr(wtypes.text, mandatory=True) + """Name of this audit template""" + + description = wtypes.wsattr(wtypes.text, mandatory=False) + """Short description of this audit template""" + + deadline = wsme.wsattr(datetime.datetime, mandatory=False) + """deadline of the audit template""" + + host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1), + mandatory=False) + """ID of the Nova host aggregate targeted by the audit template""" + + extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False) + """The metadata of the audit template""" + + goal_uuid = wtypes.wsattr(types.uuid, mandatory=True) + """Goal UUID of the audit template""" + + strategy_uuid = wsme.wsattr(types.uuid, mandatory=False) + """Strategy UUID of the audit template""" + + version = wtypes.text + """Internal version of the audit template""" + + def as_audit_template(self): + related_strategy_uuid = self.strategy_uuid or None + return AuditTemplate( + name=self.name, + description=self.description, + deadline=self.deadline, + host_aggregate=self.host_aggregate, + extra=self.extra, + goal_id=self.goal_uuid, # Dirty trick ... + goal_uuid=self.goal_uuid, + strategy_id=related_strategy_uuid, # Dirty trick ... + strategy_uuid=related_strategy_uuid, + version=self.version, + ) + + @staticmethod + def validate(audit_template): + available_goals = objects.Goal.list(AuditTemplatePostType._ctx) + available_goals_map = {g.uuid: g for g in available_goals} + if audit_template.goal_uuid not in available_goals_map: + raise exception.InvalidGoal(goal=audit_template.goal_uuid) + + if audit_template.strategy_uuid: + available_strategies = objects.Strategy.list( + AuditTemplatePostType._ctx) + available_strategies_map = { + s.uuid: s for s in available_strategies} + if audit_template.strategy_uuid not in available_strategies_map: + raise exception.InvalidStrategy( + strategy=audit_template.strategy_uuid) + + goal = available_goals_map[audit_template.goal_uuid] + strategy = available_strategies_map[audit_template.strategy_uuid] + # Check that the strategy we indicate is actually related to the + # specified goal + if strategy.goal_id != goal.id: + choices = ["'%s' (%s)" % (s.uuid, s.name) + for s in available_strategies] + raise exception.InvalidStrategy( + message=_( + "'%(strategy)s' strategy does relate to the " + "'%(goal)s' goal. Possible choices: %(choices)s") + % dict(strategy=strategy.name, goal=goal.name, + choices=", ".join(choices))) + return audit_template + + class AuditTemplatePatchType(types.JsonPatchType): + + _ctx = context_utils.make_context() + @staticmethod def mandatory_attrs(): return [] @staticmethod def validate(patch): - if patch.path == "/goal": + if patch.path == "/goal_uuid" and patch.op != "remove": AuditTemplatePatchType._validate_goal(patch) + elif patch.path == "/goal_uuid" and patch.op == "remove": + raise exception.OperationNotPermitted( + _("Cannot remove 'goal_uuid' attribute " + "from an audit template")) + if patch.path == "/strategy_uuid": + AuditTemplatePatchType._validate_strategy(patch) return types.JsonPatchType.validate(patch) @staticmethod def _validate_goal(patch): - serialized_patch = {'path': patch.path, 'op': patch.op} - if patch.value is not wsme.Unset: - serialized_patch['value'] = patch.value - new_goal = patch.value - if new_goal and new_goal not in cfg.CONF.watcher_goals.goals.keys(): - raise exception.InvalidGoal(goal=new_goal) + patch.path = "/goal_id" + goal_uuid = patch.value + + if goal_uuid: + available_goals = objects.Goal.list( + AuditTemplatePatchType._ctx) + available_goals_map = {g.uuid: g for g in available_goals} + if goal_uuid not in available_goals_map: + raise exception.InvalidGoal(goal=goal_uuid) + + patch.value = available_goals_map[goal_uuid].id + + @staticmethod + def _validate_strategy(patch): + patch.path = "/strategy_id" + strategy_uuid = patch.value + if strategy_uuid: + available_strategies = objects.Strategy.list( + AuditTemplatePatchType._ctx) + available_strategies_map = { + s.uuid: s for s in available_strategies} + if strategy_uuid not in available_strategies_map: + raise exception.InvalidStrategy(strategy=strategy_uuid) + + patch.value = available_strategies_map[strategy_uuid].id class AuditTemplate(base.APIBase): @@ -96,6 +200,65 @@ class AuditTemplate(base.APIBase): audit template. """ + _goal_uuid = None + _strategy_uuid = None + + def _get_goal(self, value): + if value == wtypes.Unset: + return None + goal = None + try: + if (common_utils.is_uuid_like(value) or + common_utils.is_int_like(value)): + goal = objects.Goal.get( + pecan.request.context, value) + else: + goal = objects.Goal.get_by_name( + pecan.request.context, value) + except exception.GoalNotFound: + pass + if goal: + self.goal_id = goal.id + return goal + + def _get_strategy(self, value): + if value == wtypes.Unset: + return None + strategy = None + try: + if (common_utils.is_uuid_like(value) or + common_utils.is_int_like(value)): + strategy = objects.Strategy.get( + pecan.request.context, value) + else: + strategy = objects.Strategy.get_by_name( + pecan.request.context, value) + except exception.StrategyNotFound: + pass + if strategy: + self.strategy_id = strategy.id + return strategy + + def _get_goal_uuid(self): + return self._goal_uuid + + def _set_goal_uuid(self, value): + if value and self._goal_uuid != value: + self._goal_uuid = None + goal = self._get_goal(value) + if goal: + self._goal_uuid = goal.uuid + + def _get_strategy_uuid(self): + return self._strategy_uuid + + def _set_strategy_uuid(self, value): + if value and self._strategy_uuid != value: + self._strategy_uuid = None + strategy = self._get_strategy(value) + if strategy: + self._strategy_uuid = strategy.uuid + uuid = wtypes.wsattr(types.uuid, readonly=True) """Unique UUID for this audit template""" @@ -114,8 +277,13 @@ class AuditTemplate(base.APIBase): extra = {wtypes.text: types.jsontype} """The metadata of the audit template""" - goal = wtypes.text - """Goal type of the audit template""" + goal_uuid = wsme.wsproperty( + wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True) + """Goal UUID of the audit template""" + + strategy_uuid = wsme.wsproperty( + wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False) + """Strategy UUID the audit template""" version = wtypes.text """Internal version of the audit template""" @@ -128,20 +296,38 @@ class AuditTemplate(base.APIBase): def __init__(self, **kwargs): super(AuditTemplate, self).__init__() - self.fields = [] - for field in objects.AuditTemplate.fields: + fields = list(objects.AuditTemplate.fields) + + for k in fields: # Skip fields we do not expose. - if not hasattr(self, field): + if not hasattr(self, k): continue - self.fields.append(field) - setattr(self, field, kwargs.get(field, wtypes.Unset)) + self.fields.append(k) + setattr(self, k, kwargs.get(k, wtypes.Unset)) + + self.fields.append('goal_id') + self.fields.append('strategy_id') + + # goal_uuid & strategy_uuid are not part of + # objects.AuditTemplate.fields because they're API-only attributes. + self.fields.append('goal_uuid') + self.fields.append('strategy_uuid') + setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset)) + setattr(self, 'strategy_uuid', + kwargs.get('strategy_id', wtypes.Unset)) @staticmethod def _convert_with_links(audit_template, url, expand=True): if not expand: - audit_template.unset_fields_except(['uuid', 'name', - 'host_aggregate', 'goal']) + audit_template.unset_fields_except( + ['uuid', 'name', 'host_aggregate', + 'goal_uuid', 'strategy_uuid']) + + # The numeric ID should not be exposed to + # the user, it's internal only. + audit_template.goal_id = wtypes.Unset + audit_template.strategy_id = wtypes.Unset audit_template.links = [link.Link.make_link('self', url, 'audit_templates', @@ -149,8 +335,7 @@ class AuditTemplate(base.APIBase): link.Link.make_link('bookmark', url, 'audit_templates', audit_template.uuid, - bookmark=True) - ] + bookmark=True)] return audit_template @classmethod @@ -165,19 +350,14 @@ class AuditTemplate(base.APIBase): name='My Audit Template', description='Description of my audit template', host_aggregate=5, - goal='DUMMY', + goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6', + strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986', extra={'automatic': True}, created_at=datetime.datetime.utcnow(), deleted_at=None, updated_at=datetime.datetime.utcnow()) return cls._convert_with_links(sample, 'http://localhost:9322', expand) - @staticmethod - def validate(audit_template): - if audit_template.goal not in cfg.CONF.watcher_goals.goals.keys(): - raise exception.InvalidGoal(audit_template.goal) - return audit_template - class AuditTemplateCollection(collection.Collection): """API representation of a collection of audit templates.""" @@ -192,12 +372,12 @@ class AuditTemplateCollection(collection.Collection): @staticmethod def convert_with_links(rpc_audit_templates, limit, url=None, expand=False, **kwargs): - collection = AuditTemplateCollection() - collection.audit_templates = \ - [AuditTemplate.convert_with_links(p, expand) - for p in rpc_audit_templates] - collection.next = collection.get_next(limit, url=url, **kwargs) - return collection + at_collection = AuditTemplateCollection() + at_collection.audit_templates = [ + AuditTemplate.convert_with_links(p, expand) + for p in rpc_audit_templates] + at_collection.next = at_collection.get_next(limit, url=url, **kwargs) + return at_collection @classmethod def sample(cls): @@ -223,7 +403,8 @@ class AuditTemplatesController(rest.RestController): sort_key, sort_dir, expand=False, resource_url=None): api_utils.validate_search_filters( - filters, objects.audit_template.AuditTemplate.fields.keys()) + filters, list(objects.audit_template.AuditTemplate.fields.keys()) + + ["goal_uuid", "strategy_uuid"]) limit = api_utils.validate_limit(limit) api_utils.validate_sort_dir(sort_dir) @@ -247,30 +428,33 @@ class AuditTemplatesController(rest.RestController): sort_key=sort_key, sort_dir=sort_dir) - @wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, + @wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, types.uuid, types.uuid, int, wtypes.text, wtypes.text) - def get_all(self, goal=None, marker=None, limit=None, - sort_key='id', sort_dir='asc'): + def get_all(self, goal_uuid=None, strategy_uuid=None, marker=None, + limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of audit templates. - :param goal: goal name to filter by (case sensitive) + :param goal_uuid: goal UUID to filter by + :param strategy_uuid: strategy UUID to filter by :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ - filters = api_utils.as_filters_dict(goal=goal) + filters = api_utils.as_filters_dict(goal_uuid=goal_uuid, + strategy_uuid=strategy_uuid) return self._get_audit_templates_collection( filters, marker, limit, sort_key, sort_dir) - @wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, types.uuid, int, - wtypes.text, wtypes.text) - def detail(self, goal=None, marker=None, limit=None, - sort_key='id', sort_dir='asc'): + @wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, types.uuid, + types.uuid, int, wtypes.text, wtypes.text) + def detail(self, goal_uuid=None, strategy_uuid=None, marker=None, + limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of audit templates with detail. - :param goal: goal name to filter by (case sensitive) + :param goal_uuid: goal UUID to filter by + :param strategy_uuid: strategy UUID to filter by :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. @@ -281,7 +465,8 @@ class AuditTemplatesController(rest.RestController): if parent != "audit_templates": raise exception.HTTPNotFound - filters = api_utils.as_filters_dict(goal=goal) + filters = api_utils.as_filters_dict(goal_uuid=goal_uuid, + strategy_uuid=strategy_uuid) expand = True resource_url = '/'.join(['audit_templates', 'detail']) @@ -309,19 +494,21 @@ class AuditTemplatesController(rest.RestController): return AuditTemplate.convert_with_links(rpc_audit_template) - @wsme.validate(types.uuid, AuditTemplate) - @wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201) - def post(self, audit_template): + @wsme.validate(types.uuid, AuditTemplatePostType) + @wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplatePostType, + status_code=201) + def post(self, audit_template_postdata): """Create a new audit template. - :param audit template: a audit template within the request body. + :param audit_template_postdata: the audit template POST data + from the request body. """ - if self.from_audit_templates: raise exception.OperationNotPermitted - audit_template_dict = audit_template.as_dict() context = pecan.request.context + audit_template = audit_template_postdata.as_audit_template() + audit_template_dict = audit_template.as_dict() new_audit_template = objects.AuditTemplate(context, **audit_template_dict) new_audit_template.create(context) diff --git a/watcher/common/exception.py b/watcher/common/exception.py index a3b497b22..8572ae8ef 100644 --- a/watcher/common/exception.py +++ b/watcher/common/exception.py @@ -154,6 +154,10 @@ class InvalidGoal(Invalid): msg_fmt = _("Goal %(goal)s is invalid") +class InvalidStrategy(Invalid): + msg_fmt = _("Strategy %(strategy)s is invalid") + + class InvalidUUID(Invalid): msg_fmt = _("Expected a uuid but received %(uuid)s") diff --git a/watcher/db/sqlalchemy/api.py b/watcher/db/sqlalchemy/api.py index 3eca71bc1..d1e88c54a 100644 --- a/watcher/db/sqlalchemy/api.py +++ b/watcher/db/sqlalchemy/api.py @@ -289,23 +289,18 @@ class Connection(api.BaseConnection): def _add_audit_templates_filters(self, query, filters): if filters is None: - filters = [] + filters = {} - if 'uuid' in filters: - query = query.filter_by(uuid=filters['uuid']) - if 'name' in filters: - query = query.filter_by(name=filters['name']) - if 'host_aggregate' in filters: - query = query.filter_by(host_aggregate=filters['host_aggregate']) - if 'goal' in filters: - query = query.filter_by(goal=filters['goal']) + plain_fields = ['uuid', 'name', 'host_aggregate', + 'goal_id', 'strategy_id'] + join_fieldmap = { + 'goal_uuid': ("uuid", models.Goal), + 'strategy_uuid': ("uuid", models.Strategy), + } - query = self.__add_soft_delete_mixin_filters( - query, filters, models.AuditTemplate) - query = self.__add_timestamp_mixin_filters( - query, filters, models.AuditTemplate) - - return query + return self._add_filters( + query=query, model=models.AuditTemplate, filters=filters, + plain_fields=plain_fields, join_fieldmap=join_fieldmap) def _add_audits_filters(self, query, filters): if filters is None: @@ -522,7 +517,7 @@ class Connection(api.BaseConnection): try: self._soft_delete(models.Strategy, strategy_id) except exception.ResourceNotFound: - raise exception.GoalNotFound(strategy=strategy_id) + raise exception.StrategyNotFound(strategy=strategy_id) # ### AUDIT TEMPLATES ### # @@ -558,98 +553,50 @@ class Connection(api.BaseConnection): name=values['name']) return audit_template - def get_audit_template_by_id(self, context, audit_template_id): - query = model_query(models.AuditTemplate) - query = query.filter_by(id=audit_template_id) + def _get_audit_template(self, context, fieldname, value): try: - audit_template = query.one() - if not context.show_deleted: - if audit_template.deleted_at is not None: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_id) - return audit_template - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_id) + return self._get(context, model=models.AuditTemplate, + fieldname=fieldname, value=value) + except exception.ResourceNotFound: + raise exception.AuditTemplateNotFound(audit_template=value) + + def get_audit_template_by_id(self, context, audit_template_id): + return self._get_audit_template( + context, fieldname="id", value=audit_template_id) def get_audit_template_by_uuid(self, context, audit_template_uuid): - query = model_query(models.AuditTemplate) - query = query.filter_by(uuid=audit_template_uuid) - - try: - audit_template = query.one() - if not context.show_deleted: - if audit_template.deleted_at is not None: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_uuid) - return audit_template - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_uuid) + return self._get_audit_template( + context, fieldname="uuid", value=audit_template_uuid) def get_audit_template_by_name(self, context, audit_template_name): - query = model_query(models.AuditTemplate) - query = query.filter_by(name=audit_template_name, - deleted_at=None) - try: - return query.one() - except exc.MultipleResultsFound: - raise exception.Conflict( - _('Multiple audit templates exist with the same name.' - ' Please use the audit template uuid instead')) - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_name) + return self._get_audit_template( + context, fieldname="name", value=audit_template_name) def destroy_audit_template(self, audit_template_id): - session = get_session() - with session.begin(): - query = model_query(models.AuditTemplate, session=session) - query = add_identity_filter(query, audit_template_id) - - try: - query.one() - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_id) - - query.delete() + try: + return self._destroy(models.AuditTemplate, audit_template_id) + except exception.ResourceNotFound: + raise exception.AuditTemplateNotFound( + audit_template=audit_template_id) def update_audit_template(self, audit_template_id, values): if 'uuid' in values: raise exception.Invalid( message=_("Cannot overwrite UUID for an existing " "Audit Template.")) - - return self._do_update_audit_template(audit_template_id, values) - - def _do_update_audit_template(self, audit_template_id, values): - session = get_session() - with session.begin(): - query = model_query(models.AuditTemplate, session=session) - query = add_identity_filter(query, audit_template_id) - try: - ref = query.with_lockmode('update').one() - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_id) - - ref.update(values) - return ref + try: + return self._update( + models.AuditTemplate, audit_template_id, values) + except exception.ResourceNotFound: + raise exception.AuditTemplateNotFound( + audit_template=audit_template_id) def soft_delete_audit_template(self, audit_template_id): - session = get_session() - with session.begin(): - query = model_query(models.AuditTemplate, session=session) - query = add_identity_filter(query, audit_template_id) - - try: - query.one() - except exc.NoResultFound: - raise exception.AuditTemplateNotFound( - audit_template=audit_template_id) - - query.soft_delete() + try: + self._soft_delete(models.AuditTemplate, audit_template_id) + except exception.ResourceNotFound: + raise exception.AuditTemplateNotFound( + audit_template=audit_template_id) # ### AUDITS ### # diff --git a/watcher/db/sqlalchemy/models.py b/watcher/db/sqlalchemy/models.py index 89171c8f5..d3379ec0a 100644 --- a/watcher/db/sqlalchemy/models.py +++ b/watcher/db/sqlalchemy/models.py @@ -152,7 +152,8 @@ class AuditTemplate(Base): name = Column(String(63), nullable=True) description = Column(String(255), nullable=True) host_aggregate = Column(Integer, nullable=True) - goal = Column(String(63), nullable=True) + goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False) + strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True) extra = Column(JSONEncodedDict) version = Column(String(15), nullable=True) diff --git a/watcher/decision_engine/strategy/context/default.py b/watcher/decision_engine/strategy/context/default.py index 174693cf8..c2e1e204f 100644 --- a/watcher/decision_engine/strategy/context/default.py +++ b/watcher/decision_engine/strategy/context/default.py @@ -52,8 +52,8 @@ class DefaultStrategyContext(base.BaseStrategyContext): cluster_data_model = collector_manager.get_latest_cluster_data_model() strategy_selector = default.DefaultStrategySelector( - goal_name=objects.Goal.get_by_name( - request_context, audit_template.goal).name, + goal_name=objects.Goal.get_by_id( + request_context, audit_template.goal_id).name, strategy_name=None, osc=osc) diff --git a/watcher/objects/audit_template.py b/watcher/objects/audit_template.py index 95b026754..25d3773cf 100644 --- a/watcher/objects/audit_template.py +++ b/watcher/objects/audit_template.py @@ -65,7 +65,8 @@ class AuditTemplate(base.WatcherObject): 'uuid': obj_utils.str_or_none, 'name': obj_utils.str_or_none, 'description': obj_utils.str_or_none, - 'goal': obj_utils.str_or_none, + 'goal_id': obj_utils.int_or_none, + 'strategy_id': obj_utils.int_or_none, 'host_aggregate': obj_utils.int_or_none, 'extra': obj_utils.dict_or_none, 'version': obj_utils.str_or_none, @@ -83,8 +84,7 @@ class AuditTemplate(base.WatcherObject): @staticmethod def _from_db_object_list(db_objects, cls, context): """Converts a list of database entities to a list of formal objects.""" - return \ - [AuditTemplate._from_db_object(cls(context), obj) + return [AuditTemplate._from_db_object(cls(context), obj) for obj in db_objects] @classmethod diff --git a/watcher/tests/api/v1/test_audit_templates.py b/watcher/tests/api/v1/test_audit_templates.py index 643bdc6e8..a87651df4 100644 --- a/watcher/tests/api/v1/test_audit_templates.py +++ b/watcher/tests/api/v1/test_audit_templates.py @@ -21,18 +21,30 @@ from wsme import types as wtypes from watcher.api.controllers.v1 import audit_template as api_audit_template from watcher.common import exception from watcher.common import utils -from watcher.db import api as db_api from watcher import objects from watcher.tests.api import base as api_base from watcher.tests.api import utils as api_utils from watcher.tests import base +from watcher.tests.db import utils as db_utils from watcher.tests.objects import utils as obj_utils +def post_get_test_audit_template(**kw): + audit_template = api_utils.audit_template_post_data(**kw) + goal = db_utils.get_test_goal() + strategy = db_utils.get_test_strategy(goal_id=goal['id']) + del audit_template['uuid'] + del audit_template['goal_id'] + del audit_template['strategy_id'] + audit_template['goal_uuid'] = kw.get('goal_uuid', goal['uuid']) + audit_template['strategy_uuid'] = kw.get('strategy_uuid', strategy['uuid']) + return audit_template + + class TestAuditTemplateObject(base.TestCase): def test_audit_template_init(self): - audit_template_dict = api_utils.audit_template_post_data() + audit_template_dict = post_get_test_audit_template() del audit_template_dict['name'] audit_template = api_audit_template.AuditTemplate( **audit_template_dict) @@ -41,12 +53,21 @@ class TestAuditTemplateObject(base.TestCase): class TestListAuditTemplate(api_base.FunctionalTest): + def setUp(self): + super(self.__class__, self).setUp() + self.fake_goal1 = obj_utils.get_test_goal( + self.context, id=1, uuid=utils.generate_uuid(), name="DUMMY_1") + self.fake_goal2 = obj_utils.get_test_goal( + self.context, id=2, uuid=utils.generate_uuid(), name="DUMMY_2") + self.fake_goal1.create() + self.fake_goal2.create() + def test_empty(self): response = self.get_json('/audit_templates') self.assertEqual([], response['audit_templates']) def _assert_audit_template_fields(self, audit_template): - audit_template_fields = ['name', 'goal', 'host_aggregate'] + audit_template_fields = ['name', 'goal_uuid', 'host_aggregate'] for field in audit_template_fields: self.assertIn(field, audit_template) @@ -57,7 +78,7 @@ class TestListAuditTemplate(api_base.FunctionalTest): response['audit_templates'][0]["uuid"]) self._assert_audit_template_fields(response['audit_templates'][0]) - def test_one_soft_deleted(self): + def test_get_one_soft_deleted_ok(self): audit_template = obj_utils.create_test_audit_template(self.context) audit_template.soft_delete() response = self.get_json('/audit_templates', @@ -208,23 +229,21 @@ class TestListAuditTemplate(api_base.FunctionalTest): next_marker = response['audit_templates'][-1]['uuid'] self.assertIn(next_marker, response['next']) - def test_filter_by_goal(self): - cfg.CONF.set_override('goals', {"DUMMY": "DUMMY", "BASIC": "BASIC"}, - group='watcher_goals', enforce_type=True) - - for id_ in range(2): + def test_filter_by_goal_uuid(self): + for id_ in range(1, 3): obj_utils.create_test_audit_template( self.context, id=id_, uuid=utils.generate_uuid(), name='My Audit Template {0}'.format(id_), - goal="DUMMY") + goal_id=self.fake_goal1.id) - for id_ in range(2, 5): + for id_ in range(3, 6): obj_utils.create_test_audit_template( self.context, id=id_, uuid=utils.generate_uuid(), name='My Audit Template {0}'.format(id_), - goal="BASIC") + goal_id=self.fake_goal2.id) - response = self.get_json('/audit_templates?goal=BASIC') + response = self.get_json( + '/audit_templates?goal_uuid=%s' % self.fake_goal2.uuid) self.assertEqual(3, len(response['audit_templates'])) @@ -233,63 +252,66 @@ class TestPatch(api_base.FunctionalTest): def setUp(self): super(TestPatch, self).setUp() self.audit_template = obj_utils.create_test_audit_template( - self.context) - p = mock.patch.object(db_api.BaseConnection, 'update_audit_template') - self.mock_audit_template_update = p.start() - self.mock_audit_template_update.side_effect = \ - self._simulate_rpc_audit_template_update - cfg.CONF.set_override('goals', {"DUMMY": "DUMMY", "BASIC": "BASIC"}, - group='watcher_goals', enforce_type=True) - self.addCleanup(p.stop) + self.context, strategy_id=None) + self.fake_goal1 = obj_utils.get_test_goal( + self.context, id=1, uuid=utils.generate_uuid(), name="DUMMY_1") + self.fake_goal2 = obj_utils.get_test_goal( + self.context, id=2, uuid=utils.generate_uuid(), name="DUMMY_2") + self.fake_goal1.create() + self.fake_goal2.create() + self.fake_strategy1 = obj_utils.get_test_strategy( + self.context, id=1, uuid=utils.generate_uuid(), name="STRATEGY_1", + goal_id=self.fake_goal1.id) + self.fake_strategy2 = obj_utils.get_test_strategy( + self.context, id=2, uuid=utils.generate_uuid(), name="STRATEGY_2", + goal_id=self.fake_goal2.id) + self.fake_strategy1.create() + self.fake_strategy2.create() - def _simulate_rpc_audit_template_update(self, audit_template): - audit_template.save() - return audit_template - - @mock.patch('oslo_utils.timeutils.utcnow') - def test_replace_ok(self, mock_utcnow): + @mock.patch.object(timeutils, 'utcnow') + def test_replace_goal_uuid(self, mock_utcnow): test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time - new_goal = "BASIC" + new_goal_uuid = self.fake_goal2.uuid response = self.get_json( '/audit_templates/%s' % self.audit_template.uuid) - self.assertNotEqual(new_goal, response['goal']) + self.assertNotEqual(new_goal_uuid, response['goal_uuid']) response = self.patch_json( '/audit_templates/%s' % self.audit_template.uuid, - [{'path': '/goal', 'value': new_goal, - 'op': 'replace'}]) + [{'path': '/goal_uuid', 'value': new_goal_uuid, + 'op': 'replace'}]) self.assertEqual('application/json', response.content_type) self.assertEqual(200, response.status_code) response = self.get_json( '/audit_templates/%s' % self.audit_template.uuid) - self.assertEqual(new_goal, response['goal']) + self.assertEqual(new_goal_uuid, response['goal_uuid']) return_updated_at = timeutils.parse_isotime( response['updated_at']).replace(tzinfo=None) self.assertEqual(test_time, return_updated_at) - @mock.patch('oslo_utils.timeutils.utcnow') - def test_replace_ok_by_name(self, mock_utcnow): + @mock.patch.object(timeutils, 'utcnow') + def test_replace_goal_uuid_by_name(self, mock_utcnow): test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time - new_goal = 'BASIC' + new_goal_uuid = self.fake_goal2.uuid response = self.get_json(urlparse.quote( '/audit_templates/%s' % self.audit_template.name)) - self.assertNotEqual(new_goal, response['goal']) + self.assertNotEqual(new_goal_uuid, response['goal_uuid']) response = self.patch_json( '/audit_templates/%s' % self.audit_template.name, - [{'path': '/goal', 'value': new_goal, - 'op': 'replace'}]) + [{'path': '/goal_uuid', 'value': new_goal_uuid, + 'op': 'replace'}]) self.assertEqual('application/json', response.content_type) self.assertEqual(200, response.status_code) response = self.get_json( '/audit_templates/%s' % self.audit_template.name) - self.assertEqual(new_goal, response['goal']) + self.assertEqual(new_goal_uuid, response['goal_uuid']) return_updated_at = timeutils.parse_isotime( response['updated_at']).replace(tzinfo=None) self.assertEqual(test_time, return_updated_at) @@ -297,8 +319,8 @@ class TestPatch(api_base.FunctionalTest): def test_replace_non_existent_audit_template(self): response = self.patch_json( '/audit_templates/%s' % utils.generate_uuid(), - [{'path': '/goal', 'value': 'DUMMY', - 'op': 'replace'}], + [{'path': '/goal_uuid', 'value': self.fake_goal1.uuid, + 'op': 'replace'}], expect_errors=True) self.assertEqual(404, response.status_int) self.assertEqual('application/json', response.content_type) @@ -312,23 +334,61 @@ class TestPatch(api_base.FunctionalTest): ) as cn_mock: response = self.patch_json( '/audit_templates/%s' % self.audit_template.uuid, - [{'path': '/goal', 'value': 'INVALID_GOAL', + [{'path': '/goal_uuid', 'value': utils.generate_uuid(), 'op': 'replace'}], expect_errors=True) self.assertEqual(400, response.status_int) assert not cn_mock.called - def test_add_ok(self): - new_goal = 'DUMMY' + def test_add_goal_uuid(self): response = self.patch_json( '/audit_templates/%s' % self.audit_template.uuid, - [{'path': '/goal', 'value': new_goal, 'op': 'add'}]) + [{'path': '/goal_uuid', + 'value': self.fake_goal2.uuid, + 'op': 'add'}]) self.assertEqual('application/json', response.content_type) self.assertEqual(200, response.status_int) response = self.get_json( '/audit_templates/%s' % self.audit_template.uuid) - self.assertEqual(new_goal, response['goal']) + self.assertEqual(self.fake_goal2.uuid, response['goal_uuid']) + + def test_add_strategy_uuid(self): + response = self.patch_json( + '/audit_templates/%s' % self.audit_template.uuid, + [{'path': '/strategy_uuid', + 'value': self.fake_strategy1.uuid, + 'op': 'add'}]) + self.assertEqual('application/json', response.content_type) + self.assertEqual(200, response.status_int) + + response = self.get_json( + '/audit_templates/%s' % self.audit_template.uuid) + self.assertEqual(self.fake_strategy1.uuid, response['strategy_uuid']) + + def test_replace_strategy_uuid(self): + response = self.patch_json( + '/audit_templates/%s' % self.audit_template.uuid, + [{'path': '/strategy_uuid', + 'value': self.fake_strategy2['uuid'], + 'op': 'replace'}]) + self.assertEqual('application/json', response.content_type) + self.assertEqual(200, response.status_int) + + response = self.get_json( + '/audit_templates/%s' % self.audit_template.uuid) + self.assertEqual( + self.fake_strategy2['uuid'], response['strategy_uuid']) + + def test_replace_invalid_strategy(self): + response = self.patch_json( + '/audit_templates/%s' % self.audit_template.uuid, + [{'path': '/strategy_uuid', + 'value': utils.generate_uuid(), # Does not exist + 'op': 'replace'}], expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(400, response.status_int) + self.assertTrue(response.json['error_message']) def test_add_non_existent_property(self): response = self.patch_json( @@ -339,20 +399,34 @@ class TestPatch(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertTrue(response.json['error_message']) - def test_remove_ok(self): + def test_remove_strategy(self): + audit_template = obj_utils.create_test_audit_template( + self.context, uuid=utils.generate_uuid(), + name="AT_%s" % utils.generate_uuid(), + goal_id=self.fake_goal1.id, + strategy_id=self.fake_strategy1.id) response = self.get_json( - '/audit_templates/%s' % self.audit_template.uuid) - self.assertIsNotNone(response['goal']) + '/audit_templates/%s' % audit_template.uuid) + self.assertIsNotNone(response['strategy_uuid']) response = self.patch_json( '/audit_templates/%s' % self.audit_template.uuid, - [{'path': '/goal', 'op': 'remove'}]) + [{'path': '/strategy_uuid', 'op': 'remove'}]) self.assertEqual('application/json', response.content_type) self.assertEqual(200, response.status_code) + def test_remove_goal(self): response = self.get_json( '/audit_templates/%s' % self.audit_template.uuid) - self.assertIsNone(response['goal']) + self.assertIsNotNone(response['goal_uuid']) + + response = self.patch_json( + '/audit_templates/%s' % self.audit_template.uuid, + [{'path': '/goal_uuid', 'op': 'remove'}], + expect_errors=True) + self.assertEqual(403, response.status_code) + self.assertEqual('application/json', response.content_type) + self.assertTrue(response.json['error_message']) def test_remove_uuid(self): response = self.patch_json( @@ -377,22 +451,29 @@ class TestPost(api_base.FunctionalTest): def setUp(self): super(TestPost, self).setUp() - p = mock.patch.object(db_api.BaseConnection, 'create_audit_template') - self.mock_create_audit_template = p.start() - self.mock_create_audit_template.side_effect = ( - self._simulate_rpc_audit_template_create) - self.addCleanup(p.stop) - def _simulate_rpc_audit_template_create(self, audit_template): - audit_template.create() - return audit_template + self.fake_goal1 = obj_utils.get_test_goal( + self.context, id=1, uuid=utils.generate_uuid(), name="DUMMY_1") + self.fake_goal2 = obj_utils.get_test_goal( + self.context, id=2, uuid=utils.generate_uuid(), name="DUMMY_2") + self.fake_goal1.create() + self.fake_goal2.create() + self.fake_strategy1 = obj_utils.get_test_strategy( + self.context, id=1, uuid=utils.generate_uuid(), name="STRATEGY_1", + goal_id=self.fake_goal1.id) + self.fake_strategy2 = obj_utils.get_test_strategy( + self.context, id=2, uuid=utils.generate_uuid(), name="STRATEGY_2", + goal_id=self.fake_goal2.id) + self.fake_strategy1.create() + self.fake_strategy2.create() - @mock.patch('oslo_utils.timeutils.utcnow') + @mock.patch.object(timeutils, 'utcnow') def test_create_audit_template(self, mock_utcnow): - audit_template_dict = api_utils.audit_template_post_data() + audit_template_dict = post_get_test_audit_template( + goal_uuid=self.fake_goal1.uuid, + strategy_uuid=self.fake_strategy1.uuid) test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time - del audit_template_dict['uuid'] response = self.post_json('/audit_templates', audit_template_dict) self.assertEqual('application/json', response.content_type) @@ -403,36 +484,37 @@ class TestPost(api_base.FunctionalTest): '/v1/audit_templates/%s' % response.json['uuid'] self.assertEqual(urlparse.urlparse(response.location).path, expected_location) + self.assertTrue(utils.is_uuid_like(response.json['uuid'])) self.assertNotIn('updated_at', response.json.keys) self.assertNotIn('deleted_at', response.json.keys) + self.assertEqual(self.fake_goal1.uuid, response.json['goal_uuid']) + self.assertEqual(self.fake_strategy1.uuid, + response.json['strategy_uuid']) return_created_at = timeutils.parse_isotime( response.json['created_at']).replace(tzinfo=None) self.assertEqual(test_time, return_created_at) - def test_create_audit_template_doesnt_contain_id(self): + def test_create_audit_template_does_autogenerate_id(self): + audit_template_dict = post_get_test_audit_template( + goal_uuid=self.fake_goal1.uuid, strategy_uuid=None) with mock.patch.object( self.dbapi, 'create_audit_template', wraps=self.dbapi.create_audit_template ) as cn_mock: - audit_template_dict = api_utils.audit_template_post_data( - goal='DUMMY') - del audit_template_dict['uuid'] response = self.post_json('/audit_templates', audit_template_dict) - self.assertEqual(audit_template_dict['goal'], - response.json['goal']) - 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]) + self.assertEqual(audit_template_dict['goal_uuid'], + response.json['goal_uuid']) + # Check that 'id' is not in first arg of positional args + self.assertNotIn('id', cn_mock.call_args[0][0]) def test_create_audit_template_generate_uuid(self): - audit_template_dict = api_utils.audit_template_post_data() - del audit_template_dict['uuid'] + audit_template_dict = post_get_test_audit_template( + goal_uuid=self.fake_goal1.uuid, strategy_uuid=None) response = self.post_json('/audit_templates', audit_template_dict) self.assertEqual('application/json', response.content_type) self.assertEqual(201, response.status_int) - self.assertEqual(audit_template_dict['goal'], response.json['goal']) self.assertTrue(utils.is_uuid_like(response.json['uuid'])) def test_create_audit_template_with_invalid_goal(self): @@ -441,8 +523,36 @@ class TestPost(api_base.FunctionalTest): 'create_audit_template', wraps=self.dbapi.create_audit_template ) as cn_mock: - audit_template_dict = api_utils.audit_template_post_data( - goal='INVALID_GOAL') + audit_template_dict = post_get_test_audit_template( + goal_uuid=utils.generate_uuid()) + response = self.post_json('/audit_templates', + audit_template_dict, expect_errors=True) + self.assertEqual(400, response.status_int) + assert not cn_mock.called + + def test_create_audit_template_with_invalid_strategy(self): + with mock.patch.object( + self.dbapi, + 'create_audit_template', + wraps=self.dbapi.create_audit_template + ) as cn_mock: + audit_template_dict = post_get_test_audit_template( + goal_uuid=self.fake_goal1['uuid'], + strategy_uuid=utils.generate_uuid()) + response = self.post_json('/audit_templates', + audit_template_dict, expect_errors=True) + self.assertEqual(400, response.status_int) + assert not cn_mock.called + + def test_create_audit_template_with_unrelated_strategy(self): + with mock.patch.object( + self.dbapi, + 'create_audit_template', + wraps=self.dbapi.create_audit_template + ) as cn_mock: + audit_template_dict = post_get_test_audit_template( + goal_uuid=self.fake_goal1['uuid'], + strategy_uuid=self.fake_strategy2['uuid']) response = self.post_json('/audit_templates', audit_template_dict, expect_errors=True) self.assertEqual(400, response.status_int) @@ -454,8 +564,7 @@ class TestPost(api_base.FunctionalTest): 'create_audit_template', wraps=self.dbapi.create_audit_template ) as cn_mock: - audit_template_dict = api_utils.audit_template_post_data() - + audit_template_dict = post_get_test_audit_template() response = self.post_json('/audit_templates', audit_template_dict, expect_errors=True) self.assertEqual('application/json', response.content_type) @@ -469,54 +578,53 @@ class TestDelete(api_base.FunctionalTest): super(TestDelete, self).setUp() self.audit_template = obj_utils.create_test_audit_template( self.context) - p = mock.patch.object(db_api.BaseConnection, 'update_audit_template') - self.mock_audit_template_update = p.start() - self.mock_audit_template_update.side_effect = \ - self._simulate_rpc_audit_template_update - self.addCleanup(p.stop) - def _simulate_rpc_audit_template_update(self, audit_template): - audit_template.save() - return audit_template - - @mock.patch('oslo_utils.timeutils.utcnow') + @mock.patch.object(timeutils, 'utcnow') def test_delete_audit_template_by_uuid(self, mock_utcnow): - test_time = datetime.datetime(2000, 1, 1, 0, 0) - mock_utcnow.return_value = test_time - self.delete('/audit_templates/%s' % self.audit_template.uuid) - response = self.get_json( - '/audit_templates/%s' % self.audit_template.uuid, - expect_errors=True) - # self.assertEqual(404, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['error_message']) - - self.context.show_deleted = True - audit_template = objects.AuditTemplate.get_by_uuid( - self.context, self.audit_template.uuid) - - return_deleted_at = timeutils.strtime(audit_template['deleted_at']) - self.assertEqual(timeutils.strtime(test_time), return_deleted_at) - - @mock.patch('oslo_utils.timeutils.utcnow') - def test_delete_audit_template_by_name(self, mock_utcnow): test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time self.delete(urlparse.quote('/audit_templates/%s' % - self.audit_template.name)) - response = self.get_json(urlparse.quote( - '/audit_templates/%s' % self.audit_template.name), + self.audit_template.uuid)) + response = self.get_json( + urlparse.quote('/audit_templates/%s' % self.audit_template.uuid), expect_errors=True) self.assertEqual(404, response.status_int) self.assertEqual('application/json', response.content_type) self.assertTrue(response.json['error_message']) + self.assertRaises(exception.AuditTemplateNotFound, + objects.AuditTemplate.get_by_uuid, + self.context, + self.audit_template.uuid) + self.context.show_deleted = True + at = objects.AuditTemplate.get_by_uuid(self.context, + self.audit_template.uuid) + self.assertEqual(self.audit_template.name, at.name) + + @mock.patch.object(timeutils, 'utcnow') + def test_delete_audit_template_by_name(self, mock_utcnow): + test_time = datetime.datetime(2000, 1, 1, 0, 0) + mock_utcnow.return_value = test_time + self.delete(urlparse.quote('/audit_templates/%s' % + self.audit_template.name)) + response = self.get_json( + urlparse.quote('/audit_templates/%s' % self.audit_template.name), + expect_errors=True) + self.assertEqual(404, response.status_int) + self.assertEqual('application/json', response.content_type) + self.assertTrue(response.json['error_message']) + self.assertRaises(exception.AuditTemplateNotFound, objects.AuditTemplate.get_by_name, self.context, self.audit_template.name) + self.context.show_deleted = True + at = objects.AuditTemplate.get_by_name(self.context, + self.audit_template.name) + self.assertEqual(self.audit_template.uuid, at.uuid) + def test_delete_audit_template_not_found(self): uuid = utils.generate_uuid() response = self.delete( diff --git a/watcher/tests/db/sqlalchemy/test_types.py b/watcher/tests/db/sqlalchemy/test_types.py index 701a4f4fd..97c1527ac 100644 --- a/watcher/tests/db/sqlalchemy/test_types.py +++ b/watcher/tests/db/sqlalchemy/test_types.py @@ -30,7 +30,8 @@ class SqlAlchemyCustomTypesTestCase(base.DbTestCase): def test_JSONEncodedDict_default_value(self): # Create audit_template w/o extra audit_template1_id = w_utils.generate_uuid() - self.dbapi.create_audit_template({'uuid': audit_template1_id}) + self.dbapi.create_audit_template({'uuid': audit_template1_id, + 'goal_id': "DUMMY"}) audit_template1 = sa_api.model_query(models.AuditTemplate) \ .filter_by(uuid=audit_template1_id).one() self.assertEqual({}, audit_template1.extra) @@ -39,6 +40,7 @@ class SqlAlchemyCustomTypesTestCase(base.DbTestCase): # Create audit_template with extra audit_template2_id = w_utils.generate_uuid() self.dbapi.create_audit_template({'uuid': audit_template2_id, + 'goal_id': "DUMMY", 'extra': {'bar': 'foo'}}) audit_template2 = sa_api.model_query(models.AuditTemplate) \ .filter_by(uuid=audit_template2_id).one() @@ -48,24 +50,3 @@ class SqlAlchemyCustomTypesTestCase(base.DbTestCase): self.assertRaises(db_exc.DBError, self.dbapi.create_audit_template, {'extra': ['this is not a dict']}) - - # def test_JSONEncodedList_default_value(self): - # # Create audit_template w/o images - # audit_template1_id = w_utils.generate_uuid() - # self.dbapi.create_audit_template({'uuid': audit_template1_id}) - # audit_template1 = sa_api.model_query(models.AuditTemplate) \ - # .filter_by(uuid=audit_template1_id).one() - # self.assertEqual([], audit_template1.images) - - # # Create audit_template with images - # audit_template2_id = w_utils.generate_uuid() - # self.dbapi.create_audit_template({'uuid': audit_template2_id, - # 'images': ['myimage1', 'myimage2']}) - # audit_template2 = sa_api.model_query(models.AuditTemplate) \ - # .filter_by(uuid=audit_template2_id).one() - # self.assertEqual(['myimage1', 'myimage2'], audit_template2.images) - - # def test_JSONEncodedList_type_check(self): - # self.assertRaises(db_exc.DBError, - # self.dbapi.create_audit_template, - # {'images': {'this is not a list': 'test'}}) diff --git a/watcher/tests/db/utils.py b/watcher/tests/db/utils.py index 4816924d2..b29fd6114 100644 --- a/watcher/tests/db/utils.py +++ b/watcher/tests/db/utils.py @@ -21,7 +21,8 @@ def get_test_audit_template(**kwargs): return { 'id': kwargs.get('id', 1), 'uuid': kwargs.get('uuid', 'e74c40e0-d825-11e2-a28f-0800200c9a66'), - 'goal': kwargs.get('goal', 'DUMMY'), + 'goal_id': kwargs.get('goal_id', 1), + 'strategy_id': kwargs.get('strategy_id', None), 'name': kwargs.get('name', 'My Audit Template'), 'description': kwargs.get('description', 'Desc. Of My Audit Template'), 'extra': kwargs.get('extra', {'automatic': False}), diff --git a/watcher/tests/objects/test_audit_template.py b/watcher/tests/objects/test_audit_template.py index 1e378c403..5837508de 100644 --- a/watcher/tests/objects/test_audit_template.py +++ b/watcher/tests/objects/test_audit_template.py @@ -14,10 +14,9 @@ # under the License. import mock -from testtools.matchers import HasLength from watcher.common import exception -# from watcher.common import utils as w_utils +from watcher.common import utils as w_utils from watcher import objects from watcher.tests.db import base from watcher.tests.db import utils @@ -28,6 +27,10 @@ class TestAuditTemplateObject(base.DbTestCase): def setUp(self): super(TestAuditTemplateObject, self).setUp() self.fake_audit_template = utils.get_test_audit_template() + self.fake_goal1 = utils.create_test_goal( + id=1, uuid=w_utils.generate_uuid(), name="DUMMY") + self.fake_goal2 = utils.create_test_goal( + id=2, uuid=w_utils.generate_uuid(), name="BALANCE_LOAD") def test_get_by_id(self): audit_template_id = self.fake_audit_template['id'] @@ -71,7 +74,7 @@ class TestAuditTemplateObject(base.DbTestCase): mock_get_list.return_value = [self.fake_audit_template] audit_templates = objects.AuditTemplate.list(self.context) self.assertEqual(1, mock_get_list.call_count) - self.assertThat(audit_templates, HasLength(1)) + self.assertEqual(1, len(audit_templates)) self.assertIsInstance(audit_templates[0], objects.AuditTemplate) self.assertEqual(self.context, audit_templates[0]._context) @@ -112,29 +115,29 @@ class TestAuditTemplateObject(base.DbTestCase): as mock_update_audit_template: audit_template = objects.AuditTemplate.get_by_uuid( self.context, uuid) - audit_template.goal = 'DUMMY' + audit_template.goal_id = self.fake_goal1.id audit_template.save() mock_get_audit_template.assert_called_once_with( self.context, uuid) mock_update_audit_template.assert_called_once_with( - uuid, {'goal': 'DUMMY'}) + uuid, {'goal_id': self.fake_goal1.id}) self.assertEqual(self.context, audit_template._context) def test_refresh(self): uuid = self.fake_audit_template['uuid'] returns = [dict(self.fake_audit_template, - goal="DUMMY"), - dict(self.fake_audit_template, goal="BALANCE_LOAD")] + goal_id=self.fake_goal1.id), + dict(self.fake_audit_template, goal_id=self.fake_goal2.id)] expected = [mock.call(self.context, uuid), mock.call(self.context, uuid)] with mock.patch.object(self.dbapi, 'get_audit_template_by_uuid', side_effect=returns, autospec=True) as mock_get_audit_template: audit_template = objects.AuditTemplate.get(self.context, uuid) - self.assertEqual("DUMMY", audit_template.goal) + self.assertEqual(1, audit_template.goal_id) audit_template.refresh() - self.assertEqual("BALANCE_LOAD", audit_template.goal) + self.assertEqual(2, audit_template.goal_id) self.assertEqual(expected, mock_get_audit_template.call_args_list) self.assertEqual(self.context, audit_template._context) diff --git a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py index 8d8420e8e..725910d98 100644 --- a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py +++ b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py @@ -61,8 +61,10 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient): :param name: The name of the audit template. Default: My Audit Template :param description: The description of the audit template. Default: AT Description - :param goal: The goal associated within the audit template. - Default: DUMMY + :param goal_uuid: The related Goal UUID associated. + Default: None + :param strategy_uuid: The related Strategy UUID associated. + Default: None :param host_aggregate: ID of the host aggregate targeted by this audit template. Default: 1 :param extra: IMetadata associated to this audit template. @@ -77,8 +79,9 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient): audit_template = { 'name': parameters.get('name', unique_name), - 'description': parameters.get('description', ''), - 'goal': parameters.get('goal', 'DUMMY'), + 'description': parameters.get('description'), + 'goal_uuid': parameters.get('goal_uuid'), + 'strategy_uuid': parameters.get('strategy_uuid'), 'host_aggregate': parameters.get('host_aggregate', 1), 'extra': parameters.get('extra', {}), } diff --git a/watcher_tempest_plugin/tests/api/admin/base.py b/watcher_tempest_plugin/tests/api/admin/base.py index c8c3c0e9d..f7602b729 100644 --- a/watcher_tempest_plugin/tests/api/admin/base.py +++ b/watcher_tempest_plugin/tests/api/admin/base.py @@ -114,15 +114,18 @@ class BaseInfraOptimTest(test.BaseTestCase): # ### AUDIT TEMPLATES ### # @classmethod - def create_audit_template(cls, name=None, description=None, goal=None, - host_aggregate=None, extra=None): + def create_audit_template(cls, goal_uuid, name=None, description=None, + strategy_uuid=None, host_aggregate=None, + extra=None): """Wrapper utility for creating a test audit template + :param goal_uuid: The goal UUID related to the audit template. + Default: DUMMY :param name: The name of the audit template. Default: My Audit Template :param description: The description of the audit template. Default: AT Description - :param goal: The goal associated within the audit template. - Default: DUMMY + :param strategy_uuid: The strategy UUID related to the audit template. + Default: dummy :param host_aggregate: ID of the host aggregate targeted by this audit template. Default: 1 :param extra: IMetadata associated to this audit template. @@ -132,8 +135,9 @@ class BaseInfraOptimTest(test.BaseTestCase): description = description or data_utils.rand_name( 'test-audit_template') resp, body = cls.client.create_audit_template( - name=name, description=description, goal=goal, - host_aggregate=host_aggregate, extra=extra) + name=name, description=description, goal_uuid=goal_uuid, + strategy_uuid=strategy_uuid, host_aggregate=host_aggregate, + extra=extra) cls.created_audit_templates.add(body['uuid']) diff --git a/watcher_tempest_plugin/tests/api/admin/test_action.py b/watcher_tempest_plugin/tests/api/admin/test_action.py index 36e07cc71..bc1ba409a 100644 --- a/watcher_tempest_plugin/tests/api/admin/test_action.py +++ b/watcher_tempest_plugin/tests/api/admin/test_action.py @@ -30,7 +30,8 @@ class TestShowListAction(base.BaseInfraOptimTest): @classmethod def resource_setup(cls): super(TestShowListAction, cls).resource_setup() - _, cls.audit_template = cls.create_audit_template() + _, cls.goal = cls.client.show_goal("DUMMY") + _, cls.audit_template = cls.create_audit_template(cls.goal['uuid']) _, cls.audit = cls.create_audit(cls.audit_template['uuid']) assert test.call_until_true( diff --git a/watcher_tempest_plugin/tests/api/admin/test_action_plan.py b/watcher_tempest_plugin/tests/api/admin/test_action_plan.py index da0a85b66..9bc4add8d 100644 --- a/watcher_tempest_plugin/tests/api/admin/test_action_plan.py +++ b/watcher_tempest_plugin/tests/api/admin/test_action_plan.py @@ -29,7 +29,8 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_action_plan(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) _, audit = self.create_audit(audit_template['uuid']) self.assertTrue(test.call_until_true( @@ -48,7 +49,8 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_delete_action_plan(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) _, audit = self.create_audit(audit_template['uuid']) self.assertTrue(test.call_until_true( @@ -69,7 +71,8 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_execute_dummy_action_plan(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) _, audit = self.create_audit(audit_template['uuid']) self.assertTrue(test.call_until_true( @@ -107,7 +110,8 @@ class TestShowListActionPlan(base.BaseInfraOptimTest): @classmethod def resource_setup(cls): super(TestShowListActionPlan, cls).resource_setup() - _, cls.audit_template = cls.create_audit_template() + _, cls.goal = cls.client.show_goal("DUMMY") + _, cls.audit_template = cls.create_audit_template(cls.goal['uuid']) _, cls.audit = cls.create_audit(cls.audit_template['uuid']) assert test.call_until_true( diff --git a/watcher_tempest_plugin/tests/api/admin/test_audit.py b/watcher_tempest_plugin/tests/api/admin/test_audit.py index 4a1bfef5b..408eaaed0 100644 --- a/watcher_tempest_plugin/tests/api/admin/test_audit.py +++ b/watcher_tempest_plugin/tests/api/admin/test_audit.py @@ -33,7 +33,8 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_oneshot(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) audit_params = dict( audit_template_uuid=audit_template['uuid'], @@ -48,7 +49,8 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_continuous(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) audit_params = dict( audit_template_uuid=audit_template['uuid'], @@ -73,7 +75,8 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_with_invalid_state(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) audit_params = dict( audit_template_uuid=audit_template['uuid'], @@ -85,7 +88,8 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_with_no_state(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) audit_params = dict( audit_template_uuid=audit_template['uuid'], @@ -104,7 +108,8 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_delete_audit(self): - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) _, body = self.create_audit(audit_template['uuid']) audit_uuid = body['uuid'] @@ -123,7 +128,8 @@ class TestShowListAudit(base.BaseInfraOptimTest): @classmethod def resource_setup(cls): super(TestShowListAudit, cls).resource_setup() - _, cls.audit_template = cls.create_audit_template() + _, cls.goal = cls.client.show_goal("DUMMY") + _, cls.audit_template = cls.create_audit_template(cls.goal['uuid']) _, cls.audit = cls.create_audit(cls.audit_template['uuid']) def assert_expected(self, expected, actual, diff --git a/watcher_tempest_plugin/tests/api/admin/test_audit_template.py b/watcher_tempest_plugin/tests/api/admin/test_audit_template.py index 2bddbc644..9c86b24c4 100644 --- a/watcher_tempest_plugin/tests/api/admin/test_audit_template.py +++ b/watcher_tempest_plugin/tests/api/admin/test_audit_template.py @@ -29,10 +29,12 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_template(self): + _, goal = self.client.show_goal("DUMMY") + params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my at description', 'host_aggregate': 12, - 'goal': 'DUMMY', + 'goal_uuid': goal['uuid'], 'extra': {'str': 'value', 'int': 123, 'float': 0.123, 'bool': True, 'list': [1, 2, 3], 'dict': {'foo': 'bar'}}} @@ -45,11 +47,13 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_create_audit_template_unicode_description(self): + _, goal = self.client.show_goal("DUMMY") + # Use a unicode string for testing: params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my àt déscrïptïôn', 'host_aggregate': 12, - 'goal': 'DUMMY', + 'goal_uuid': goal['uuid'], 'extra': {'foo': 'bar'}} _, body = self.create_audit_template(**params) @@ -60,7 +64,8 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_delete_audit_template(self): - _, body = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, body = self.create_audit_template(goal_uuid=goal['uuid']) audit_uuid = body['uuid'] self.delete_audit_template(audit_uuid) @@ -75,7 +80,10 @@ class TestAuditTemplate(base.BaseInfraOptimTest): @classmethod def resource_setup(cls): super(TestAuditTemplate, cls).resource_setup() - _, cls.audit_template = cls.create_audit_template() + _, cls.goal = cls.client.show_goal("DUMMY") + _, cls.strategy = cls.client.show_strategy("dummy") + _, cls.audit_template = cls.create_audit_template( + goal_uuid=cls.goal['uuid'], strategy_uuid=cls.strategy['uuid']) @test.attr(type='smoke') def test_show_audit_template(self): @@ -85,9 +93,18 @@ class TestAuditTemplate(base.BaseInfraOptimTest): self.assert_expected(self.audit_template, audit_template) @test.attr(type='smoke') - def test_filter_audit_template_by_goal(self): + def test_filter_audit_template_by_goal_uuid(self): _, audit_templates = self.client.list_audit_templates( - goal=self.audit_template['goal']) + goal_uuid=self.audit_template['goal_uuid']) + + audit_template_uuids = [ + at["uuid"] for at in audit_templates['audit_templates']] + self.assertIn(self.audit_template['uuid'], audit_template_uuids) + + @test.attr(type='smoke') + def test_filter_audit_template_by_strategy_uuid(self): + _, audit_templates = self.client.list_audit_templates( + strategy_uuid=self.audit_template['strategy_uuid']) audit_template_uuids = [ at["uuid"] for at in audit_templates['audit_templates']] @@ -116,7 +133,7 @@ class TestAuditTemplate(base.BaseInfraOptimTest): def test_list_with_limit(self): # We create 3 extra audit templates to exceed the limit we fix for _ in range(3): - self.create_audit_template() + self.create_audit_template(self.goal['uuid']) _, body = self.client.list_audit_templates(limit=3) @@ -126,10 +143,13 @@ class TestAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_update_audit_template_replace(self): + _, new_goal = self.client.show_goal("SERVER_CONSOLIDATION") + _, new_strategy = self.client.show_strategy("basic") + params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my at description', 'host_aggregate': 12, - 'goal': 'DUMMY', + 'goal_uuid': self.goal['uuid'], 'extra': {'key1': 'value1', 'key2': 'value2'}} _, body = self.create_audit_template(**params) @@ -137,7 +157,6 @@ class TestAuditTemplate(base.BaseInfraOptimTest): new_name = 'my at new name %s' % uuid.uuid4() new_description = 'my new at description' new_host_aggregate = 10 - new_goal = 'SERVER_CONSOLIDATION' new_extra = {'key1': 'new-value1', 'key2': 'new-value2'} patch = [{'path': '/name', @@ -149,9 +168,12 @@ class TestAuditTemplate(base.BaseInfraOptimTest): {'path': '/host_aggregate', 'op': 'replace', 'value': new_host_aggregate}, - {'path': '/goal', + {'path': '/goal_uuid', 'op': 'replace', - 'value': new_goal}, + 'value': new_goal['uuid']}, + {'path': '/strategy_uuid', + 'op': 'replace', + 'value': new_strategy['uuid']}, {'path': '/extra/key1', 'op': 'replace', 'value': new_extra['key1']}, @@ -165,19 +187,19 @@ class TestAuditTemplate(base.BaseInfraOptimTest): self.assertEqual(new_name, body['name']) self.assertEqual(new_description, body['description']) self.assertEqual(new_host_aggregate, body['host_aggregate']) - self.assertEqual(new_goal, body['goal']) + self.assertEqual(new_goal['uuid'], body['goal_uuid']) + self.assertEqual(new_strategy['uuid'], body['strategy_uuid']) self.assertEqual(new_extra, body['extra']) @test.attr(type='smoke') def test_update_audit_template_remove(self): extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} description = 'my at description' - goal = 'DUMMY' name = 'my at name %s' % uuid.uuid4() params = {'name': name, 'description': description, 'host_aggregate': 12, - 'goal': goal, + 'goal_uuid': self.goal['uuid'], 'extra': extra} _, audit_template = self.create_audit_template(**params) @@ -208,14 +230,14 @@ class TestAuditTemplate(base.BaseInfraOptimTest): # Assert nothing else was changed self.assertEqual(name, body['name']) self.assertEqual(description, body['description']) - self.assertEqual(goal, body['goal']) + self.assertEqual(self.goal['uuid'], body['goal_uuid']) @test.attr(type='smoke') def test_update_audit_template_add(self): params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my at description', 'host_aggregate': 12, - 'goal': 'DUMMY'} + 'goal_uuid': self.goal['uuid']} _, body = self.create_audit_template(**params) diff --git a/watcher_tempest_plugin/tests/scenario/base.py b/watcher_tempest_plugin/tests/scenario/base.py index 9a18fff50..a1bd3a7bf 100644 --- a/watcher_tempest_plugin/tests/scenario/base.py +++ b/watcher_tempest_plugin/tests/scenario/base.py @@ -73,27 +73,30 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest): # ### AUDIT TEMPLATES ### # - def create_audit_template(self, name=None, description=None, goal=None, - host_aggregate=None, extra=None): + def create_audit_template(self, goal_uuid, name=None, description=None, + strategy_uuid=None, host_aggregate=None, + extra=None): """Wrapper utility for creating a test audit template + :param goal_uuid: The goal UUID related to the audit template. + Default: DUMMY :param name: The name of the audit template. Default: My Audit Template :param description: The description of the audit template. Default: AT Description - :param goal: The goal associated within the audit template. - Default: DUMMY + :param strategy_uuid: The strategy UUID related to the audit template. + Default: dummy :param host_aggregate: ID of the host aggregate targeted by this audit template. Default: 1 :param extra: IMetadata associated to this audit template. Default: {} :return: A tuple with The HTTP response and its body """ - description = description or data_utils.rand_name( 'test-audit_template') resp, body = self.client.create_audit_template( - name=name, description=description, goal=goal, - host_aggregate=host_aggregate, extra=extra) + name=name, description=description, goal_uuid=goal_uuid, + strategy_uuid=strategy_uuid, host_aggregate=host_aggregate, + extra=extra) self.addCleanup( self.delete_audit_template, diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py b/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py index 60f2caa83..57b10db9d 100644 --- a/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py +++ b/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py @@ -108,8 +108,8 @@ class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest): """ self.addCleanup(self.rollback_compute_nodes_status) self._create_one_instance_per_host() - - _, audit_template = self.create_audit_template(goal=self.BASIC_GOAL) + _, goal = self.client.show_goal(self.BASIC_GOAL) + _, audit_template = self.create_audit_template(goal['uuid']) _, audit = self.create_audit(audit_template['uuid']) self.assertTrue(test.call_until_true( diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py b/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py index 1db2a520c..033914ec6 100644 --- a/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py +++ b/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py @@ -37,7 +37,8 @@ class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest): - run the action plan - get results and make sure it succeeded """ - _, audit_template = self.create_audit_template() + _, goal = self.client.show_goal("DUMMY") + _, audit_template = self.create_audit_template(goal['uuid']) _, audit = self.create_audit(audit_template['uuid']) self.assertTrue(test.call_until_true(