Add BetaMax Fixture
Add a BetaMax fixture that handles wrapping the constructor for KeystoneAuth Session in a way to provide the BetaMax interfaces. This is in support of enabling easy functional testing that includes responses to live-clouds for consumers of KeystoneAuth. A future patch will provide a template for stripping out sensitive (e.g. password) data from the recordings so that the cassetts can be more easily included in git repositories for functional testing. Change-Id: Ibd6e525fef9f2541f97d33de0d2f02f991d122e5 Depends-On: I8787954ab5d0e3963e97c160783301b48ab8299f
This commit is contained in:
74
keystoneauth1/fixture/keystoneauth_betamax.py
Normal file
74
keystoneauth1/fixture/keystoneauth_betamax.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""A fixture to wrap the session constructor for use with Betamax"""
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
import betamax
|
||||||
|
import fixtures
|
||||||
|
import mock
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from keystoneauth1 import session
|
||||||
|
|
||||||
|
|
||||||
|
class BetamaxFixture(fixtures.Fixture):
|
||||||
|
|
||||||
|
def __init__(self, cassette_name, cassette_library_dir=None,
|
||||||
|
serializer=None, record=False):
|
||||||
|
self.cassette_library_dir = cassette_library_dir
|
||||||
|
self.serializer = serializer
|
||||||
|
self.record = record
|
||||||
|
self.cassette_name = cassette_name
|
||||||
|
if serializer:
|
||||||
|
betamax.Betamax.register_serializer(serializer)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BetamaxFixture, self).setUp()
|
||||||
|
self.mockpatch = mock.patch.object(
|
||||||
|
session, '_construct_session',
|
||||||
|
partial(_construct_session_with_betamax, self))
|
||||||
|
self.mockpatch.start()
|
||||||
|
# Unpatch during cleanup
|
||||||
|
self.addCleanup(self.mockpatch.stop)
|
||||||
|
|
||||||
|
|
||||||
|
def _construct_session_with_betamax(fixture, session_obj=None):
|
||||||
|
# NOTE(morganfainberg): This function should contain the logic of
|
||||||
|
# keystoneauth1.session._construct_session as it replaces the
|
||||||
|
# _construct_session function to apply betamax magic to the requests
|
||||||
|
# session object.
|
||||||
|
if not session_obj:
|
||||||
|
session_obj = requests.Session()
|
||||||
|
# Use TCPKeepAliveAdapter to fix bug 1323862
|
||||||
|
for scheme in session_obj.adapters.keys():
|
||||||
|
session_obj.mount(scheme, session.TCPKeepAliveAdapter())
|
||||||
|
fixture.recorder = betamax.Betamax(
|
||||||
|
session_obj, cassette_library_dir=fixture.cassette_library_dir)
|
||||||
|
|
||||||
|
record = 'none'
|
||||||
|
serializer = None
|
||||||
|
|
||||||
|
if fixture.record:
|
||||||
|
record = 'all'
|
||||||
|
|
||||||
|
if fixture.serializer:
|
||||||
|
serializer = fixture.serializer.name
|
||||||
|
|
||||||
|
fixture.recorder.use_cassette(fixture.cassette_name,
|
||||||
|
serialize_with=serializer,
|
||||||
|
record=record)
|
||||||
|
|
||||||
|
fixture.recorder.start()
|
||||||
|
fixture.addCleanup(fixture.recorder.stop)
|
||||||
|
return session_obj
|
||||||
@@ -41,6 +41,18 @@ USER_AGENT = 'keystoneauth1'
|
|||||||
_logger = utils.get_logger(__name__)
|
_logger = utils.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _construct_session(session_obj=None):
|
||||||
|
# NOTE(morganfainberg): if the logic in this function changes be sure to
|
||||||
|
# update the betamax fixture's '_construct_session_with_betamax" function
|
||||||
|
# as well.
|
||||||
|
if not session_obj:
|
||||||
|
session_obj = requests.Session()
|
||||||
|
# Use TCPKeepAliveAdapter to fix bug 1323862
|
||||||
|
for scheme in session_obj.adapters.keys():
|
||||||
|
session_obj.mount(scheme, TCPKeepAliveAdapter())
|
||||||
|
return session_obj
|
||||||
|
|
||||||
|
|
||||||
class _JSONEncoder(json.JSONEncoder):
|
class _JSONEncoder(json.JSONEncoder):
|
||||||
|
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
@@ -101,14 +113,9 @@ class Session(object):
|
|||||||
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
||||||
cert=None, timeout=None, user_agent=None,
|
cert=None, timeout=None, user_agent=None,
|
||||||
redirect=_DEFAULT_REDIRECT_LIMIT):
|
redirect=_DEFAULT_REDIRECT_LIMIT):
|
||||||
if not session:
|
|
||||||
session = requests.Session()
|
|
||||||
# Use TCPKeepAliveAdapter to fix bug 1323862
|
|
||||||
for scheme in session.adapters.keys():
|
|
||||||
session.mount(scheme, TCPKeepAliveAdapter())
|
|
||||||
|
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
self.session = session
|
self.session = _construct_session(session)
|
||||||
self.original_ip = original_ip
|
self.original_ip = original_ip
|
||||||
self.verify = verify
|
self.verify = verify
|
||||||
self.cert = cert
|
self.cert = cert
|
||||||
|
|||||||
7
keystoneauth1/tests/unit/data/README
Normal file
7
keystoneauth1/tests/unit/data/README
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
This directory holds the betamax test cassettes that are pre-generated
|
||||||
|
for unit testing. This can be removed in the future with a functional
|
||||||
|
test that stands up a full devstack, records a cassette and then
|
||||||
|
replays it as part of the test suite.
|
||||||
|
|
||||||
|
Until the functional testing is implemented do not remove this
|
||||||
|
directory or enclosed files.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"http_interactions": [{"request": {"body": {"string": "{\"auth\": {\"tenantName\": \"test_tenant_name\", \"passwordCredentials\": {\"username\": \"test_user_name\", \"password\": \"test_password\"}}}", "encoding": "utf-8"}, "headers": {"Content-Length": ["128"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/json"], "User-Agent": ["keystoneauth1"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "POST", "uri": "http://keystonauth.betamax_test/v2.0/tokens"}, "response": {"body": {"string": "{\"access\": {\"token\": {\"issued_at\": \"2015-11-27T15:17:19.755470\", \"expires\": \"2015-11-27T16:17:19Z\", \"id\": \"c000c5ee4ba04594a00886028584b50d\", \"tenant\": {\"description\": null, \"enabled\": true, \"id\": \"6932cad596634a61ac9c759fb91beef1\", \"name\": \"test_tenant_name\"}, \"audit_ids\": [\"jY3gYg_YTbmzY2a4ioGuCw\"]}, \"user\": {\"username\": \"test_user_name\", \"roles_links\": [], \"id\": \"96995e6cc15b40fa8e7cd762f6a5d4c0\", \"roles\": [{\"name\": \"_member_\"}], \"name\": \"67eff5f6-9477-4961-88b4-437e6596a795\"}, \"metadata\": {\"is_admin\": 0, \"roles\": [\"9fe2ff9ee4384b1894a90878d3e92bab\"]}}}", "encoding": null}, "headers": {"X-Openstack-Request-Id": ["req-f9e188b4-06fd-4a4c-a952-2315b368218c"], "Content-Length": ["2684"], "Connection": ["keep-alive"], "Date": ["Fri, 27 Nov 2015 15:17:19 GMT"], "Content-Type": ["application/json"], "Vary": ["X-Auth-Token"], "X-Distribution": ["Ubuntu"], "Server": ["Fake"]}, "status": {"message": "OK", "code": 200}, "url": "http://keystonauth.betamax_test/v2.0/tokens"}, "recorded_at": "2015-11-27T15:17:19"}], "recorded_with": "betamax/0.5.1"}
|
||||||
|
|
||||||
60
keystoneauth1/tests/unit/test_betamax_fixture.py
Normal file
60
keystoneauth1/tests/unit/test_betamax_fixture.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 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 testtools
|
||||||
|
|
||||||
|
from betamax import exceptions
|
||||||
|
|
||||||
|
from keystoneauth1.fixture import keystoneauth_betamax
|
||||||
|
from keystoneauth1.fixture import v2 as v2Fixtures
|
||||||
|
from keystoneauth1.identity import v2
|
||||||
|
from keystoneauth1 import session
|
||||||
|
|
||||||
|
|
||||||
|
class TestBetamaxFixture(testtools.TestCase):
|
||||||
|
|
||||||
|
TEST_USERNAME = 'test_user_name'
|
||||||
|
TEST_PASSWORD = 'test_password'
|
||||||
|
TEST_TENANT_NAME = 'test_tenant_name'
|
||||||
|
TEST_AUTH_URL = 'http://keystonauth.betamax_test/v2.0/'
|
||||||
|
|
||||||
|
V2_TOKEN = v2Fixtures.Token(tenant_name=TEST_TENANT_NAME,
|
||||||
|
user_name=TEST_USERNAME)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBetamaxFixture, self).setUp()
|
||||||
|
self.ksa_betamax_fixture = self.useFixture(
|
||||||
|
keystoneauth_betamax.BetamaxFixture(
|
||||||
|
cassette_name='ksa_betamax_test_cassette',
|
||||||
|
cassette_library_dir='keystoneauth1/tests/unit/data/',
|
||||||
|
record=False))
|
||||||
|
|
||||||
|
def _replay_cassette(self):
|
||||||
|
plugin = v2.Password(
|
||||||
|
auth_url=self.TEST_AUTH_URL,
|
||||||
|
password=self.TEST_PASSWORD,
|
||||||
|
username=self.TEST_USERNAME,
|
||||||
|
tenant_name=self.TEST_TENANT_NAME)
|
||||||
|
s = session.Session()
|
||||||
|
s.get_token(auth=plugin)
|
||||||
|
|
||||||
|
def test_keystoneauth_betamax_fixture(self):
|
||||||
|
self._replay_cassette()
|
||||||
|
|
||||||
|
def test_replay_of_bad_url_fails(self):
|
||||||
|
plugin = v2.Password(
|
||||||
|
auth_url='http://invalid-auth-url/v2.0/',
|
||||||
|
password=self.TEST_PASSWORD,
|
||||||
|
username=self.TEST_USERNAME,
|
||||||
|
tenant_name=self.TEST_TENANT_NAME)
|
||||||
|
s = session.Session()
|
||||||
|
self.assertRaises(exceptions.BetamaxError, s.get_token, auth=plugin)
|
||||||
@@ -27,6 +27,10 @@ kerberos =
|
|||||||
requests-kerberos>=0.6:python_version=='2.7' or python_version=='2.6' # MIT
|
requests-kerberos>=0.6:python_version=='2.7' or python_version=='2.6' # MIT
|
||||||
saml2 =
|
saml2 =
|
||||||
lxml>=2.3
|
lxml>=2.3
|
||||||
|
betamax =
|
||||||
|
betamax>=0.5.1
|
||||||
|
fixtures>=1.3.1
|
||||||
|
mock>=1.2
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
|
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -12,7 +12,7 @@ setenv = VIRTUAL_ENV={envdir}
|
|||||||
|
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
.[kerberos,saml2]
|
.[kerberos,saml2,betamax]
|
||||||
commands = ostestr {posargs}
|
commands = ostestr {posargs}
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
|
|||||||
Reference in New Issue
Block a user