From 97cee572f017696415b212024fc28c85b892b0cd Mon Sep 17 00:00:00 2001 From: spisarski Date: Thu, 11 Jan 2018 10:24:32 -0700 Subject: Fixed vm instance instantiation from Heat when using nested resources * Simplified the configuration of file resources * Added integration test JIRA: SNAPS-255 Change-Id: I1065d9352865d7a9f946a5d9947e32e7340f20bc Signed-off-by: spisarski --- docs/how-to-use/IntegrationTests.rst | 12 +++ snaps/config/stack.py | 12 +-- snaps/config/tests/stack_tests.py | 19 ++++ snaps/openstack/create_stack.py | 3 +- snaps/openstack/tests/create_stack_tests.py | 108 +++++++++++++++++++++- snaps/openstack/tests/heat/agent-group.yaml | 115 ++++++++++++++++++++++++ snaps/openstack/tests/heat/agent.yaml | 110 +++++++++++++++++++++++ snaps/openstack/utils/heat_utils.py | 54 +++++++---- snaps/openstack/utils/tests/heat_utils_tests.py | 4 +- snaps/test_suite_builder.py | 9 +- 10 files changed, 418 insertions(+), 28 deletions(-) create mode 100644 snaps/openstack/tests/heat/agent-group.yaml create mode 100644 snaps/openstack/tests/heat/agent.yaml diff --git a/docs/how-to-use/IntegrationTests.rst b/docs/how-to-use/IntegrationTests.rst index b7aa864..eb627ad 100644 --- a/docs/how-to-use/IntegrationTests.rst +++ b/docs/how-to-use/IntegrationTests.rst @@ -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 ---------------------------------------------- diff --git a/snaps/config/stack.py b/snaps/config/stack.py index fd427af..4d5db29 100644 --- a/snaps/config/stack.py +++ b/snaps/config/stack.py @@ -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 diff --git a/snaps/config/tests/stack_tests.py b/snaps/config/tests/stack_tests.py index cf6e7d8..773e9c2 100644 --- a/snaps/config/tests/stack_tests.py +++ b/snaps/config/tests/stack_tests.py @@ -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) diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py index 74fde9d..d4a6b10 100644 --- a/snaps/openstack/create_stack.py +++ b/snaps/openstack/create_stack.py @@ -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 != diff --git a/snaps/openstack/tests/create_stack_tests.py b/snaps/openstack/tests/create_stack_tests.py index 2db89e5..6041735 100644 --- a/snaps/openstack/tests/create_stack_tests.py +++ b/snaps/openstack/tests/create_stack_tests.py @@ -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 index 0000000..540ea93 --- /dev/null +++ b/snaps/openstack/tests/heat/agent-group.yaml @@ -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 index 0000000..014b14f --- /dev/null +++ b/snaps/openstack/tests/heat/agent.yaml @@ -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 ] } diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py index 8e49c53..e440717 100644 --- a/snaps/openstack/utils/heat_utils.py +++ b/snaps/openstack/utils/heat_utils.py @@ -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) diff --git a/snaps/openstack/utils/tests/heat_utils_tests.py b/snaps/openstack/utils/tests/heat_utils_tests.py index 7d43adf..67fbdec 100644 --- a/snaps/openstack/utils/tests/heat_utils_tests.py +++ b/snaps/openstack/utils/tests/heat_utils_tests.py @@ -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)) diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py index 1990cd6..7b3ece7 100644 --- a/snaps/test_suite_builder.py +++ b/snaps/test_suite_builder.py @@ -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, -- cgit 1.2.3-korg