Swithc to using dynamic credentials in tempest tests

Switch to using DynamicCredentialProvider, which
provides a unique non-admin user per test case, making
it possible to functional test in parallel.

Change-Id: I6bb5d493bb01d0dd2b4cd092f87f111b13b1fe02
This commit is contained in:
dimtruck 2016-01-04 23:00:53 -06:00
parent 51ed8c4633
commit 5dc3211b71
9 changed files with 167 additions and 173 deletions

View File

@ -142,14 +142,6 @@ if [[ "api" == "$coe" ]]; then
iniset $BASE/new/tempest/etc/tempest.conf magnum keypair_id default
iniset $BASE/new/tempest/etc/tempest.conf magnum flavor_id m1.magnum
# This is a quick fix in case the other patch fails. Just to unblock gate
iniset $BASE/new/tempest/etc/tempest.conf identity username ${TEMPEST_USERNAME:-demo}
iniset $BASE/new/tempest/etc/tempest.conf identity password "${ADMIN_PASSWORD:-secrete}"
iniset $BASE/new/tempest/etc/tempest.conf identity tenant_name ${TEMPEST_TENANT_NAME:-demo}
iniset $BASE/new/tempest/etc/tempest.conf identity alt_username ${ALT_USERNAME:-alt_demo}
iniset $BASE/new/tempest/etc/tempest.conf identity alt_password "${ADMIN_PASSWORD:-secrete}"
iniset $BASE/new/tempest/etc/tempest.conf identity alt_tenant_name ${ALT_TENANT_NAME:-alt_demo}
# show tempest config with magnum
cat etc/tempest.conf

View File

