223 lines
7.0 KiB
Python
223 lines
7.0 KiB
Python
#
|
|
# Copyright 2015 Rackspace, Inc
|
|
# All Rights Reserved
|
|
#
|
|
# 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 datetime
|
|
import functools
|
|
from http import client as http_client
|
|
import inspect
|
|
import json
|
|
import sys
|
|
import traceback
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
import pecan
|
|
from webob import static
|
|
|
|
from ironic.api import args as api_args
|
|
from ironic.api import functions
|
|
from ironic.api import types as atypes
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class JSonRenderer(object):
|
|
@staticmethod
|
|
def __init__(path, extra_vars):
|
|
pass
|
|
|
|
@staticmethod
|
|
def render(template_path, namespace):
|
|
if 'faultcode' in namespace:
|
|
return encode_error(None, namespace)
|
|
result = encode_result(
|
|
namespace['result'],
|
|
namespace['datatype']
|
|
)
|
|
return result
|
|
|
|
|
|
pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer
|
|
|
|
pecan_json_decorate = pecan.expose(
|
|
template='wsmejson:',
|
|
content_type='application/json',
|
|
generic=False)
|
|
|
|
|
|
def expose(*args, **kwargs):
|
|
sig = functions.signature(*args, **kwargs)
|
|
|
|
def decorate(f):
|
|
sig(f)
|
|
funcdef = functions.FunctionDefinition.get(f)
|
|
funcdef.resolve_types(atypes.registry)
|
|
|
|
@functools.wraps(f)
|
|
def callfunction(self, *args, **kwargs):
|
|
return_type = funcdef.return_type
|
|
|
|
try:
|
|
args, kwargs = api_args.get_args(
|
|
funcdef, args, kwargs, pecan.request.params,
|
|
pecan.request.body, pecan.request.content_type
|
|
)
|
|
result = f(self, *args, **kwargs)
|
|
|
|
# NOTE: Support setting of status_code with default 201
|
|
pecan.response.status = funcdef.status_code
|
|
if isinstance(result, atypes.PassthruResponse):
|
|
pecan.response.status = result.status_code
|
|
|
|
# NOTE(lucasagomes): If the return code is 204
|
|
# (No Response) we have to make sure that we are not
|
|
# returning anything in the body response and the
|
|
# content-length is 0
|
|
if result.status_code == 204:
|
|
return_type = None
|
|
|
|
if callable(getattr(result.obj, 'read', None)):
|
|
# Stream the files-like data directly to the response
|
|
pecan.response.app_iter = static.FileIter(result.obj)
|
|
return_type = None
|
|
result = None
|
|
else:
|
|
result = result.obj
|
|
|
|
except Exception:
|
|
try:
|
|
exception_info = sys.exc_info()
|
|
orig_exception = exception_info[1]
|
|
orig_code = getattr(orig_exception, 'code', None)
|
|
data = format_exception(
|
|
exception_info,
|
|
cfg.CONF.debug_tracebacks_in_api
|
|
)
|
|
finally:
|
|
del exception_info
|
|
|
|
if orig_code and orig_code in http_client.responses:
|
|
pecan.response.status = orig_code
|
|
else:
|
|
pecan.response.status = 500
|
|
|
|
return data
|
|
|
|
if return_type is None:
|
|
pecan.request.pecan['content_type'] = None
|
|
pecan.response.content_type = None
|
|
return ''
|
|
|
|
return dict(
|
|
datatype=return_type,
|
|
result=result
|
|
)
|
|
|
|
pecan_json_decorate(callfunction)
|
|
pecan.util._cfg(callfunction)['argspec'] = inspect.getfullargspec(f)
|
|
callfunction._wsme_definition = funcdef
|
|
return callfunction
|
|
|
|
return decorate
|
|
|
|
|
|
def tojson(datatype, value):
|
|
"""A generic converter from python to jsonify-able datatypes.
|
|
|
|
"""
|
|
if value is None:
|
|
return None
|
|
if isinstance(datatype, atypes.ArrayType):
|
|
return [tojson(datatype.item_type, item) for item in value]
|
|
if isinstance(datatype, atypes.DictType):
|
|
return dict((
|
|
(tojson(datatype.key_type, item[0]),
|
|
tojson(datatype.value_type, item[1]))
|
|
for item in value.items()
|
|
))
|
|
if isinstance(value, datetime.datetime):
|
|
return value.isoformat()
|
|
if atypes.iscomplex(datatype):
|
|
d = dict()
|
|
for attr in atypes.list_attributes(datatype):
|
|
attr_value = getattr(value, attr.key)
|
|
if attr_value is not atypes.Unset:
|
|
d[attr.name] = tojson(attr.datatype, attr_value)
|
|
return d
|
|
if isinstance(datatype, atypes.UserType):
|
|
return tojson(datatype.basetype, datatype.tobasetype(value))
|
|
return value
|
|
|
|
|
|
def encode_result(value, datatype, **options):
|
|
jsondata = tojson(datatype, value)
|
|
return json.dumps(jsondata)
|
|
|
|
|
|
def encode_error(context, errordetail):
|
|
return json.dumps(errordetail)
|
|
|
|
|
|
def format_exception(excinfo, debug=False):
|
|
"""Extract informations that can be sent to the client."""
|
|
error = excinfo[1]
|
|
code = getattr(error, 'code', None)
|
|
if code and code in http_client.responses and (400 <= code < 500):
|
|
faultstring = (error.faultstring if hasattr(error, 'faultstring')
|
|
else str(error))
|
|
faultcode = getattr(error, 'faultcode', 'Client')
|
|
r = dict(faultcode=faultcode,
|
|
faultstring=faultstring)
|
|
LOG.debug("Client-side error: %s", r['faultstring'])
|
|
r['debuginfo'] = None
|
|
return r
|
|
else:
|
|
faultstring = str(error)
|
|
debuginfo = "\n".join(traceback.format_exception(*excinfo))
|
|
|
|
LOG.error('Server-side error: "%s". Detail: \n%s',
|
|
faultstring, debuginfo)
|
|
|
|
faultcode = getattr(error, 'faultcode', 'Server')
|
|
r = dict(faultcode=faultcode, faultstring=faultstring)
|
|
if debug:
|
|
r['debuginfo'] = debuginfo
|
|
else:
|
|
r['debuginfo'] = None
|
|
return r
|
|
|
|
|
|
class validate(object):
|
|
"""Decorator that define the arguments types of a function.
|
|
|
|
|
|
Example::
|
|
|
|
class MyController(object):
|
|
@expose(str)
|
|
@validate(datetime.date, datetime.time)
|
|
def format(self, d, t):
|
|
return d.isoformat() + ' ' + t.isoformat()
|
|
"""
|
|
def __init__(self, *param_types):
|
|
self.param_types = param_types
|
|
|
|
def __call__(self, func):
|
|
argspec = functions.getargspec(func)
|
|
fd = functions.FunctionDefinition.get(func)
|
|
fd.set_arg_types(argspec, self.param_types)
|
|
return func
|