Fixed vm instance instantiation from Heat when using nested resources 43/50443/3
authorspisarski <s.pisarski@cablelabs.com>
Thu, 11 Jan 2018 17:24:32 +0000 (10:24 -0700)
committerspisarski <s.pisarski@cablelabs.com>
Thu, 11 Jan 2018 19:00:23 +0000 (12:00 -0700)
* Simplified the configuration of file resources
* Added integration test

JIRA: SNAPS-255

Change-Id: I1065d9352865d7a9f946a5d9947e32e7340f20bc
Signed-off-by: spisarski <s.pisarski@cablelabs.com>
docs/how-to-use/IntegrationTests.rst
snaps/config/stack.py
snaps/config/tests/stack_tests.py
snaps/openstack/create_stack.py
snaps/openstack/tests/create_stack_tests.py
snaps/openstack/tests/heat/agent-group.yaml [new file with mode: 0644]
snaps/openstack/tests/heat/agent.yaml [new file with mode: 0644]
snaps/openstack/utils/heat_utils.py
snaps/openstack/utils/tests/heat_utils_tests.py
snaps/test_suite_builder.py

index b7aa864..eb627ad 100644 (file)
@@ -419,6 +419,18 @@ create_stack_tests.py - CreateStackFloatingIpTests
 |                                       |               | OpenStackVmInstance                                       |
 +---------------------------------------+---------------+-----------------------------------------------------------+
 
+create_stack_tests.py - CreateStackNestedResourceTests
+------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name                             |   Heat API    | Description                                               |
++=======================================+===============+===========================================================+
+| test_nested                           | 1             | Ensures that an OpenStackHeatStack with an external       |
+|                                       |               | resource file with VMs with floating IPs can be accessed  |
+|                                       |               | in the class OpenStackVmInstance and return the associated|
+|                                       |               | initialized OpenStackVmInstance objects                   |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
 create_stack_tests.py - CreateStackRouterTests
 ----------------------------------------------
 
index fd427af..4d5db29 100644 (file)
@@ -35,19 +35,17 @@ class StackConfig(object):
                          template_path attribute is None)
         :param template_path: the location of the heat template file (required
                               if template attribute is None)
+        :param resource_files: List of file paths to the resources referred to
+                               by the template
         :param env_values: dict() of strings for substitution of template
                            default values (optional)
