diff options
-rw-r--r-- | docs/how-to-use/IntegrationTests.rst | 11 | ||||
-rw-r--r-- | snaps/config/stack.py | 3 | ||||
-rw-r--r-- | snaps/openstack/create_stack.py | 47 | ||||
-rw-r--r-- | snaps/openstack/tests/create_stack_tests.py | 170 | ||||
-rw-r--r-- | snaps/openstack/tests/heat/agent-group.yaml | 2 | ||||
-rw-r--r-- | snaps/openstack/utils/heat_utils.py | 18 | ||||
-rw-r--r-- | snaps/openstack/utils/nova_utils.py | 11 | ||||
-rw-r--r-- | snaps/test_suite_builder.py | 8 |
8 files changed, 243 insertions, 27 deletions
diff --git a/docs/how-to-use/IntegrationTests.rst b/docs/how-to-use/IntegrationTests.rst index 98f1d1d..bc4b706 100644 --- a/docs/how-to-use/IntegrationTests.rst +++ b/docs/how-to-use/IntegrationTests.rst @@ -536,6 +536,17 @@ create_stack_tests.py - CreateStackNestedResourceTests | | | initialized OpenStackVmInstance objects | +---------------------------------------+---------------+-----------------------------------------------------------+ +create_stack_tests.py - CreateStackUpdateTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_update | 1 | Ensures that an OpenStackHeatStack can have the number of | +| | | VMs updated and they are spawned and access can be | +| | | obtained with SSH over floating IPs | ++---------------------------------------+---------------+-----------------------------------------------------------+ + create_stack_tests.py - CreateStackRouterTests ---------------------------------------------- diff --git a/snaps/config/stack.py b/snaps/config/stack.py index 4d5db29..3fc820e 100644 --- a/snaps/config/stack.py +++ b/snaps/config/stack.py @@ -15,9 +15,12 @@ STACK_DELETE_TIMEOUT = 1200 STACK_COMPLETE_TIMEOUT = 1200 +STACK_UPDATE_TIMEOUT = 1200 POLL_INTERVAL = 3 STATUS_CREATE_FAILED = 'CREATE_FAILED' STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE' +STATUS_UPDATE_FAILED = 'UPDATE_FAILED' +STATUS_UPDATE_COMPLETE = 'UPDATE_COMPLETE' STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE' STATUS_DELETE_FAILED = 'DELETE_FAILED' diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py index 7ecf449..71e5d0a 100644 --- a/snaps/openstack/create_stack.py +++ b/snaps/openstack/create_stack.py @@ -102,7 +102,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object): logger.info('Found stack with name - ' + self.stack_settings.name) return self.__stack - def create(self): + def create(self, block=False): """ Creates the heat stack in OpenStack if it does not already exist and returns the domain Stack object @@ -118,7 +118,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object): self.stack_settings) logger.info( 'Created stack with name - %s', self.stack_settings.name) - if self.__stack and self.stack_complete(block=True): + if self.__stack and self.stack_complete(block=block): logger.info('Stack is now active with name - %s', self.stack_settings.name) return self.__stack @@ -128,6 +128,28 @@ class OpenStackHeatStack(OpenStackCloudObject, object): logger.error('ERROR: STACK CREATION FAILED: %s', status) raise StackCreationError('Failure while creating stack') + def update(self, env_vals, block=False): + """ + Updates the heat stack in OpenStack + :param: env_vals - the values to update + :return: The Stack domain object or None + """ + if self.__stack: + logger.info('Updating stack - %s', self.__stack.name) + heat_utils.update_stack(self.__heat_cli, self.__stack, env_vals) + if self.stack_updated(block=block): + logger.info('Stack %s is now updated with params: %s', + self.__stack.name, env_vals) + self.stack_settings.env_values = env_vals + self.__stack = heat_utils.get_stack_by_id( + self.__heat_cli, self.__stack.id) + return self.__stack + else: + status = heat_utils.get_stack_status_reason(self.__heat_cli, + self.__stack.id) + logger.error('ERROR: STACK UPDATE FAILED: %s', status) + raise StackUpdateError('Failure while updating stack') + def clean(self): """ Cleanse environment of all artifacts @@ -220,6 +242,22 @@ class OpenStackHeatStack(OpenStackCloudObject, object): snaps.config.stack.STATUS_CREATE_COMPLETE, block, timeout, poll_interval, snaps.config.stack.STATUS_CREATE_FAILED) + def stack_updated(self, block=False, + timeout=snaps.config.stack.STACK_UPDATE_TIMEOUT, + poll_interval=snaps.config.stack.POLL_INTERVAL): + """ + Returns true when the stack status returns the value of + expected_status_code + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param timeout: The timeout value + :param poll_interval: The polling interval in seconds + :return: T/F + """ + return self._stack_status_check( + snaps.config.stack.STATUS_UPDATE_COMPLETE, block, timeout, + poll_interval, snaps.config.stack.STATUS_UPDATE_FAILED) + def stack_deleted(self, block=False, timeout=snaps.config.stack.STACK_DELETE_TIMEOUT, poll_interval=snaps.config.stack.POLL_INTERVAL): @@ -546,6 +584,11 @@ class StackCreationError(Exception): Exception to be thrown when an stack cannot be created """ +class StackUpdateError(Exception): + """ + Exception to be thrown when an stack update failed + """ + class StackError(Exception): """ diff --git a/snaps/openstack/tests/create_stack_tests.py b/snaps/openstack/tests/create_stack_tests.py index a320aa0..be4d6ea 100644 --- a/snaps/openstack/tests/create_stack_tests.py +++ b/snaps/openstack/tests/create_stack_tests.py @@ -22,7 +22,8 @@ import snaps from snaps import file_utils from snaps.config.flavor import FlavorConfig from snaps.config.image import ImageConfig -from snaps.config.stack import StackConfigError, StackConfig +from snaps.config.stack import (StackConfigError, StackConfig, + STATUS_UPDATE_COMPLETE) from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_image import OpenStackImage @@ -208,7 +209,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, @@ -239,7 +240,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) with self.assertRaises(StackCreationError): - self.stack_creator.create() + self.stack_creator.create(block=True) def test_create_stack_template_dict(self): """ @@ -256,7 +257,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, @@ -280,7 +281,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, @@ -324,7 +325,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack1 = self.stack_creator.create() + created_stack1 = self.stack_creator.create(block=True) retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, created_stack1.id) @@ -335,7 +336,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Should be retrieving the instance data stack_creator2 = OpenStackHeatStack(self.os_creds, stack_settings) - stack2 = stack_creator2.create() + stack2 = stack_creator2.create(block=True) self.assertEqual(created_stack1.id, stack2.id) def test_retrieve_network_creators(self): @@ -349,7 +350,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) net_creators = self.stack_creator.get_network_creators() @@ -390,7 +391,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) vm_inst_creators = self.stack_creator.get_vm_inst_creators() @@ -502,7 +503,7 @@ class CreateStackFloatingIpTests(OSIntegrationTestCase): self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings, [self.image_creator.image_settings]) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) self.vm_inst_creators = self.stack_creator.get_vm_inst_creators( @@ -531,7 +532,7 @@ class CreateStackFloatingIpTests(OSIntegrationTestCase): self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings, [self.image_creator.image_settings]) - created_stack = self.stack_creator.create() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) derived_stack = create_stack.generate_creator( @@ -647,7 +648,7 @@ class CreateStackNestedResourceTests(OSIntegrationTestCase): 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() + created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(created_stack) self.vm_inst_creators = self.stack_creator.get_vm_inst_creators( @@ -660,6 +661,135 @@ class CreateStackNestedResourceTests(OSIntegrationTestCase): create_instance_tests.validate_ssh_client(vm_inst_creator)) +class CreateStackUpdateTests(OSIntegrationTestCase): + """ + Tests to ensure that stack update commands work + """ + + def setUp(self): + self.user_roles = ['heat_stack_owner'] + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session) + self.stack_creator = None + + self.image_creator = OpenStackImage( + self.os_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 = { + 'network_name': self.guid + '-network', + 'public_network': self.ext_net_name, + 'agent_image': self.image_creator.image_settings.name, + 'agent_flavor': self.flavor_creator.flavor_settings.name, + 'key_name': self.guid + '-key', + } + + 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.os_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_update(self): + """ + Tests the update of an OpenStack stack from Heat template file + by changing the number of VM instances from 1 to 2, 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(block=True) + 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)) + + env_values = { + 'network_name': self.guid + '-network', + 'public_network': self.ext_net_name, + 'agent_count': 2, + 'agent_image': self.image_creator.image_settings.name, + 'agent_flavor': self.flavor_creator.flavor_settings.name, + 'key_name': self.guid + '-key', + } + + updated_stack = self.stack_creator.update(env_values, block=True) + self.assertIsNotNone(updated_stack) + self.assertEqual(STATUS_UPDATE_COMPLETE, updated_stack.status) + + self.vm_inst_creators = self.stack_creator.get_vm_inst_creators( + heat_keypair_option='private_key') + self.assertIsNotNone(self.vm_inst_creators) + self.assertEqual(2, 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 @@ -701,7 +831,7 @@ class CreateStackRouterTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - self.created_stack = self.stack_creator.create() + self.created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(self.created_stack) def tearDown(self): @@ -767,7 +897,7 @@ class CreateStackVolumeTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - self.created_stack = self.stack_creator.create() + self.created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(self.created_stack) def tearDown(self): @@ -853,7 +983,7 @@ class CreateStackFlavorTests(OSIntegrationTestCase): template_path=self.heat_tmplt_path) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - self.created_stack = self.stack_creator.create() + self.created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(self.created_stack) def tearDown(self): @@ -919,7 +1049,7 @@ class CreateStackKeypairTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - self.created_stack = self.stack_creator.create() + self.created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(self.created_stack) self.keypair_creators = list() @@ -1003,7 +1133,7 @@ class CreateStackSecurityGroupTests(OSIntegrationTestCase): env_values=self.env_values) self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) - self.created_stack = self.stack_creator.create() + self.created_stack = self.stack_creator.create(block=True) self.assertIsNotNone(self.created_stack) def tearDown(self): @@ -1092,7 +1222,7 @@ class CreateStackNegativeTests(OSIntegrationTestCase): self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) with self.assertRaises(HTTPBadRequest): - self.stack_creator.create() + self.stack_creator.create(block=True) def test_bad_stack_file(self): """ @@ -1103,7 +1233,7 @@ class CreateStackNegativeTests(OSIntegrationTestCase): self.stack_creator = OpenStackHeatStack( self.os_creds, stack_settings) with self.assertRaises(IOError): - self.stack_creator.create() + self.stack_creator.create(block=True) class CreateStackFailureTests(OSIntegrationTestCase): @@ -1200,7 +1330,7 @@ class CreateStackFailureTests(OSIntegrationTestCase): with self.assertRaises(StackError): try: - self.stack_creator.create() + self.stack_creator.create(block=True) except StackError: resources = heat_utils.get_resources( self.heat_cli, self.stack_creator.get_stack().id) diff --git a/snaps/openstack/tests/heat/agent-group.yaml b/snaps/openstack/tests/heat/agent-group.yaml index 4b97495..494da0a 100644 --- a/snaps/openstack/tests/heat/agent-group.yaml +++ b/snaps/openstack/tests/heat/agent-group.yaml @@ -70,7 +70,7 @@ resources: network: type: OS::Neutron::Net properties: - name: { get_param: agent_count } + name: { get_param: network_name } subnet: type: OS::Neutron::Subnet diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py index a90690b..17de020 100644 --- a/snaps/openstack/utils/heat_utils.py +++ b/snaps/openstack/utils/heat_utils.py @@ -144,6 +144,24 @@ def create_stack(heat_cli, stack_settings): return get_stack_by_id(heat_cli, stack_id=stack['stack']['id']) +def update_stack(heat_cli, stack, env_vals): + """ + Updates the specified parameters in the stack + :param heat_cli: the OpenStack heat client object + :param stack_settings: the stack configuration + """ + args = dict() + + args['stack_name'] = stack.name + args['existing'] = True + + if env_vals: + args['parameters'] = env_vals + heat_cli.stacks.update(stack.id, **args) + else: + logger.warn('Stack not updated, env_vals are None') + + def delete_stack(heat_cli, stack): """ Deletes the Heat stack diff --git a/snaps/openstack/utils/nova_utils.py b/snaps/openstack/utils/nova_utils.py index 8be9b2a..005b56f 100644 --- a/snaps/openstack/utils/nova_utils.py +++ b/snaps/openstack/utils/nova_utils.py @@ -206,9 +206,14 @@ def __map_os_server_obj_to_vm_inst(neutron, keystone, os_server, network = neutron_utils.get_network( neutron, keystone, network_name=net_name, project_name=project_name) - ports = neutron_utils.get_ports(neutron, network, ips) - for port in ports: - out_ports.append(port) + if network: + ports = neutron_utils.get_ports(neutron, network, ips) + for port in ports: + out_ports.append(port) + else: + raise NovaException( + 'Unable to locate network in project {} with ' + 'name {}'.format(project_name, net_name)) volumes = None if hasattr(os_server, 'os-extended-volumes:volumes_attached'): diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py index 35a39f2..1fe1f8a 100644 --- a/snaps/test_suite_builder.py +++ b/snaps/test_suite_builder.py @@ -94,7 +94,8 @@ from snaps.openstack.tests.create_stack_tests import ( StackSettingsUnitTests, CreateStackSuccessTests, CreateStackNegativeTests, CreateStackFlavorTests, CreateStackFloatingIpTests, CreateStackNestedResourceTests, CreateStackKeypairTests, - CreateStackVolumeTests, CreateStackSecurityGroupTests) + CreateStackVolumeTests, CreateStackSecurityGroupTests, + CreateStackUpdateTests) from snaps.openstack.tests.create_user_tests import ( UserSettingsUnitTests, CreateUserSuccessTests) from snaps.openstack.tests.create_volume_tests import ( @@ -683,6 +684,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( + CreateStackUpdateTests, 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)) def add_ansible_integration_tests(suite, os_creds, ext_net_name, |