@ -14,7 +14,7 @@ from magnum.tests.functional.api.v1.models import baymodel_model
from magnum.tests.functional.common import client
class BayModelClient(client.ClientMixin):
class BayModelClient(client.MagnumClient):
"""Encapsulates REST calls and maps JSON to/from models"""
@classmethod
@ -49,7 +49,7 @@ class BayModelClient(client.ClientMixin):
:returns: response object and BayModelCollection object
"""
resp, body = self.client.get(self.baymodels_uri(filters), **kwargs)
resp, body = self.get(self.baymodels_uri(filters), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelCollection)
def get_baymodel(self, baymodel_id, **kwargs):
@ -61,7 +61,7 @@ class BayModelClient(client.ClientMixin):
:returns: response object and BayModelCollection object
"""
resp, body = self.client.get(self.baymodel_uri(baymodel_id))
resp, body = self.get(self.baymodel_uri(baymodel_id))
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
def post_baymodel(self, model, **kwargs):
@ -73,7 +73,7 @@ class BayModelClient(client.ClientMixin):
:returns: response object and BayModelEntity object
"""
resp, body = self.client.post(
resp, body = self.post(
self.baymodels_uri(),
body=model.to_json(), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
@ -88,7 +88,7 @@ class BayModelClient(client.ClientMixin):
:returns: response object and BayModelEntity object
"""
resp, body = self.client.patch(
resp, body = self.patch(
self.baymodel_uri(baymodel_id),
body=baymodelpatch_listmodel.to_json(), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
@ -102,4 +102,4 @@ class BayModelClient(client.ClientMixin):
:returns: response object
"""
return self.client.delete(self.baymodel_uri(baymodel_id), **kwargs)
return self.delete(self.baymodel_uri(baymodel_id), **kwargs)

View File

@ -14,7 +14,7 @@ from magnum.tests.functional.api.v1.models import magnum_service_model
from magnum.tests.functional.common import client
class MagnumServiceClient(client.ClientMixin):
class MagnumServiceClient(client.MagnumClient):
"""Encapsulates REST calls and maps JSON to/from models"""
@classmethod
@ -39,7 +39,6 @@ class MagnumServiceClient(client.ClientMixin):
:returns: response object and MagnumServiceCollection object
"""
resp, body = self.client.get(self.magnum_service_uri(filters),
**kwargs)
resp, body = self.get(self.magnum_service_uri(filters), **kwargs)
return self.deserialize(resp, body,
magnum_service_model.MagnumServiceCollection)

View File

@ -14,7 +14,6 @@
from tempest_lib import exceptions
import testtools
from magnum.tests.functional.api.v1.clients import baymodel_client as cli
from magnum.tests.functional.common import base
from magnum.tests.functional.common import datagen
@ -26,34 +25,39 @@ class BayModelTest(base.BaseMagnumTest):
def __init__(self, *args, **kwargs):
super(BayModelTest, self).__init__(*args, **kwargs)
self.baymodels = []
self.baymodel_client = None
self.keypairs_client = None
def setUp(self):
super(BayModelTest, self).setUp()
(self.baymodel_client,
self.keypairs_client) = self.get_clients_with_isolated_creds(
type_of_creds='default',
request_type='baymodel')
def tearDown(self):
super(BayModelTest, self).tearDown()
for (baymodel_id, user) in self.baymodels:
self._delete_baymodel(baymodel_id, user)
self.baymodels.remove((baymodel_id, user))
for baymodel_id in self.baymodels:
self._delete_baymodel(baymodel_id)
self.baymodels.remove(baymodel_id)
def _create_baymodel(self, baymodel_model, user='default'):
resp, model = cli.BayModelClient.as_user(user).post_baymodel(
baymodel_model)
def _create_baymodel(self, baymodel_model):
resp, model = self.baymodel_client.post_baymodel(baymodel_model)
self.assertEqual(201, resp.status)
self.baymodels.append((model.uuid, user))
self.baymodels.append(model.uuid)
return resp, model
def _delete_baymodel(self, baymodel_id, user='default'):
resp, model = cli.BayModelClient.as_user(user).delete_baymodel(
baymodel_id)
def _delete_baymodel(self, baymodel_id):
resp, model = self.baymodel_client.delete_baymodel(baymodel_id)
self.assertEqual(204, resp.status)
return resp, model
@testtools.testcase.attr('positive')
def test_list_baymodels(self):
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
_, temp_model = self._create_baymodel(gen_model, user='default')
resp, model = cli.BayModelClient.as_user('default').list_baymodels()
_, temp_model = self._create_baymodel(gen_model)
resp, model = self.baymodel_client.list_baymodels()
self.assertEqual(200, resp.status)
self.assertGreater(len(model.baymodels), 0)
self.assertIn(
@ -61,123 +65,117 @@ class BayModelTest(base.BaseMagnumTest):
@testtools.testcase.attr('positive')
def test_create_baymodel(self):
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
resp, model = self._create_baymodel(gen_model, user='default')
resp, model = self._create_baymodel(gen_model)
@testtools.testcase.attr('positive')
def test_update_baymodel_by_uuid(self):
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
resp, old_model = self._create_baymodel(gen_model, user='default')
resp, old_model = self._create_baymodel(gen_model)
patch_model = datagen.random_baymodel_name_patch_data()
bay_model_client = cli.BayModelClient.as_user('default')
resp, new_model = bay_model_client.patch_baymodel(
resp, new_model = self.baymodel_client.patch_baymodel(
old_model.uuid, patch_model)
self.assertEqual(200, resp.status)
resp, model = cli.BayModelClient.as_user('default').get_baymodel(
new_model.uuid)
resp, model = self.baymodel_client.get_baymodel(new_model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual(old_model.uuid, new_model.uuid)
self.assertEqual(model.name, new_model.name)
@testtools.testcase.attr('positive')
def test_delete_baymodel_by_uuid(self):
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
resp, model = self._create_baymodel(gen_model, user='default')
resp, _ = cli.BayModelClient.as_user('default').delete_baymodel(
model.uuid)
resp, model = self._create_baymodel(gen_model)
resp, _ = self.baymodel_client.delete_baymodel(model.uuid)
self.assertEqual(204, resp.status)
self.baymodels.remove((model.uuid, 'default'))
self.baymodels.remove(model.uuid)
@testtools.testcase.attr('positive')
def test_delete_baymodel_by_name(self):
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
resp, model = self._create_baymodel(gen_model, user='default')
resp, _ = cli.BayModelClient.as_user('default').delete_baymodel(
model.name)
resp, model = self._create_baymodel(gen_model)
resp, _ = self.baymodel_client.delete_baymodel(model.name)
self.assertEqual(204, resp.status)
self.baymodels.remove((model.uuid, 'default'))
self.baymodels.remove(model.uuid)
@testtools.testcase.attr('negative')
def test_get_baymodel_by_uuid_404(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.get_baymodel, datagen.random_uuid())
self.baymodel_client.get_baymodel, datagen.random_uuid())
@testtools.testcase.attr('negative')
def test_update_baymodel_404(self):
patch_model = datagen.random_baymodel_name_patch_data()
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.patch_baymodel,
self.baymodel_client.patch_baymodel,
datagen.random_uuid(), patch_model)
@testtools.testcase.attr('negative')
def test_delete_baymodel_404(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.delete_baymodel, datagen.random_uuid())
self.baymodel_client.delete_baymodel, datagen.random_uuid())
@testtools.testcase.attr('negative')
def test_get_baymodel_by_name_404(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.get_baymodel, 'fooo')
self.baymodel_client.get_baymodel, 'fooo')
@testtools.testcase.attr('negative')
def test_update_baymodel_name_not_found(self):
patch_model = datagen.random_baymodel_name_patch_data()
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.patch_baymodel, 'fooo', patch_model)
self.baymodel_client.patch_baymodel, 'fooo', patch_model)
@testtools.testcase.attr('negative')
def test_delete_baymodel_by_name_404(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.NotFound,
bay_model_client.get_baymodel, 'fooo')
self.baymodel_client.get_baymodel, 'fooo')
@testtools.testcase.attr('negative')
def test_create_baymodel_missing_image(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair()
self.assertRaises(
exceptions.NotFound,
bay_model_client.post_baymodel, gen_model)
self.baymodel_client.post_baymodel, gen_model)
@testtools.testcase.attr('negative')
def test_create_baymodel_missing_keypair(self):
bay_model_client = cli.BayModelClient.as_user('default')
gen_model = datagen.random_baymodel_data_w_valid_image_id()
self.assertRaises(
exceptions.NotFound,
bay_model_client.post_baymodel, gen_model)
self.baymodel_client.post_baymodel, gen_model)
@testtools.testcase.attr('negative')
def test_update_baymodel_invalid_patch(self):
# get json object
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
resp, old_model = self._create_baymodel(gen_model)
bay_model_client = cli.BayModelClient.as_user('default')
self.assertRaises(
exceptions.BadRequest,
bay_model_client.patch_baymodel, datagen.random_uuid(), gen_model)
self.baymodel_client.patch_baymodel, datagen.random_uuid(),
gen_model)
@testtools.testcase.attr('negative')
def test_create_baymodel_invalid_network_driver(self):
bay_model_client = cli.BayModelClient.as_user('default')
self.keypairs_client.create_keypair(name='default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
gen_model.network_driver = 'invalid_network_driver'
self.assertRaises(
exceptions.BadRequest,
bay_model_client.post_baymodel, gen_model)
self.baymodel_client.post_baymodel, gen_model)

