Fixed vm instance instantiation from Heat when using nested resources
[snaps.git] / snaps / openstack / utils / tests / heat_utils_tests.py
index 6f75f89..67fbdec 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import logging
+import os
+
 import pkg_resources
 import uuid
 
 import time
 
-from snaps.openstack import create_stack
-from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
+import snaps.config.stack as stack_config
+from snaps.config.flavor import FlavorConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
 
 from snaps.openstack.create_image import OpenStackImage
 from snaps.openstack.create_instance import OpenStackVmInstance
-from snaps.openstack.create_stack import StackSettings
+from snaps.openstack.create_stack import StackConfig
 from snaps.openstack.tests import openstack_tests
 from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
 from snaps.openstack.utils import (
-    heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils)
+    heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils,
+    cinder_utils)
 
 __author__ = 'spisarski'
 
@@ -93,7 +97,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
         # Create Flavor
         self.flavor_creator = OpenStackFlavor(
             self.os_creds,
-            FlavorSettings(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
+            FlavorConfig(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
         self.flavor_creator.create()
 
         env_values = {'image_name': self.image_creator.image_settings.name,
@@ -103,10 +107,10 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
                       'inst_name': self.vm_inst_name}
         heat_tmplt_path = pkg_resources.resource_filename(
             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
-        self.stack_settings1 = StackSettings(
+        self.stack_settings1 = StackConfig(
             name=stack_name1, template_path=heat_tmplt_path,
             env_values=env_values)
-        self.stack_settings2 = StackSettings(
+        self.stack_settings2 = StackConfig(
             name=stack_name2, template_path=heat_tmplt_path,
             env_values=env_values)
         self.stack1 = None
@@ -115,7 +119,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
 
     def tearDown(self):
         """
-        Cleans the image and downloaded image file
+        Cleans the stack and image
         """
         if self.stack1:
             try:
@@ -160,7 +164,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
                                                    self.stack1.id)
         self.assertEqual(self.stack1, stack_query_3)
 
-        resources = heat_utils.get_resources(self.heat_client, self.stack1)
+        resources = heat_utils.get_resources(self.heat_client, self.stack1.id)
         self.assertIsNotNone(resources)
         self.assertEqual(4, len(resources))
 
@@ -168,22 +172,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
         self.assertIsNotNone(outputs)
         self.assertEqual(0, len(outputs))
 
-        # Wait until stack deployment has completed
-        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.stack1.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.stack1))
 
         neutron = neutron_utils.neutron_client(self.os_creds)
         networks = heat_utils.get_stack_networks(
@@ -198,7 +187,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
 
         nova = nova_utils.nova_client(self.os_creds)
         servers = heat_utils.get_stack_servers(
-            self.heat_client, nova, self.stack1)
+            self.heat_client, nova, neutron, self.stack1)
         self.assertIsNotNone(servers)
         self.assertEqual(1, len(servers))
         self.assertEqual(self.vm_inst_name, servers[0].name)
@@ -222,21 +211,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
                                                     self.stack1.id)
         self.assertEqual(self.stack1, stack1_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.stack1.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.stack1))
 
         self.stack2 = heat_utils.create_stack(self.heat_client,
                                               self.stack_settings2)
@@ -253,22 +228,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):
@@ -312,45 +272,35 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
                       'external_net_name': self.ext_net_name}
         heat_tmplt_path = pkg_resources.resource_filename(
             'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
-        stack_settings = StackSettings(
+        stack_settings = StackConfig(
             name=stack_name, template_path=heat_tmplt_path,
             env_values=env_values)
         self.heat_client = heat_utils.heat_client(self.os_creds)
         self.stack = heat_utils.create_stack(self.heat_client, stack_settings)
 
-        # Wait until stack deployment has completed
-        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.stack.id)
-            if status == create_stack.STATUS_CREATE_COMPLETE:
-                is_active = True
-                break
-            elif status == create_stack.STATUS_CREATE_FAILED:
-                is_active = False
-                break
+        self.assertTrue(stack_active(self.heat_client, self.stack))
 
-            time.sleep(3)
-        self.assertTrue(is_active)
+        self.keypair1_settings = None
+        self.keypair2_settings = None
 
     def tearDown(self):
         """
-        Cleans the image and downloaded image file
+        Cleans the stack and image
         """
         if self.stack:
             try:
                 heat_utils.delete_stack(self.heat_client, self.stack)
                 # Wait until stack deployment has completed
-                end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
+                end_time = (time.time() +
+                            stack_config.STACK_COMPLETE_TIMEOUT)
                 is_deleted = False
                 while time.time() < end_time:
                     status = heat_utils.get_stack_status(self.heat_client,
                                                          self.stack.id)
-                    if status == create_stack.STATUS_DELETE_COMPLETE:
+                    if status == stack_config.STATUS_DELETE_COMPLETE:
                         is_deleted = True
                         break
-                    elif status == create_stack.STATUS_DELETE_FAILED:
+                    elif status == stack_config.STATUS_DELETE_FAILED:
                         is_deleted = False
                         break
 
@@ -361,11 +311,11 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
                     neutron = neutron_utils.neutron_client(self.os_creds)
                     glance = glance_utils.glance_client(self.os_creds)
                     servers = heat_utils.get_stack_servers(
-                        self.heat_client, nova, self.stack)
+                        self.heat_client, nova, neutron, self.stack)
                     for server in servers:
