Use microversion parse 0.2.1

That change extracts much of the microversion handling code from
placement and uses code from the microversion-parse library instead.
The code in microversion-parse was taken from placement and lightly
adjusted to make it more generally useful.

Depends-On: https://review.openstack.org/555332
Change-Id: I5a70ebf2843d2b50dccb44e66d50e1e5105005f4
This commit is contained in:
Chris Dent 2018-03-06 21:24:02 +00:00
parent 000391041f
commit 16e1910d6e
7 changed files with 29 additions and 135 deletions

View File

@ -11,6 +11,7 @@
# under the License.
"""Deployment handling for Placmenent API."""
from microversion_parse import middleware as mp_middleware
import oslo_middleware
from oslo_middleware import cors
@ -19,6 +20,7 @@ from nova.api.openstack.placement import fault_wrap
from nova.api.openstack.placement import handler
from nova.api.openstack.placement import microversion
from nova.api.openstack.placement import requestlog
from nova.api.openstack.placement import util
# TODO(cdent): NAME points to the config project being used, so for
@ -49,11 +51,15 @@ def deploy(conf):
context_middleware = auth.PlacementKeystoneContext
req_id_middleware = oslo_middleware.RequestId
microversion_middleware = microversion.MicroversionMiddleware
microversion_middleware = mp_middleware.MicroversionMiddleware
fault_middleware = fault_wrap.FaultWrapper
request_log = requestlog.RequestLog
application = handler.PlacementHandler()
# configure microversion middleware in the old school way
application = microversion_middleware(
application, microversion.SERVICE_TYPE, microversion.VERSIONS,
json_error_formatter=util.json_error_formatter)
# NOTE(cdent): The ordering here is important. The list is ordered
# from the inside out. For a single request req_id_middleware is called
@ -63,8 +69,7 @@ def deploy(conf):
# order the request went in. This order ensures that log messages
# all see the same contextual information including request id and
# authentication information.
for middleware in (microversion_middleware,
fault_middleware,
for middleware in (fault_middleware,
request_log,
context_middleware,
auth_middleware,

View File

@ -22,11 +22,6 @@ import inspect
import microversion_parse
import webob
# NOTE(cdent): avoid cyclical import conflict between util and
# microversion
import nova.api.openstack.placement.util
from nova.i18n import _
SERVICE_TYPE = 'placement'
MICROVERSION_ENVIRON = '%s.microversion' % SERVICE_TYPE
@ -77,119 +72,6 @@ def min_version_string():
return VERSIONS[0]
def parse_version_string(version_string):
"""Turn a version string into a Version
:param version_string: A string of two numerals, X.Y, or 'latest'
:returns: a Version
:raises: TypeError
"""
if version_string == 'latest':
version_string = max_version_string()
try:
# The combination of int and a limited split with the
# named tuple means that this incantation will raise
# ValueError or TypeError when the incoming data is
# poorly formed but will, however, naturally adapt to
# extraneous whitespace.
return Version(*(int(value) for value
in version_string.split('.', 1)))
except (ValueError, TypeError) as exc:
raise TypeError(
_('invalid version string: %(version_string)s; %(exc)s') %
{'version_string': version_string, 'exc': exc})
class MicroversionMiddleware(object):
"""WSGI middleware for getting microversion info."""
def __init__(self, application):
self.application = application
@webob.dec.wsgify
def __call__(self, req):
util = nova.api.openstack.placement.util
try:
microversion = extract_version(req.headers)
except ValueError as exc:
raise webob.exc.HTTPNotAcceptable(
_('Invalid microversion: %(error)s') % {'error': exc},
json_formatter=util.json_error_formatter)
except TypeError as exc:
raise webob.exc.HTTPBadRequest(
_('Invalid microversion: %(error)s') % {'error': exc},
json_formatter=util.json_error_formatter)
req.environ[MICROVERSION_ENVIRON] = microversion
microversion_header = '%s %s' % (SERVICE_TYPE, microversion)
try:
response = req.get_response(self.application)
except webob.exc.HTTPError as exc:
# If there was an error in the application we still need
# to send the microversion header, so add the header and
# re-raise the exception.
exc.headers.add(Version.HEADER, microversion_header)
raise exc
response.headers.add(Version.HEADER, microversion_header)
response.headers.add('vary', Version.HEADER)
return response
class Version(collections.namedtuple('Version', 'major minor')):
"""A namedtuple containing major and minor values.
Since it is a tuple is automatically comparable.
"""
HEADER = 'OpenStack-API-Version'
MIN_VERSION = None
MAX_VERSION = None
def __str__(self):
return '%s.%s' % (self.major, self.minor)
@property
def max_version(self):
if not self.MAX_VERSION:
self.MAX_VERSION = parse_version_string(max_version_string())
return self.MAX_VERSION
@property
def min_version(self):
if not self.MIN_VERSION:
self.MIN_VERSION = parse_version_string(min_version_string())
return self.MIN_VERSION
def matches(self, min_version=None, max_version=None):
if min_version is None:
min_version = self.min_version
if max_version is None:
max_version = self.max_version
return min_version <= self <= max_version
def extract_version(headers):
"""Extract the microversion from Version.HEADER
There may be multiple headers and some which don't match our
service.
"""
found_version = microversion_parse.get_version(headers,
service_type=SERVICE_TYPE)
version_string = found_version or min_version_string()
request_version = parse_version_string(version_string)
# We need a version that is in VERSION and within MIX and MAX.
# This gives us the option to administratively disable a
# version if we really need to.
if (str(request_version) in VERSIONS and request_version.matches()):
return request_version
raise ValueError(_('Unacceptable version header: %s') % version_string)
# From twisted
# https://github.com/twisted/twisted/blob/trunk/twisted/python/deprecate.py
def _fully_qualified_name(obj):
@ -253,11 +135,12 @@ def version_handler(min_ver, max_ver=None, status_code=404):
:param status_code: A status code to indicate error, 404 by default
"""
def decorator(f):
min_version = parse_version_string(min_ver)
min_version = microversion_parse.parse_version_string(min_ver)
if max_ver:
max_version = parse_version_string(max_ver)
max_version = microversion_parse.parse_version_string(max_ver)
else:
max_version = parse_version_string(max_version_string())
max_version = microversion_parse.parse_version_string(
max_version_string())
qualified_name = _fully_qualified_name(f)
VERSIONED_METHODS[qualified_name].append(
(min_version, max_version, f))

View File

@ -13,7 +13,7 @@ tests:
- name: root has microversion header
GET: /
response_headers:
vary: /OpenStack-API-Version/
vary: /openstack-api-version/
openstack-api-version: /^placement \d+\.\d+$/
- name: root has microversion info
@ -44,7 +44,7 @@ tests:
request_headers:
openstack-api-version: placement latest
response_headers:
vary: /OpenStack-API-Version/
vary: /openstack-api-version/
openstack-api-version: placement 1.21
- name: other accept header bad version

View File

@ -13,6 +13,7 @@
# under the License.
"""Unit tests for the functions used by the placement API handlers."""
import microversion_parse
import mock
import routes
import webob
@ -38,7 +39,7 @@ def _environ(path='/moo', method='GET'):
'wsgi.url_scheme': 'http',
# The microversion version value is not used, but it
# needs to be set to avoid a KeyError.
microversion.MICROVERSION_ENVIRON: microversion.Version(1, 12),
microversion.MICROVERSION_ENVIRON: microversion_parse.Version(1, 12),
}

View File

@ -16,6 +16,7 @@ import collections
import operator
import webob
import microversion_parse
import mock
# import the handlers to load up handler decorators
@ -54,13 +55,16 @@ class TestMicroversionDecoration(test.NoDBTestCase):
stored_method_data = methods_data[-1]
self.assertEqual(2, len(methods_data))
self.assertEqual(microversion.Version(1, 1), stored_method_data[0])
self.assertEqual(microversion.Version(1, 10), stored_method_data[1])
self.assertEqual(microversion_parse.Version(1, 1),
stored_method_data[0])
self.assertEqual(microversion_parse.Version(1, 10),
stored_method_data[1])
self.assertEqual(handler, stored_method_data[2])
self.assertEqual(microversion.Version(2, 0), methods_data[0][0])
self.assertEqual(microversion_parse.Version(2, 0),
methods_data[0][0])
def test_version_handler_float_exception(self):
self.assertRaises(AttributeError,
self.assertRaises(TypeError,
microversion.version_handler(1.1),
handler)
@ -70,7 +74,7 @@ class TestMicroversionDecoration(test.NoDBTestCase):
handler)
def test_version_handler_tuple_exception(self):
self.assertRaises(AttributeError,
self.assertRaises(TypeError,
microversion.version_handler((1, 1)),
handler)
@ -140,7 +144,7 @@ class MicroversionSequentialTest(test.NoDBTestCase):
for method_name, method_list in microversion.VERSIONED_METHODS.items():
previous_min_version = method_list[0][0]
for method in method_list[1:]:
previous_min_version = microversion.parse_version_string(
previous_min_version = microversion_parse.parse_version_string(
'%s.%s' % (previous_min_version.major,
previous_min_version.minor - 1))
self.assertEqual(previous_min_version, method[1],

View File

@ -16,6 +16,7 @@
import datetime
import fixtures
import microversion_parse
import mock
from oslo_middleware import request_id
from oslo_utils import timeutils
@ -228,7 +229,7 @@ class TestJSONErrorFormatter(test.NoDBTestCase):
# parsing was successful), no version info
# required.
status = '406 Not Acceptable'
version_obj = microversion.parse_version_string('2.3')
version_obj = microversion_parse.parse_version_string('2.3')
self.environ[microversion.MICROVERSION_ENVIRON] = version_obj
result = util.json_error_formatter(

View File

@ -57,7 +57,7 @@ os-traits>=0.4.0 # Apache-2.0
os-vif!=1.8.0,>=1.7.0 # Apache-2.0
os-win>=3.0.0 # Apache-2.0
castellan>=0.16.0 # Apache-2.0
microversion-parse>=0.1.2 # Apache-2.0
microversion-parse>=0.2.1 # Apache-2.0
os-xenapi>=0.3.1 # Apache-2.0
tooz>=1.58.0 # Apache-2.0
cursive>=0.2.1 # Apache-2.0