Merge pull request #10 from alvarolopez/refactor_rendering
Refactor rendering
This commit is contained in:
commit
8ab4be59bf
|
@ -14,7 +14,9 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ooi.wsgi import utils
|
||||
from ooi import utils
|
||||
|
||||
import webob.exc
|
||||
|
||||
|
||||
class Controller(object):
|
||||
|
@ -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)
|
||||
|
|
|
@ -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"])
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -24,5 +24,10 @@ class Action(category.Category):
|
|||
instance.
|
||||
"""
|
||||
|
||||
def __init__(self, scheme, term, title, attributes=None, location=None):
|
||||
super(Action, self).__init__(scheme, term, title,
|
||||
attributes=attributes,
|
||||
location="?action=%s" % term)
|
||||
|
||||
def _class_name(self):
|
||||
return "action"
|
||||
|
|
|
@ -35,24 +35,6 @@ class Attribute(object):
|
|||
def value(self):
|
||||
return self._value
|
||||
|
||||
def _as_str(self):
|
||||
value_str = ''
|
||||
if isinstance(self._value, six.string_types):
|
||||
value_str = '"%s"' % self._value
|
||||
elif isinstance(self._value, bool):
|
||||
value_str = '"%s"' % str(self._value).lower()
|
||||
else:
|
||||
value_str = "%s" % self._value
|
||||
return "%s=%s" % (self.name, value_str)
|
||||
|
||||
def __str__(self):
|
||||
"""Render the attribute to text/plain."""
|
||||
return ": ".join(self.headers()[0])
|
||||
|
||||
def headers(self):
|
||||
"""Render the attribute to text/occi."""
|
||||
return [("X-OCCI-Attribute", self._as_str())]
|
||||
|
||||
|
||||
class MutableAttribute(Attribute):
|
||||
@Attribute.value.setter
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# under the License.
|
||||
|
||||
from ooi.occi.core import attribute
|
||||
from ooi import utils
|
||||
|
||||
|
||||
class Category(object):
|
||||
|
@ -37,17 +38,10 @@ class Category(object):
|
|||
"""Returns this class name (see OCCI v1.1 rendering)."""
|
||||
raise ValueError
|
||||
|
||||
def _as_str(self):
|
||||
d = {
|
||||
"term": self.term,
|
||||
"scheme": self.scheme,
|
||||
"class": self._class_name()
|
||||
}
|
||||
@property
|
||||
def occi_class(self):
|
||||
return self._class_name()
|
||||
|
||||
return '%(term)s; scheme="%(scheme)s"; class="%(class)s"' % d
|
||||
|
||||
def headers(self):
|
||||
return [("Category", self._as_str())]
|
||||
|
||||
def __str__(self):
|
||||
return ": ".join(self.headers()[0])
|
||||
@property
|
||||
def type_id(self):
|
||||
return utils.join_url(self.scheme, "#%s" % self.term)
|
||||
|
|
|
@ -30,27 +30,3 @@ class Collection(object):
|
|||
self.actions = actions
|
||||
self.resources = resources
|
||||
self.links = links
|
||||
|
||||
def __str__(self):
|
||||
"""Render the collection to text/plain."""
|
||||
# NOTE(aloga): This is unfinished, we need to check what is inside the
|
||||
# collection and render it properly. For example, if we have a
|
||||
# collection of resources, we should render only their locations.
|
||||
ret = []
|
||||
for what in [self.kinds, self.mixins, self.actions,
|
||||
self.resources, self.links]:
|
||||
for el in what:
|
||||
ret.append("X-OCCI-Location: %s" % el.location)
|
||||
return "\n".join(ret)
|
||||
|
||||
def headers(self):
|
||||
"""Render the collection to text/occi."""
|
||||
# NOTE(aloga): This is unfinished, we need to check what is inside the
|
||||
# collection and render it properly. For example, if we have a
|
||||
# collection of resources, we should render only their locations.
|
||||
headers = []
|
||||
for what in [self.kinds, self.mixins, self.actions,
|
||||
self.resources, self.links]:
|
||||
for el in what:
|
||||
headers.append(("X-OCCI-Location", el.location))
|
||||
return headers
|
||||
|
|
|
@ -22,6 +22,7 @@ from ooi.occi.core import attribute
|
|||
from ooi.occi.core import kind
|
||||
from ooi.occi.core import mixin
|
||||
from ooi.occi import helpers
|
||||
from ooi import utils
|
||||
|
||||
|
||||
class EntityMeta(type):
|
||||
|
@ -94,21 +95,4 @@ class Entity(object):
|
|||
|
||||
@property
|
||||
def location(self):
|
||||
return helpers.join_url(self.kind.location, self.id)
|
||||
|
||||
def headers(self):
|
||||
"""Render the entity to text/occi."""
|
||||
h = self.kind.headers()
|
||||
for m in self.mixins:
|
||||
h.extend(m.headers())
|
||||
for attr_name in self.attributes:
|
||||
if self.attributes[attr_name].value is not None:
|
||||
h.extend(self.attributes[attr_name].headers())
|
||||
return h
|
||||
|
||||
def __str__(self):
|
||||
"""Render the entity to text/plain."""
|
||||
ret = []
|
||||
for h in self.headers():
|
||||
ret.append(": ".join(h))
|
||||
return "\n".join(ret)
|
||||
return utils.join_url(self.kind.location, self.id)
|
||||
|
|
|
@ -60,10 +60,3 @@ class Resource(entity.Entity):
|
|||
@summary.setter
|
||||
def summary(self, value):
|
||||
self.attributes["occi.core.summary"].value = value
|
||||
|
||||
def __str__(self):
|
||||
"""Render the resource to text/plain."""
|
||||
ret = [super(Resource, self).__str__()]
|
||||
for link in self.links:
|
||||
ret.append("%s" % link)
|
||||
return "\n".join(ret)
|
||||
|
|
|
@ -30,9 +30,3 @@ def check_type(obj_list, obj_type):
|
|||
|
||||
if not all([isinstance(i, obj_type) for i in obj_list]):
|
||||
raise TypeError('object must be of class %s' % obj_type)
|
||||
|
||||
|
||||
def join_url(prefix, remainder, fragments=None):
|
||||
if fragments:
|
||||
remainder = "%s#%s" % (remainder, fragments)
|
||||
return urlparse.urljoin(prefix, remainder)
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Spanish National Research Council
|
||||
#
|
||||
# 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
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
import webob.exc
|
||||
|
||||
from ooi.occi.core import action
|
||||
from ooi.occi.core import collection
|
||||
from ooi.occi.core import kind
|
||||
from ooi.occi.core import mixin
|
||||
from ooi.occi.core import resource
|
||||
from ooi import utils
|
||||
|
||||
|
||||
class HeaderRenderer(object):
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
def render(self, env={}):
|
||||
raise NotImplementedError("%s for %s object not implemented" %
|
||||
(type(self), type(self.obj)))
|
||||
|
||||
|
||||
class ExceptionRenderer(HeaderRenderer):
|
||||
def render(self, env={}):
|
||||
return []
|
||||
|
||||
|
||||
class CategoryRenderer(HeaderRenderer):
|
||||
def render(self, env={}):
|
||||
d = {
|
||||
"term": self.obj.term,
|
||||
"scheme": self.obj.scheme,
|
||||
"class": self.obj.occi_class
|
||||
}
|
||||
return [('Category',
|
||||
'%(term)s; scheme="%(scheme)s"; class="%(class)s"' % d)]
|
||||
|
||||
|
||||
class KindRenderer(CategoryRenderer):
|
||||
pass
|
||||
|
||||
|
||||
class ActionRenderer(CategoryRenderer):
|
||||
def render(self, instance=None, env={}):
|
||||
# We have an instance id, render it as a link
|
||||
if instance is not None:
|
||||
url = env.get("application_url", "")
|
||||
url = utils.join_url(url, [instance, self.obj.location])
|
||||
d = {"location": url,
|
||||
"rel": self.obj.type_id}
|
||||
link = "<%(location)s>; rel=%(rel)s" % d
|
||||
return [('Link', link)]
|
||||
else:
|
||||
# Otherwise, render as category
|
||||
return super(ActionRenderer, self).render(env=env)
|
||||
|
||||
|
||||
class MixinRenderer(CategoryRenderer):
|
||||
pass
|
||||
|
||||
|
||||
class CollectionRenderer(HeaderRenderer):
|
||||
def render(self, env={}):
|
||||
app_url = env.get("application_url", "")
|
||||
ret = []
|
||||
for what in [self.obj.kinds, self.obj.mixins, self.obj.actions,
|
||||
self.obj.resources, self.obj.links]:
|
||||
for el in what:
|
||||
url = app_url + el.location
|
||||
ret.append(('X-OCCI-Location', '%s' % url))
|
||||
return ret
|
||||
|
||||
|
||||
class AttributeRenderer(HeaderRenderer):
|
||||
def render(self, env={}):
|
||||
value_str = ''
|
||||
if isinstance(self.obj.value, six.string_types):
|
||||
value_str = '"%s"' % self.obj.value
|
||||
elif isinstance(self.obj.value, bool):
|
||||
value_str = '"%s"' % str(self.obj.value).lower()
|
||||
else:
|
||||
value_str = "%s" % self.obj.value
|
||||
return [('X-OCCI-Attribute', '%s=%s' % (self.obj.name, value_str))]
|
||||
|
||||
|
||||
class ResourceRenderer(HeaderRenderer):
|
||||
def render(self, env={}):
|
||||
ret = []
|
||||
ret.extend(KindRenderer(self.obj.kind).render())
|
||||
for m in self.obj.mixins:
|
||||
ret.extend(MixinRenderer(m).render())
|
||||
for a in self.obj.attributes:
|
||||
# FIXME(aloga): I dont like this test here
|
||||
if self.obj.attributes[a].value is None:
|
||||
continue
|
||||
ret.extend(AttributeRenderer(self.obj.attributes[a]).render())
|
||||
for a in self.obj.actions:
|
||||
ret.extend(ActionRenderer(a).render(instance=self.obj.id))
|
||||
for l in self.obj.links:
|
||||
pass
|
||||
# FIXME(aloga): we need to fix this
|
||||
# ret.append(LinkRenderer(l))
|
||||
return ret
|
||||
|
||||
|
||||
_MAP = {
|
||||
"action": ActionRenderer,
|
||||
"kind": KindRenderer,
|
||||
"mixin": MixinRenderer,
|
||||
"collection": CollectionRenderer,
|
||||
"resource": ResourceRenderer,
|
||||
"exception": ExceptionRenderer,
|
||||
None: HeaderRenderer,
|
||||
}
|
||||
|
||||
|
||||
def get_renderer(obj):
|
||||
if isinstance(obj, action.Action):
|
||||
type_ = "action"
|
||||
elif isinstance(obj, collection.Collection):
|
||||
type_ = "collection"
|
||||
elif isinstance(obj, mixin.Mixin):
|
||||
type_ = "mixin"
|
||||
elif isinstance(obj, kind.Kind):
|
||||
type_ = "kind"
|
||||
elif isinstance(obj, resource.Resource):
|
||||
type_ = "resource"
|
||||
elif isinstance(obj, webob.exc.HTTPException):
|
||||
type_ = "exception"
|
||||
else:
|
||||
type_ = None
|
||||
return _MAP.get(type_)(obj)
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Spanish National Research Council
|
||||
#
|
||||
# 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
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import webob.exc
|
||||
|
||||
from ooi.occi.rendering import headers
|
||||
|
||||
|
||||
class TextRenderer(object):
|
||||
"""Render OCCI objects into text.
|
||||
|
||||
The text rendering is just the representation of the OCCI HTTP headers into
|
||||
text plain, so this renderer wraps around the actual header renderer and
|
||||
converts the headers into text.
|
||||
"""
|
||||
def __init__(self, renderer):
|
||||
self.renderer = renderer
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
"""Render the OCCI object into text."""
|
||||
hdrs = self.renderer.render(*args, **kwargs)
|
||||
result = []
|
||||
for hdr in hdrs:
|
||||
result.append("%s: %s" % hdr)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
class ExceptionRenderer(object):
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
return self.obj.explanation
|
||||
|
||||
|
||||
def get_renderer(obj):
|
||||
"""Get the correct renderer for the given object."""
|
||||
if isinstance(obj, webob.exc.HTTPException):
|
||||
return ExceptionRenderer(obj)
|
||||
else:
|
||||
return TextRenderer(headers.get_renderer(obj))
|
|
@ -18,6 +18,10 @@ import json
|
|||
import uuid
|
||||
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from ooi import utils
|
||||
import ooi.wsgi
|
||||
|
||||
|
||||
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 = 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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -55,9 +57,21 @@ def build_occi_server(server):
|
|||
'occi.compute.hostname="%s"' % name,
|
||||
'occi.core.id="%s"' % server_id,
|
||||
]
|
||||
links = []
|
||||
links.append('<%s?action=restart>; rel=http://schemas.ogf.org/occi/'
|
||||
'infrastructure/compute/action#restart' % server_id)
|
||||
links.append('<%s?action=start>; rel=http://schemas.ogf.org/occi/'
|
||||
'infrastructure/compute/action#start' % server_id)
|
||||
links.append('<%s?action=stop>; rel=http://schemas.ogf.org/occi/'
|
||||
'infrastructure/compute/action#stop' % server_id)
|
||||
links.append('<%s?action=suspend>; rel=http://schemas.ogf.org/occi/'
|
||||
'infrastructure/compute/action#suspend' % server_id)
|
||||
|
||||
result = []
|
||||
for c in cats:
|
||||
result.append(("Category", c))
|
||||
for l in links:
|
||||
result.append(("Link", l))
|
||||
for a in attrs:
|
||||
result.append(("X-OCCI-Attribute", a))
|
||||
return result
|
||||
|
@ -115,6 +129,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"]
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -46,16 +46,6 @@ class TestAttributes(base.TestCase):
|
|||
|
||||
self.assertRaises(AttributeError, set_val)
|
||||
|
||||
def test_as_str(self):
|
||||
attr = attribute.MutableAttribute("occi.foo.bar", "bar")
|
||||
self.assertEqual('occi.foo.bar="bar"', attr._as_str())
|
||||
attr.value = True
|
||||
self.assertEqual('occi.foo.bar="true"', attr._as_str())
|
||||
attr.value = False
|
||||
self.assertEqual('occi.foo.bar="false"', attr._as_str())
|
||||
attr.value = 4.5
|
||||
self.assertEqual("occi.foo.bar=4.5", attr._as_str())
|
||||
|
||||
|
||||
class TestAttributeCollection(base.TestCase):
|
||||
def test_collection(self):
|
||||
|
@ -121,9 +111,7 @@ class BaseTestCoreOCCICategory(base.TestCase):
|
|||
|
||||
|
||||
class TestCoreOCCICategory(BaseTestCoreOCCICategory):
|
||||
def test_str(self):
|
||||
cat = self.obj(*self.args)
|
||||
self.assertRaises(ValueError, cat.__str__)
|
||||
pass
|
||||
|
||||
|
||||
class TestCoreOCCIKind(BaseTestCoreOCCICategory):
|
||||
|
|
|
@ -42,7 +42,7 @@ class FakeController(object):
|
|||
def show(self, req, id):
|
||||
# Returning a ResponseObject should stop the pipepline
|
||||
# so the application won't be called.
|
||||
resp = wsgi.ResponseObject("Show and stop")
|
||||
resp = wsgi.ResponseObject([])
|
||||
return resp
|
||||
|
||||
|
||||
|
@ -69,7 +69,7 @@ class TestMiddleware(base.TestCase):
|
|||
result = webob.Request.blank("/foos/stop",
|
||||
method="GET").get_response(self.app)
|
||||
self.assertEqual(200, result.status_code)
|
||||
self.assertEqual("Show and stop", result.text)
|
||||
self.assertEqual("", result.text)
|
||||
|
||||
def test_post(self):
|
||||
result = webob.Request.blank("/foos",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# under the License.
|
||||
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
|
||||
def utf8(value):
|
||||
|
@ -28,3 +29,18 @@ def utf8(value):
|
|||
return value.encode('utf-8')
|
||||
assert isinstance(value, str)
|
||||
return value
|
||||
|
||||
|
||||
def join_url(base, parts):
|
||||
"""Join several parts into a url.
|
||||
|
||||
:param base: the base url
|
||||
:parts: parts to join into the url
|
||||
"""
|
||||
url = base
|
||||
if not isinstance(parts, (list, tuple)):
|
||||
parts = [parts]
|
||||
|
||||
for p in parts:
|
||||
url = urlparse.urljoin(url, p)
|
||||
return url
|
|
@ -23,8 +23,8 @@ import ooi.api.compute
|
|||
from ooi.api import query
|
||||
from ooi import exception
|
||||
from ooi.occi.core import collection
|
||||
from ooi import utils
|
||||
from ooi.wsgi import serializers
|
||||
from ooi.wsgi import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -358,20 +358,6 @@ class ResourceExceptionHandler(object):
|
|||
class Fault(webob.exc.HTTPException):
|
||||
"""Wrap webob.exc.HTTPException to provide API friendly response."""
|
||||
|
||||
_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"}
|
||||
|
||||
def __init__(self, exception):
|
||||
"""Create a Fault for the given webob.exc.exception."""
|
||||
self.wrapped_exc = exception
|
||||
|
@ -385,25 +371,16 @@ class Fault(webob.exc.HTTPException):
|
|||
|
||||
# Replace the body with fault details.
|
||||
code = self.wrapped_exc.status_int
|
||||
fault_name = self._fault_names.get(code, "occiFault")
|
||||
explanation = self.wrapped_exc.explanation
|
||||
LOG.debug("Returning %(code)s to user: %(explanation)s",
|
||||
{'code': code, 'explanation': explanation})
|
||||
|
||||
fault_data = {
|
||||
fault_name: {
|
||||
'code': code,
|
||||
'message': explanation}}
|
||||
if code == 413 or code == 429:
|
||||
retry = self.wrapped_exc.headers.get('Retry-After', None)
|
||||
if retry:
|
||||
fault_data[fault_name]['retryAfter'] = retry
|
||||
|
||||
content_type = req.content_type or "text/plain"
|
||||
mtype = serializers.get_media_map().get(content_type,
|
||||
"text")
|
||||
serializer = serializers.get_default_serializers()[mtype]
|
||||
self.wrapped_exc.body = serializer().serialize(fault_data)[-1]
|
||||
serialized_exc = serializer().serialize(self.wrapped_exc)
|
||||
self.wrapped_exc.body = serialized_exc[1]
|
||||
self.wrapped_exc.content_type = content_type
|
||||
|
||||
return self.wrapped_exc
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
import collections
|
||||
|
||||
from ooi.occi.rendering import headers as header_rendering
|
||||
from ooi.occi.rendering import text as text_rendering
|
||||
from ooi.wsgi import utils
|
||||
|
||||
|
||||
|
@ -30,7 +32,11 @@ class TextSerializer(object):
|
|||
if not isinstance(data, list):
|
||||
data = [data]
|
||||
|
||||
ret = "\n".join([str(d) for d in data])
|
||||
renderers = []
|
||||
for d in data:
|
||||
renderers.append(text_rendering.get_renderer(d))
|
||||
|
||||
ret = "\n".join([r.render() for r in renderers])
|
||||
return None, utils.utf8(ret)
|
||||
|
||||
|
||||
|
@ -39,14 +45,13 @@ class HeaderSerializer(object):
|
|||
if not isinstance(data, list):
|
||||
data = [data]
|
||||
|
||||
headers = []
|
||||
renderers = []
|
||||
for d in data:
|
||||
if hasattr(d, "headers"):
|
||||
headers.extend(d.headers())
|
||||
else:
|
||||
# NOTE(aloga): we should not be here.
|
||||
pass
|
||||
renderers.append(header_rendering.get_renderer(d))
|
||||
|
||||
# Header renderers will return a list, so we must flatten the results
|
||||
# before returning them
|
||||
headers = [i for r in renderers for i in r.render()]
|
||||
return headers, ""
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue