From 4e71a0c655a53651c6331c5d0940f6acf3c856da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fran=C3=A7oise?= Date: Fri, 12 Feb 2016 13:45:49 +0100 Subject: [PATCH] Added goal filter in Watcher API Although it was proposed via python-watcherclient, the feature was not implemented on the Watcher API. As the notion of host aggregate is currently unused in Watcher, decision was made to only implement the filtering of goal within the Watcher API whilst removing the host_aggregate filter from the Watcher client. Thus, this patchset adds this missing functionality by adding the 'goal' parameter to the API. Change-Id: I54d248f7e470249c6412650ddf50a3e3631d2a09 Related-Bug: #1510189 --- watcher/api/controllers/v1/audit_template.py | 30 +++++++++++++------- watcher/api/controllers/v1/utils.py | 18 ++++++++++++ watcher/objects/audit_template.py | 4 ++- watcher/tests/api/v1/test_audit_templates.py | 19 +++++++++++++ watcher/tests/api/v1/test_utils.py | 22 ++++++++++++-- watcher/tests/common/test_clients.py | 2 +- 6 files changed, 80 insertions(+), 15 deletions(-) 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