Stop using NoAuthMiddleware in tests
This rips NoAuthMiddleware completely out of functional tests by: - Removing our override of [api]auth_strategy in ConfFixture, allowing it to default to ``keystone``. This causes the fake wsgi setup to go through the same pipeline as real API requests; so making that work entails... - Mocking out the keystonecontext piece of the paste pipeline to create the context previously mashed (somewhat inappropriately) into NoAuthMiddleware. In the future we may want to mock this more shallowly or find a way to make the req more realistic so it needn't be mocked at all, but for now this is close to what the noauth2 pipeline used to do. - Stubbing out the keystonemiddleware piece of the paste pipeline. In the future we should try to use keystonemiddleware's AuthTokenFixture so our tests can occur in a more realistic environment, but for now this is just mimicking what the noauth2 pipeline used to do; except for... - Removing the authentication portion of the TestOpenStackClient. This used to make an actual request(), which landed in NoAuthMiddleware, which was hacking together some headers (based, it appears, on a protocol which is many years out of date and no longer approximates what keystone does, which should be the point if it's going to exist at all). So now we just hack up the necessary headers inline. - Doing the addition of project_id in request URIs in OSAPIFixture. This is another thing that NoAuthMiddleware was doing inappropriately (IRL the project_id will either be part of the request from the start, or it won't). It was also only doing it part of the time; as a result, a couple of tests requesting version documents, which were previously not expecting the project ID to be present, needed to be modified to expect it. This better reflects reality. Change-Id: I459a605b4a9390f0e36356ca1fe432948159acd4
This commit is contained in:
parent
18de63deaa
commit
52fe8c0285
|
@ -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,
|
||||
|
|
|
@ -953,18 +953,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()
|
||||
|
@ -980,6 +983,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.
|
||||
|
@ -997,12 +1017,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()
|
||||
|
@ -124,8 +127,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,
|
||||
|
|
Loading…
Reference in New Issue