View File

@ -15,7 +15,6 @@ from tempest_lib import exceptions
import testtools
import unittest
from magnum.tests.functional.api.v1.clients import magnum_service_client as cli
from magnum.tests.functional.common import base
@ -23,6 +22,10 @@ class MagnumServiceTest(base.BaseMagnumTest):
"""Tests for magnum-service ."""
def __init__(self, *args, **kwargs):
super(MagnumServiceTest, self).__init__(*args, **kwargs)
self.service_client = None
# NOTE(suro-patz): The following test fails in lieu of non-admin user
# available for functional-test-gate.
# For now, I will keep the test, but skipped.
@ -30,14 +33,19 @@ class MagnumServiceTest(base.BaseMagnumTest):
@testtools.testcase.attr('negative')
def test_magnum_service_list_needs_admin(self):
# Ensure that policy enforcement does not allow 'default' user
client = cli.MagnumServiceClient.as_user('default')
self.assertRaises(exceptions.ServerFault, client.magnum_service_list)
(self.service_client, _) = self.get_clients_with_isolated_creds(
type_of_creds='default',
request_type='service')
self.assertRaises(exceptions.ServerFault,
self.service_client.magnum_service_list)
@testtools.testcase.attr('positive')
def test_magnum_service_list(self):
# get json object
client = cli.MagnumServiceClient.as_user('admin')
resp, msvcs = client.magnum_service_list()
(self.service_client, _) = self.get_clients_with_isolated_creds(
type_of_creds='admin',
request_type='service')
resp, msvcs = self.service_client.magnum_service_list()
self.assertEqual(200, resp.status)
# Note(suro-patz): Following code assumes that we have only
# one service, magnum-conductor enabled, as of now.

