summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/how-to-use/IntegrationTests.rst11
-rw-r--r--snaps/config/stack.py3
-rw-r--r--snaps/openstack/create_stack.py47
-rw-r--r--snaps/openstack/tests/create_stack_tests.py170
-rw-r--r--snaps/openstack/tests/heat/agent-group.yaml2
-rw-r--r--snaps/openstack/utils/heat_utils.py18
-rw-r--r--snaps/openstack/utils/nova_utils.py11
-rw-r--r--snaps/test_suite_builder.py8
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,