diff options
-rw-r--r-- | examples/demo.py | 56 | ||||
-rw-r--r-- | snaps/openstack/create_project.py | 4 | ||||
-rw-r--r-- | snaps/openstack/create_user.py | 33 | ||||
-rw-r--r-- | snaps/openstack/tests/create_project_tests.py | 21 | ||||
-rw-r--r-- | snaps/openstack/tests/create_user_tests.py | 27 | ||||
-rw-r--r-- | snaps/openstack/tests/os_source_file_test.py | 97 | ||||
-rw-r--r-- | snaps/openstack/utils/keystone_utils.py | 61 | ||||
-rw-r--r-- | snaps/openstack/utils/tests/keystone_utils_tests.py | 38 |
8 files changed, 264 insertions, 73 deletions
diff --git a/examples/demo.py b/examples/demo.py new file mode 100644 index 0000000..b2231f8 --- /dev/null +++ b/examples/demo.py @@ -0,0 +1,56 @@ +import logging +logging.basicConfig(level=logging.INFO) + +# Credentials +from snaps.openstack.os_credentials import OSCreds, ProxySettings + + +proxy_settings = ProxySettings(host='10.197.123.27', port='3128', + ssh_proxy_cmd='/usr/local/bin/corkscrew 10.197.123.27 3128 %h %p') + +os_creds = OSCreds(username='admin', password='cable123', auth_url='http://192.168.67.10:5000/v2.0/', + project_name='admin', proxy_settings=proxy_settings) + + +# Images +from snaps.openstack.create_image import ImageSettings, OpenStackImage + +image_settings = ImageSettings(name='cirros-test', image_user='cirros', img_format='qcow2', + url='http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img') + +image = OpenStackImage(os_creds, image_settings) +image.create() +# See in Horizon + + +# Network +from snaps.openstack.create_network import NetworkSettings, SubnetSettings, OpenStackNetwork + +subnet_settings = SubnetSettings(name='test-subnet', cidr='10.0.0.1/24') +network_settings = NetworkSettings(name='test-net', subnet_settings=[subnet_settings]) +network = OpenStackNetwork(os_creds, network_settings) +network.create() + + +# Flavors +from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor + +flavor_settings = FlavorSettings(name='test-flavor', ram=128, disk=10, vcpus=2) +flavor = OpenStackFlavor(os_creds, flavor_settings) +flavor.create() + +# Instances +from snaps.openstack.create_network import PortSettings +from snaps.openstack.create_instance import VmInstanceSettings, OpenStackVmInstance + +port_settings = PortSettings(name='test-port', network_name=network_settings.name) +instance_settings = VmInstanceSettings(name='test-inst', flavor=flavor_settings.name, port_settings=[port_settings]) + +vm_inst = OpenStackVmInstance(os_creds, instance_settings, image_settings) +vm_inst.create(block=True) + +# Cleanup +vm_inst.clean() +flavor.clean() +network.clean() +image.clean() diff --git a/snaps/openstack/create_project.py b/snaps/openstack/create_project.py index c865f15..0384ccc 100644 --- a/snaps/openstack/create_project.py +++ b/snaps/openstack/create_project.py @@ -96,8 +96,8 @@ class OpenStackProject: self.__role = keystone_utils.create_role( self.__keystone, self.project_settings.name + '-role') - keystone_utils.assoc_user_to_project(self.__keystone, self.__role, - user, self.__project) + keystone_utils.grant_user_role_to_project(self.__keystone, self.__role, + user, self.__project) class ProjectSettings: diff --git a/snaps/openstack/create_user.py b/snaps/openstack/create_user.py index 4c96121..18de215 100644 --- a/snaps/openstack/create_user.py +++ b/snaps/openstack/create_user.py @@ -49,13 +49,9 @@ class OpenStackUser: self.__keystone = keystone_utils.keystone_client(self.__os_creds) self.__user = keystone_utils.get_user(self.__keystone, self.user_settings.name) - if self.__user: - logger.info('Found user with name - ' + self.user_settings.name) - elif not cleanup: + if not self.__user and not cleanup: self.__user = keystone_utils.create_user(self.__keystone, self.user_settings) - else: - logger.info('Did not create user due to cleanup mode') return self.__user @@ -111,27 +107,30 @@ class UserSettings: :param email: the user's email address (optional) :param enabled: denotes whether or not the user is enabled (default True) + :param roles: dict where key is the role name and value is a list of + project names """ self.name = kwargs.get('name') self.password = kwargs.get('password') self.project_name = kwargs.get('project_name') self.email = kwargs.get('email') - - if kwargs.get('domain_name'): - self.domain_name = kwargs['domain_name'] - else: - self.domain_name = 'default' - - if kwargs.get('enabled') is not None: - self.enabled = kwargs['enabled'] - else: - self.enabled = True + self.domain_name = kwargs.get('domain_name', 'default') + self.enabled = kwargs.get('enabled', True) + self.roles = kwargs.get('roles', dict()) if not self.name or not self.password: - raise Exception( + raise UserSettingsException( 'The attributes name and password are required for ' 'UserSettings') if not isinstance(self.enabled, bool): - raise Exception('The attribute enabled must be of type boolean') + raise UserSettingsException('The attribute enabled must be of type' + ' boolean') + + +class UserSettingsException(Exception): + """ + Raised when there is a problem with the values set in the UserSettings + class + """ diff --git a/snaps/openstack/tests/create_project_tests.py b/snaps/openstack/tests/create_project_tests.py index da93b13..3c6b2d1 100644 --- a/snaps/openstack/tests/create_project_tests.py +++ b/snaps/openstack/tests/create_project_tests.py @@ -194,9 +194,10 @@ class CreateProjectUserTests(OSComponentTestCase): created_project = self.project_creator.create() self.assertIsNotNone(created_project) - user_creator = OpenStackUser(self.os_creds, - UserSettings(name=self.guid + '-user', - password=self.guid)) + user_creator = OpenStackUser( + self.os_creds, UserSettings( + name=self.guid + '-user', + password=self.guid, roles={'admin': 'admin'})) self.project_creator.assoc_user(user_creator.create()) self.user_creators.append(user_creator) @@ -222,15 +223,17 @@ class CreateProjectUserTests(OSComponentTestCase): created_project = self.project_creator.create() self.assertIsNotNone(created_project) - user_creator_1 = OpenStackUser(self.os_creds, - UserSettings(name=self.guid + '-user1', - password=self.guid)) + user_creator_1 = OpenStackUser( + self.os_creds, UserSettings( + name=self.guid + '-user1', password=self.guid, + roles={'admin': 'admin'})) self.project_creator.assoc_user(user_creator_1.create()) self.user_creators.append(user_creator_1) - user_creator_2 = OpenStackUser(self.os_creds, - UserSettings(name=self.guid + '-user2', - password=self.guid)) + user_creator_2 = OpenStackUser( + self.os_creds, UserSettings( + name=self.guid + '-user2', password=self.guid, + roles={'admin': 'admin'})) self.project_creator.assoc_user(user_creator_2.create()) self.user_creators.append(user_creator_2) diff --git a/snaps/openstack/tests/create_user_tests.py b/snaps/openstack/tests/create_user_tests.py index fdc3644..a3cc8ce 100644 --- a/snaps/openstack/tests/create_user_tests.py +++ b/snaps/openstack/tests/create_user_tests.py @@ -103,7 +103,8 @@ class CreateUserSuccessTests(OSComponentTestCase): guid = str(uuid.uuid4())[:-19] guid = self.__class__.__name__ + '-' + guid self.user_settings = UserSettings(name=guid + '-name', - password=guid + '-password') + password=guid + '-password', + roles={'admin': 'admin'}) self.keystone = keystone_utils.keystone_client(self.os_creds) @@ -162,3 +163,27 @@ class CreateUserSuccessTests(OSComponentTestCase): # Delete user self.user_creator.clean() self.assertIsNone(self.user_creator.get_user()) + + def test_create_admin_user(self): + """ + Tests the creation of an OpenStack user. + """ + self.user_creator = OpenStackUser(self.os_creds, self.user_settings) + created_user = self.user_creator.create() + self.assertIsNotNone(created_user) + + retrieved_user = keystone_utils.get_user(self.keystone, + self.user_settings.name) + self.assertIsNotNone(retrieved_user) + self.assertEqual(created_user, retrieved_user) + + role = keystone_utils.get_os_role_by_name(self.keystone, 'admin') + self.assertIsNotNone(role) + + os_proj = keystone_utils.get_project( + keystone=self.keystone, project_name=self.os_creds.project_name) + user_roles = keystone_utils.get_os_roles_by_user( + self.keystone, retrieved_user, os_proj) + self.assertIsNotNone(user_roles) + self.assertEqual(1, len(user_roles)) + self.assertEqual(role.id, user_roles[0].id) diff --git a/snaps/openstack/tests/os_source_file_test.py b/snaps/openstack/tests/os_source_file_test.py index 4b421e8..01aa88a 100644 --- a/snaps/openstack/tests/os_source_file_test.py +++ b/snaps/openstack/tests/os_source_file_test.py @@ -24,23 +24,23 @@ from snaps.openstack.tests import openstack_tests from snaps.openstack.utils import deploy_utils, keystone_utils -dev_os_env_file = pkg_resources.resource_filename('snaps.openstack.tests.conf', 'os_env.yaml') - -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# To run these tests from an IDE, the CWD must be set to the snaps directory of this project -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +dev_os_env_file = pkg_resources.resource_filename( + 'snaps.openstack.tests.conf', 'os_env.yaml') class OSComponentTestCase(unittest.TestCase): - def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None, image_metadata=None, - log_level=logging.DEBUG): + def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None, + image_metadata=None, log_level=logging.DEBUG): """ Super for test classes requiring a connection to OpenStack :param method_name: default 'runTest' - :param os_creds: the OSCreds object, when null it searches for the file {cwd}/openstack/tests/conf/os_env.yaml - :param ext_net_name: the name of the external network that is used for creating routers for floating IPs - :param image_metadata: ability to override images being used in the tests (see examples/image-metadata) + :param os_creds: the OSCreds object, when null it searches for the file + in the package snaps.openstack.tests.conf.os_env.yaml + :param ext_net_name: the name of the external network that is used for + creating routers for floating IPs + :param image_metadata: ability to override images being used in the + tests (see examples/image-metadata) :param log_level: the logging level of your test run (default DEBUG) """ super(OSComponentTestCase, self).__init__(method_name) @@ -50,7 +50,8 @@ class OSComponentTestCase(unittest.TestCase): if os_creds: self.os_creds = os_creds else: - self.os_creds = openstack_tests.get_credentials(dev_os_env_file=dev_os_env_file) + self.os_creds = openstack_tests.get_credentials( + dev_os_env_file=dev_os_env_file) self.ext_net_name = ext_net_name @@ -61,7 +62,8 @@ class OSComponentTestCase(unittest.TestCase): self.image_metadata = image_metadata @staticmethod - def parameterize(testcase_klass, os_creds, ext_net_name, image_metadata=None, log_level=logging.DEBUG): + def parameterize(testcase_klass, os_creds, ext_net_name, + image_metadata=None, log_level=logging.DEBUG): """ Create a suite containing all tests taken from the given subclass, passing them the parameter 'param'. """ @@ -69,37 +71,46 @@ class OSComponentTestCase(unittest.TestCase): test_names = test_loader.getTestCaseNames(testcase_klass) suite = unittest.TestSuite() for name in test_names: - suite.addTest(testcase_klass(name, os_creds, ext_net_name, image_metadata, log_level)) + suite.addTest(testcase_klass(name, os_creds, ext_net_name, + image_metadata, log_level)) return suite class OSIntegrationTestCase(OSComponentTestCase): - def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None, use_keystone=False, - flavor_metadata=None, image_metadata=None, log_level=logging.DEBUG): + def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None, + use_keystone=False, flavor_metadata=None, image_metadata=None, + log_level=logging.DEBUG): """ Super for integration tests requiring a connection to OpenStack :param method_name: default 'runTest' - :param os_creds: the OSCreds object, when null it searches for the file {cwd}/openstack/tests/conf/os_env.yaml - :param ext_net_name: the name of the external network that is used for creating routers for floating IPs - :param use_keystone: when true, these tests will create a new user/project under which to run the test - :param image_metadata: dict() containing the URLs for the disk, kernel, and ramdisk images when multi-part - images are required. - image_metadata={'disk_url': 'http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img', - 'kernel_url': 'http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-kernel', - 'ramdisk_url': 'http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-initramfs'}) - :param flavor_metadata: dict() to be sent directly into the Nova client generally used for page sizes + :param os_creds: the OSCreds object, when null it searches for the file + in the package snaps.openstack.tests.conf.os_env.yaml + :param ext_net_name: the name of the external network that is used for + creating routers for floating IPs + :param use_keystone: when true, these tests will create a new + user/project under which to run the test + :param image_metadata: dict() containing the URLs for the disk, kernel, + and ramdisk images when multi-part images are + required. See below for a simple example + image_metadata={'disk_url': '{URI}/cirros-0.3.4-x86_64-disk.img', + 'kernel_url': '{URI}/cirros-0.3.4-x86_64-kernel', + 'ramdisk_url': '{URI}/cirros-0.3.4-x86_64-initramfs'}) + :param flavor_metadata: dict() to be sent directly into the Nova client + generally used for page sizes :param log_level: the logging level of your test run (default DEBUG) """ - super(OSIntegrationTestCase, self).__init__(method_name=method_name, os_creds=os_creds, - ext_net_name=ext_net_name, image_metadata=image_metadata, - log_level=log_level) + super(OSIntegrationTestCase, self).__init__( + method_name=method_name, os_creds=os_creds, + ext_net_name=ext_net_name, image_metadata=image_metadata, + log_level=log_level) self.use_keystone = use_keystone self.keystone = None self.flavor_metadata = flavor_metadata @staticmethod - def parameterize(testcase_klass, os_creds, ext_net_name, use_keystone=False, flavor_metadata=None, + def parameterize(testcase_klass, os_creds, ext_net_name, + use_keystone=False, flavor_metadata=None, image_metadata=None, log_level=logging.DEBUG): """ Create a suite containing all tests taken from the given @@ -109,17 +120,20 @@ class OSIntegrationTestCase(OSComponentTestCase): test_names = test_loader.getTestCaseNames(testcase_klass) suite = unittest.TestSuite() for name in test_names: - suite.addTest(testcase_klass(name, os_creds, ext_net_name, use_keystone, flavor_metadata, image_metadata, - log_level)) + suite.addTest(testcase_klass(name, os_creds, ext_net_name, + use_keystone, flavor_metadata, + image_metadata, log_level)) return suite """ - Super for test classes that should be run within their own project/tenant as they can run for quite some time + Super for test classes that should be run within their own project/tenant + as they can run for quite some time """ def __start__(self): """ - Creates a project and user to be leveraged by subclass test methods. If implementing class uses this method, - it must call __clean__() else you will be left with unwanted users and tenants + Creates a project and user to be leveraged by subclass test methods. If + implementing class uses this method, it must call __clean__() else you + will be left with unwanted users and tenants """ self.project_creator = None self.user_creator = None @@ -130,11 +144,17 @@ class OSIntegrationTestCase(OSComponentTestCase): self.keystone = keystone_utils.keystone_client(self.os_creds) guid = self.__class__.__name__ + '-' + str(uuid.uuid4())[:-19] project_name = guid + '-proj' - self.project_creator = deploy_utils.create_project(self.admin_os_creds, ProjectSettings(name=project_name)) + self.project_creator = deploy_utils.create_project( + self.admin_os_creds, ProjectSettings(name=project_name)) self.user_creator = deploy_utils.create_user( - self.admin_os_creds, UserSettings(name=guid + '-user', password=guid, project_name=project_name)) - self.os_creds = self.user_creator.get_os_creds(self.project_creator.project_settings.name) + self.admin_os_creds, UserSettings( + name=guid + '-user', password=guid, + project_name=project_name, roles={ + 'admin': self.project_creator.project_settings.name})) + + self.os_creds = self.user_creator.get_os_creds( + self.project_creator.project_settings.name) # add user to project self.project_creator.assoc_user(self.user_creator.get_user()) @@ -142,8 +162,9 @@ class OSIntegrationTestCase(OSComponentTestCase): def __clean__(self): """ Cleans up test user and project. - Must be called at the end of child classes tearDown() if __start__() is called during setUp() else these - objects will persist after the test is run + Must be called at the end of child classes tearDown() if __start__() is + called during setUp() else these objects will persist after the test is + run """ if self.role: keystone_utils.delete_role(self.keystone, self.role) diff --git a/snaps/openstack/utils/keystone_utils.py b/snaps/openstack/utils/keystone_utils.py index 3823914..0a850d3 100644 --- a/snaps/openstack/utils/keystone_utils.py +++ b/snaps/openstack/utils/keystone_utils.py @@ -207,12 +207,28 @@ def create_user(keystone, user_settings): email=user_settings.email, tenant_id=project_id, enabled=user_settings.enabled) else: - # TODO - need to support groups os_user = keystone.users.create( name=user_settings.name, password=user_settings.password, email=user_settings.email, project=project, domain=user_settings.domain_name, enabled=user_settings.enabled) + for role_name, role_project in user_settings.roles.items(): + os_role = get_os_role_by_name(keystone, role_name) + os_project = get_project(keystone=keystone, project_name=role_project) + + if os_role and os_project: + existing_roles = get_os_roles_by_user(keystone, os_user, + os_project) + found = False + for role in existing_roles: + if role.id == os_role.id: + found = True + + if not found: + grant_user_role_to_project( + keystone=keystone, user=os_user, role=os_role, + project=os_project) + if os_user: return User(name=os_user.name, user_id=os_user.id) @@ -226,6 +242,45 @@ def delete_user(keystone, user): keystone.users.delete(user.id) +def get_os_role_by_name(keystone, name): + """ + Returns an OpenStack role object of a given name or None if not exists + :param keystone: the keystone client + :param name: the role name + :return: the OpenStack role object + """ + roles = keystone.roles.list() + for role in roles: + if role.name == name: + return role + + +def get_os_roles_by_user(keystone, user, project): + """ + Returns a list of OpenStack role object associated with a user + :param keystone: the keystone client + :param user: the OpenStack user object + :param project: the OpenStack project object (only required for v2) + :return: a list of OpenStack role objects + """ + if keystone.version == V2_VERSION: + os_user = get_os_user(keystone, user) + roles = keystone.roles.roles_for_user(os_user, project) + return roles + else: + return keystone.roles.list(user=user, project=project) + + +def get_os_role_by_id(keystone, role_id): + """ + Returns an OpenStack role object of a given name or None if not exists + :param keystone: the keystone client + :param role_id: the role ID + :return: the OpenStack role object + """ + return keystone.roles.get(role_id) + + def create_role(keystone, name): """ Creates an OpenStack role @@ -246,9 +301,9 @@ def delete_role(keystone, role): keystone.roles.delete(role) -def assoc_user_to_project(keystone, role, user, project): +def grant_user_role_to_project(keystone, role, user, project): """ - Adds a user to a project + Grants user and role to a project :param keystone: the Keystone client :param role: the role used to join a project/user :param user: the user to add to the project (SNAPS-OO User Domain object diff --git a/snaps/openstack/utils/tests/keystone_utils_tests.py b/snaps/openstack/utils/tests/keystone_utils_tests.py index 1fc9d38..89b2b2c 100644 --- a/snaps/openstack/utils/tests/keystone_utils_tests.py +++ b/snaps/openstack/utils/tests/keystone_utils_tests.py @@ -59,12 +59,13 @@ class KeystoneUtilsTests(OSComponentTestCase): Instantiates the CreateImage object that is responsible for downloading and creating an OS image file within OpenStack """ - guid = uuid.uuid4() - self.username = self.__class__.__name__ + '-' + str(guid) + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.username = self.guid + '-username' self.user = None - self.project_name = self.__class__.__name__ + '-' + str(guid) + self.project_name = self.guid + '-projName' self.project = None + self.role = None self.keystone = keystone_utils.keystone_client(self.os_creds) def tearDown(self): @@ -77,6 +78,9 @@ class KeystoneUtilsTests(OSComponentTestCase): if self.user: keystone_utils.delete_user(self.keystone, self.user) + if self.role: + keystone_utils.delete_role(self.keystone, self.role) + def test_create_user_minimal(self): """ Tests the keystone_utils.create_user() function @@ -151,3 +155,31 @@ class KeystoneUtilsTests(OSComponentTestCase): self.assertNotEqual(endpoint_public, endpoint_internal) self.assertNotEqual(endpoint_public, endpoint_admin) self.assertEqual(endpoint_admin, endpoint_internal) + + def test_grant_user_role_to_project(self): + """ + Tests the keystone_utils function grant_user_role_to_project() + :return: + """ + user_settings = UserSettings(name=self.username, + password=str(uuid.uuid4())) + self.user = keystone_utils.create_user(self.keystone, user_settings) + self.assertEqual(self.username, self.user.name) + + project_settings = ProjectSettings(name=self.project_name) + self.project = keystone_utils.create_project(self.keystone, + project_settings) + self.assertEqual(self.project_name, self.project.name) + + role_name = self.guid + '-role' + self.role = keystone_utils.create_role(self.keystone, role_name) + self.assertEqual(role_name, self.role.name) + + keystone_utils.grant_user_role_to_project( + self.keystone, self.role, self.user, self.project) + + user_roles = keystone_utils.get_os_roles_by_user( + self.keystone, self.user, self.project) + self.assertIsNotNone(user_roles) + self.assertEqual(1, len(user_roles)) + self.assertEqual(self.role.id, user_roles[0].id) |