View File

@ -10,9 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import inspect
from tempest.common import credentials_factory as common_creds
from tempest.common import dynamic_creds
from tempest_lib import base
from magnum.tests.functional.common import config
from magnum.tests.functional.common import manager
class BaseMagnumTest(base.BaseTestCase):
@ -20,8 +25,63 @@ class BaseMagnumTest(base.BaseTestCase):
def __init__(self, *args, **kwargs):
super(BaseMagnumTest, self).__init__(*args, **kwargs)
self.ic = None
@classmethod
def setUpClass(cls):
super(BaseMagnumTest, cls).setUpClass()
config.Config.setUp()
@classmethod
def tearDownClass(cls):
super(BaseMagnumTest, cls).tearDownClass()
def tearDown(self):
super(BaseMagnumTest, self).tearDown()
if self.ic is not None:
self.ic.clear_creds()
def get_clients_with_isolated_creds(self,
name=None,
type_of_creds="default",
request_type=None):
"""Creates isolated creds.
:param name: name, will be used for dynamic creds
:param type_of_creds: admin, alt or default
:param request_type: baymodel or service
:returns: MagnumClient -- client with isolated creds.
:returns: KeypairClient -- allows for creating of keypairs
"""
if name is None:
# Get name of test method
name = inspect.stack()[1][3]
if len(name) > 32:
name = name[0:32]
# Choose type of isolated creds
self.ic = dynamic_creds.DynamicCredentialProvider(
identity_version=config.Config.auth_version,
name=name,
admin_role=config.Config.admin_role,
admin_creds=common_creds.get_configured_credentials(
'identity_admin'))
if "admin" == type_of_creds:
creds = self.ic.get_admin_creds()
manager_inst = manager.AdminManager(credentials=creds,
request_type=request_type)
elif "alt" == type_of_creds:
creds = self.ic.get_alt_creds()
manager_inst = manager.AltManager(credentials=creds,
request_type=request_type)
elif "default" == type_of_creds:
creds = self.ic.get_primary_creds()
manager_inst = manager.DefaultManager(credentials=creds,
request_type=request_type)
else:
creds = self.ic.self.get_credentials(type_of_creds)
manager_inst = manager.DefaultManager(credentials=creds,
request_type=request_type)
# create client with isolated creds
return (manager_inst.client, manager_inst.keypairs_client)

View File

