From 00f5a5361151b279dea7a932f74afb1465483800 Mon Sep 17 00:00:00 2001 From: Gustavo Herzmann Date: Mon, 14 Nov 2022 11:05:52 -0300 Subject: [PATCH] Fix subcloud-group endpoint routing conflict Pecan default routing algorithm fails to find the requested resource when the requested URL contains keywords that match the name of any method declared inside of a pecan controller decorated with the @expose(generic=True, ...) or @index.when() decorators. This commit implements a custom routing through the use of pecan's _route() method, where the routing is redirected to the generic index method, allowing the keywords to be correctly passed as arguments to the correct method handler based on the request method (GET, POST, PATCH, DELETE, etc.). Test plan: PASS: Execute all of the subcloud-group sub-commands (add, delete, list, list-subclouds, show and update) with the following group names: get, post, patch, delete, index, _validate_name, _route and foo. Closes-Bug: #1996507 Signed-off-by: Gustavo Herzmann Change-Id: I98a22f2bf1da205b68ee4199fb339776dd3d8dde --- .../dcmanager/api/controllers/restcomm.py | 61 +++++++++++++++++++ .../api/controllers/v1/subcloud_group.py | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/distributedcloud/dcmanager/api/controllers/restcomm.py b/distributedcloud/dcmanager/api/controllers/restcomm.py index 63e919d05..a95b5bf93 100644 --- a/distributedcloud/dcmanager/api/controllers/restcomm.py +++ b/distributedcloud/dcmanager/api/controllers/restcomm.py @@ -15,8 +15,11 @@ # under the License. # +import abc +from pecan import expose from pecan import request +import six import dcmanager.common.context as k_context @@ -51,3 +54,61 @@ def extract_credentials_for_policy(): context_paras[key] = environ.get(val) context_paras['roles'] = context_paras['roles'].split(',') return context_paras + + +def _get_pecan_data(obj): + return getattr(obj, "_pecan", {}) + + +def _is_exposed(obj): + return getattr(obj, "exposed", False) + + +def _is_generic(obj): + data = _get_pecan_data(obj) + return "generic" in data.keys() + + +def _is_generic_handler(obj): + data = _get_pecan_data(obj) + return "generic_handler" in data.keys() + + +@six.add_metaclass(abc.ABCMeta) +class GenericPathController(object): + """A controller that allows path parameters to be equal to handler names. + + The _route method provides a custom route resolution that checks if the + next object is marked as generic or a generic handler, pointing to the + generic index method in case it is. Pecan will properly handle the rest + of the routing process by redirecting it to the proper method function + handler (GET, POST, PATCH, DELETE, etc.). + + Useful when part of the URL contains path parameters that might have + the same name as an already defined exposed controller method. + + Requires the definition of an index method with the generator: + @expose(generic=True, ...) + + Does not support nested subcontrollers. + """ + + RESERVED_NAMES = ("_route", "_default", "_lookup") + + @abc.abstractmethod + def index(self): + pass + + @expose() + def _route(self, remainder, request): + next_url_part, rest = remainder[0], remainder[1:] + next_obj = getattr(self, next_url_part, None) + + is_generic = _is_generic(next_obj) or _is_generic_handler(next_obj) + is_reserved_name = next_url_part in self.__class__.RESERVED_NAMES + + if _is_exposed(next_obj) and not is_generic and not is_reserved_name: + # A non-generic exposed method with a non-reserved name + return next_obj, rest + else: + return self.index, remainder diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py index 58cce6a44..3b01190d7 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py @@ -49,7 +49,7 @@ MIN_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS = 1 MAX_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS = 500 -class SubcloudGroupsController(object): +class SubcloudGroupsController(restcomm.GenericPathController): def __init__(self): super(SubcloudGroupsController, self).__init__()