Set API version request information on request objects
This adds the looking for the X-Openstack-Compute-API-Version header on REST API requests. If it is found it is parsed and the version information attached to the request which is passed to the API code. If the header does not exist then a version of 2.1 is set. If the header is specified but badly formatted a 400 Bad Request is returned to the client. Partially implements blueprint api-microversions Change-Id: I8e73533cb0256b8a9329870714ffacb9893bb4c7
This commit is contained in:

committed by
Michael Still

parent
bd84cf4a7a
commit
b81de68d05
@@ -25,6 +25,7 @@ from oslo.utils import strutils
|
||||
import six
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import api_version_request as api_version
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova import exception
|
||||
from nova import i18n
|
||||
@@ -73,6 +74,12 @@ _METHODS_WITH_BODY = [
|
||||
]
|
||||
|
||||
|
||||
# The default api version request if none is requested in the headers
|
||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||
# support is fully merged. It does not affect the V2 API.
|
||||
DEFAULT_API_VERSION = "2.1"
|
||||
|
||||
|
||||
class Request(webob.Request):
|
||||
"""Add some OpenStack API-specific logic to the base webob.Request."""
|
||||
|
||||
@@ -198,6 +205,15 @@ class Request(webob.Request):
|
||||
return self.accept_language.best_match(
|
||||
i18n.get_available_languages())
|
||||
|
||||
def set_api_version_request(self):
|
||||
"""Set API version request based on the request header information."""
|
||||
if 'X-OpenStack-Compute-API-Version' in self.headers:
|
||||
self.api_version_request = api_version.APIVersionRequest(
|
||||
self.headers['X-OpenStack-Compute-API-Version'])
|
||||
else:
|
||||
self.api_version_request = api_version.APIVersionRequest(
|
||||
DEFAULT_API_VERSION)
|
||||
|
||||
|
||||
class ActionDispatcher(object):
|
||||
"""Maps method name to local methods through action name."""
|
||||
@@ -869,6 +885,13 @@ class Resource(wsgi.Application):
|
||||
def __call__(self, request):
|
||||
"""WSGI method that controls (de)serialization and method dispatch."""
|
||||
|
||||
# Set the version of the API requested based on the header
|
||||
try:
|
||||
request.set_api_version_request()
|
||||
except exception.InvalidAPIVersionString as e:
|
||||
return Fault(webob.exc.HTTPBadRequest(
|
||||
explanation=e.format_message()))
|
||||
|
||||
# Identify the action, its arguments, and the requested
|
||||
# content type
|
||||
action_args = self.get_action_args(request.environ)
|
||||
|
@@ -14,6 +14,7 @@ import inspect
|
||||
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import api_version_request as api_version
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import exception
|
||||
@@ -187,6 +188,26 @@ class RequestTest(test.NoDBTestCase):
|
||||
request.headers = {'Accept-Language': accepted}
|
||||
self.assertIs(request.best_match_language(), None)
|
||||
|
||||
def test_api_version_request_header_none(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.set_api_version_request()
|
||||
self.assertEqual(api_version.APIVersionRequest(
|
||||
wsgi.DEFAULT_API_VERSION), request.api_version_request)
|
||||
|
||||
def test_api_version_request_header(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.headers = {'X-OpenStack-Compute-API-Version': '2.14'}
|
||||
request.set_api_version_request()
|
||||
self.assertEqual(api_version.APIVersionRequest("2.14"),
|
||||
request.api_version_request)
|
||||
|
||||
def test_api_version_request_header_invalid(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.headers = {'X-OpenStack-Compute-API-Version': '2.1.3'}
|
||||
|
||||
self.assertRaises(exception.InvalidAPIVersionString,
|
||||
request.set_api_version_request)
|
||||
|
||||
|
||||
class ActionDispatcherTest(test.NoDBTestCase):
|
||||
def test_dispatch(self):
|
||||
@@ -352,6 +373,50 @@ class ResourceTest(test.NoDBTestCase):
|
||||
|
||||
return header_name
|
||||
|
||||
def test_resource_receives_api_version_request_default(self):
|
||||
class Controller(object):
|
||||
def index(self, req):
|
||||
if req.api_version_request != \
|
||||
api_version.APIVersionRequest(wsgi.DEFAULT_API_VERSION):
|
||||
raise webob.exc.HTTPInternalServerError()
|
||||
return 'success'
|
||||
|
||||
app = fakes.TestRouter(Controller())
|
||||
req = webob.Request.blank('/tests')
|
||||
response = req.get_response(app)
|
||||
self.assertEqual(response.body, 'success')
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
||||
def test_resource_receives_api_version_request(self):
|
||||
version = "2.5"
|
||||
|
||||
class Controller(object):
|
||||
def index(self, req):
|
||||
if req.api_version_request != \
|
||||
api_version.APIVersionRequest(version):
|
||||
raise webob.exc.HTTPInternalServerError()
|
||||
return 'success'
|
||||
|
||||
app = fakes.TestRouter(Controller())
|
||||
req = webob.Request.blank('/tests')
|
||||
req.headers = {'X-OpenStack-Compute-API-Version': version}
|
||||
response = req.get_response(app)
|
||||
self.assertEqual(response.body, 'success')
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
||||
def test_resource_receives_api_version_request_invalid(self):
|
||||
invalid_version = "2.5.3"
|
||||
|
||||
class Controller(object):
|
||||
def index(self, req):
|
||||
return 'success'
|
||||
|
||||
app = fakes.TestRouter(Controller())
|
||||
req = webob.Request.blank('/tests')
|
||||
req.headers = {'X-OpenStack-Compute-API-Version': invalid_version}
|
||||
response = req.get_response(app)
|
||||
self.assertEqual(400, response.status_int)
|
||||
|
||||
def test_resource_call_with_method_get(self):
|
||||
class Controller(object):
|
||||
def index(self, req):
|
||||
|
Reference in New Issue
Block a user