From be10840d86b820c7bd91c43e5d12b21fdcd24ad8 Mon Sep 17 00:00:00 2001 From: Kristi Nikolla Date: Fri, 20 Oct 2017 17:56:44 -0400 Subject: [PATCH] More sophisticated extension matching Rewrote the extension matching code to work with variables. Now using the Routes library. Change-Id: I361e6e597421bc71ed2cf9ed82516ad8b7bd3a8c --- mixmatch/extend/base.py | 59 +++++---------------- mixmatch/extend/name_routing.py | 6 +-- mixmatch/proxy.py | 1 + mixmatch/tests/unit/test_extend.py | 85 +++++++++++++++++++++++------- requirements.txt | 1 + 5 files changed, 83 insertions(+), 69 deletions(-) diff --git a/mixmatch/extend/base.py b/mixmatch/extend/base.py index 315a105..5953c52 100644 --- a/mixmatch/extend/base.py +++ b/mixmatch/extend/base.py @@ -12,61 +12,28 @@ # License for the specific language governing permissions and limitations # under the License. +from routes import mapper + class Extension(object): ROUTES = [] OPTS = [] def matches(self, request): - for route in self.ROUTES: - if route.match(request): - return True - return False + route_map = mapper.Mapper() + for (path, methods) in self.ROUTES: + conditions = None if not methods else {'method': methods} + + route_map.connect(path.strip('/'), + action=self, + conditions=conditions) + + match = route_map.match(url=request.path.strip('/'), + environ=request.environ) + return bool(match) def handle_request(self, request): pass def handle_response(self, response): pass - - -class Route(object): - def __init__(self, service=None, version=None, method=None, action=None): - self.service = service - self.version = version - self.method = method - self.action = action - - def _match_service(self, service): - if self.service: - return self.service == service - return True - - def _match_version(self, version): - if self.version: - return self.version == version - return True - - def _match_method(self, method): - if self.method: - return self.method == method - return True - - def _match_action(self, action): - if self.action is None: - return True - elif action is None: - return False - elif len(self.action) != len(action): - return False - else: - for i in range(len(self.action)): - if self.action[i] != action[i]: - return False - return True - - def match(self, request): - return (self._match_service(request.service) and - self._match_version(request.version) and - self._match_method(request.method) and - self._match_action(request.action)) diff --git a/mixmatch/extend/name_routing.py b/mixmatch/extend/name_routing.py index f35a681..a841a3a 100644 --- a/mixmatch/extend/name_routing.py +++ b/mixmatch/extend/name_routing.py @@ -20,10 +20,8 @@ from oslo_serialization import jsonutils class NameRouting(base.Extension): ROUTES = [ - base.Route(service='volume', version=None, - action=['volumes'], method='POST'), - base.Route(service='image', version=None, - action=['images'], method='POST'), + ('/volume/{version}/{project_id}/volumes', ['POST']), + ('/image/{version}/images', ['POST']) ] @staticmethod diff --git a/mixmatch/proxy.py b/mixmatch/proxy.py index 73cee77..856ac7b 100644 --- a/mixmatch/proxy.py +++ b/mixmatch/proxy.py @@ -98,6 +98,7 @@ class RequestDetails(object): # NOTE(jfreud): if chunked transfer, body must be accessed through # utilities found in mixmatch.session self.body = request.data + self.environ = request.environ class RequestHandler(object): diff --git a/mixmatch/tests/unit/test_extend.py b/mixmatch/tests/unit/test_extend.py index 297900f..5d1d8fc 100644 --- a/mixmatch/tests/unit/test_extend.py +++ b/mixmatch/tests/unit/test_extend.py @@ -1,4 +1,4 @@ -# Copyright 2016 Massachusetts Open Cloud +# Copyright 2017 Massachusetts Open Cloud # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -11,31 +11,78 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + from testtools import testcase -from mixmatch.extend.base import Route + +from mixmatch.extend import base + + +class FakeRequest(object): + def __init__(self, path, method): + self.path = path + self.full_path = path + self.environ = {'REQUEST_METHOD': method} class TestRoutes(testcase.TestCase): def setUp(self): super(TestRoutes, self).setUp() + self.ext = base.Extension() - def test_match_action_True(self): - testRouteT = Route(None, None, None, [123, 'test']) - self.assertEqual(testRouteT._match_action([123, 'test']), True) + def assertMatches(self, request): + self.assertTrue(self.ext.matches(request)) - def test_match_action_False(self): - testRouteF = Route(None, None, None, [123, 'test']) - self.assertEqual(testRouteF._match_action([12, 'test']), False) + def assertDoesntMatch(self, request): + self.assertFalse(self.ext.matches(request)) - def test_match_action_Different_Length(self): - routeLen3 = Route(None, None, None, [123, 'test', None]) - routeLen2 = Route(None, None, None, [123, 'test']) - self.assertEqual(routeLen3._match_action([123, 'test']), False) - self.assertEqual(routeLen2._match_action([123, 'test', None]), False) + def test_no_routes_doesnt_match(self): + self.ext.ROUTES = [] + self.assertDoesntMatch(FakeRequest('service/version', 'GET')) - def test_match_action_None(self): - routeNone = Route(None, None, None, None) - testRoute = Route(None, None, None, [123]) - self.assertEqual(routeNone._match_action([123]), True) - self.assertEqual(testRoute._match_action(None), False) - self.assertEqual(routeNone._match_action(None), True) + def test_simple_routes(self): + self.ext.ROUTES = [('service', [])] + self.assertMatches(FakeRequest('service', 'GET')) + self.assertMatches(FakeRequest('service/', 'GET')) + self.assertDoesntMatch(FakeRequest('not-service', 'GET')) + + self.ext.ROUTES = [('service', []), ('not-service', [])] + self.assertMatches(FakeRequest('service', 'GET')) + self.assertMatches(FakeRequest('not-service', 'GET')) + + self.ext.ROUTES = [('service', ['GET'])] + self.assertMatches(FakeRequest('service', 'GET')) + self.assertDoesntMatch(FakeRequest('service', 'POST')) + + self.ext.ROUTES = [('service', ['GET', 'POST'])] + self.assertMatches(FakeRequest('service', 'GET')) + self.assertMatches(FakeRequest('service', 'POST')) + + def test_wildcard_routes(self): + self.ext.ROUTES = [('service/{version}', [])] + self.assertMatches(FakeRequest('service/v1', 'GET')) + self.assertMatches(FakeRequest('service/v2', 'GET')) + self.assertDoesntMatch(FakeRequest('service', 'GET')) + self.assertDoesntMatch(FakeRequest('service/', 'GET')) + + self.ext.ROUTES = [('service/{version}/resource', [])] + self.assertMatches(FakeRequest('service/v1/resource', 'GET')) + self.assertDoesntMatch(FakeRequest('service/v1', 'GET')) + self.assertDoesntMatch(FakeRequest('service/v1/', 'GET')) + + self.ext.ROUTES = [('service/{version}/resource/{resource_id}', [])] + self.assertMatches(FakeRequest('service/v1/resource/123', 'GET')) + self.assertDoesntMatch(FakeRequest('service/v1/resource/', 'GET')) + self.assertDoesntMatch(FakeRequest('service/v1/resource', 'GET')) + + self.ext.ROUTES = [ + ('service/{version}/resource/{resource_id}/action', []) + ] + self.assertMatches( + FakeRequest('service/v1/resource/123/action', 'GET') + ) + self.assertDoesntMatch( + FakeRequest('service/v1/resource/123', 'GET') + ) + self.assertDoesntMatch( + FakeRequest('service/v1/resource/123/action/other', 'GET') + ) diff --git a/requirements.txt b/requirements.txt index 1be4d30..6d2c1dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,4 @@ python-keystoneclient>=3.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 six>=1.9.0 # MIT stevedore>=1.20.0 # Apache-2.0 +Routes>=2.3.1 # MIT