-        :param files: Supplies the contents of files referenced in the template
-                      or the environment. This object must be a dict() of
-                      strings where the key is the file URI or filename and the
-                      value contains the file's contents (optional)
         """
 
         self.name = kwargs.get('name')
         self.template = kwargs.get('template')
         self.template_path = kwargs.get('template_path')
+        self.resource_files = kwargs.get('resource_files')
         self.env_values = kwargs.get('env_values')
-        self.files = kwargs.get('files')
 
         if 'stack_create_timeout' in kwargs:
             self.stack_create_timeout = kwargs['stack_create_timeout']
@@ -60,6 +58,10 @@ class StackConfig(object):
         if not self.template and not self.template_path:
             raise StackConfigError('A Heat template is required')
 
+        if self.resource_files and not isinstance(self.resource_files, list):
+            raise StackConfigError(
+                'resource_files must be a list when not None')
+
     def __eq__(self, other):
         return (self.name == other.name and
                 self.template == other.template and
index cf6e7d8..773e9c2 100644 (file)
@@ -39,11 +39,16 @@ class StackConfigUnitTests(unittest.TestCase):
         with self.assertRaises(StackConfigError):
             StackConfig(**{'name': 'foo'})
 
+    def test_resource_not_list(self):
+        with self.assertRaises(StackConfigError):
+            StackConfig(**{'name': 'foo', 'resource_files': 'bar'})
+
     def test_config_minimum_template(self):
         settings = StackConfig(**{'name': 'stack', 'template': 'foo'})
         self.assertEqual('stack', settings.name)
         self.assertEqual('foo', settings.template)
         self.assertIsNone(settings.template_path)
+        self.assertIsNone(settings.resource_files)
         self.assertIsNone(settings.env_values)
         self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
                          settings.stack_create_timeout)
@@ -53,6 +58,7 @@ class StackConfigUnitTests(unittest.TestCase):
         self.assertEqual('stack', settings.name)
         self.assertIsNone(settings.template)
         self.assertEqual('foo', settings.template_path)
+        self.assertIsNone(settings.resource_files)
         self.assertIsNone(settings.env_values)
         self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
                          settings.stack_create_timeout)
@@ -62,6 +68,7 @@ class StackConfigUnitTests(unittest.TestCase):
         self.assertEqual('stack', settings.name)
         self.assertEqual('foo', settings.template)
         self.assertIsNone(settings.template_path)
+        self.assertIsNone(settings.resource_files)
         self.assertIsNone(settings.env_values)
         self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
                          settings.stack_create_timeout)
@@ -71,6 +78,18 @@ class StackConfigUnitTests(unittest.TestCase):
         self.assertEqual('stack', settings.name)
         self.assertEqual('foo', settings.template_path)
         self.assertIsNone(settings.template)
+        self.assertIsNone(settings.resource_files)
+        self.assertIsNone(settings.env_values)
+        self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+                         settings.stack_create_timeout)
+
+    def test_resource(self):
+        settings = StackConfig(
+            name='stack', template_path='foo', resource_files=['foo', 'bar'])
+        self.assertEqual('stack', settings.name)
+        self.assertEqual('foo', settings.template_path)
+        self.assertIsNone(settings.template)
+        self.assertEqual(['foo', 'bar'], settings.resource_files)
         self.assertIsNone(settings.env_values)
         self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
                          settings.stack_create_timeout)
index 74fde9d..d4a6b10 100644 (file)
@@ -467,7 +467,8 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
             return False
 
         if fail_status and status == fail_status:
-            resources = heat_utils.get_resources(self.__heat_cli, self.__stack)
+            resources = heat_utils.get_resources(
+                self.__heat_cli, self.__stack.id)
             logger.error('Stack %s failed', self.__stack.name)
             for resource in resources:
                 if (resource.status !=
index 2db89e5..6041735 100644 (file)
@@ -506,6 +506,112 @@ class CreateStackFloatingIpTests(OSIntegrationTestCase):
                 self.assertEqual(0, len(vm_settings.floating_ip_settings))
 
 
+class CreateStackNestedResourceTests(OSIntegrationTestCase):
+    """
+    Tests to ensure that nested heat templates work
+    """
+
+    def setUp(self):
+
+        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.image_creator = OpenStackImage(
+            self.heat_creds, openstack_tests.cirros_image_settings(
+                name=self.guid + '-image',
+                image_metadata=self.image_metadata))
+        self.image_creator.create()
+
+        self.flavor_creator = OpenStackFlavor(
+            self.admin_os_creds,
+            FlavorConfig(
+                name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=1))
+        self.flavor_creator.create()
+
+        env_values = {
+            'public_network': self.ext_net_name,
+            'agent_image': self.image_creator.image_settings.name,
+            'agent_flavor': self.flavor_creator.flavor_settings.name,
+        }
+
+        heat_tmplt_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'agent-group.yaml')
+        heat_resource_path = pkg_resources.resource_filename(
+            'snaps.openstack.tests.heat', 'agent.yaml')
+
+        stack_settings = StackConfig(
+            name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+            template_path=heat_tmplt_path,
+            resource_files=[heat_resource_path],
+            env_values=env_values)
+
+        self.stack_creator = OpenStackHeatStack(
+            self.heat_creds, stack_settings,
+            [self.image_creator.image_settings])
+
+        self.vm_inst_creators = list()
+
+    def tearDown(self):
+        """
+        Cleans the stack and downloaded stack file
+        """
+        if self.stack_creator:
+            try:
+                self.stack_creator.clean()
+            except:
+                pass
+
+        if self.image_creator:
+            try:
+                self.image_creator.clean()
+            except:
+                pass
+
+        if self.flavor_creator:
+            try:
+                self.flavor_creator.clean()
+            except:
+                pass
+
+        for vm_inst_creator in self.vm_inst_creators:
+            try:
+                keypair_settings = vm_inst_creator.keypair_settings
+                if keypair_settings and keypair_settings.private_filepath:
+                    expanded_path = os.path.expanduser(
+                        keypair_settings.private_filepath)
+                    os.chmod(expanded_path, 0o755)
+                    os.remove(expanded_path)
+            except:
+                pass
+
+        super(self.__class__, self).__clean__()
+
+    def test_nested(self):
+        """
+        Tests the creation of an OpenStack stack from Heat template file and
+        the retrieval of two VM instance creators and attempt to connect via
+        SSH to the first one with a floating IP.
+        """
+        created_stack = self.stack_creator.create()
+        self.assertIsNotNone(created_stack)
+
+        self.vm_inst_creators = self.stack_creator.get_vm_inst_creators(
+            heat_keypair_option='private_key')
+        self.assertIsNotNone(self.vm_inst_creators)
+        self.assertEqual(1, len(self.vm_inst_creators))
+
+        for vm_inst_creator in self.vm_inst_creators:
+            self.assertTrue(
+                create_instance_tests.validate_ssh_client(vm_inst_creator))
+
+
 class CreateStackRouterTests(OSIntegrationTestCase):
     """
     Tests for the CreateStack class defined in create_stack.py where the
