diff --git a/watcher/api/controllers/v1/audit_template.py b/watcher/api/controllers/v1/audit_template.py index 3870e0433..9b223b718 100644 --- a/watcher/api/controllers/v1/audit_template.py +++ b/watcher/api/controllers/v1/audit_template.py @@ -197,10 +197,11 @@ class AuditTemplatesController(rest.RestController): 'detail': ['GET'], } - def _get_audit_templates_collection(self, marker, limit, + def _get_audit_templates_collection(self, filters, marker, limit, sort_key, sort_dir, expand=False, resource_url=None): - + api_utils.validate_search_filters( + filters, objects.audit_template.AuditTemplate.fields.keys()) limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) @@ -212,6 +213,7 @@ class AuditTemplatesController(rest.RestController): audit_templates = objects.AuditTemplate.list( pecan.request.context, + filters, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) @@ -223,26 +225,30 @@ class AuditTemplatesController(rest.RestController): sort_key=sort_key, sort_dir=sort_dir) - @wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int, - wtypes.text, wtypes.text) - def get_all(self, marker=None, limit=None, + @wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, + types.uuid, int, wtypes.text, wtypes.text) + def get_all(self, goal=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 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. """ - return self._get_audit_templates_collection(marker, limit, sort_key, - sort_dir) + filters = api_utils.as_filters_dict(goal=goal) - @wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int, + 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, marker=None, limit=None, + def detail(self, goal=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 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. @@ -253,9 +259,11 @@ class AuditTemplatesController(rest.RestController): if parent != "audit_templates": raise exception.HTTPNotFound + filters = api_utils.as_filters_dict(goal=goal) + expand = True resource_url = '/'.join(['audit_templates', 'detail']) - return self._get_audit_templates_collection(marker, limit, + return self._get_audit_templates_collection(filters, marker, limit, sort_key, sort_dir, expand, resource_url) @@ -263,7 +271,7 @@ class AuditTemplatesController(rest.RestController): def get_one(self, audit_template): """Retrieve information about the given audit template. - :param audit template_uuid: UUID or name of an audit template. + :param audit audit_template: UUID or name of an audit template. """ if self.from_audit_templates: raise exception.OperationNotPermitted diff --git a/watcher/api/controllers/v1/utils.py b/watcher/api/controllers/v1/utils.py index 4fa0dc3df..18b4c625a 100644 --- a/watcher/api/controllers/v1/utils.py +++ b/watcher/api/controllers/v1/utils.py @@ -50,6 +50,15 @@ def validate_sort_dir(sort_dir): return sort_dir +def validate_search_filters(filters, allowed_fields): + # Very leightweight validation for now + # todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries) + for filter_name in filters.keys(): + if filter_name not in allowed_fields: + raise wsme.exc.ClientSideError( + _("Invalid filter: %s") % filter_name) + + def apply_jsonpatch(doc, patch): for p in patch: if p['op'] == 'add' and p['path'].count('/') == 1: @@ -58,3 +67,12 @@ def apply_jsonpatch(doc, patch): ' the resource is not allowed') raise wsme.exc.ClientSideError(msg % p['path']) return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch)) + + +def as_filters_dict(**filters): + filters_dict = {} + for filter_name, filter_value in filters.items(): + if filter_value: + filters_dict[filter_name] = filter_value + + return filters_dict diff --git a/watcher/objects/audit_template.py b/watcher/objects/audit_template.py index 7dac13b99..33bdb870b 100644 --- a/watcher/objects/audit_template.py +++ b/watcher/objects/audit_template.py @@ -164,7 +164,7 @@ class AuditTemplate(base.WatcherObject): return audit_template @classmethod - def list(cls, context, limit=None, marker=None, + def list(cls, context, filters=None, limit=None, marker=None, sort_key=None, sort_dir=None): """Return a list of :class:`AuditTemplate` objects. @@ -174,6 +174,7 @@ class AuditTemplate(base.WatcherObject): argument, even though we don't use it. A context should be set when instantiating the object, e.g.: AuditTemplate(context) + :param filters: dict mapping the filter key to a value. :param limit: maximum number of resources to return in a single result. :param marker: pagination marker for large data sets. :param sort_key: column to sort results by. @@ -183,6 +184,7 @@ class AuditTemplate(base.WatcherObject): db_audit_templates = cls.dbapi.get_audit_template_list( context, + filters=filters, limit=limit, marker=marker, sort_key=sort_key, diff --git a/watcher/tests/api/v1/test_audit_templates.py b/watcher/tests/api/v1/test_audit_templates.py index 5224132af..7039961e7 100644 --- a/watcher/tests/api/v1/test_audit_templates.py +++ b/watcher/tests/api/v1/test_audit_templates.py @@ -207,6 +207,25 @@ 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): + obj_utils.create_test_audit_template( + self.context, id=id_, uuid=utils.generate_uuid(), + name='My Audit Template {0}'.format(id_), + goal="DUMMY") + + for id_ in range(2, 5): + obj_utils.create_test_audit_template( + self.context, id=id_, uuid=utils.generate_uuid(), + name='My Audit Template {0}'.format(id_), + goal="BASIC") + + response = self.get_json('/audit_templates?goal=BASIC') + self.assertEqual(3, len(response['audit_templates'])) + class TestPatch(api_base.FunctionalTest): diff --git a/watcher/tests/api/v1/test_utils.py b/watcher/tests/api/v1/test_utils.py index fcc7bebad..a5e0bc86a 100644 --- a/watcher/tests/api/v1/test_utils.py +++ b/watcher/tests/api/v1/test_utils.py @@ -15,11 +15,11 @@ import wsme +from oslo_config import cfg + from watcher.api.controllers.v1 import utils from watcher.tests import base -from oslo_config import cfg - CONF = cfg.CONF @@ -47,3 +47,21 @@ class TestApiUtils(base.TestCase): self.assertRaises(wsme.exc.ClientSideError, utils.validate_sort_dir, 'fake-sort') + + def test_validate_search_filters(self): + allowed_fields = ["allowed", "authorized"] + + test_filters = {"allowed": 1, "authorized": 2} + try: + utils.validate_search_filters(test_filters, allowed_fields) + except Exception as exc: + self.fail(exc) + + def test_validate_search_filters_with_invalid_key(self): + allowed_fields = ["allowed", "authorized"] + + test_filters = {"allowed": 1, "unauthorized": 2} + + self.assertRaises( + wsme.exc.ClientSideError, utils.validate_search_filters, + test_filters, allowed_fields) diff --git a/watcher/tests/common/test_clients.py b/watcher/tests/common/test_clients.py index 78b2a9006..95deb04f7 100644 --- a/watcher/tests/common/test_clients.py +++ b/watcher/tests/common/test_clients.py @@ -227,7 +227,7 @@ class TestClients(base.BaseTestCase): @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_neutron_diff_vers(self, mock_session): '''neutronclient currently only has one version (v2)''' - cfg.CONF.set_override('api_version', '2', + cfg.CONF.set_override('api_version', '2.0', group='neutron_client') osc = clients.OpenStackClients() osc._neutron = None