Added method to OpenStackHeatStack to return OpenStackFlavor objects. 41/46441/2
authorspisarski <s.pisarski@cablelabs.com>
Mon, 30 Oct 2017 20:22:20 +0000 (14:22 -0600)
committerspisarski <s.pisarski@cablelabs.com>
Tue, 31 Oct 2017 13:57:06 +0000 (07:57 -0600)
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 <s.pisarski@cablelabs.com>
14 files changed:
docs/how-to-use/APITests.rst
docs/how-to-use/IntegrationTests.rst
docs/how-to-use/UnitTests.rst
snaps/domain/flavor.py
snaps/openstack/create_stack.py
snaps/openstack/tests/create_flavor_tests.py
snaps/openstack/tests/create_stack_tests.py
snaps/openstack/tests/heat/flavor_heat_template.yaml [new file with mode: 0644]
snaps/openstack/utils/heat_utils.py
snaps/openstack/utils/keystone_utils.py
snaps/openstack/utils/settings_utils.py
snaps/openstack/utils/tests/heat_utils_tests.py
snaps/openstack/utils/tests/settings_utils_tests.py
snaps/test_suite_builder.py

index 9110162..ed4779a 100644 (file)
@@ -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
 ------------------------------------------------------
 
index e00bc40..4772357 100644 (file)
@@ -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
 -----------------------------------------------
 
index 3cb26db..4a360d4 100644 (file)
@@ -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
index 035ca64..bf84cf4 100644 (file)
@@ -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')
index b565118..1820e2a 100644 (file)
@@ -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):
         """
index 4852d06..3eb07bd 100644 (file)
@@ -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
 
index 8f9339a..94085a0 100644 (file)
@@ -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 (file)
index 0000000..060c85c
--- /dev/null
@@ -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
index f09857a..ad354e0 100644 (file)
@@ -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
index 46f6fb8..387f6c7 100644 (file)
@@ -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:
index 68dbf71..2ab3c28 100644 (file)
@@ -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
index b021701..567cf7b 100644 (file)
@@ -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
index cb14039..69bdf7c 100644 (file)
@@ -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)
index 1795a52..6d2ba5f 100644 (file)
@@ -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,