@@ -1056,7 +1162,7 @@ class CreateStackFailureTests(OSIntegrationTestCase):
                 self.stack_creator.create()
             except StackError:
                 resources = heat_utils.get_resources(
-                    self.heat_cli, self.stack_creator.get_stack())
+                    self.heat_cli, self.stack_creator.get_stack().id)
 
                 found = False
                 for resource in resources:
diff --git a/snaps/openstack/tests/heat/agent-group.yaml b/snaps/openstack/tests/heat/agent-group.yaml
new file mode 100644 (file)
index 0000000..540ea93
--- /dev/null
@@ -0,0 +1,115 @@
+##############################################################################
+# 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: 2013-05-23
+
+parameters:
+  public_network:
+    type: string
+    constraints:
+        - custom_constraint: neutron.network
+  agent_flavor:
+    type: string
+  agent_image:
+    type: string
+  volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 1
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+  agent_count:
+    type: number
+    default: 1
+    constraints:
+      - range: { min: 1, max: 512 }
+        description: must be between 1 and 512 agents.
+  availability_zone:
+    type: string
+    default: nova
+
+resources:
+  slaves:
+    type: OS::Heat::ResourceGroup
+    depends_on: [subnet, network_router_interface,
+      open_security_group, key_pair]
+    properties:
+      count: {get_param: agent_count}
+      resource_def: {
+        type: "agent.yaml",
+        properties: {
+          public_network: {get_param: public_network},
+          agent_network: {get_resource: network},
+          flavor: {get_param: agent_flavor},
+          image: {get_param: agent_image},
+          availability_zone: {get_param: availability_zone},
+          open_security_group: {get_resource: open_security_group},
+          key_name: {get_resource: key_pair},
+          volume_size: {get_param: volume_size}
+        }
+      }
+
+  network:
+        type: OS::Neutron::Net
+        properties:
+          name: network
+
+  subnet:
+        type: OS::Neutron::Subnet
+        properties:
+          network_id: { get_resource: network }
+          cidr: 172.16.0.0/16
+          gateway_ip: 172.16.0.1
+
+  network_router:
+        type: OS::Neutron::Router
+        properties:
+          external_gateway_info:
+                network: { get_param: public_network }
+
+  network_router_interface:
+        type: OS::Neutron::RouterInterface
+        properties:
+          router_id: { get_resource: network_router }
+          subnet_id: { get_resource: subnet }
+
+  key_pair:
+    type: OS::Nova::KeyPair
+    properties:
+      save_private_key: true
+      name: agent_keypair
+
+  open_security_group:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      description: An open security group to allow all access to the slaves
+      rules:
+        - remote_ip_prefix: 0.0.0.0/0
+          protocol: tcp
+          port_range_min: 22
+          port_range_max: 22
+        - remote_ip_prefix: 0.0.0.0/0
+          protocol: icmp
+
+outputs:
+  slave_ips: {
+      description: "Slave addresses",
+      value: { get_attr: [ slaves, agent_ip] }
+  }
+  private_key:
+    description: "SSH Private Key"
+    value: { get_attr: [ key_pair, private_key ]}
diff --git a/snaps/openstack/tests/heat/agent.yaml b/snaps/openstack/tests/heat/agent.yaml
new file mode 100644 (file)
index 0000000..014b14f
--- /dev/null
@@ -0,0 +1,110 @@
+##############################################################################
+# 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: 2013-05-23
+
+parameters:
+  flavor:
+    type: string
+    default: test
+  image:
+    type: string
+    default: 'Ubuntu 16.04'
+  key_name:
+    type: string
+    default: test_key
+  username:
+    type: string
+    default: test_user
+  open_security_group:
+    type: string
+  volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 1
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+  agent_network:
+    type: string
+    constraints:
+        - custom_constraint: neutron.network
+  public_network:
+    type: string
+    constraints:
+        - custom_constraint: neutron.network
+  availability_zone:
+    type: string
+    default: nova
+
+resources:
+  agent:
+    type: "OS::Nova::Server"
+    properties:
+      name: agent
+      image: { get_param: image }
+      flavor: { get_param: flavor }
+      key_name: { get_param: key_name }
+      networks:
+        - port: { get_resource: agent_port }
+      user_data: { get_resource: agent_config }
+      user_data_format: RAW
+      availability_zone: { get_param: availability_zone}
+
+  agent_config:
+    type: "OS::Heat::CloudConfig"
+    properties:
+      cloud_config:
+        users:
+        - name: { get_param: username }
+          groups: users
+          shell: /bin/bash
+          sudo: "ALL=(ALL) NOPASSWD:ALL"
+          ssh_authorized_keys:
+          - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEbnDiqZ8RjQJJzJPf074J41XlYED+zYBzaUZ5UkkUquXzymyUmoWaFBXJP+XPu4Ns44U/S8614+JxGk96tjUdJlIjL0Ag8HP6KLtTNCabucKcEASpgJIVWqJvE3E9upZLIEiTGsF8I8S67T2qq1J1uvtxyeZmyjm7NMamjyFXE53dhR2EHqSutyKK1CK74NkRY9wr3qWUIt35kLdKSVSfrr4gOOicDALbIRu77skHIvrjt+wK1VWphBdMg6ytuq5mIE6pjWAU3Gwl4aTxOU0z43ARzCLq8HVf8s/dKjYMj8plNqaIfceMbaEUqpNHv/xbvtGNG7N0aB/a4pkUQL07
+        - default
+        package_update: false
+        package_upgrade: false
+        manage_etc_hosts: localhost
+
+  agent_port:
+    type: "OS::Neutron::Port"
+    properties:
+      network_id: { get_param: agent_network }
+      security_groups:
+        - { get_param: open_security_group }
+
+  floating_ip:
+    type: OS::Neutron::FloatingIP
+    properties:
+      floating_network_id: { get_param: public_network }
+      port_id: { get_resource: agent_port }
+
+  agent_volume:
+    type: OS::Cinder::Volume
+    properties:
+      size: { get_param: volume_size }
+
+  agent_volume_att:
+    type: OS::Cinder::VolumeAttachment
+    properties:
+      instance_uuid: { get_resource: agent }
+      volume_id: { get_resource: agent_volume}
+
+outputs:
+  agent_ip:
+    description: The floating IP address of the agent on the public network
+    value: { get_attr: [ floating_ip, floating_ip_address ] }
index 8e49c53..e440717 100644 (file)
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import logging
+import os
 
 import yaml
 from heatclient.client import Client
