diff --git a/ooi/api/base.py b/ooi/api/base.py index 9e7d090..32370ec 100644 --- a/ooi/api/base.py +++ b/ooi/api/base.py @@ -16,6 +16,8 @@ from ooi.wsgi import utils +import webob.exc + class Controller(object): def __init__(self, app, openstack_version): @@ -31,3 +33,53 @@ class Controller(object): if body is not None: req.body = utils.utf8(body) return req + + @staticmethod + def get_from_response(response, element, default): + """Get a JSON element from a valid response or raise an exception. + + This method will extract an element a JSON response (falling back to a + default value) if the response has a code of 200, otherwise it will + raise a webob.exc.exception + + :param response: The webob.Response object + :param element: The element to look for in the JSON body + :param default: The default element to be returned if not found. + """ + if response.status_int == 200: + return response.json_body.get(element, default) + else: + raise exception_from_response(response) + + +def exception_from_response(response): + """Convert an OpenStack V2 Fault into a webob exception. + + Since we are calling the OpenStack API we should process the Faults + produced by them. Extract the Fault information according to [1] and + convert it back to a webob exception. + + [1] http://docs.openstack.org/developer/nova/v2/faults.html + + :param response: a webob.Response containing an exception + :returns: a webob.exc.exception object + """ + exceptions = { + 400: webob.exc.HTTPBadRequest, + 401: webob.exc.HTTPUnauthorized, + 403: webob.exc.HTTPForbidden, + 404: webob.exc.HTTPNotFound, + 405: webob.exc.HTTPMethodNotAllowed, + 406: webob.exc.HTTPNotAcceptable, + 409: webob.exc.HTTPConflict, + 413: webob.exc.HTTPRequestEntityTooLarge, + 415: webob.exc.HTTPUnsupportedMediaType, + 429: webob.exc.HTTPTooManyRequests, + 501: webob.exc.HTTPNotImplemented, + 503: webob.exc.HTTPServiceUnavailable, + } + code = response.status_int + message = response.json_body.popitem()[1].get("message") + + exc = exceptions.get(code, webob.exc.HTTPInternalServerError) + return exc(explanation=message) diff --git a/ooi/api/compute.py b/ooi/api/compute.py index c5983eb..75810b7 100644 --- a/ooi/api/compute.py +++ b/ooi/api/compute.py @@ -38,7 +38,7 @@ class Controller(ooi.api.base.Controller): tenant_id = req.environ["keystone.token_auth"].user.project_id req = self._get_req(req, path="/%s/servers" % tenant_id) response = req.get_response(self.app) - servers = response.json_body.get("servers", []) + servers = self.get_from_response(response, "servers", []) occi_compute_resources = self._get_compute_resources(servers) return collection.Collection(resources=occi_compute_resources) @@ -63,7 +63,7 @@ class Controller(ooi.api.base.Controller): }})) response = req.get_response(self.app) # We only get one server - server = response.json_body.get("server", {}) + server = self.get_from_response(response, "server", {}) # The returned JSON does not contain the server name server["name"] = params["/occi/infrastructure"] @@ -77,13 +77,13 @@ class Controller(ooi.api.base.Controller): # get info from server req = self._get_req(req, path="/%s/servers/%s" % (tenant_id, id)) response = req.get_response(self.app) - s = response.json_body.get("server", {}) + s = self.get_from_response(response, "server", {}) # get info from flavor req = self._get_req(req, path="/%s/flavors/%s" % (tenant_id, s["flavor"]["id"])) response = req.get_response(self.app) - flavor = response.json_body.get("flavor", {}) + flavor = self.get_from_response(response, "flavor", {}) res_tpl = templates.OpenStackResourceTemplate(flavor["name"], flavor["vcpus"], flavor["ram"], @@ -93,7 +93,7 @@ class Controller(ooi.api.base.Controller): req = self._get_req(req, path="/%s/images/%s" % (tenant_id, s["image"]["id"])) response = req.get_response(self.app) - image = response.json_body.get("image", {}) + image = self.get_from_response(response, "image", {}) os_tpl = templates.OpenStackOSTemplate(image["id"], image["name"]) diff --git a/ooi/api/query.py b/ooi/api/query.py index 50a2469..58a0aa1 100644 --- a/ooi/api/query.py +++ b/ooi/api/query.py @@ -29,7 +29,7 @@ class Controller(base.Controller): tenant_id = req.environ["keystone.token_auth"].user.project_id req = self._get_req(req, path="/%s/flavors/detail" % tenant_id) response = req.get_response(self.app) - flavors = response.json_body.get("flavors", []) + flavors = self.get_from_response(response, "flavors", []) occi_resource_templates = [] if flavors: for f in flavors: @@ -44,7 +44,7 @@ class Controller(base.Controller): tenant_id = req.environ["keystone.token_auth"].user.project_id req = self._get_req(req, path="/%s/images/detail" % tenant_id) response = req.get_response(self.app) - images = response.json_body.get("images", []) + images = self.get_from_response(response, "images", []) occi_os_templates = [] if images: for i in images: diff --git a/ooi/tests/fakes.py b/ooi/tests/fakes.py index fb0569d..dee3035 100644 --- a/ooi/tests/fakes.py +++ b/ooi/tests/fakes.py @@ -18,6 +18,10 @@ import json import uuid import webob.dec +import webob.exc + +import ooi.wsgi +import ooi.wsgi.utils tenants = { @@ -156,6 +160,35 @@ def fake_query_results(): return result +class FakeOpenStackFault(ooi.wsgi.Fault): + _fault_names = { + 400: "badRequest", + 401: "unauthorized", + 403: "forbidden", + 404: "itemNotFound", + 405: "badMethod", + 406: "notAceptable", + 409: "conflictingRequest", + 413: "overLimit", + 415: "badMediaType", + 429: "overLimit", + 501: "notImplemented", + 503: "serviceUnavailable"} + + @webob.dec.wsgify() + def __call__(self, req): + code = self.wrapped_exc.status_int + fault_name = self._fault_names.get(code) + explanation = self.wrapped_exc.explanation + fault_data = { + fault_name: { + 'code': code, + 'message': explanation}} + self.wrapped_exc.body = ooi.wsgi.utils.utf8(json.dumps(fault_data)) + self.wrapped_exc.content_type = "application/json" + return self.wrapped_exc + + class FakeApp(object): """Poor man's fake application.""" @@ -205,8 +238,9 @@ class FakeApp(object): def _do_get(self, req): try: ret = self.routes[req.path_info] - except Exception: - raise + except KeyError: + exc = webob.exc.HTTPNotFound() + ret = FakeOpenStackFault(exc) return ret diff --git a/ooi/tests/middleware/test_compute_controller.py b/ooi/tests/middleware/test_compute_controller.py index 27255d3..0621cef 100644 --- a/ooi/tests/middleware/test_compute_controller.py +++ b/ooi/tests/middleware/test_compute_controller.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import uuid + import mock from ooi.tests import fakes @@ -115,6 +117,15 @@ class TestComputeController(test_middleware.TestMiddleware): self.assertExpectedResult(expected, resp) self.assertEqual(200, resp.status_code) + def test_vm_not_found(self): + tenant = fakes.tenants["foo"] + + app = self.get_app() + req = self._build_req("/compute/%s" % uuid.uuid4().hex, + tenant["id"], method="GET") + resp = req.get_response(app) + self.assertEqual(404, resp.status_code) + def test_create_vm(self): tenant = fakes.tenants["foo"] diff --git a/ooi/tests/middleware/test_middleware.py b/ooi/tests/middleware/test_middleware.py index 1b58dfa..7763f8a 100644 --- a/ooi/tests/middleware/test_middleware.py +++ b/ooi/tests/middleware/test_middleware.py @@ -16,6 +16,8 @@ import mock import webob +import webob.dec +import webob.exc from ooi.tests import base from ooi.tests import fakes @@ -63,6 +65,16 @@ class TestMiddleware(base.TestCase): result = self._build_req("/", "tenant").get_response(self.get_app()) self.assertEqual(404, result.status_code) + def test_400_from_openstack(self): + @webob.dec.wsgify() + def _fake_app(req): + exc = webob.exc.HTTPBadRequest() + resp = fakes.FakeOpenStackFault(exc) + return resp + + result = self._build_req("/-/", "tenant").get_response(_fake_app) + self.assertEqual(400, result.status_code) + class TestMiddlewareTextPlain(TestMiddleware): """OCCI middleware test with Accept: text/plain."""