Add middleware filterout Microversions http headers

This patch adds new WSGI middleware which filter out
Microversions http header X-OpenStack-Nova-API-Version
in the request and response and Mircoversion field in
http header 'Vary'.

Partial implement blueprint api-relax-validation

Change-Id: I153ba2e71cd2fa4e2d4e5edf9993d076f7d0adb3
This commit is contained in:
He Jie Xu 2015-06-21 09:50:55 +08:00
parent 7555df8acf
commit d709d6c339
5 changed files with 189 additions and 3 deletions

View File

@ -108,6 +108,9 @@ paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.
[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory
[filter:legacy_v2_compatible]
paste.filter_factory = nova.api.openstack:LegacyV2CompatibleWrapper.factory
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory

View File

@ -128,6 +128,46 @@ class FaultWrapper(base_wsgi.Middleware):
return self._error(ex, req)
class LegacyV2CompatibleWrapper(base_wsgi.Middleware):
def _filter_request_headers(self, req):
"""For keeping same behavior with v2 API, ignores microversions
HTTP header X-OpenStack-Nova-API-Version in the request.
"""
if wsgi.API_VERSION_REQUEST_HEADER in req.headers:
del req.headers[wsgi.API_VERSION_REQUEST_HEADER]
return req
def _filter_response_headers(self, response):
"""For keeping same behavior with v2 API, filter out microversions
HTTP header and microversions field in header 'Vary'.
"""
if wsgi.API_VERSION_REQUEST_HEADER in response.headers:
del response.headers[wsgi.API_VERSION_REQUEST_HEADER]
if 'Vary' in response.headers:
vary_headers = response.headers['Vary'].split(',')
filtered_vary = []
for vary in vary_headers:
vary = vary.strip()
if vary == wsgi.API_VERSION_REQUEST_HEADER:
continue
filtered_vary.append(vary)
if filtered_vary:
response.headers['Vary'] = ','.join(filtered_vary)
else:
del response.headers['Vary']
return response
@webob.dec.wsgify
def __call__(self, req):
req = self._filter_request_headers(req)
response = req.get_response(self.application)
return self._filter_response_headers(response)
class APIMapper(routes.Mapper):
def routematch(self, url=None, environ=None):
if url == "":

View File

@ -0,0 +1,34 @@
# Copyright 2015 Intel Corporation.
# 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.
from nova.api.openstack import wsgi
from nova.tests.functional import integrated_helpers
from nova.tests.functional.v3 import api_paste_fixture
class LegacyV2CompatibleTestBase(integrated_helpers._IntegratedTestBase):
_api_version = 'v2'
def setUp(self):
self.useFixture(api_paste_fixture.ApiPasteV2CompatibleFixture())
super(LegacyV2CompatibleTestBase, self).setUp()
def test_request_with_microversion_headers(self):
response = self.api.api_post('os-keypairs',
{"keypair": {"name": "test"}},
headers={wsgi.API_VERSION_REQUEST_HEADER: '2.100'})
self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers)
self.assertNotIn('Vary', response.headers)
self.assertNotIn('type', response.body["keypair"])

View File

@ -25,6 +25,11 @@ CONF = cfg.CONF
class ApiPasteFixture(fixtures.Fixture):
def _replace_line(self, target_file, line):
target_file.write(line.replace(
"/v2: openstack_compute_api_v2",
"/v2: openstack_compute_api_v21"))
def setUp(self):
super(ApiPasteFixture, self).setUp()
CONF.set_default('api_paste_config',
@ -35,7 +40,16 @@ class ApiPasteFixture(fixtures.Fixture):
with open(CONF.api_paste_config, 'r') as orig_api_paste:
with open(tmp_api_paste_file_name, 'w') as tmp_file:
for line in orig_api_paste:
tmp_file.write(line.replace(
"/v2: openstack_compute_api_v2",
"/v2: openstack_compute_api_v21"))
self._replace_line(tmp_file, line)
CONF.set_override('api_paste_config', tmp_api_paste_file_name)
class ApiPasteV2CompatibleFixture(ApiPasteFixture):
def _replace_line(self, target_file, line):
line = line.replace("noauth2 osapi_compute_app_v21",
"noauth2 legacy_v2_compatible osapi_compute_app_v21")
line = line.replace(
"/v2: openstack_compute_api_v2",
"/v2: openstack_compute_api_v21")
target_file.write(line)

View File

@ -0,0 +1,95 @@
# Copyright 2015 Intel Corporation
# 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 webob
import webob.dec
import nova.api.openstack
from nova.api.openstack import wsgi
from nova import test
class TestLegacyV2CompatibleWrapper(test.NoDBTestCase):
def test_filter_out_microverions_request_header(self):
req = webob.Request.blank('/')
req.headers[wsgi.API_VERSION_REQUEST_HEADER] = '2.2'
@webob.dec.wsgify
def fake_app(req, *args, **kwargs):
self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, req)
resp = webob.Response()
return resp
wrapper = nova.api.openstack.LegacyV2CompatibleWrapper(fake_app)
req.get_response(wrapper)
def test_filter_out_microverions_response_header(self):
req = webob.Request.blank('/')
@webob.dec.wsgify
def fake_app(req, *args, **kwargs):
resp = webob.Response()
resp.status_int = 204
resp.headers[wsgi.API_VERSION_REQUEST_HEADER] = '2.3'
return resp
wrapper = nova.api.openstack.LegacyV2CompatibleWrapper(fake_app)
response = req.get_response(wrapper)
self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers)
def test_filter_out_microverions_vary_header(self):
req = webob.Request.blank('/')
@webob.dec.wsgify
def fake_app(req, *args, **kwargs):
resp = webob.Response()
resp.status_int = 204
resp.headers['Vary'] = wsgi.API_VERSION_REQUEST_HEADER
return resp
wrapper = nova.api.openstack.LegacyV2CompatibleWrapper(fake_app)
response = req.get_response(wrapper)
self.assertNotIn('Vary', response.headers)
def test_filter_out_microverions_vary_header_with_multi_fields(self):
req = webob.Request.blank('/')
@webob.dec.wsgify
def fake_app(req, *args, **kwargs):
resp = webob.Response()
resp.status_int = 204
resp.headers['Vary'] = '%s, %s, %s' % (
wsgi.API_VERSION_REQUEST_HEADER, 'FAKE_HEADER1',
'FAKE_HEADER2')
return resp
wrapper = nova.api.openstack.LegacyV2CompatibleWrapper(fake_app)
response = req.get_response(wrapper)
self.assertEqual('FAKE_HEADER1,FAKE_HEADER2',
response.headers['Vary'])
def test_filter_out_microverions_no_vary_header(self):
req = webob.Request.blank('/')
@webob.dec.wsgify
def fake_app(req, *args, **kwargs):
resp = webob.Response()
resp.status_int = 204
return resp
wrapper = nova.api.openstack.LegacyV2CompatibleWrapper(fake_app)
response = req.get_response(wrapper)
self.assertNotIn('Vary', response.headers)