From e2a87ff044a29ccd205744d30afd04d6b510fab1 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Thu, 11 Jun 2015 07:35:48 -0700 Subject: [PATCH] Switch controller to actually call the plugins This swaps out the controller methods to actually dispatch the requests to the plugins and return the result. After this patch merges, pecan should serve as a minimally working API server for development purposes. TODOs: * AMQP server - this will not listen for AMQP messages * Cleanup policy and context modules - they only serve a happy path * Implement the notifiers * Move the functional tests into an appropriate location * Bulk operations * Address any other of the TODOs in the code Partially-Implements: blueprint wsgi-pecan-switch Co-Authored-By: Brandon Logan Change-Id: If5dc76a2974d13d45f0cc88419bcccb3332576cf --- neutron/newapi/controllers/root.py | 60 ++++++++++++++----- .../functional/newapi/test_functional.py | 9 +-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/neutron/newapi/controllers/root.py b/neutron/newapi/controllers/root.py index 4185080f5..3426d9c38 100644 --- a/neutron/newapi/controllers/root.py +++ b/neutron/newapi/controllers/root.py @@ -15,6 +15,7 @@ # under the License. import pecan +from pecan import request from neutron.api import extensions @@ -52,7 +53,7 @@ class V2Controller(object): def _lookup(self, endpoint, *remainder): if endpoint == 'extensions': return ExtensionsController(), remainder - return GeneralController(endpoint), remainder + return CollectionsController(endpoint), remainder class ExtensionsController(object): @@ -85,30 +86,57 @@ class ExtensionController(object): return {'extension': extensions.ExtensionController._translate(ext)} -class GeneralController(object): +class CollectionsController(object): - def __init__(self, token): - self.token = token + def __init__(self, collection): + self.collection = collection @expose() - def _lookup(self, token, *remainder): - return GeneralController(token), remainder + def _lookup(self, item, *remainder): + return ItemController(item), remainder @expose(generic=True) - def index(self): + def index(self, *args, **kwargs): + return self.get(*args, **kwargs) + + def get(self, *args, **kwargs): + # list request + # TODO(kevinbenton): allow fields after policy enforced fields present + kwargs.pop('fields', None) + _listify = lambda x: x if isinstance(x, list) else [x] + filters = {k: _listify(v) for k, v in kwargs.items()} + lister = getattr(request.plugin, 'get_%s' % self.collection) + return {self.collection: lister(request.context, filters=filters)} + + @when(index, method='POST') + def post(self, *args, **kwargs): + # TODO(kevinbenton): bulk! + creator = getattr(request.plugin, 'create_%s' % request.resource_type) + return {request.resource_type: creator(request.context, + request.prepared_data)} + + +class ItemController(object): + + def __init__(self, item): + self.item = item + + @expose(generic=True) + def index(self, *args, **kwargs): return self.get() - def get(self): - return {'message': 'GET'} + def get(self, *args, **kwargs): + getter = getattr(request.plugin, 'get_%s' % request.resource_type) + return {request.resource_type: getter(request.context, self.item)} @when(index, method='PUT') - def put(self, **kw): - return {'message': 'PUT'} - - @when(index, method='POST') - def post(self, **kw): - return {'message': 'POST'} + def put(self, *args, **kwargs): + # TODO(kevinbenton): bulk? + updater = getattr(request.plugin, 'update_%s' % request.resource_type) + return updater(request.context, self.item, request.prepared_data) @when(index, method='DELETE') def delete(self): - return {'message': 'DELETE'} + # TODO(kevinbenton): bulk? + deleter = getattr(request.plugin, 'delete_%s' % request.resource_type) + return deleter(request.context, self.item) diff --git a/neutron/tests/functional/newapi/test_functional.py b/neutron/tests/functional/newapi/test_functional.py index e4e4506d2..458be23ac 100644 --- a/neutron/tests/functional/newapi/test_functional.py +++ b/neutron/tests/functional/newapi/test_functional.py @@ -85,7 +85,8 @@ class TestV2Controller(PecanFunctionalTest): self.assertEqual(response.status_int, 200) def test_delete(self): - response = self.app.delete('/v2.0/ports/%s.json' % self.port['id']) + response = self.app.delete('/v2.0/ports/%s.json' % self.port['id'], + headers={'X-Tenant-Id': 'tenid'}) self.assertEqual(response.status_int, 200) def test_plugin_initialized(self): @@ -151,14 +152,14 @@ class TestExceptionTranslationHook(PecanFunctionalTest): # this endpoint raises a Neutron notfound exception. make sure it gets # translated into a 404 error with mock.patch( - 'neutron.newapi.controllers.root.GeneralController.get', + 'neutron.newapi.controllers.root.CollectionsController.get', side_effect=n_exc.NotFound()): response = self.app.get('/v2.0/ports.json', expect_errors=True) self.assertEqual(response.status_int, 404) def test_unexpected_exception(self): with mock.patch( - 'neutron.newapi.controllers.root.GeneralController.get', + 'neutron.newapi.controllers.root.CollectionsController.get', side_effect=ValueError('secretpassword')): response = self.app.get('/v2.0/ports.json', expect_errors=True) self.assertNotIn(response.body, 'secretpassword') @@ -181,7 +182,7 @@ class TestRequestPopulatingHooks(PecanFunctionalTest): 'plugin': request.plugin } mock.patch( - 'neutron.newapi.controllers.root.GeneralController.get', + 'neutron.newapi.controllers.root.CollectionsController.get', side_effect=capture_request_details ).start() -- 2.45.2