Convert root path / to use plain JSON
This change replaces the Version and Root types with simple functions that return data as a dict. A new @expose decorator is defined in ironic.api.method which currently only needs to format exceptions. In the future it may also do input validation. Change-Id: I4e3b84fb652a74463b44c3ae6c75cdac4d2ab6db Story: 1651346 Task: 10551
This commit is contained in:
@@ -17,53 +17,28 @@
|
||||
import pecan
|
||||
from pecan import rest
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import v1
|
||||
from ironic.api.controllers import version
|
||||
from ironic.api import expose
|
||||
from ironic.api import method
|
||||
|
||||
|
||||
class Root(base.Base):
|
||||
|
||||
name = str
|
||||
"""The name of the API"""
|
||||
|
||||
description = str
|
||||
"""Some information about this API"""
|
||||
|
||||
versions = [version.Version]
|
||||
"""Links to all the versions available in this API"""
|
||||
|
||||
default_version = version.Version
|
||||
"""A link to the default version of the API"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
root = Root()
|
||||
root.name = "OpenStack Ironic API"
|
||||
root.description = ("Ironic is an OpenStack project which aims to "
|
||||
"provision baremetal machines.")
|
||||
root.default_version = version.default_version()
|
||||
root.versions = [root.default_version]
|
||||
return root
|
||||
def root():
|
||||
return {
|
||||
'name': "OpenStack Ironic API",
|
||||
'description': ("Ironic is an OpenStack project which aims to "
|
||||
"provision baremetal machines."),
|
||||
'default_version': version.default_version(),
|
||||
'versions': version.all_versions()
|
||||
}
|
||||
|
||||
|
||||
class RootController(rest.RestController):
|
||||
|
||||
_versions = [version.ID_VERSION1]
|
||||
"""All supported API versions"""
|
||||
|
||||
_default_version = version.ID_VERSION1
|
||||
"""The default API version"""
|
||||
|
||||
v1 = v1.Controller()
|
||||
|
||||
@expose.expose(Root)
|
||||
@method.expose()
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
# the request object to make the links.
|
||||
return Root.convert()
|
||||
return root()
|
||||
|
||||
@pecan.expose()
|
||||
def _route(self, args, request=None):
|
||||
@@ -73,6 +48,6 @@ class RootController(rest.RestController):
|
||||
if the version number is not specified in the url.
|
||||
"""
|
||||
|
||||
if args[0] and args[0] not in self._versions:
|
||||
args = [self._default_version] + args
|
||||
if args[0] and args[0] != version.ID_VERSION1:
|
||||
args = [version.ID_VERSION1] + args
|
||||
return super(RootController, self)._route(args, request)
|
||||
|
||||
@@ -113,7 +113,7 @@ class V1(base.Base):
|
||||
deploy_templates = None
|
||||
"""Links to the deploy_templates resource"""
|
||||
|
||||
version = version.Version
|
||||
version = None
|
||||
"""Version discovery information."""
|
||||
|
||||
events = None
|
||||
|
||||
@@ -11,53 +11,40 @@
|
||||
# under the License.
|
||||
|
||||
from ironic import api
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
|
||||
ID_VERSION1 = 'v1'
|
||||
|
||||
|
||||
class Version(base.Base):
|
||||
"""An API version representation.
|
||||
|
||||
This class represents an API version, including the minimum and
|
||||
maximum minor versions that are supported within the major version.
|
||||
"""
|
||||
|
||||
id = str
|
||||
"""The ID of the (major) version, also acts as the release number"""
|
||||
|
||||
links = None
|
||||
"""A Link that point to a specific version of the API"""
|
||||
|
||||
status = str
|
||||
"""Status of the version.
|
||||
|
||||
One of:
|
||||
* CURRENT - the latest version of API,
|
||||
* SUPPORTED - supported, but not latest, version of API,
|
||||
* DEPRECATED - supported, but deprecated, version of API.
|
||||
"""
|
||||
|
||||
version = str
|
||||
"""The current, maximum supported (major.minor) version of API."""
|
||||
|
||||
min_version = str
|
||||
"""Minimum supported (major.minor) version of API."""
|
||||
|
||||
def __init__(self, id, min_version, version, status='CURRENT'):
|
||||
self.id = id
|
||||
self.links = [link.make_link('self', api.request.public_url,
|
||||
self.id, '', bookmark=True)]
|
||||
self.status = status
|
||||
self.version = version
|
||||
self.min_version = min_version
|
||||
def all_versions():
|
||||
return [default_version()]
|
||||
|
||||
|
||||
def default_version():
|
||||
"""Return a dict representing the current default version
|
||||
|
||||
id: The ID of the (major) version, also acts as the release number
|
||||
links: A list containing one link that points to the current version
|
||||
of the API
|
||||
|
||||
status: Status of the version, one of CURRENT, SUPPORTED, DEPRECATED
|
||||
|
||||
min_version: The current, maximum supported (major.minor) version of API.
|
||||
|
||||
version: Minimum supported (major.minor) version of API.
|
||||
"""
|
||||
|
||||
# NOTE(dtantsur): avoid circular imports
|
||||
from ironic.api.controllers.v1 import versions
|
||||
|
||||
return Version(ID_VERSION1,
|
||||
versions.min_version_string(),
|
||||
versions.max_version_string())
|
||||
return {
|
||||
'id': ID_VERSION1,
|
||||
'links': [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
ID_VERSION1, '', bookmark=True)
|
||||
],
|
||||
'status': 'CURRENT',
|
||||
'min_version': versions.min_version_string(),
|
||||
'version': versions.max_version_string()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#
|
||||
# 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 functools
|
||||
from http import client as http_client
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
pecan_json_decorate = pecan.expose(
|
||||
content_type='application/json',
|
||||
generic=False)
|
||||
|
||||
|
||||
def expose():
|
||||
|
||||
def decorate(f):
|
||||
|
||||
@functools.wraps(f)
|
||||
def callfunction(self, *args, **kwargs):
|
||||
try:
|
||||
result = f(self, *args, **kwargs)
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
exception_info = sys.exc_info()
|
||||
orig_exception = exception_info[1]
|
||||
orig_code = getattr(orig_exception, 'code', None)
|
||||
result = 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 json.dumps(result)
|
||||
|
||||
pecan_json_decorate(callfunction)
|
||||
return callfunction
|
||||
|
||||
return decorate
|
||||
|
||||
|
||||
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
|
||||
@@ -103,7 +103,7 @@ class TestNoExceptionTracebackHook(base.BaseApiTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNoExceptionTracebackHook, self).setUp()
|
||||
p = mock.patch.object(root.Root, 'convert')
|
||||
p = mock.patch.object(root, 'root')
|
||||
self.root_convert_mock = p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user