@ -17,82 +17,23 @@ from six.moves.urllib import parse
from tempest_lib.common import rest_client
from magnum.tests.functional.common import config
from magnum.tests.functional.common import manager
from magnum.tests.functional.common.utils import memoized
@six.add_metaclass(abc.ABCMeta)
class BaseMagnumClient(rest_client.RestClient):
class MagnumClient(rest_client.RestClient):
"""Abstract class responsible for setting up auth provider"""
def __init__(self):
super(BaseMagnumClient, self).__init__(
auth_provider=self.get_auth_provider(),
def __init__(self, auth_provider):
super(MagnumClient, self).__init__(
auth_provider=auth_provider,
service='container',
region=config.Config.region
)
@abc.abstractmethod
def get_auth_provider(self):
pass
class MagnumClient(BaseMagnumClient):
"""Responsible for setting up auth provider for default user"""
def get_auth_provider(self):
mgr = manager.Manager()
return mgr.get_auth_provider(
username=config.Config.user,
password=config.Config.passwd,
tenant_name=config.Config.tenant
)
class AdminMagnumClient(BaseMagnumClient):
"""Responsible for setting up auth provider for admin user"""
def get_auth_provider(self):
mgr = manager.Manager()
return mgr.get_auth_provider(
username=config.Config.admin_user,
password=config.Config.admin_passwd,
tenant_name=config.Config.admin_tenant
)
class ClientMixin(object):
"""Responsible for mapping setting up common client use cases:
- deserializing response data to a model
- mapping user requests to a specific client for authentication
- generating request URLs
"""
@classmethod
@memoized
def get_clients(cls):
return {
'default': MagnumClient(),
'admin': AdminMagnumClient(),
}
def __init__(self, client):
self.client = client
@classmethod
def deserialize(cls, resp, body, model_type):
return resp, model_type.from_json(body)
@classmethod
def as_user(cls, user):
"""Retrieves Magnum client based on user parameter
:param user: type of user ('default')
:returns: a class that maps to user type in get_clients dict
"""
return cls(cls.get_clients()[user])
@property
def tenant_id(self):
return self.client.tenant_id

View File

@ -48,6 +48,14 @@ class Config(object):
raise Exception('config missing auth_url key')
cls.auth_url = CONF.identity.uri
@classmethod
def set_admin_role(cls, config):
# admin_role for client authentication
if cls.auth_version == 'v3':
cls.admin_role = CONF.identity.admin_role
else:
cls.admin_role = 'admin'
@classmethod
def set_region(cls, config):
if 'region' in CONF.identity:
@ -79,6 +87,7 @@ class Config(object):
cls.set_user_creds(config)
cls.set_auth_version(config)
cls.set_auth_url(config)
cls.set_admin_role(config)
cls.set_region(config)
cls.set_image_id(config)

View File

@ -10,53 +10,40 @@
# License for the specific language governing permissions and limitations
# under the License.
from tempest_lib import auth
from tempest_lib import exceptions
from tempest import clients
from tempest.common import credentials_factory as common_creds
import magnum.tests.functional.common.config as config
from magnum.tests.functional.api.v1.clients import baymodel_client
from magnum.tests.functional.api.v1.clients import magnum_service_client
from magnum.tests.functional.common import client
class Manager(object):
"""Responsible for providing an auth provider object"""
def _get_auth_credentials(self, auth_version, **credentials):
"""Retrieves auth credentials based on passed in creds and version
:param auth_version: auth version ('v2' or 'v3')
:param credentials: credentials dict to validate against
:returns: credentials object
"""
if credentials is None:
raise exceptions.InvalidCredentials(
'Credentials must be specified')
if auth_version == 'v3':
return auth.KeystoneV3Credentials(**credentials)
elif auth_version == 'v2':
return auth.KeystoneV2Credentials(**credentials)
class Manager(clients.Manager):
def __init__(
self,
credentials=common_creds.get_configured_credentials(
'identity_admin'),
request_type=None):
super(Manager, self).__init__(credentials, 'container')
if request_type == 'baymodel':
self.client = baymodel_client.BayModelClient(self.auth_provider)
elif request_type == 'service':
self.client = magnum_service_client.MagnumServiceClient(
self.auth_provider)
else:
raise exceptions.InvalidCredentials('Specify identity version')
self.client = client.MagnumClient(self.auth_provider)
def get_auth_provider(self, **credentials):
"""Validates credentials and returns auth provider
Auth provider will contain required security context to pass to magnum
class DefaultManager(Manager):
def __init__(self, credentials, request_type=None):
super(DefaultManager, self).__init__(credentials, request_type)
:param credentials: credentials dict to validate against
:returns: auth provider object
"""
auth_version = config.Config.auth_version
creds = self._get_auth_credentials(auth_version, **credentials)
if auth_version == 'v3':
auth_provider = auth.KeystoneV3AuthProvider(
creds, config.Config.auth_url)
elif auth_version == 'v2':
auth_provider = auth.KeystoneV2AuthProvider(
creds, config.Config.auth_url)
else:
raise exceptions.InvalidCredentials('Specify identity version')
class AltManager(Manager):
def __init__(self, credentials, request_type=None):
super(AltManager, self).__init__(credentials, request_type)
auth_provider.fill_credentials()
return auth_provider
class AdminManager(Manager):
def __init__(self, credentials, request_type=None):
super(AdminManager, self).__init__(credentials, request_type)