@@ -117,8 +118,15 @@ def create_stack(heat_cli, stack_settings):
     if stack_settings.env_values:
         args['parameters'] = stack_settings.env_values
 
-    if stack_settings.files:
-        args['files'] = stack_settings.files
+    if stack_settings.resource_files:
+        resources = dict()
+        for res_file in stack_settings.resource_files:
+            heat_resource_contents = file_utils.read_file(res_file)
+            base_filename = os.path.basename(res_file)
+
+            if heat_resource_contents and base_filename:
+                resources[base_filename] = heat_resource_contents
+        args['files'] = resources
 
     stack = heat_cli.stacks.create(**args)
 
@@ -134,25 +142,25 @@ def delete_stack(heat_cli, stack):
     heat_cli.stacks.delete(stack.id)
 
 
-def __get_os_resources(heat_cli, stack):
+def __get_os_resources(heat_cli, res_id):
     """
     Returns all of the OpenStack resource objects for a given stack
     :param heat_cli: the OpenStack heat client
-    :param stack: the SNAPS-OO Stack domain object
+    :param res_id: the resource ID
     :return: a list
     """
-    return heat_cli.resources.list(stack.id)
+    return heat_cli.resources.list(res_id)
 
 
-def get_resources(heat_cli, stack, res_type=None):
+def get_resources(heat_cli, res_id, res_type=None):
     """
     Returns all of the OpenStack resource objects for a given stack
     :param heat_cli: the OpenStack heat client
-    :param stack: the SNAPS-OO Stack domain object
+    :param res_id: the SNAPS-OO Stack domain object
     :param res_type: the type name to filter
     :return: a list of Resource domain objects
     """
