Handle OpenStack API faults

We should start handling the OpenStack Faults, in case they exist. The
easiest way is checking if the status code belongs to one of the HTTP
error codes. If this is the case, we convert it back to a webob
exception and raise it so that it is eventually handled in our WSGI
middleware.
This commit is contained in:
Alvaro Lopez Garcia 2015-04-07 14:15:50 +02:00
parent c96b600452
commit f53f9744da
6 changed files with 118 additions and 9 deletions

View File

@ -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)

View File

@ -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"])

View File

@ -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:

View File

@ -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

View File

@ -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"]

View File

@ -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."""