diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 893e866b0b..5f50e8a6c0 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -65,14 +65,20 @@ use = call:nova.api.openstack.urlmap:urlmap_factory [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory -noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 -keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 -keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 +noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 +keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 +keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 [composite:openstack_compute_api_v3] use = call:nova.api.auth:pipeline_factory_v3 -noauth = faultwrap sizelimit noauth_v3 osapi_compute_app_v3 -keystone = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 +noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3 +keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 + +[filter:request_id] +paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory + +[filter:compute_req_id] +paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory diff --git a/nova/api/auth.py b/nova/api/auth.py index 2648d94e99..b0015cce2b 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -24,6 +24,7 @@ from nova import context from nova.openstack.common.gettextutils import _ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging +from nova.openstack.common.middleware import request_id from nova import wsgi @@ -113,6 +114,8 @@ class NovaKeystoneContext(wsgi.Middleware): project_name = req.headers.get('X_TENANT_NAME') user_name = req.headers.get('X_USER_NAME') + req_id = req.environ.get(request_id.ENV_REQUEST_ID) + # Get the auth token auth_token = req.headers.get('X_AUTH_TOKEN', req.headers.get('X_STORAGE_TOKEN')) @@ -138,7 +141,8 @@ class NovaKeystoneContext(wsgi.Middleware): roles=roles, auth_token=auth_token, remote_address=remote_address, - service_catalog=service_catalog) + service_catalog=service_catalog, + request_id=req_id) req.environ['nova.context'] = ctx return self.application diff --git a/nova/api/compute_req_id.py b/nova/api/compute_req_id.py new file mode 100644 index 0000000000..2118d96eb0 --- /dev/null +++ b/nova/api/compute_req_id.py @@ -0,0 +1,45 @@ +# Copyright (c) 2014 IBM Corp. +# 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. + +"""Middleware that ensures x-compute-request-id + +Using this middleware provides a convenient way to attach the +x-compute-request-id to only v2 responses. Previously, this header was set in +api/openstack/wsgi.py + +Responses for APIv3 are taken care of by the request_id middleware provided +in oslo. +""" + +import webob.dec + +from nova.openstack.common import context +from nova.openstack.common.middleware import base + + +ENV_REQUEST_ID = 'openstack.request_id' +HTTP_RESP_HEADER_REQUEST_ID = 'x-compute-request-id' + + +class ComputeReqIdMiddleware(base.Middleware): + + @webob.dec.wsgify + def __call__(self, req): + req_id = context.generate_request_id() + req.environ[ENV_REQUEST_ID] = req_id + response = req.get_response(self.application) + if HTTP_RESP_HEADER_REQUEST_ID not in response.headers: + response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id) + return response diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 85bb9ecf65..4275645d4a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1013,9 +1013,6 @@ class Resource(wsgi.Application): self.default_serializers) if hasattr(response, 'headers'): - if context: - response.headers.add('x-compute-request-id', - context.request_id) for hdr, val in response.headers.items(): # Headers must be utf-8 strings @@ -1238,7 +1235,6 @@ class Fault(webob.exc.HTTPException): self.wrapped_exc.body = serializer.serialize(fault_data) self.wrapped_exc.content_type = content_type - _set_request_id_header(req, self.wrapped_exc.headers) return self.wrapped_exc @@ -1298,9 +1294,3 @@ class RateLimitFault(webob.exc.HTTPException): self.wrapped_exc.content_type = content_type return self.wrapped_exc - - -def _set_request_id_header(req, headers): - context = req.environ.get('nova.context') - if context: - headers['x-compute-request-id'] = context.request_id diff --git a/nova/tests/api/openstack/compute/test_api.py b/nova/tests/api/openstack/compute/test_api.py index 437458cac6..35f3976f21 100644 --- a/nova/tests/api/openstack/compute/test_api.py +++ b/nova/tests/api/openstack/compute/test_api.py @@ -20,7 +20,6 @@ import webob.exc from nova.api import openstack as openstack_api from nova.api.openstack import wsgi -import nova.context from nova import exception from nova.openstack.common import jsonutils from nova import test @@ -187,13 +186,3 @@ class APITest(test.NoDBTestCase): api = self._wsgi_app(fail) resp = webob.Request.blank('/').get_response(api) self.assertEqual(500, resp.status_int) - - def test_request_id_in_response(self): - req = webob.Request.blank('/') - req.method = 'GET' - context = nova.context.RequestContext('bob', 1) - context.request_id = 'test-req-id' - req.environ['nova.context'] = context - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.headers['x-compute-request-id'], 'test-req-id') diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 2759f64ccc..9a857c95c9 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -335,6 +335,14 @@ class XMLDeserializerTest(test.NoDBTestCase): class ResourceTest(test.NoDBTestCase): + + def get_req_id_header_name(self, request): + header_name = 'x-openstack-request-id' + if utils.get_api_version(request) < 3: + header_name = 'x-compute-request-id' + + return header_name + def test_resource_call_with_method_get(self): class Controller(object): def index(self, req): @@ -639,8 +647,6 @@ class ResourceTest(test.NoDBTestCase): context = req.environ['nova.context'] app = fakes.TestRouter(Controller()) response = req.get_response(app) - self.assertEqual(response.headers['x-compute-request-id'], - context.request_id) self.assertEqual(response.body, '{"foo": "bar"}') self.assertEqual(response.status_int, 200) @@ -655,7 +661,8 @@ class ResourceTest(test.NoDBTestCase): # NOTE(alaski): This test is really to ensure that a str response # doesn't error. Not having a request_id header is a side effect of # our wsgi setup, ideally it would be there. - self.assertFalse(hasattr(response.headers, 'x-compute-request-id')) + expected_header = self.get_req_id_header_name(req) + self.assertFalse(hasattr(response.headers, expected_header)) self.assertEqual(response.body, 'foo') self.assertEqual(response.status_int, 200) @@ -668,8 +675,6 @@ class ResourceTest(test.NoDBTestCase): context = req.environ['nova.context'] app = fakes.TestRouter(Controller()) response = req.get_response(app) - self.assertEqual(response.headers['x-compute-request-id'], - context.request_id) self.assertEqual(response.body, '') self.assertEqual(response.status_int, 200) diff --git a/nova/tests/api/test_auth.py b/nova/tests/api/test_auth.py index 6d964369ec..f4746e5704 100644 --- a/nova/tests/api/test_auth.py +++ b/nova/tests/api/test_auth.py @@ -19,6 +19,7 @@ import webob.exc import nova.api.auth from nova.openstack.common.gettextutils import _ +from nova.openstack.common.middleware import request_id from nova import test CONF = cfg.CONF @@ -70,6 +71,14 @@ class TestNovaKeystoneContextMiddleware(test.NoDBTestCase): response = self.request.get_response(self.middleware) self.assertEqual(response.status, '500 Internal Server Error') + def test_request_id_extracted_from_env(self): + req_id = 'dummy-request-id' + self.request.headers['X_PROJECT_ID'] = 'testtenantid' + self.request.headers['X_USER_ID'] = 'testuserid' + self.request.environ[request_id.ENV_REQUEST_ID] = req_id + self.request.get_response(self.middleware) + self.assertEqual(req_id, self.context.request_id) + class TestKeystoneMiddlewareRoles(test.NoDBTestCase): diff --git a/nova/tests/api/test_compute_req_id.py b/nova/tests/api/test_compute_req_id.py new file mode 100644 index 0000000000..bbdbfab726 --- /dev/null +++ b/nova/tests/api/test_compute_req_id.py @@ -0,0 +1,40 @@ +# Copyright (c) 2014 IBM Corp. +# 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 testtools import matchers +import webob +import webob.dec + +from nova.api import compute_req_id +from nova.openstack.common import context +from nova import test + + +class RequestIdTest(test.TestCase): + def test_generate_request_id(self): + @webob.dec.wsgify + def application(req): + return req.environ[compute_req_id.ENV_REQUEST_ID] + + app = compute_req_id.ComputeReqIdMiddleware(application) + req = webob.Request.blank('/test') + req_id = context.generate_request_id() + req.environ[compute_req_id.ENV_REQUEST_ID] = req_id + res = req.get_response(app) + + res_id = res.headers.get(compute_req_id.HTTP_RESP_HEADER_REQUEST_ID) + self.assertThat(res_id, matchers.StartsWith('req-')) + self.assertEqual(res_id, res.body) diff --git a/nova/tests/utils.py b/nova/tests/utils.py index ef1e6b6d5a..da8fd5a153 100644 --- a/nova/tests/utils.py +++ b/nova/tests/utils.py @@ -206,3 +206,9 @@ def is_ipv6_supported(): has_ipv6_support = False return has_ipv6_support + + +def get_api_version(request): + api_version = 2 + if request.path[2:3].isdigit(): + return int(request.path[2:3])