@@ -81,6 +81,14 @@ class InjectContext(wsgi.Middleware): | |||
class NovaKeystoneContext(wsgi.Middleware): | |||
"""Make a request context from keystone headers.""" | |||
@staticmethod | |||
def _create_context(env, **kwargs): | |||
"""Create a context from a request environ. | |||
This exists to make test stubbing easier. | |||
""" | |||
return context.RequestContext.from_environ(env, **kwargs) | |||
@webob.dec.wsgify(RequestClass=wsgi.Request) | |||
def __call__(self, req): | |||
# Build a context, including the auth_token... | |||
@@ -101,7 +109,7 @@ class NovaKeystoneContext(wsgi.Middleware): | |||
# middleware in newer versions. | |||
user_auth_plugin = req.environ.get('keystone.token_auth') | |||
ctx = context.RequestContext.from_environ( | |||
ctx = self._create_context( | |||
req.environ, | |||
user_auth_plugin=user_auth_plugin, | |||
remote_address=remote_address, |
@@ -955,18 +955,21 @@ class OSAPIFixture(fixtures.Fixture): | |||
""" | |||
def __init__(self, api_version='v2', | |||
project_id='6f70656e737461636b20342065766572'): | |||
project_id='6f70656e737461636b20342065766572', | |||
use_project_id_in_urls=False): | |||
"""Constructor | |||
:param api_version: the API version that we're interested in | |||
using. Currently this expects 'v2' or 'v2.1' as possible | |||
options. | |||
:param project_id: the project id to use on the API. | |||
:param use_project_id_in_urls: If True, act like the "endpoint" in the | |||
"service catalog" has the legacy format including the project_id. | |||
""" | |||
super(OSAPIFixture, self).__init__() | |||
self.api_version = api_version | |||
self.project_id = project_id | |||
self.use_project_id_in_urls = use_project_id_in_urls | |||
def setUp(self): | |||
super(OSAPIFixture, self).setUp() | |||
@@ -982,6 +985,23 @@ class OSAPIFixture(fixtures.Fixture): | |||
} | |||
self.useFixture(ConfPatcher(**conf_overrides)) | |||
# Stub out authentication middleware | |||
# TODO(efried): Use keystonemiddleware.fixtures.AuthTokenFixture | |||
self.useFixture(fixtures.MockPatch( | |||
'keystonemiddleware.auth_token.filter_factory', | |||
return_value=lambda _app: _app)) | |||
# Stub out context middleware | |||
def fake_ctx(env, **kwargs): | |||
user_id = env['HTTP_X_AUTH_USER'] | |||
project_id = env['HTTP_X_AUTH_PROJECT_ID'] | |||
is_admin = user_id == 'admin' | |||
return context.RequestContext( | |||
user_id, project_id, is_admin=is_admin, **kwargs) | |||
self.useFixture(fixtures.MonkeyPatch( | |||
'nova.api.auth.NovaKeystoneContext._create_context', fake_ctx)) | |||
# Turn off manipulation of socket_options in TCPKeepAliveAdapter | |||
# to keep wsgi-intercept happy. Replace it with the method | |||
# from its superclass. | |||
@@ -999,12 +1019,15 @@ class OSAPIFixture(fixtures.Fixture): | |||
intercept.install_intercept() | |||
self.addCleanup(intercept.uninstall_intercept) | |||
self.auth_url = 'http://%(host)s:%(port)s/%(api_version)s' % ({ | |||
base_url = 'http://%(host)s:%(port)s/%(api_version)s' % ({ | |||
'host': hostname, 'port': port, 'api_version': self.api_version}) | |||
self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url, | |||
self.project_id) | |||
if self.use_project_id_in_urls: | |||
base_url += '/' + self.project_id | |||
self.api = client.TestOpenStackClient( | |||
'fake', base_url, project_id=self.project_id) | |||
self.admin_api = client.TestOpenStackClient( | |||
'admin', 'admin', self.auth_url, self.project_id) | |||
'admin', base_url, project_id=self.project_id) | |||
# Provide a way to access the wsgi application to tests using | |||
# the fixture. | |||
self.app = app |
@@ -124,13 +124,10 @@ class TestOpenStackClient(object): | |||
""" | |||
def __init__(self, auth_user, auth_key, auth_url, | |||
project_id=None): | |||
def __init__(self, auth_user, base_url, project_id=None): | |||
super(TestOpenStackClient, self).__init__() | |||
self.auth_result = None | |||
self.auth_user = auth_user | |||
self.auth_key = auth_key | |||
self.auth_url = auth_url | |||
self.base_url = base_url | |||
if project_id is None: | |||
self.project_id = "6f70656e737461636b20342065766572" | |||
else: | |||
@@ -144,49 +141,22 @@ class TestOpenStackClient(object): | |||
response = requests.request(method, url, data=body, headers=_headers) | |||
return response | |||
def _authenticate(self, retry_count=0): | |||
if self.auth_result: | |||
return self.auth_result | |||
auth_url = self.auth_url | |||
headers = {'X-Auth-User': self.auth_user, | |||
'X-Auth-Key': self.auth_key, | |||
'X-Auth-Project-Id': self.project_id} | |||
response = self.request(auth_url, | |||
headers=headers) | |||
http_status = response.status_code | |||
LOG.debug("%(auth_url)s => code %(http_status)s", | |||
{'auth_url': auth_url, | |||
'http_status': http_status}) | |||
# NOTE(cdent): This is a workaround for an issue where the placement | |||
# API fixture may respond when a request was supposed to go to the | |||
# compute API fixture. Retry a few times, hoping to hit the right | |||
# fixture. | |||
if http_status == 401: | |||
if retry_count <= 3: | |||
return self._authenticate(retry_count=retry_count + 1) | |||
else: | |||
raise OpenStackApiAuthenticationException(response=response) | |||
self.auth_result = response.headers | |||
return self.auth_result | |||
def api_request(self, relative_uri, check_response_status=None, | |||
strip_version=False, **kwargs): | |||
auth_result = self._authenticate() | |||
# NOTE(justinsb): httplib 'helpfully' converts headers to lower case | |||
base_uri = auth_result['x-server-management-url'] | |||
base_uri = self.base_url | |||
if strip_version: | |||
# NOTE(vish): cut out version number and tenant_id | |||
base_uri = '/'.join(base_uri.split('/', 3)[:-1]) | |||
# The base_uri is either http://%(host)s:%(port)s/%(api_version)s | |||
# or http://%(host)s:%(port)s/%(api_version)s/%(project_id)s | |||
# NOTE(efried): Using urlparse was not easier :) | |||
chunked = base_uri.split('/') | |||
base_uri = '/'.join(chunked[:3]) | |||
# Restore the project ID if present | |||
if len(chunked) == 5: | |||
base_uri += '/' + chunked[-1] | |||
full_uri = '%s/%s' % (base_uri, relative_uri) | |||
headers = kwargs.setdefault('headers', {}) | |||
headers['X-Auth-Token'] = auth_result['x-auth-token'] | |||
if ('X-OpenStack-Nova-API-Version' in headers or | |||
'OpenStack-API-Version' in headers): | |||
raise Exception('Microversion should be set via ' | |||
@@ -195,6 +165,10 @@ class TestOpenStackClient(object): | |||
headers['X-OpenStack-Nova-API-Version'] = self.microversion | |||
headers['OpenStack-API-Version'] = 'compute %s' % self.microversion | |||
headers.setdefault('X-Auth-User', self.auth_user) | |||
headers.setdefault('X-User-Id', self.auth_user) | |||
headers.setdefault('X-Auth-Project-Id', self.project_id) | |||
response = self.request(full_uri, **kwargs) | |||
http_status = response.status_code |
@@ -62,6 +62,11 @@ class ApiSampleTestBaseV21(testscenarios.WithScenarios, | |||
# any additional fixtures needed for this scenario | |||
_additional_fixtures = [] | |||
sample_dir = None | |||
# Include the project ID in request URLs by default. This is overridden | |||
# for certain `scenarios` and by certain subclasses. | |||
# Note that API sample tests also use this in substitutions to validate | |||
# that URLs in responses (e.g. location of a server just created) are | |||
# correctly constructed. | |||
_use_project_id = True | |||
# Availability zones for the API samples tests. Can be overridden by | |||
# sub-classes. If set, the AvailabilityZoneFilter is not used. |
@@ -74,6 +74,7 @@ class SimpleTenantUsageV240Test(test_servers.ServersSampleBase): | |||
sample_dir = 'os-simple-tenant-usage' | |||
microversion = '2.40' | |||
scenarios = [('v2_40', {'api_major_version': 'v2.1'})] | |||
_use_project_id = False | |||
def setUp(self): | |||
super(SimpleTenantUsageV240Test, self).setUp() |
@@ -19,6 +19,7 @@ from nova.tests.functional.api_sample_tests import api_sample_base | |||
class VersionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21): | |||
sample_dir = 'versions' | |||
_use_project_id = False | |||
# NOTE(gmann): Setting empty scenario for 'version' API testing | |||
# as those does not send request on particular endpoint and running | |||
# its tests alone is enough. |
@@ -80,6 +80,9 @@ class _IntegratedTestBase(test.TestCase): | |||
# New tests should rely on Neutron and old ones migrated to use this since | |||
# nova-network is deprecated. | |||
USE_NEUTRON = True | |||
# This indicates whether to include the project ID in the URL for API | |||
# requests through OSAPIFixture. Overridden by subclasses. | |||
_use_project_id = False | |||
def setUp(self): | |||
super(_IntegratedTestBase, self).setUp() | |||
@@ -125,8 +128,11 @@ class _IntegratedTestBase(test.TestCase): | |||
self.scheduler = self._setup_scheduler_service() | |||
self.compute = self._setup_compute_service() | |||
self.api_fixture = self.useFixture( | |||
nova_fixtures.OSAPIFixture(self.api_major_version)) | |||
nova_fixtures.OSAPIFixture( | |||
api_version=self.api_major_version, | |||
use_project_id_in_urls=self._use_project_id)) | |||
# if the class needs to run as admin, make the api endpoint | |||
# the admin, otherwise it's safer to run as non admin user. |
@@ -41,6 +41,10 @@ class TestCORSMiddleware(api_sample_base.ApiSampleTestBaseV21): | |||
self._original_call_method = cfg.ConfigOpts.GroupAttr.__getattr__ | |||
cfg.ConfigOpts.GroupAttr.__getattr__ = _mock_getattr | |||
# With the project_id in the URL, we get the 300 'multiple choices' | |||
# response from nova.api.openstack.compute.versions.Versions. | |||
self.exp_version_status = 300 if self._use_project_id else 200 | |||
# Initialize the application after all the config overrides are in | |||
# place. | |||
super(TestCORSMiddleware, self).setUp() | |||
@@ -103,7 +107,7 @@ class TestCORSMiddleware(api_sample_base.ApiSampleTestBaseV21): | |||
'Access-Control-Request-Method': 'GET' | |||
}) | |||
self.assertEqual(response.status_code, 200) | |||
self.assertEqual(response.status_code, self.exp_version_status) | |||
self.assertIn('Access-Control-Allow-Origin', response.headers) | |||
self.assertEqual('http://valid.example.com', | |||
response.headers['Access-Control-Allow-Origin']) | |||
@@ -116,5 +120,5 @@ class TestCORSMiddleware(api_sample_base.ApiSampleTestBaseV21): | |||
'Access-Control-Request-Method': 'GET' | |||
}) | |||
self.assertEqual(response.status_code, 200) | |||
self.assertEqual(response.status_code, self.exp_version_status) | |||
self.assertNotIn('Access-Control-Allow-Origin', response.headers) |
@@ -63,18 +63,15 @@ class TestRequestLogMiddleware(testtools.TestCase): | |||
api = self.useFixture(fixtures.OSAPIFixture()).api | |||
resp = api.api_request('/', strip_version=True) | |||
log1 = ('INFO [nova.api.openstack.requestlog] 127.0.0.1 ' | |||
'"GET /v2" status: 204 len: 0 microversion: - time:') | |||
self.assertIn(log1, self.stdlog.logger.output) | |||
# the content length might vary, but the important part is | |||
# what we log is what we return to the user (which turns out | |||
# to excitingly not be the case with eventlet!) | |||
content_length = resp.headers['content-length'] | |||
log2 = ('INFO [nova.api.openstack.requestlog] 127.0.0.1 ' | |||
log1 = ('INFO [nova.api.openstack.requestlog] 127.0.0.1 ' | |||
'"GET /" status: 200 len: %s' % content_length) | |||
self.assertIn(log2, self.stdlog.logger.output) | |||
self.assertIn(log1, self.stdlog.logger.output) | |||
@mock.patch('nova.api.openstack.requestlog.RequestLog._should_emit') | |||
def test_logs_mv(self, emit): |
@@ -45,9 +45,6 @@ class ConfFixture(config_fixture.Config): | |||
self.conf.set_default('use_ipv6', True) | |||
self.conf.set_default('vlan_interface', 'eth0') | |||
# api group | |||
self.conf.set_default('auth_strategy', 'noauth2', group='api') | |||
# api_database group | |||
self.conf.set_default('connection', "sqlite://", group='api_database') | |||
self.conf.set_default('sqlite_synchronous', False, |