-    os_resources = __get_os_resources(heat_cli, stack)
+    os_resources = __get_os_resources(heat_cli, res_id)
 
     if os_resources:
         out = list()
@@ -201,7 +209,7 @@ def get_stack_networks(heat_cli, neutron, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Neutron::Net')
+    resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Net')
     for resource in resources:
         network = neutron_utils.get_network_by_id(neutron, resource.id)
         if network:
@@ -220,7 +228,7 @@ def get_stack_routers(heat_cli, neutron, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Neutron::Router')
+    resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Router')
     for resource in resources:
         router = neutron_utils.get_router_by_id(neutron, resource.id)
         if router:
@@ -239,7 +247,7 @@ def get_stack_security_groups(heat_cli, neutron, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Neutron::SecurityGroup')
+    resources = get_resources(heat_cli, stack.id, 'OS::Neutron::SecurityGroup')
     for resource in resources:
         security_group = neutron_utils.get_security_group_by_id(
             neutron, resource.id)
@@ -260,8 +268,8 @@ def get_stack_servers(heat_cli, nova, neutron, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
-    for resource in resources:
+    srvr_res = get_resources(heat_cli, stack.id, 'OS::Nova::Server')
+    for resource in srvr_res:
         try:
             server = nova_utils.get_server_object_by_id(
                 nova, neutron, resource.id)
@@ -270,6 +278,18 @@ def get_stack_servers(heat_cli, nova, neutron, stack):
         except NotFound:
             logger.warn('VmInst cannot be located with ID %s', resource.id)
 
+    res_grps = get_resources(heat_cli, stack.id, 'OS::Heat::ResourceGroup')
+    for res_grp in res_grps:
+        res_ress = get_resources(heat_cli, res_grp.id)
+        for res_res in res_ress:
+            res_res_srvrs = get_resources(
+                heat_cli, res_res.id, 'OS::Nova::Server')
+            for res_srvr in res_res_srvrs:
+                server = nova_utils.get_server_object_by_id(
+                    nova, neutron, res_srvr.id)
+                if server:
+                    out.append(server)
+
     return out
 
 
@@ -283,7 +303,7 @@ def get_stack_keypairs(heat_cli, nova, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
+    resources = get_resources(heat_cli, stack.id, 'OS::Nova::KeyPair')
     for resource in resources:
         try:
             keypair = nova_utils.get_keypair_by_id(nova, resource.id)
@@ -305,7 +325,7 @@ def get_stack_volumes(heat_cli, cinder, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
+    resources = get_resources(heat_cli, stack.id, 'OS::Cinder::Volume')
     for resource in resources:
         try:
             server = cinder_utils.get_volume_by_id(cinder, resource.id)
@@ -327,7 +347,7 @@ def get_stack_volume_types(heat_cli, cinder, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
+    resources = get_resources(heat_cli, stack.id, 'OS::Cinder::VolumeType')
     for resource in resources:
         try:
             vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
@@ -350,7 +370,7 @@ def get_stack_flavors(heat_cli, nova, stack):
     """
 
     out = list()
-    resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
+    resources = get_resources(heat_cli, stack.id, 'OS::Nova::Flavor')
     for resource in resources:
         try:
             flavor = nova_utils.get_flavor_by_id(nova, resource.id)
index 7d43adf..67fbdec 100644 (file)
@@ -164,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))
 
@@ -359,7 +359,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
         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))
 
index 1990cd6..7b3ece7 100644 (file)
@@ -91,8 +91,8 @@ from snaps.openstack.tests.create_security_group_tests import (
 from snaps.openstack.tests.create_stack_tests import (
     StackSettingsUnitTests, CreateStackSuccessTests, CreateStackNegativeTests,
     CreateStackFlavorTests, CreateStackFloatingIpTests,
-    CreateStackKeypairTests, CreateStackVolumeTests,
-    CreateStackSecurityGroupTests)
+    CreateStackNestedResourceTests, CreateStackKeypairTests,
+    CreateStackVolumeTests, CreateStackSecurityGroupTests)
 from snaps.openstack.tests.create_user_tests import (
     UserSettingsUnitTests, CreateUserSuccessTests)
 from snaps.openstack.tests.create_volume_tests import (
@@ -637,6 +637,11 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
             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(
+            CreateStackNestedResourceTests, 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(
             AnsibleProvisioningTests, os_creds=os_creds,
             ext_net_name=ext_net_name, use_keystone=use_keystone,