-                        vm_settings = settings_utils.create_vm_inst_settings(
+                        vm_settings = settings_utils.create_vm_inst_config(
                             nova, neutron, server)
-                        img_settings = settings_utils.determine_image_settings(
+                        img_settings = settings_utils.determine_image_config(
                             glance, server,
                             [self.image_creator1.image_settings,
                              self.image_creator2.image_settings])
@@ -392,12 +342,24 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
             except:
                 pass
 
+        if self.keypair1_settings:
+            expanded_path = os.path.expanduser(
+                self.keypair1_settings.private_filepath)
+            os.chmod(expanded_path, 0o755)
+            os.remove(expanded_path)
+
+        if self.keypair2_settings:
+            expanded_path = os.path.expanduser(
+                self.keypair2_settings.private_filepath)
+            os.chmod(expanded_path, 0o755)
+            os.remove(expanded_path)
+
     def test_get_settings_from_stack(self):
         """
         Tests that a heat template with floating IPs and can have the proper
         settings derived from settings_utils.py.
         """
-        resources = heat_utils.get_resources(self.heat_client, self.stack)
+        resources = heat_utils.get_resources(self.heat_client, self.stack.id)
         self.assertIsNotNone(resources)
         self.assertEqual(12, len(resources))
 
@@ -412,7 +374,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
         self.assertEqual(1, len(networks))
         self.assertEqual(self.network_name, networks[0].name)
 
-        network_settings = settings_utils.create_network_settings(
+        network_settings = settings_utils.create_network_config(
             neutron, networks[0])
         self.assertIsNotNone(network_settings)
         self.assertEqual(self.network_name, network_settings.name)
@@ -421,11 +383,11 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
         glance = glance_utils.glance_client(self.os_creds)
 
         servers = heat_utils.get_stack_servers(
-            self.heat_client, nova, self.stack)
+            self.heat_client, nova, neutron, self.stack)
         self.assertIsNotNone(servers)
         self.assertEqual(2, len(servers))
 
-        image_settings = settings_utils.determine_image_settings(
+        image_settings = settings_utils.determine_image_config(
             glance, servers[0],
             [self.image_creator1.image_settings,
              self.image_creator2.image_settings])
@@ -438,7 +400,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
             self.assertEqual(
                 self.image_creator2.image_settings.name, image_settings.name)
 
-        image_settings = settings_utils.determine_image_settings(
+        image_settings = settings_utils.determine_image_config(
             glance, servers[1],
             [self.image_creator1.image_settings,
              self.image_creator2.image_settings])
@@ -449,14 +411,398 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
             self.assertEqual(
                 self.image_creator2.image_settings.name, image_settings.name)
 
-        keypair1_settings = settings_utils.determine_keypair_settings(
+        self.keypair1_settings = settings_utils.determine_keypair_config(
             self.heat_client, self.stack, servers[0],
             priv_key_key='private_key')
-        self.assertIsNotNone(keypair1_settings)
-        self.assertEqual(self.keypair_name, keypair1_settings.name)
+        self.assertIsNotNone(self.keypair1_settings)
+        self.assertEqual(self.keypair_name, self.keypair1_settings.name)
 
-        keypair2_settings = settings_utils.determine_keypair_settings(
+        self.keypair2_settings = settings_utils.determine_keypair_config(
             self.heat_client, self.stack, servers[1],
             priv_key_key='private_key')
-        self.assertIsNotNone(keypair2_settings)
-        self.assertEqual(self.keypair_name, keypair2_settings.name)
+        self.assertIsNotNone(self.keypair2_settings)
+        self.assertEqual(self.keypair_name, self.keypair2_settings.name)
+
+
+class HeatUtilsRouterTests(OSComponentTestCase):
+    """
+    Test Heat volume functionality
+    """
+
+    def setUp(self):
+        """
+        Instantiates OpenStack instances that cannot be spawned by Heat
+        """
+        guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+        stack_name = guid + '-stack'
+
+        self.net_name = guid + '-net'
+        self.subnet_name = guid + '-subnet'
+        self.router_name = guid + '-router'
+
+        env_values = {
+            'net_name': self.net_name,
+            'subnet_name': self.subnet_name,
+            'router_name': self.router_name,
+            'external_net_name': self.ext_net_name}
+
+        heat_tmplt_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'router_heat_template.yaml')
+        self.stack_settings = StackConfig(
+            name=stack_name, template_path=heat_tmplt_path,
+            env_values=env_values)
+        self.stack = None
+        self.heat_client = heat_utils.heat_client(self.os_creds)
+        self.neutron = neutron_utils.neutron_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_router_with_stack(self):
+        """
+        Tests the creation of an OpenStack router with Heat and the retrieval
+        of the Router Domain objects from heat_utils#get_stack_routers().
+        """
+        self.stack = heat_utils.create_stack(
+            self.heat_client, self.stack_settings)
+
+        # Wait until stack deployment has completed
+        end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
+        is_active = False
+        while time.time() < end_time:
+            status = heat_utils.get_stack_status(self.heat_client,
+                                                 self.stack.id)
+            if status == stack_config.STATUS_CREATE_COMPLETE:
+                is_active = True
+                break
+            elif status == stack_config.STATUS_CREATE_FAILED:
+                is_active = False
+                break
+
+            time.sleep(3)
+
+        self.assertTrue(is_active)
+
+        routers = heat_utils.get_stack_routers(
+            self.heat_client, self.neutron, self.stack)
+
+        self.assertEqual(1, len(routers))
+
+        router = routers[0]
+        self.assertEqual(self.router_name, router.name)
+
+        ext_net = neutron_utils.get_network(
+            self.neutron, network_name=self.ext_net_name)
+        self.assertEqual(ext_net.id, router.external_network_id)
+
+
+class HeatUtilsVolumeTests(OSComponentTestCase):
+    """
+    Test Heat volume functionality
+    """
+
+    def setUp(self):
+        """
+        Instantiates OpenStack instances that cannot be spawned by Heat
+        """
+        guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+        stack_name = guid + '-stack'
+        self.volume_name = guid + '-vol'
+        self.volume_type_name = guid + '-vol-type'
+
+        env_values = {
+            'volume_name': self.volume_name,
+            'volume_type_name': self.volume_type_name}
+
+        heat_tmplt_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
+        self.stack_settings = StackConfig(
+            name=stack_name, template_path=heat_tmplt_path,
+            env_values=env_values)
+        self.stack = None
+        self.heat_client = heat_utils.heat_client(self.os_creds)
+        self.cinder = cinder_utils.cinder_client(self.os_creds)
+
+    def tearDown(self):
+        """
+        Cleans the stack
+        """
+        if self.stack:
+            try:
+                heat_utils.delete_stack(self.heat_client, self.stack)
+            except:
+                pass
+
+    def test_create_vol_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))
+
+        volumes = heat_utils.get_stack_volumes(
+            self.heat_client, self.cinder, self.stack)
+
+        self.assertEqual(1, len(volumes))
+
+        volume = volumes[0]
+        self.assertEqual(self.volume_name, volume.name)
+        self.assertEqual(self.volume_type_name, volume.type)
+        self.assertEqual(1, volume.size)
+        self.assertEqual(False, volume.multi_attach)
+
+    def test_create_vol_types_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))
+
+        volume_types = heat_utils.get_stack_volume_types(
+            self.heat_client, self.cinder, self.stack)
+
+        self.assertEqual(1, len(volume_types))
+
+        volume_type = volume_types[0]
+
+        self.assertEqual(self.volume_type_name, volume_type.name)
+        self.assertTrue(volume_type.public)
+        self.assertIsNone(volume_type.qos_spec)
+
+        # TODO - Add encryption back and find out why it broke in Pike
+        # encryption = volume_type.encryption
+        # self.assertIsNotNone(encryption)
+        # self.assertIsNone(encryption.cipher)
+        # self.assertEqual('front-end', encryption.control_location)
+        # self.assertIsNone(encryption.key_size)
+        # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
+        #                  encryption.provider)
+        # 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 = StackConfig(
+            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 stack
+        """
+        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
+    """
+
+    def setUp(self):
+        """
+        Instantiates OpenStack instances that cannot be spawned by Heat
+        """
+        guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+        stack_name = guid + '-stack'
+        self.keypair_name = guid + '-kp'
+
+        env_values = {'keypair_name': self.keypair_name}
+
+        heat_tmplt_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
+        self.stack_settings = StackConfig(
+            name=stack_name, template_path=heat_tmplt_path,
+            env_values=env_values)
+        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 stack
+        """
+        if self.stack:
+            try:
+                heat_utils.delete_stack(self.heat_client, self.stack)
+            except:
+                pass
+
+    def test_create_keypair_with_stack(self):
+        """
+        Tests the creation of an OpenStack keypair with Heat.
+        """
+        self.stack = heat_utils.create_stack(
+            self.heat_client, self.stack_settings)
+        self.assertTrue(stack_active(self.heat_client, self.stack))
+
+        keypairs = heat_utils.get_stack_keypairs(
+            self.heat_client, self.nova, self.stack)
+
+        self.assertEqual(1, len(keypairs))
+        keypair = keypairs[0]
+
+        self.assertEqual(self.keypair_name, keypair.name)
+
+        outputs = heat_utils.get_outputs(self.heat_client, self.stack)
+
+        for output in outputs:
+            if output.key == 'private_key':
+                self.assertTrue(output.value.startswith(
+                    '-----BEGIN RSA PRIVATE KEY-----'))
+
+        keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
+        self.assertIsNotNone(keypair)
+
+        self.assertEqual(self.keypair_name, keypair.name)
+
+
+class HeatUtilsSecurityGroupTests(OSComponentTestCase):
+    """
+    Test Heat volume functionality
+    """
+
+    def setUp(self):
+        """
+        Instantiates OpenStack instances that cannot be spawned by Heat
+        """
+        guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+        stack_name = guid + '-stack'
+        self.sec_grp_name = guid + '-sec-grp'
+
+        env_values = {'security_group_name': self.sec_grp_name}
+
+        heat_tmplt_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
+        self.stack_settings = StackConfig(
+            name=stack_name, template_path=heat_tmplt_path,
+            env_values=env_values)
+        self.stack = None
+        self.heat_client = heat_utils.heat_client(self.os_creds)
+        self.neutron = neutron_utils.neutron_client(self.os_creds)
+
+    def tearDown(self):
+        """
+        Cleans the stack
+        """
+        if self.stack:
+            try:
+                heat_utils.delete_stack(self.heat_client, self.stack)
+            except:
+                pass
+
+    def test_create_security_group_with_stack(self):
+        """
+        Tests the creation of an OpenStack SecurityGroup with Heat.
+        """
+        self.stack = heat_utils.create_stack(
+            self.heat_client, self.stack_settings)
+        self.assertTrue(stack_active(self.heat_client, self.stack))
+
+        sec_grp = heat_utils.get_stack_security_groups(
+            self.heat_client, self.neutron, self.stack)[0]
+
+        self.assertEqual(self.sec_grp_name, sec_grp.name)
+        self.assertEqual('Test description', sec_grp.description)
+        self.assertEqual(2, len(sec_grp.rules))
+
+        has_ssh_rule = False
+        has_icmp_rule = False
+
+        for rule in sec_grp.rules:
+            if (rule.security_group_id == sec_grp.id
+                    and rule.direction == 'egress'
+                    and rule.ethertype == 'IPv4'
+                    and rule.port_range_min == 22
+                    and rule.port_range_max == 22
+                    and rule.protocol == 'tcp'
+                    and rule.remote_group_id is None
+                    and rule.remote_ip_prefix == '0.0.0.0/0'):
+                has_ssh_rule = True
+            if (rule.security_group_id == sec_grp.id
+                    and rule.direction == 'ingress'
+                    and rule.ethertype == 'IPv4'
+                    and rule.port_range_min is None
+                    and rule.port_range_max is None
+                    and rule.protocol == 'icmp'
+                    and rule.remote_group_id is None
+                    and rule.remote_ip_prefix == '0.0.0.0/0'):
+                has_icmp_rule = True
+
+        self.assertTrue(has_ssh_rule)
+        self.assertTrue(has_icmp_rule)
+
+
+def stack_active(heat_cli, stack):
+    """
+    Blocks until stack application has successfully completed or failed
+    :param heat_cli: the Heat client
+    :param stack: the Stack domain object
+    :return: T/F
+    """
+    # Wait until stack deployment has completed
+    end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
+    is_active = False
+    while time.time() < end_time:
+        status = heat_utils.get_stack_status(heat_cli, stack.id)
+        if status == stack_config.STATUS_CREATE_COMPLETE:
+            is_active = True
+            break
+        elif status == stack_config.STATUS_CREATE_FAILED:
+            is_active = False
+            break
+
+        time.sleep(3)
+
+    return is_active