Merge "Updates microversion root and error messages"

This commit is contained in:
Jenkins 2016-07-12 09:06:10 +00:00 committed by Gerrit Code Review
commit bbb0f6c2b2
5 changed files with 141 additions and 46 deletions

View File

@ -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

View File

@ -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
View 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)

View File

@ -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

View File

@ -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':