Merge "Updates microversion root and error messages"
This commit is contained in:
commit
bbb0f6c2b2
@ -31,12 +31,24 @@ class Version(base.APIBase):
|
|||||||
links = [link.Link]
|
links = [link.Link]
|
||||||
"""A Link that point to a specific version of the API"""
|
"""A Link that point to a specific version of the API"""
|
||||||
|
|
||||||
|
status = wtypes.text
|
||||||
|
"""The current status of the version: CURRENT, SUPPORTED, UNSUPPORTED"""
|
||||||
|
|
||||||
|
max_version = wtypes.text
|
||||||
|
"""The max microversion supported by this version"""
|
||||||
|
|
||||||
|
min_version = wtypes.text
|
||||||
|
"""The min microversion supported by this version"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert(id):
|
def convert(id, status, max, min):
|
||||||
version = Version()
|
version = Version()
|
||||||
version.id = id
|
version.id = id
|
||||||
version.links = [link.Link.make_link('self', pecan.request.host_url,
|
version.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
id, '', bookmark=True)]
|
id, '', bookmark=True)]
|
||||||
|
version.status = status
|
||||||
|
version.max_version = max
|
||||||
|
version.min_version = min
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
@ -51,17 +63,13 @@ class Root(base.APIBase):
|
|||||||
versions = [Version]
|
versions = [Version]
|
||||||
"""Links to all the versions available in this API"""
|
"""Links to all the versions available in this API"""
|
||||||
|
|
||||||
default_version = Version
|
|
||||||
"""A link to the default version of the API"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert():
|
def convert():
|
||||||
root = Root()
|
root = Root()
|
||||||
root.name = "OpenStack Magnum API"
|
root.name = "OpenStack Magnum API"
|
||||||
root.description = ("Magnum is an OpenStack project which aims to "
|
root.description = ("Magnum is an OpenStack project which aims to "
|
||||||
"provide container management.")
|
"provide container management.")
|
||||||
root.versions = [Version.convert('v1')]
|
root.versions = [Version.convert('v1', "CURRENT", "1.1", "1.1")]
|
||||||
root.default_version = Version.convert('v1')
|
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ NOTE: IN PROGRESS AND NOT FULLY IMPLEMENTED.
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
from webob import exc
|
|
||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
|
|
||||||
from magnum.api.controllers import base as controllers_base
|
from magnum.api.controllers import base as controllers_base
|
||||||
@ -31,6 +30,7 @@ from magnum.api.controllers.v1 import baymodel
|
|||||||
from magnum.api.controllers.v1 import certificate
|
from magnum.api.controllers.v1 import certificate
|
||||||
from magnum.api.controllers.v1 import magnum_services
|
from magnum.api.controllers.v1 import magnum_services
|
||||||
from magnum.api import expose
|
from magnum.api import expose
|
||||||
|
from magnum.api import http_error
|
||||||
from magnum.i18n import _
|
from magnum.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@ -157,32 +157,41 @@ class Controller(rest.RestController):
|
|||||||
headers = {}
|
headers = {}
|
||||||
# ensure that major version in the URL matches the header
|
# ensure that major version in the URL matches the header
|
||||||
if version.major != BASE_VERSION:
|
if version.major != BASE_VERSION:
|
||||||
raise exc.HTTPNotAcceptable(_(
|
raise http_error.HTTPNotAcceptableAPIVersion(_(
|
||||||
"Mutually exclusive versions requested. Version %(ver)s "
|
"Mutually exclusive versions requested. Version %(ver)s "
|
||||||
"requested but not supported by this service."
|
"requested but not supported by this service."
|
||||||
"The supported version range is: "
|
"The supported version range is: "
|
||||||
"[%(min)s, %(max)s].") % {'ver': version,
|
"[%(min)s, %(max)s].") % {'ver': version,
|
||||||
'min': MIN_VER_STR,
|
'min': MIN_VER_STR,
|
||||||
'max': MAX_VER_STR},
|
'max': MAX_VER_STR},
|
||||||
headers=headers)
|
headers=headers,
|
||||||
|
max_version=str(MAX_VER),
|
||||||
|
min_version=str(MIN_VER))
|
||||||
# ensure the minor version is within the supported range
|
# ensure the minor version is within the supported range
|
||||||
if version < MIN_VER or version > MAX_VER:
|
if version < MIN_VER or version > MAX_VER:
|
||||||
raise exc.HTTPNotAcceptable(_(
|
raise http_error.HTTPNotAcceptableAPIVersion(_(
|
||||||
"Version %(ver)s was requested but the minor version is not "
|
"Version %(ver)s was requested but the minor version is not "
|
||||||
"supported by this service. The supported version range is: "
|
"supported by this service. The supported version range is: "
|
||||||
"[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR,
|
"[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR,
|
||||||
'max': MAX_VER_STR}, headers=headers)
|
'max': MAX_VER_STR},
|
||||||
|
headers=headers,
|
||||||
|
max_version=str(MAX_VER),
|
||||||
|
min_version=str(MIN_VER))
|
||||||
|
|
||||||
@pecan.expose()
|
@pecan.expose()
|
||||||
def _route(self, args):
|
def _route(self, args):
|
||||||
version = controllers_base.Version(
|
version = controllers_base.Version(
|
||||||
pecan.request.headers, MIN_VER_STR, MAX_VER_STR)
|
pecan.request.headers, MIN_VER_STR, MAX_VER_STR)
|
||||||
|
|
||||||
# Always set the min and max headers
|
# Always set the basic version headers
|
||||||
pecan.response.headers[
|
pecan.response.headers[
|
||||||
controllers_base.Version.min_string] = MIN_VER_STR
|
controllers_base.Version.min_string] = MIN_VER_STR
|
||||||
pecan.response.headers[
|
pecan.response.headers[
|
||||||
controllers_base.Version.max_string] = MAX_VER_STR
|
controllers_base.Version.max_string] = MAX_VER_STR
|
||||||
|
pecan.response.headers[
|
||||||
|
controllers_base.Version.string] = " ".join(
|
||||||
|
[controllers_base.Version.service_string, str(version)])
|
||||||
|
pecan.response.headers["vary"] = controllers_base.Version.string
|
||||||
|
|
||||||
# assert that requested version is supported
|
# assert that requested version is supported
|
||||||
self._check_version(version, pecan.response.headers)
|
self._check_version(version, pecan.response.headers)
|
||||||
|
70
magnum/api/http_error.py
Normal file
70
magnum/api/http_error.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# 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 json
|
||||||
|
import six
|
||||||
|
from webob import exc
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotAcceptableAPIVersion(exc.HTTPNotAcceptable):
|
||||||
|
# subclass of :class:`~HTTPNotAcceptable`
|
||||||
|
#
|
||||||
|
# This indicates the resource identified by the request is only
|
||||||
|
# capable of generating response entities which have content
|
||||||
|
# characteristics not acceptable according to the accept headers
|
||||||
|
# sent in the request.
|
||||||
|
#
|
||||||
|
# code: 406, title: Not Acceptable
|
||||||
|
#
|
||||||
|
# differences from webob.exc.HTTPNotAcceptable:
|
||||||
|
#
|
||||||
|
# - additional max and min version paramters
|
||||||
|
# - additional error info for code, title, and links
|
||||||
|
code = 406
|
||||||
|
title = 'Not Acceptable'
|
||||||
|
max_version = ''
|
||||||
|
min_version = ''
|
||||||
|
|
||||||
|
def __init__(self, detail=None, headers=None, comment=None,
|
||||||
|
body_template=None, max_version='', min_version='', **kw):
|
||||||
|
|
||||||
|
super(HTTPNotAcceptableAPIVersion, self).__init__(
|
||||||
|
detail=detail, headers=headers, comment=comment,
|
||||||
|
body_template=body_template, **kw)
|
||||||
|
|
||||||
|
self.max_version = max_version
|
||||||
|
self.min_version = min_version
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
for err_str in self.app_iter:
|
||||||
|
err = {}
|
||||||
|
try:
|
||||||
|
err = json.loads(err_str.decode('utf-8'))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
links = {'rel': 'help', 'href': 'http://developer.openstack.org'
|
||||||
|
'/api-guide/compute/microversions.html'}
|
||||||
|
|
||||||
|
err['max_version'] = self.max_version
|
||||||
|
err['min_version'] = self.min_version
|
||||||
|
err['code'] = "magnum.microversion-unsupported"
|
||||||
|
err['links'] = [links]
|
||||||
|
err['title'] = "Requested microversion is unsupported"
|
||||||
|
|
||||||
|
self.app_iter = [six.b(json.dumps(err))]
|
||||||
|
self.headers['Content-Length'] = str(len(self.app_iter[0]))
|
||||||
|
|
||||||
|
return super(HTTPNotAcceptableAPIVersion, self).__call__(
|
||||||
|
environ, start_response)
|
@ -29,6 +29,41 @@ class ParsableErrorMiddleware(object):
|
|||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
|
def _update_errors(self, app_iter, status_code):
|
||||||
|
errs = []
|
||||||
|
for err_str in app_iter:
|
||||||
|
err = {}
|
||||||
|
try:
|
||||||
|
err = json.loads(err_str.decode('utf-8'))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'title' in err and 'description' in err:
|
||||||
|
title = err['title']
|
||||||
|
desc = err['description']
|
||||||
|
elif 'faultstring' in err:
|
||||||
|
title = err['faultstring'].split('.', 1)[0]
|
||||||
|
desc = err['faultstring']
|
||||||
|
else:
|
||||||
|
title = ''
|
||||||
|
desc = ''
|
||||||
|
|
||||||
|
code = err['faultcode'].lower() if 'faultcode' in err else ''
|
||||||
|
|
||||||
|
# if already formatted by custom exception, don't update
|
||||||
|
if 'min_version' in err:
|
||||||
|
errs.append(err)
|
||||||
|
else:
|
||||||
|
errs.append({
|
||||||
|
'request_id': '',
|
||||||
|
'code': code,
|
||||||
|
'status': status_code,
|
||||||
|
'title': title,
|
||||||
|
'detail': desc,
|
||||||
|
'links': []})
|
||||||
|
|
||||||
|
return errs
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
# Request for this state, modified by replace_start_response()
|
# Request for this state, modified by replace_start_response()
|
||||||
# and used when an error is being reported.
|
# and used when an error is being reported.
|
||||||
@ -54,44 +89,17 @@ class ParsableErrorMiddleware(object):
|
|||||||
]
|
]
|
||||||
# Save the headers in case we need to modify them.
|
# Save the headers in case we need to modify them.
|
||||||
state['headers'] = headers
|
state['headers'] = headers
|
||||||
|
|
||||||
return start_response(status, headers, exc_info)
|
return start_response(status, headers, exc_info)
|
||||||
|
|
||||||
app_iter = self.app(environ, replacement_start_response)
|
app_iter = self.app(environ, replacement_start_response)
|
||||||
|
|
||||||
if (state['status_code'] // 100) not in (2, 3):
|
if (state['status_code'] // 100) not in (2, 3):
|
||||||
errs = []
|
errs = self._update_errors(app_iter, state['status_code'])
|
||||||
for err_str in app_iter:
|
|
||||||
err = {}
|
|
||||||
try:
|
|
||||||
err = json.loads(err_str.decode('utf-8'))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if 'title' in err and 'description' in err:
|
|
||||||
title = err['title']
|
|
||||||
desc = err['description']
|
|
||||||
elif 'faultstring' in err:
|
|
||||||
title = err['faultstring'].split('.', 1)[0]
|
|
||||||
desc = err['faultstring']
|
|
||||||
else:
|
|
||||||
title = ''
|
|
||||||
desc = ''
|
|
||||||
|
|
||||||
code = err['faultcode'].lower() if 'faultcode' in err else ''
|
|
||||||
|
|
||||||
errs.append({
|
|
||||||
'request_id': '',
|
|
||||||
'code': code,
|
|
||||||
'status': state['status_code'],
|
|
||||||
'title': title,
|
|
||||||
'detail': desc,
|
|
||||||
'links': []
|
|
||||||
})
|
|
||||||
|
|
||||||
body = [six.b(json.dumps({'errors': errs}))]
|
body = [six.b(json.dumps({'errors': errs}))]
|
||||||
|
|
||||||
state['headers'].append(('Content-Type', 'application/json'))
|
state['headers'].append(('Content-Type', 'application/json'))
|
||||||
state['headers'].append(('Content-Length', str(len(body[0]))))
|
state['headers'].append(('Content-Length', str(len(body[0]))))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
body = app_iter
|
body = app_iter
|
||||||
return body
|
return body
|
||||||
|
@ -32,16 +32,16 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestRootController, self).setUp()
|
super(TestRootController, self).setUp()
|
||||||
self.root_expected = {
|
self.root_expected = {
|
||||||
u'default_version':
|
|
||||||
{u'id': u'v1', u'links':
|
|
||||||
[{u'href': u'http://localhost/v1/', u'rel': u'self'}]},
|
|
||||||
u'description': u'Magnum is an OpenStack project which '
|
u'description': u'Magnum is an OpenStack project which '
|
||||||
'aims to provide container management.',
|
'aims to provide container management.',
|
||||||
u'name': u'OpenStack Magnum API',
|
u'name': u'OpenStack Magnum API',
|
||||||
u'versions': [{u'id': u'v1',
|
u'versions': [{u'id': u'v1',
|
||||||
u'links':
|
u'links':
|
||||||
[{u'href': u'http://localhost/v1/',
|
[{u'href': u'http://localhost/v1/',
|
||||||
u'rel': u'self'}]}]}
|
u'rel': u'self'}],
|
||||||
|
u'status': u'CURRENT',
|
||||||
|
u'max_version': u'1.1',
|
||||||
|
u'min_version': u'1.1'}]}
|
||||||
|
|
||||||
self.v1_expected = {
|
self.v1_expected = {
|
||||||
u'media_types':
|
u'media_types':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user