# Copyright 2014 # The Cloudscaling Group, Inc. # # 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. from oslo_log import log as logging from oslo_serialization import jsonutils import webob from gceapi import exception from gceapi.i18n import _ from gceapi import wsgi_ext as openstack_wsgi LOG = logging.getLogger(__name__) class JSONDictSerializer(openstack_wsgi.DictSerializer): """JSON request body serialization.""" def serialize(self, data, request): params = {'false': False, 'true': True} pretty_print = request.params.get("prettyPrint", True) if pretty_print in params: pretty_print = params[pretty_print] ident = None if pretty_print: ident = 4 ret = jsonutils.dumps(data, default=jsonutils.to_primitive, indent=ident) return ret class GCEResponse(openstack_wsgi.ResponseObject): """GCE Response body serialization.""" def serialize(self, request, content_type, default_serializers=None): if self.serializer: serializer = self.serializer else: _mtype, _serializer = self.get_serializer(content_type, default_serializers) serializer = _serializer() response = webob.Response() response.status_int = self.code for hdr, value in self._headers.items(): response.headers[hdr] = value response.headers['Content-Type'] = content_type if self.obj is not None: response.body = serializer.serialize(self.obj, request) return response class GCEFault(webob.exc.HTTPException): """Wrap webob.exc.HTTPException to provide API friendly response.""" def __init__(self, exception): """ Create a Fault for the given webob.exc.exception or gceapi.exception. """ self.wrapped_exc = exception for key, value in self.wrapped_exc.headers.items(): self.wrapped_exc.headers[key] = str(value) class GCEResourceExceptionHandler(object): """Context manager to handle Resource exceptions. Used when processing exceptions generated by API implementation methods (or their extensions). Converts most exceptions to Fault exceptions, with the appropriate logging. """ def __enter__(self): return None def __exit__(self, ex_type, ex_value, ex_traceback): if not ex_value: return True if isinstance(ex_value, exception.NotAuthorized): msg = unicode(ex_value) raise GCEFault(webob.exc.HTTPForbidden(explanation=msg)) elif isinstance(ex_value, exception.Invalid): msg = unicode(ex_value) raise GCEFault(exception.ConvertedException( code=ex_value.code, explanation=msg)) # Under python 2.6, TypeError's exception value is actually a string, # so test # here via ex_type instead: # http://bugs.python.org/issue7853 elif issubclass(ex_type, TypeError): exc_info = (ex_type, ex_value, ex_traceback) LOG.error(_('Exception handling resource: %s') % ex_value, exc_info=exc_info) raise GCEFault(webob.exc.HTTPBadRequest()) elif isinstance(ex_value, GCEFault): LOG.info(_("Fault thrown: %s"), unicode(ex_value)) raise ex_value elif isinstance(ex_value, webob.exc.HTTPException): LOG.info(_("HTTP exception thrown: %s"), unicode(ex_value)) raise GCEFault(ex_value) elif isinstance(ex_value, exception.GceapiException): LOG.info(_("Gceapi exception thrown: %s"), unicode(ex_value)) raise GCEFault(ex_value) else: msg = unicode(ex_value) raise GCEFault(exception.ConvertedException( code=500, title=ex_type.__name__, explanation=msg)) class GCEResource(openstack_wsgi.Resource): """Common GCE resource response formatter""" def __init__(self, *args, **kwargs): super(GCEResource, self).__init__(*args, **kwargs) self.default_serializers = dict(json=JSONDictSerializer) def _check_requested_project(self, project_id, context): if (not context or project_id is None or (project_id not in [context.project_id, context.project_name])): msg = _("Project '%s' could not be found") % project_id \ if project_id is not None \ else _("Project hasn`t been provided") raise GCEFault(webob.exc.HTTPBadRequest( explanation=msg)) def _process_stack(self, request, action, action_args, content_type, body, accept): """Implement the processing stack.""" method = None try: # Get the implementing method try: method = self.get_method(request, action, content_type, body) except (AttributeError, TypeError): msg = _("There is no such action: %s") % action raise GCEFault(webob.exc.HTTPNotFound( explanation=msg)) except KeyError as ex: msg = _("There is no such action: %s") % ex.args[0] raise GCEFault(webob.exc.HTTPBadRequest( explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") raise GCEFault(webob.exc.HTTPBadRequest( explanation=msg)) # Now, deserialize the request body... try: if content_type: contents = self.deserialize(method, content_type, body) else: contents = {} except exception.InvalidContentType: msg = _("Unsupported Content-Type") raise GCEFault(webob.exc.HTTPBadRequest( explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") raise GCEFault(webob.exc.HTTPBadRequest( explanation=msg)) # Update the action args action_args.update(contents) # Check project project_id = action_args.pop("project_id", None) context = request.environ.get('gceapi.context') action_result = self._check_requested_project(project_id, context) if action_result is None: with GCEResourceExceptionHandler(): action_result = self.dispatch(method, request, action_args) except GCEFault as ex: action_result = ex.wrapped_exc response = None resp_obj = None if (action_result is None or type(action_result) is dict or isinstance(action_result, Exception)): action_result, result_code = self.controller.process_result( request, action, action_result) resp_obj = GCEResponse(action_result, code=result_code) elif isinstance(action_result, GCEResponse): resp_obj = action_result else: response = action_result # Serialize response object if resp_obj: if method is not None: serializers = getattr(method, 'wsgi_serializers', {}) else: serializers = {} resp_obj._bind_method_serializers(serializers) if method is not None and hasattr(method, 'wsgi_code'): resp_obj._default_code = method.wsgi_code resp_obj.preserialize(accept, self.default_serializers) response = resp_obj.serialize(request, accept, self.default_serializers) try: msg_dict = dict(url=request.url, status=response.status_int) msg = _("%(url)s returned with HTTP %(status)d") % msg_dict except AttributeError as e: msg_dict = dict(url=request.url, e=e) msg = _("%(url)s returned a fault: %(e)s") % msg_dict LOG.info(msg) return response