From b20a368daa581e3f649ac5a772da31cd09fdb484 Mon Sep 17 00:00:00 2001 From: spisarski Date: Mon, 30 Oct 2017 14:22:20 -0600 Subject: [PATCH] Added method to OpenStackHeatStack to return OpenStackFlavor objects. Continuation of the story SNAPS-153 for adding creator/state machine instances for OpenStack objects deployed via Heat. JIRA: SNAPS-174 Change-Id: I791e427efc487045c0f72cd836dabd9a66a4f60f Signed-off-by: spisarski --- docs/how-to-use/APITests.rst | 11 ++++ docs/how-to-use/IntegrationTests.rst | 11 ++++ docs/how-to-use/UnitTests.rst | 9 ++- snaps/domain/flavor.py | 11 +++- snaps/openstack/create_stack.py | 27 ++++++++ snaps/openstack/tests/create_flavor_tests.py | 2 +- snaps/openstack/tests/create_stack_tests.py | 62 ++++++++++++++++++ .../openstack/tests/heat/flavor_heat_template.yaml | 27 ++++++++ snaps/openstack/utils/heat_utils.py | 23 +++++++ snaps/openstack/utils/keystone_utils.py | 2 +- snaps/openstack/utils/settings_utils.py | 13 ++++ snaps/openstack/utils/tests/heat_utils_tests.py | 73 +++++++++++++++++----- .../openstack/utils/tests/settings_utils_tests.py | 18 +++++- snaps/test_suite_builder.py | 21 +++++-- 14 files changed, 280 insertions(+), 30 deletions(-) create mode 100644 snaps/openstack/tests/heat/flavor_heat_template.yaml diff --git a/docs/how-to-use/APITests.rst b/docs/how-to-use/APITests.rst index 9110162..ed4779a 100644 --- a/docs/how-to-use/APITests.rst +++ b/docs/how-to-use/APITests.rst @@ -473,6 +473,17 @@ heat_utils_tests.py - HeatUtilsKeypairTests | | | Keypair domain objects deployed with Heat | +---------------------------------------+---------------+-----------------------------------------------------------+ +heat_utils_tests.py - HeatUtilsFlavorTests +------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_flavor_with_stack | 1 | Tests ability of the function | +| | | heat_utils.get_stack_flavors() to return the correct | +| | | Flavor domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ + settings_utils_tests.py - SettingsUtilsNetworkingTests ------------------------------------------------------ diff --git a/docs/how-to-use/IntegrationTests.rst b/docs/how-to-use/IntegrationTests.rst index e00bc40..4772357 100644 --- a/docs/how-to-use/IntegrationTests.rst +++ b/docs/how-to-use/IntegrationTests.rst @@ -396,6 +396,17 @@ create_stack_tests.py - CreateStackVolumeTests | | | deploying | +---------------------------------------+---------------+-----------------------------------------------------------+ +create_stack_tests.py - CreateStackFlavorTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_flavor_creator | 1 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackFlavor instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + create_stack_tests.py - CreateStackKeypairTests ----------------------------------------------- diff --git a/docs/how-to-use/UnitTests.rst b/docs/how-to-use/UnitTests.rst index 3cb26db..4a360d4 100644 --- a/docs/how-to-use/UnitTests.rst +++ b/docs/how-to-use/UnitTests.rst @@ -294,11 +294,16 @@ VmInstDomainObjectTests Ensures that all required members are included when constructing a VmInst domain object -SettingsUtilsVolumeTests ------------------------- +SettingsUtilsUnitTests +---------------------- Ensures that the settings_utils.py#create_volume_settings() function properly maps a snaps.domain.Volume object correctly to a snaps.openstack.create_volume.VolumeSettings object as well as a snaps.domain.VolumeType object to a snaps.openstack.create_volume.VolumeSettings object + + +Ensures that the settings_utils.py#create_flavor_settings() function properly +maps a snaps.domain.Flavor object correctly to a +snaps.openstack.create_flavor.FlavorSettings object \ No newline at end of file diff --git a/snaps/domain/flavor.py b/snaps/domain/flavor.py index 035ca64..bf84cf4 100644 --- a/snaps/domain/flavor.py +++ b/snaps/domain/flavor.py @@ -23,7 +23,7 @@ class Flavor: """ Constructor :param name: the flavor's name - :param flavor_id: the flavor's id + :param flavor_id or id: the flavor's id :param ram: the flavor's RAM in MB :param disk: the flavor's disk size in GB :param vcpus: the flavor's number of virtual CPUs @@ -33,11 +33,16 @@ class Flavor: :param is_public: denotes if flavor can be used by other projects """ self.name = kwargs.get('name') - self.id = kwargs.get('id') + self.id = kwargs.get('flavor_id', kwargs.get('id')) self.ram = kwargs.get('ram') self.disk = kwargs.get('disk') self.vcpus = kwargs.get('vcpus') self.ephemeral = kwargs.get('ephemeral') - self.swap = kwargs.get('swap') + + if kwargs.get('swap'): + self.swap = int(kwargs.get('swap')) + else: + self.swap = None + self.rxtx_factor = kwargs.get('rxtx_factor') self.is_public = kwargs.get('is_public') diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py index b565118..1820e2a 100644 --- a/snaps/openstack/create_stack.py +++ b/snaps/openstack/create_stack.py @@ -18,6 +18,7 @@ import time from heatclient.exc import HTTPNotFound +from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_instance import OpenStackVmInstance from snaps.openstack.create_keypairs import OpenStackKeypair from snaps.openstack.create_volume import OpenStackVolume @@ -347,6 +348,32 @@ class OpenStackHeatStack(OpenStackCloudObject, object): return out + def get_flavor_creators(self): + """ + Returns a list of Flavor creator objects as configured by the heat + template + :return: list() of OpenStackFlavor objects + """ + + out = list() + nova = nova_utils.nova_client(self._os_creds) + + flavors = heat_utils.get_stack_flavors( + self.__heat_cli, nova, self.__stack) + + for flavor in flavors: + settings = settings_utils.create_flavor_settings(flavor) + creator = OpenStackFlavor(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume creator - %s', e) + + return out + def _stack_status_check(self, expected_status_code, block, timeout, poll_interval, fail_status): """ diff --git a/snaps/openstack/tests/create_flavor_tests.py b/snaps/openstack/tests/create_flavor_tests.py index 4852d06..3eb07bd 100644 --- a/snaps/openstack/tests/create_flavor_tests.py +++ b/snaps/openstack/tests/create_flavor_tests.py @@ -399,7 +399,7 @@ def validate_flavor(nova, flavor_settings, flavor): equals = False break - swap = str() + swap = None if flavor_settings.swap != 0: swap = flavor_settings.swap diff --git a/snaps/openstack/tests/create_stack_tests.py b/snaps/openstack/tests/create_stack_tests.py index 8f9339a..94085a0 100644 --- a/snaps/openstack/tests/create_stack_tests.py +++ b/snaps/openstack/tests/create_stack_tests.py @@ -589,6 +589,68 @@ class CreateStackVolumeTests(OSIntegrationTestCase): self.assertEqual(volume_type.id, encryption.volume_type_id) +class CreateStackFlavorTests(OSIntegrationTestCase): + """ + Tests for the CreateStack class defined in create_stack.py + """ + + def setUp(self): + """ + Instantiates the CreateStack object that is responsible for downloading + and creating an OS stack file within OpenStack + """ + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.stack_creator = None + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml') + + stack_settings = StackSettings( + name=self.guid + '-stack', + template_path=self.heat_tmplt_path) + self.stack_creator = create_stack.OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_flavor_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackVolume creator/state machine instance + """ + flavor_creators = self.stack_creator.get_flavor_creators() + self.assertEqual(1, len(flavor_creators)) + + creator = flavor_creators[0] + self.assertTrue(creator.get_flavor().name.startswith(self.guid)) + self.assertEqual(1024, creator.get_flavor().ram) + self.assertEqual(200, creator.get_flavor().disk) + self.assertEqual(8, creator.get_flavor().vcpus) + self.assertEqual(0, creator.get_flavor().ephemeral) + self.assertIsNone(creator.get_flavor().swap) + self.assertEqual(1.0, creator.get_flavor().rxtx_factor) + self.assertTrue(creator.get_flavor().is_public) + + class CreateStackKeypairTests(OSIntegrationTestCase): """ Tests for the CreateStack class as they pertain to keypairs diff --git a/snaps/openstack/tests/heat/flavor_heat_template.yaml b/snaps/openstack/tests/heat/flavor_heat_template.yaml new file mode 100644 index 0000000..060c85c --- /dev/null +++ b/snaps/openstack/tests/heat/flavor_heat_template.yaml @@ -0,0 +1,27 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# 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. +############################################################################## +heat_template_version: 2015-04-30 + +description: Simple template to deploy a single volume with encryption + +resources: + flavor: + type: OS::Nova::Flavor + properties: + ram: 1024 + vcpus: 8 + disk: 200 diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py index f09857a..ad354e0 100644 --- a/snaps/openstack/utils/heat_utils.py +++ b/snaps/openstack/utils/heat_utils.py @@ -293,6 +293,29 @@ def get_stack_volume_types(heat_cli, cinder, stack): return out +def get_stack_flavors(heat_cli, nova, stack): + """ + Returns an instance of Flavor SNAPS domain object for each flavor created + by this stack + :param heat_cli: the OpenStack heat client object + :param nova: the OpenStack cinder client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of Volume domain objects + """ + + out = list() + resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor') + for resource in resources: + try: + flavor = nova_utils.get_flavor_by_id(nova, resource.id) + if flavor: + out.append(flavor) + except NotFound: + logger.warn('Flavor cannot be located with ID %s', resource.id) + + return out + + def parse_heat_template_str(tmpl_str): """ Takes a heat template string, performs some simple validation and returns a diff --git a/snaps/openstack/utils/keystone_utils.py b/snaps/openstack/utils/keystone_utils.py index 46f6fb8..387f6c7 100644 --- a/snaps/openstack/utils/keystone_utils.py +++ b/snaps/openstack/utils/keystone_utils.py @@ -367,7 +367,7 @@ def grant_user_role_to_project(keystone, role, user, project): """ os_role = get_role_by_id(keystone, role.id) - logger.info('Granting role %s to project %s', role.name, project) + logger.info('Granting role %s to project %s', role.name, project.name) if keystone.version == V2_VERSION_STR: keystone.roles.add_user_role(user, os_role, tenant=project) else: diff --git a/snaps/openstack/utils/settings_utils.py b/snaps/openstack/utils/settings_utils.py index 68dbf71..2ab3c28 100644 --- a/snaps/openstack/utils/settings_utils.py +++ b/snaps/openstack/utils/settings_utils.py @@ -15,6 +15,7 @@ import uuid from snaps import file_utils +from snaps.openstack.create_flavor import FlavorSettings from snaps.openstack.create_instance import ( VmInstanceSettings, FloatingIpSettings) from snaps.openstack.create_keypairs import KeypairSettings @@ -109,6 +110,18 @@ def create_volume_type_settings(volume_type): qos_spec_name=qos_spec_name, public=volume_type.public) +def create_flavor_settings(flavor): + """ + Returns a VolumeSettings object + :param flavor: a SNAPS-OO Volume object + """ + return FlavorSettings( + name=flavor.name, flavor_id=flavor.id, ram=flavor.ram, + disk=flavor.disk, vcpus=flavor.vcpus, ephemeral=flavor.ephemeral, + swap=flavor.swap, rxtx_factor=flavor.rxtx_factor, + is_public=flavor.is_public) + + def create_keypair_settings(heat_cli, stack, keypair, pk_output_key): """ Instantiates a KeypairSettings object from a Keypair domain objects diff --git a/snaps/openstack/utils/tests/heat_utils_tests.py b/snaps/openstack/utils/tests/heat_utils_tests.py index b021701..567cf7b 100644 --- a/snaps/openstack/utils/tests/heat_utils_tests.py +++ b/snaps/openstack/utils/tests/heat_utils_tests.py @@ -225,22 +225,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): self.stack2.id) self.assertEqual(self.stack2, stack2_query_3) - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT - - is_active = False - while time.time() < end_time: - status = heat_utils.get_stack_status(self.heat_client, - self.stack2.id) - if status == create_stack.STATUS_CREATE_COMPLETE: - is_active = True - break - elif status == create_stack.STATUS_CREATE_FAILED: - is_active = False - break - - time.sleep(3) - - self.assertTrue(is_active) + self.assertTrue(stack_active(self.heat_client, self.stack2)) class HeatUtilsCreateComplexStackTests(OSComponentTestCase): @@ -505,6 +490,62 @@ class HeatUtilsVolumeTests(OSComponentTestCase): self.assertEqual(volume_type.id, encryption.volume_type_id) +class HeatUtilsFlavorTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.name_prefix = guid + stack_name = guid + '-stack' + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml') + self.stack_settings = StackSettings( + name=stack_name, template_path=heat_tmplt_path) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.nova = nova_utils.nova_client(self.os_creds) + + def tearDown(self): + """ + Cleans the image and downloaded image file + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_flavor_with_stack(self): + """ + Tests the creation of an OpenStack volume with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + + self.assertTrue(stack_active(self.heat_client, self.stack)) + + flavors = heat_utils.get_stack_flavors( + self.heat_client, self.nova, self.stack) + + self.assertEqual(1, len(flavors)) + + flavor = flavors[0] + self.assertTrue(flavor.name.startswith(self.name_prefix)) + self.assertEqual(1024, flavor.ram) + self.assertEqual(200, flavor.disk) + self.assertEqual(8, flavor.vcpus) + self.assertEqual(0, flavor.ephemeral) + self.assertIsNone(flavor.swap) + self.assertEqual(1.0, flavor.rxtx_factor) + self.assertTrue(flavor.is_public) + + class HeatUtilsKeypairTests(OSComponentTestCase): """ Test Heat volume functionality diff --git a/snaps/openstack/utils/tests/settings_utils_tests.py b/snaps/openstack/utils/tests/settings_utils_tests.py index cb14039..69bdf7c 100644 --- a/snaps/openstack/utils/tests/settings_utils_tests.py +++ b/snaps/openstack/utils/tests/settings_utils_tests.py @@ -18,6 +18,7 @@ import unittest import os import uuid +from snaps.domain.flavor import Flavor from snaps.domain.volume import ( Volume, VolumeType, VolumeTypeEncryption, QoSSpec) from snaps.openstack import ( @@ -346,7 +347,7 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): derived_image_settings.name) -class SettingsUtilsVolumeTests(unittest.TestCase): +class SettingsUtilsUnitTests(unittest.TestCase): """ Exercises the settings_utils.py functions around volumes """ @@ -386,3 +387,18 @@ class SettingsUtilsVolumeTests(unittest.TestCase): self.assertEqual(encryption.key_size, encrypt_settings.key_size) self.assertEqual(qos_spec.name, settings.qos_spec_name) + + def test_flavor_settings_from_flavor(self): + flavor = Flavor( + name='flavor-name', flavor_id='flavor-id', ram=99, disk=101, + vcpus=9, ephemeral=3, swap=5, rxtx_factor=7, is_public=False) + settings = settings_utils.create_flavor_settings(flavor) + self.assertEqual(flavor.name, settings.name) + self.assertEqual(flavor.id, settings.flavor_id) + self.assertEqual(flavor.ram, settings.ram) + self.assertEqual(flavor.disk, settings.disk) + self.assertEqual(flavor.vcpus, settings.vcpus) + self.assertEqual(flavor.ephemeral, settings.ephemeral) + self.assertEqual(flavor.swap, settings.swap) + self.assertEqual(flavor.rxtx_factor, settings.rxtx_factor) + self.assertEqual(flavor.is_public, settings.is_public) diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py index 1795a52..6d2ba5f 100644 --- a/snaps/test_suite_builder.py +++ b/snaps/test_suite_builder.py @@ -68,8 +68,8 @@ from snaps.openstack.tests.create_security_group_tests import ( SecurityGroupSettingsUnitTests) from snaps.openstack.tests.create_stack_tests import ( StackSettingsUnitTests, CreateStackSuccessTests, CreateStackNegativeTests, - CreateStackFloatingIpTests, CreateStackKeypairTests, - CreateStackVolumeTests) + CreateStackFlavorTests, CreateStackFloatingIpTests, + CreateStackKeypairTests, CreateStackVolumeTests) from snaps.openstack.tests.create_user_tests import ( UserSettingsUnitTests, CreateUserSuccessTests) from snaps.openstack.tests.create_volume_tests import ( @@ -89,8 +89,8 @@ from snaps.openstack.utils.tests.glance_utils_tests import ( GlanceSmokeTests, GlanceUtilsTests) from snaps.openstack.utils.tests.heat_utils_tests import ( HeatSmokeTests, HeatUtilsCreateSimpleStackTests, - HeatUtilsCreateComplexStackTests, HeatUtilsVolumeTests, - HeatUtilsKeypairTests) + HeatUtilsCreateComplexStackTests, HeatUtilsFlavorTests, + HeatUtilsKeypairTests, HeatUtilsVolumeTests) from snaps.openstack.utils.tests.keystone_utils_tests import ( KeystoneSmokeTests, KeystoneUtilsTests) from snaps.openstack.utils.tests.neutron_utils_tests import ( @@ -101,7 +101,7 @@ from snaps.openstack.utils.tests.nova_utils_tests import ( NovaSmokeTests, NovaUtilsKeypairTests, NovaUtilsFlavorTests, NovaUtilsInstanceTests, NovaUtilsInstanceVolumeTests) from snaps.openstack.utils.tests.settings_utils_tests import ( - SettingsUtilsVolumeTests) + SettingsUtilsUnitTests) from snaps.provisioning.tests.ansible_utils_tests import ( AnsibleProvisioningTests) from snaps.tests.file_utils_tests import FileUtilsTests @@ -203,7 +203,7 @@ def add_unit_tests(suite): suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VolumeSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( - SettingsUtilsVolumeTests)) + SettingsUtilsUnitTests)) def add_openstack_client_tests(suite, os_creds, ext_net_name, @@ -331,6 +331,10 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True, HeatUtilsVolumeTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level, image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( + HeatUtilsFlavorTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) suite.addTest(OSComponentTestCase.parameterize( HeatUtilsKeypairTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level, @@ -521,6 +525,11 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackFlavorTests, os_creds=os_creds, ext_net_name=ext_net_name, + use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) suite.addTest(OSIntegrationTestCase.parameterize( CreateStackKeypairTests, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, -- 2.16.6