Merge tag '2015.1.0'
This is a null-merge of the 2015.1.0 release tag back into the master
branch so that the 2015.1.0 tag will appear in the git commit history of
the master branch. It contains no actual changes to the master branch,
regardless of how our code review system's UI represents it. Please
ask in #openstack-infra if you have any questions, and otherwise try
to merge this as quickly as possible to avoid later conflicts on the
master branch.
Change-Id: I2e88cb9f04864c901a67340620949eb7c54d5543
diff --git a/common/clients.py b/common/clients.py
index 1ba3a21..c7a7f60 100644
--- a/common/clients.py
+++ b/common/clients.py
@@ -12,6 +12,7 @@
import os
+import ceilometerclient.client
import cinderclient.client
import heatclient.client
import keystoneclient.exceptions
@@ -30,6 +31,7 @@
CINDERCLIENT_VERSION = '1'
HEATCLIENT_VERSION = '1'
NOVACLIENT_VERSION = '2'
+ CEILOMETER_VERSION = '2'
def __init__(self, conf):
self.conf = conf
@@ -39,6 +41,7 @@
self.network_client = self._get_network_client()
self.volume_client = self._get_volume_client()
self.object_client = self._get_object_client()
+ self.metering_client = self._get_metering_client()
def _get_orchestration_client(self):
region = self.conf.region
@@ -136,3 +139,31 @@
'insecure': dscv,
}
return swiftclient.client.Connection(**args)
+
+ def _get_metering_client(self):
+ dscv = self.conf.disable_ssl_certificate_validation
+
+ keystone = self._get_identity_client()
+ try:
+ endpoint = keystone.service_catalog.url_for(
+ attr='region',
+ filter_value=self.conf.region,
+ service_type='metering',
+ endpoint_type='publicURL')
+
+ except keystoneclient.exceptions.EndpointNotFound:
+ return None
+ else:
+ args = {
+ 'username': self.conf.username,
+ 'password': self.conf.password,
+ 'tenant_name': self.conf.tenant_name,
+ 'auth_url': self.conf.auth_url,
+ 'insecure': dscv,
+ 'region_name': self.conf.region,
+ 'endpoint_type': 'publicURL',
+ 'service_type': 'metering',
+ }
+
+ return ceilometerclient.client.Client(self.CEILOMETER_VERSION,
+ endpoint, **args)
diff --git a/common/config.py b/common/config.py
index 5ced77b..4f6ea3d 100644
--- a/common/config.py
+++ b/common/config.py
@@ -62,10 +62,10 @@
default=1200,
help="Timeout in seconds to wait for a stack to build."),
cfg.StrOpt('network_for_ssh',
- default='private',
+ default='heat-net',
help="Network used for SSH connections."),
cfg.StrOpt('fixed_network_name',
- default='private',
+ default='heat-net',
help="Visible fixed network name "),
cfg.StrOpt('floating_network_name',
default='public',
@@ -77,7 +77,7 @@
"resource type Heat::InstallConfigAgent. Needs to "
"be appropriate for the image_ref."),
cfg.StrOpt('fixed_subnet_name',
- default='private-subnet',
+ default='heat-subnet',
help="Visible fixed sub-network name "),
cfg.IntOpt('ssh_timeout',
default=300,
diff --git a/common/remote_client.py b/common/remote_client.py
index f0405b8..c8c4f95 100644
--- a/common/remote_client.py
+++ b/common/remote_client.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import cStringIO
import re
import select
import socket
@@ -34,7 +33,7 @@
self.password = password
if isinstance(pkey, six.string_types):
pkey = paramiko.RSAKey.from_private_key(
- cStringIO.StringIO(str(pkey)))
+ six.moves.cStringIO(str(pkey)))
self.pkey = pkey
self.look_for_keys = look_for_keys
self.key_filename = key_filename
diff --git a/common/test.py b/common/test.py
index b88c72b..acfe1ad 100644
--- a/common/test.py
+++ b/common/test.py
@@ -15,13 +15,13 @@
import re
import subprocess
import time
-import urllib
import fixtures
from heatclient import exc as heat_exceptions
from oslo_log import log as logging
from oslo_utils import timeutils
import six
+from six.moves import urllib
import testscenarios
import testtools
@@ -87,7 +87,9 @@
self.network_client = self.manager.network_client
self.volume_client = self.manager.volume_client
self.object_client = self.manager.object_client
+ self.metering_client = self.manager.metering_client
self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
+ self.updated_time = {}
def get_remote_client(self, server_or_ip, username, private_key=None):
if isinstance(server_or_ip, six.string_types):
@@ -111,7 +113,7 @@
def check_connectivity(self, check_ip):
def try_connect(ip):
try:
- urllib.urlopen('http://%s/' % ip)
+ urllib.request.urlopen('http://%s/' % ip)
return True
except IOError:
return False
@@ -191,6 +193,15 @@
return call_until_true(
self.conf.build_timeout, 1, ping)
+ def _wait_for_all_resource_status(self, stack_identifier,
+ status, failure_pattern='^.*_FAILED$',
+ success_on_not_found=False):
+ for res in self.client.resources.list(stack_identifier):
+ self._wait_for_resource_status(
+ stack_identifier, res.resource_name,
+ status, failure_pattern=failure_pattern,
+ success_on_not_found=success_on_not_found)
+
def _wait_for_resource_status(self, stack_identifier, resource_name,
status, failure_pattern='^.*_FAILED$',
success_on_not_found=False):
@@ -213,7 +224,10 @@
else:
if res.resource_status == status:
return
- if fail_regexp.search(res.resource_status):
+ wait_for_action = status.split('_')[0]
+ resource_action = res.resource_status.split('_')[0]
+ if (resource_action == wait_for_action and
+ fail_regexp.search(res.resource_status)):
raise exceptions.StackResourceBuildErrorException(
resource_name=res.resource_name,
stack_identifier=stack_identifier,
@@ -226,6 +240,36 @@
(resource_name, status, build_timeout))
raise exceptions.TimeoutException(message)
+ def _verify_status(self, stack, stack_identifier, status, fail_regexp):
+ if stack.stack_status == status:
+ # Handle UPDATE_COMPLETE case: Make sure we don't
+ # wait for a stale UPDATE_COMPLETE status.
+ if status == 'UPDATE_COMPLETE':
+ if self.updated_time.get(
+ stack_identifier) != stack.updated_time:
+ self.updated_time[stack_identifier] = stack.updated_time
+ return True
+ else:
+ return True
+
+ wait_for_action = status.split('_')[0]
+ if (stack.action == wait_for_action and
+ fail_regexp.search(stack.stack_status)):
+ # Handle UPDATE_FAILED case.
+ if status == 'UPDATE_FAILED':
+ if self.updated_time.get(
+ stack_identifier) != stack.updated_time:
+ self.updated_time[stack_identifier] = stack.updated_time
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack.stack_status,
+ stack_status_reason=stack.stack_status_reason)
+ else:
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack.stack_status,
+ stack_status_reason=stack.stack_status_reason)
+
def _wait_for_stack_status(self, stack_identifier, status,
failure_pattern='^.*_FAILED$',
success_on_not_found=False):
@@ -251,13 +295,10 @@
# ignore this, as the resource may not have
# been created yet
else:
- if stack.stack_status == status:
+ if self._verify_status(stack, stack_identifier, status,
+ fail_regexp):
return
- if fail_regexp.search(stack.stack_status):
- raise exceptions.StackBuildErrorException(
- stack_identifier=stack_identifier,
- stack_status=stack.stack_status,
- stack_status_reason=stack.stack_status_reason)
+
time.sleep(build_interval)
message = ('Stack %s failed to reach %s status within '
@@ -275,22 +316,48 @@
success_on_not_found=True)
def update_stack(self, stack_identifier, template, environment=None,
- files=None, parameters=None,
- expected_status='UPDATE_COMPLETE'):
+ files=None, parameters=None, tags=None,
+ expected_status='UPDATE_COMPLETE',
+ disable_rollback=True):
env = environment or {}
env_files = files or {}
parameters = parameters or {}
stack_name = stack_identifier.split('/')[0]
- self.client.stacks.update(
- stack_id=stack_identifier,
- stack_name=stack_name,
- template=template,
- files=env_files,
- disable_rollback=True,
- parameters=parameters,
- environment=env
- )
- self._wait_for_stack_status(stack_identifier, expected_status)
+
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ self.client.stacks.update(
+ stack_id=stack_identifier,
+ stack_name=stack_name,
+ template=template,
+ files=env_files,
+ disable_rollback=disable_rollback,
+ parameters=parameters,
+ environment=env,
+ tags=tags
+ )
+ except heat_exceptions.HTTPConflict as ex:
+ # FIXME(sirushtim): Wait a little for the stack lock to be
+ # released and hopefully, the stack should be updatable again.
+ if ex.error['error']['type'] != 'ActionInProgress':
+ raise ex
+
+ time.sleep(build_interval)
+ else:
+ break
+
+ kwargs = {'stack_identifier': stack_identifier,
+ 'status': expected_status}
+ if expected_status in ['ROLLBACK_COMPLETE']:
+ # To trigger rollback you would intentionally fail the stack
+ # Hence check for rollback failures
+ kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+
+ self._wait_for_stack_status(**kwargs)
def assert_resource_is_a_stack(self, stack_identifier, res_name,
wait=False):
@@ -333,8 +400,9 @@
return dict((r.resource_name, r.resource_type) for r in resources)
def stack_create(self, stack_name=None, template=None, files=None,
- parameters=None, environment=None,
- expected_status='CREATE_COMPLETE'):
+ parameters=None, environment=None, tags=None,
+ expected_status='CREATE_COMPLETE',
+ disable_rollback=True, enable_cleanup=True):
name = stack_name or self._stack_rand_name()
templ = template or self.template
templ_files = files or {}
@@ -344,16 +412,24 @@
stack_name=name,
template=templ,
files=templ_files,
- disable_rollback=True,
+ disable_rollback=disable_rollback,
parameters=params,
- environment=env
+ environment=env,
+ tags=tags
)
- self.addCleanup(self.client.stacks.delete, name)
+ if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
+ self.addCleanup(self.client.stacks.delete, name)
stack = self.client.stacks.get(name)
stack_identifier = '%s/%s' % (name, stack.id)
+ kwargs = {'stack_identifier': stack_identifier,
+ 'status': expected_status}
if expected_status:
- self._wait_for_stack_status(stack_identifier, expected_status)
+ if expected_status in ['ROLLBACK_COMPLETE']:
+ # To trigger rollback you would intentionally fail the stack
+ # Hence check for rollback failures
+ kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+ self._wait_for_stack_status(**kwargs)
return stack_identifier
def stack_adopt(self, stack_name=None, files=None,
@@ -390,11 +466,19 @@
def stack_suspend(self, stack_identifier):
stack_name = stack_identifier.split('/')[0]
self.client.actions.suspend(stack_name)
+
+ # improve debugging by first checking the resource's state.
+ self._wait_for_all_resource_status(stack_identifier,
+ 'SUSPEND_COMPLETE')
self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
def stack_resume(self, stack_identifier):
stack_name = stack_identifier.split('/')[0]
self.client.actions.resume(stack_name)
+
+ # improve debugging by first checking the resource's state.
+ self._wait_for_all_resource_status(stack_identifier,
+ 'RESUME_COMPLETE')
self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
def wait_for_event_with_reason(self, stack_identifier, reason,
diff --git a/common/test_resources/test_resource.py b/common/test_resources/test_resource.py
new file mode 100644
index 0000000..88f1745
--- /dev/null
+++ b/common/test_resources/test_resource.py
@@ -0,0 +1,134 @@
+#
+# 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.
+
+import eventlet
+
+from heat.common.i18n import _
+from heat.engine import attributes
+from heat.engine import properties
+from heat.engine import resource
+from heat.engine import support
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class TestResource(resource.Resource):
+ '''
+ A resource which stores the string value that was provided.
+
+ This resource is to be used only for testing.
+ It has control knobs such as 'update_replace', 'fail', 'wait_secs'
+
+ '''
+
+ support_status = support.SupportStatus(version='2014.1')
+
+ PROPERTIES = (
+ VALUE, UPDATE_REPLACE, FAIL, WAIT_SECS
+ ) = (
+ 'value', 'update_replace', 'fail', 'wait_secs'
+ )
+
+ ATTRIBUTES = (
+ OUTPUT,
+ ) = (
+ 'output',
+ )
+
+ properties_schema = {
+ VALUE: properties.Schema(
+ properties.Schema.STRING,
+ _('The input string to be stored.'),
+ default='test_string',
+ update_allowed=True
+ ),
+ FAIL: properties.Schema(
+ properties.Schema.BOOLEAN,
+ _('Value which can be set to fail the resource operation '
+ 'to test failure scenarios.'),
+ update_allowed=True,
+ default=False
+ ),
+ UPDATE_REPLACE: properties.Schema(
+ properties.Schema.BOOLEAN,
+ _('Value which can be set to trigger update replace for '
+ 'the particular resource'),
+ update_allowed=True,
+ default=False
+ ),
+ WAIT_SECS: properties.Schema(
+ properties.Schema.NUMBER,
+ _('Value which can be set for resource to wait after an action '
+ 'is performed.'),
+ update_allowed=True,
+ default=0,
+ ),
+ }
+
+ attributes_schema = {
+ OUTPUT: attributes.Schema(
+ _('The string that was stored. This value is '
+ 'also available by referencing the resource.'),
+ cache_mode=attributes.Schema.CACHE_NONE
+ ),
+ }
+
+ def handle_create(self):
+ value = self.properties.get(self.VALUE)
+ fail_prop = self.properties.get(self.FAIL)
+ sleep_secs = self.properties.get(self.WAIT_SECS)
+
+ self.data_set('value', value, redact=False)
+ self.resource_id_set(self.physical_resource_name())
+
+ # sleep for specified time
+ if sleep_secs:
+ LOG.debug("Resource %s sleeping for %s seconds",
+ self.name, sleep_secs)
+ eventlet.sleep(sleep_secs)
+
+ # emulate failure
+ if fail_prop:
+ raise Exception("Test Resource failed %s", self.name)
+
+ def handle_update(self, json_snippet=None, tmpl_diff=None, prop_diff=None):
+ value = prop_diff.get(self.VALUE)
+ new_prop = json_snippet._properties
+ if value:
+ update_replace = new_prop.get(self.UPDATE_REPLACE, False)
+ if update_replace:
+ raise resource.UpdateReplace(self.name)
+ else:
+ fail_prop = new_prop.get(self.FAIL, False)
+ sleep_secs = new_prop.get(self.WAIT_SECS, 0)
+ # emulate failure
+ if fail_prop:
+ raise Exception("Test Resource failed %s", self.name)
+ # update in place
+ self.data_set('value', value, redact=False)
+
+ if sleep_secs:
+ LOG.debug("Update of Resource %s sleeping for %s seconds",
+ self.name, sleep_secs)
+ eventlet.sleep(sleep_secs)
+
+ def _resolve_attribute(self, name):
+ if name == self.OUTPUT:
+ return self.data().get('value')
+
+
+def resource_mapping():
+ return {
+ 'OS::Heat::TestResource': TestResource,
+ }
diff --git a/functional/test_autoscaling.py b/functional/test_autoscaling.py
index 932245d..301981f 100644
--- a/functional/test_autoscaling.py
+++ b/functional/test_autoscaling.py
@@ -183,7 +183,6 @@
'flavor': self.conf.instance_type}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
- self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 5)
@@ -318,21 +317,11 @@
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
- self.client.actions.suspend(stack_id=stack_identifier)
- self._wait_for_resource_status(
- stack_identifier, 'JobServerGroup', 'SUSPEND_COMPLETE')
- for res in self.client.resources.list(nested_ident):
- self._wait_for_resource_status(nested_ident,
- res.resource_name,
- 'SUSPEND_COMPLETE')
+ self.stack_suspend(stack_identifier)
+ self._wait_for_all_resource_status(nested_ident, 'SUSPEND_COMPLETE')
- self.client.actions.resume(stack_id=stack_identifier)
- self._wait_for_resource_status(
- stack_identifier, 'JobServerGroup', 'RESUME_COMPLETE')
- for res in self.client.resources.list(nested_ident):
- self._wait_for_resource_status(nested_ident,
- res.resource_name,
- 'RESUME_COMPLETE')
+ self.stack_resume(stack_identifier)
+ self._wait_for_all_resource_status(nested_ident, 'RESUME_COMPLETE')
class AutoscalingGroupUpdatePolicyTest(AutoscalingGroupTest):
@@ -384,7 +373,6 @@
# test stack update
self.update_stack(stack_identifier, updt_template,
environment=env, files=files)
- self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
updt_stack = self.client.stacks.get(stack_identifier)
# test that the launch configuration is replaced
diff --git a/functional/test_aws_stack.py b/functional/test_aws_stack.py
index 5aabe95..172bc24 100644
--- a/functional/test_aws_stack.py
+++ b/functional/test_aws_stack.py
@@ -13,9 +13,9 @@
import hashlib
import json
import random
-import urlparse
from oslo_log import log as logging
+from six.moves.urllib import parse
from swiftclient import utils as swiftclient_utils
import yaml
@@ -103,7 +103,7 @@
timeout = self.conf.build_timeout * 10
tempurl = swiftclient_utils.generate_temp_url(path, timeout,
key, 'GET')
- sw_url = urlparse.urlparse(oc.url)
+ sw_url = parse.urlparse(oc.url)
return '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl)
def test_nested_stack_create(self):
@@ -203,11 +203,5 @@
url = self.publish_template(self.nested_name, self.nested_template)
self.template = self.test_template.replace('the.yaml', url)
stack_identifier = self.stack_create(template=self.template)
-
- self.client.actions.suspend(stack_id=stack_identifier)
- self._wait_for_resource_status(
- stack_identifier, 'the_nested', 'SUSPEND_COMPLETE')
-
- self.client.actions.resume(stack_id=stack_identifier)
- self._wait_for_resource_status(
- stack_identifier, 'the_nested', 'RESUME_COMPLETE')
+ self.stack_suspend(stack_identifier)
+ self.stack_resume(stack_identifier)
diff --git a/functional/test_create_update.py b/functional/test_create_update.py
new file mode 100644
index 0000000..6053303
--- /dev/null
+++ b/functional/test_create_update.py
@@ -0,0 +1,390 @@
+# 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.
+
+
+import copy
+from heat_integrationtests.common import test
+import json
+
+test_template_one_resource = {
+ 'heat_template_version': '2013-05-23',
+ 'description': 'Test template to create one instance.',
+ 'resources': {
+ 'test1': {
+ 'type': 'OS::Heat::TestResource',
+ 'properties': {
+ 'value': 'Test1',
+ 'fail': False,
+ 'update_replace': False,
+ 'wait_secs': 0
+ }
+ }
+ }
+}
+
+test_template_two_resource = {
+ 'heat_template_version': '2013-05-23',
+ 'description': 'Test template to create two instance.',
+ 'resources': {
+ 'test1': {
+ 'type': 'OS::Heat::TestResource',
+ 'properties': {
+ 'value': 'Test1',
+ 'fail': False,
+ 'update_replace': False,
+ 'wait_secs': 0
+ }
+ },
+ 'test2': {
+ 'type': 'OS::Heat::TestResource',
+ 'properties': {
+ 'value': 'Test1',
+ 'fail': False,
+ 'update_replace': False,
+ 'wait_secs': 0
+ }
+ }
+ }
+}
+
+
+def _change_rsrc_properties(template, rsrcs, values):
+ modified_template = copy.deepcopy(template)
+ for rsrc_name in rsrcs:
+ rsrc_prop = modified_template['resources'][
+ rsrc_name]['properties']
+ for prop in rsrc_prop:
+ if prop in values:
+ rsrc_prop[prop] = values[prop]
+ return modified_template
+
+
+class CreateStackTest(test.HeatIntegrationTest):
+ def setUp(self):
+ super(CreateStackTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_create_rollback(self):
+ values = {'fail': True, 'value': 'test_create_rollback'}
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'], values)
+
+ self.stack_create(
+ template=template,
+ expected_status='ROLLBACK_COMPLETE',
+ disable_rollback=False)
+
+
+class UpdateStackTest(test.HeatIntegrationTest):
+
+ provider_template = {
+ 'heat_template_version': '2013-05-23',
+ 'description': 'foo',
+ 'resources': {
+ 'test1': {
+ 'type': 'My::TestResource'
+ }
+ }
+ }
+
+ provider_group_template = '''
+heat_template_version: 2013-05-23
+resources:
+ test_group:
+ type: OS::Heat::ResourceGroup
+ properties:
+ count: 2
+ resource_def:
+ type: My::TestResource
+'''
+
+ update_userdata_template = '''
+heat_template_version: 2014-10-16
+parameters:
+ flavor:
+ type: string
+ user_data:
+ type: string
+ image:
+ type: string
+ network:
+ type: string
+
+resources:
+ server:
+ type: OS::Nova::Server
+ properties:
+ image: {get_param: image}
+ flavor: {get_param: flavor}
+ networks: [{network: {get_param: network} }]
+ user_data_format: SOFTWARE_CONFIG
+ user_data: {get_param: user_data}
+'''
+
+ def setUp(self):
+ super(UpdateStackTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_stack_update_nochange(self):
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'],
+ {'value': 'test_no_change'})
+ stack_identifier = self.stack_create(
+ template=template)
+ expected_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(expected_resources,
+ self.list_resources(stack_identifier))
+
+ # Update with no changes, resources should be unchanged
+ self.update_stack(stack_identifier, template)
+ self.assertEqual(expected_resources,
+ self.list_resources(stack_identifier))
+
+ def test_stack_in_place_update(self):
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'],
+ {'value': 'test_in_place'})
+ stack_identifier = self.stack_create(
+ template=template)
+ expected_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(expected_resources,
+ self.list_resources(stack_identifier))
+ resource = self.client.resources.list(stack_identifier)
+ initial_phy_id = resource[0].physical_resource_id
+
+ tmpl_update = _change_rsrc_properties(
+ test_template_one_resource, ['test1'],
+ {'value': 'test_in_place_update'})
+ # Update the Value
+ self.update_stack(stack_identifier, tmpl_update)
+ resource = self.client.resources.list(stack_identifier)
+ # By default update_in_place
+ self.assertEqual(initial_phy_id,
+ resource[0].physical_resource_id)
+
+ def test_stack_update_replace(self):
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'],
+ {'value': 'test_replace'})
+ stack_identifier = self.stack_create(
+ template=template)
+ expected_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(expected_resources,
+ self.list_resources(stack_identifier))
+ resource = self.client.resources.list(stack_identifier)
+ initial_phy_id = resource[0].physical_resource_id
+
+ # Update the value and also set update_replace prop
+ tmpl_update = _change_rsrc_properties(
+ test_template_one_resource, ['test1'],
+ {'value': 'test_in_place_update', 'update_replace': True})
+ self.update_stack(stack_identifier, tmpl_update)
+ resource = self.client.resources.list(stack_identifier)
+ # update Replace
+ self.assertNotEqual(initial_phy_id,
+ resource[0].physical_resource_id)
+
+ def test_stack_update_add_remove(self):
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'],
+ {'value': 'test_add_remove'})
+ stack_identifier = self.stack_create(
+ template=template)
+ initial_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ tmpl_update = _change_rsrc_properties(
+ test_template_two_resource, ['test1', 'test2'],
+ {'value': 'test_add_remove_update'})
+ # Add one resource via a stack update
+ self.update_stack(stack_identifier, tmpl_update)
+ updated_resources = {'test1': 'OS::Heat::TestResource',
+ 'test2': 'OS::Heat::TestResource'}
+ self.assertEqual(updated_resources,
+ self.list_resources(stack_identifier))
+
+ # Then remove it by updating with the original template
+ self.update_stack(stack_identifier, template)
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ def test_stack_update_rollback(self):
+ template = _change_rsrc_properties(test_template_one_resource,
+ ['test1'],
+ {'value': 'test_update_rollback'})
+ stack_identifier = self.stack_create(
+ template=template)
+ initial_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ tmpl_update = _change_rsrc_properties(
+ test_template_two_resource, ['test1', 'test2'],
+ {'value': 'test_update_rollback', 'fail': True})
+ # stack update, also set failure
+ self.update_stack(stack_identifier, tmpl_update,
+ expected_status='ROLLBACK_COMPLETE',
+ disable_rollback=False)
+ # since stack update failed only the original resource is present
+ updated_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(updated_resources,
+ self.list_resources(stack_identifier))
+
+ def test_stack_update_provider(self):
+ template = _change_rsrc_properties(
+ test_template_one_resource, ['test1'],
+ {'value': 'test_provider_template'})
+ files = {'provider.template': json.dumps(template)}
+ env = {'resource_registry':
+ {'My::TestResource': 'provider.template'}}
+ stack_identifier = self.stack_create(
+ template=self.provider_template,
+ files=files,
+ environment=env
+ )
+
+ initial_resources = {'test1': 'My::TestResource'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ # Prove the resource is backed by a nested stack, save the ID
+ nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+ 'test1')
+ nested_id = nested_identifier.split('/')[-1]
+
+ # Then check the expected resources are in the nested stack
+ nested_resources = {'test1': 'OS::Heat::TestResource'}
+ self.assertEqual(nested_resources,
+ self.list_resources(nested_identifier))
+ tmpl_update = _change_rsrc_properties(
+ test_template_two_resource, ['test1', 'test2'],
+ {'value': 'test_provider_template'})
+ # Add one resource via a stack update by changing the nested stack
+ files['provider.template'] = json.dumps(tmpl_update)
+ self.update_stack(stack_identifier, self.provider_template,
+ environment=env, files=files)
+
+ # Parent resources should be unchanged and the nested stack
+ # should have been updated in-place without replacement
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+ rsrc = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual(rsrc.physical_resource_id, nested_id)
+
+ # Then check the expected resources are in the nested stack
+ nested_resources = {'test1': 'OS::Heat::TestResource',
+ 'test2': 'OS::Heat::TestResource'}
+ self.assertEqual(nested_resources,
+ self.list_resources(nested_identifier))
+
+ def test_stack_update_provider_group(self):
+ '''Test two-level nested update.'''
+ # Create a ResourceGroup (which creates a nested stack),
+ # containing provider resources (which create a nested
+ # stack), thus excercising an update which traverses
+ # two levels of nesting.
+ template = _change_rsrc_properties(
+ test_template_one_resource, ['test1'],
+ {'value': 'test_provider_group_template'})
+ files = {'provider.template': json.dumps(template)}
+ env = {'resource_registry':
+ {'My::TestResource': 'provider.template'}}
+
+ stack_identifier = self.stack_create(
+ template=self.provider_group_template,
+ files=files,
+ environment=env
+ )
+
+ initial_resources = {'test_group': 'OS::Heat::ResourceGroup'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ # Prove the resource is backed by a nested stack, save the ID
+ nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+ 'test_group')
+
+ # Then check the expected resources are in the nested stack
+ nested_resources = {'0': 'My::TestResource',
+ '1': 'My::TestResource'}
+ self.assertEqual(nested_resources,
+ self.list_resources(nested_identifier))
+
+ for n_rsrc in nested_resources:
+ rsrc = self.client.resources.get(nested_identifier, n_rsrc)
+ provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
+ provider_identifier = '%s/%s' % (provider_stack.stack_name,
+ provider_stack.id)
+ provider_resources = {u'test1': u'OS::Heat::TestResource'}
+ self.assertEqual(provider_resources,
+ self.list_resources(provider_identifier))
+
+ tmpl_update = _change_rsrc_properties(
+ test_template_two_resource, ['test1', 'test2'],
+ {'value': 'test_provider_group_template'})
+ # Add one resource via a stack update by changing the nested stack
+ files['provider.template'] = json.dumps(tmpl_update)
+ self.update_stack(stack_identifier, self.provider_group_template,
+ environment=env, files=files)
+
+ # Parent resources should be unchanged and the nested stack
+ # should have been updated in-place without replacement
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+
+ # Resource group stack should also be unchanged (but updated)
+ nested_stack = self.client.stacks.get(nested_identifier)
+ self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status)
+ self.assertEqual(nested_resources,
+ self.list_resources(nested_identifier))
+
+ for n_rsrc in nested_resources:
+ rsrc = self.client.resources.get(nested_identifier, n_rsrc)
+ provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
+ provider_identifier = '%s/%s' % (provider_stack.stack_name,
+ provider_stack.id)
+ provider_resources = {'test1': 'OS::Heat::TestResource',
+ 'test2': 'OS::Heat::TestResource'}
+ self.assertEqual(provider_resources,
+ self.list_resources(provider_identifier))
+
+ def test_stack_update_with_replacing_userdata(self):
+ """Confirm that we can update userdata of instance during updating
+ stack by the user of member role.
+
+ Make sure that a resource that inherites from StackUser can be deleted
+ during updating stack.
+ """
+ if not self.conf.minimal_image_ref:
+ raise self.skipException("No minimal image configured to test")
+ if not self.conf.minimal_instance_type:
+ raise self.skipException("No flavor configured to test")
+
+ parms = {'flavor': self.conf.minimal_instance_type,
+ 'image': self.conf.minimal_image_ref,
+ 'network': self.conf.fixed_network_name,
+ 'user_data': ''}
+ name = self._stack_rand_name()
+
+ stack_identifier = self.stack_create(
+ stack_name=name,
+ template=self.update_userdata_template,
+ parameters=parms
+ )
+
+ parms_updated = parms
+ parms_updated['user_data'] = 'two'
+ self.update_stack(
+ stack_identifier,
+ template=self.update_userdata_template,
+ parameters=parms_updated)
diff --git a/functional/test_create_update_neutron_port.py b/functional/test_create_update_neutron_port.py
new file mode 100644
index 0000000..3d82bc4
--- /dev/null
+++ b/functional/test_create_update_neutron_port.py
@@ -0,0 +1,108 @@
+# 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.
+
+from testtools import testcase
+
+from heat_integrationtests.common import test
+
+
+test_template = '''
+heat_template_version: 2015-04-30
+description: Test template to create port wit ip_address.
+parameters:
+ mac:
+ type: string
+ default: 00-00-00-00-BB-BB
+resources:
+ net:
+ type: OS::Neutron::Net
+ subnet:
+ type: OS::Neutron::Subnet
+ properties:
+ network: { get_resource: net }
+ cidr: 11.11.11.0/24
+ port:
+ type: OS::Neutron::Port
+ properties:
+ network: {get_resource: net}
+ mac_address: {get_param: mac}
+ fixed_ips:
+ - subnet: {get_resource: subnet}
+ ip_address: 11.11.11.11
+outputs:
+ port_ip:
+ value: {get_attr: [port, fixed_ips, 0, ip_address]}
+'''
+
+
+class UpdatePortTest(test.HeatIntegrationTest):
+
+ def setUp(self):
+ super(UpdatePortTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def get_port_id_and_ip(self, stack_identifier):
+ resources = self.client.resources.list(stack_identifier)
+ port_id = [res.physical_resource_id for res in resources
+ if res.resource_name == 'port']
+ stack = self.client.stacks.get(stack_identifier)
+ port_ip = self._stack_output(stack, 'port_ip')
+ return port_id[0], port_ip
+
+ def test_stack_update_replace_no_ip(self):
+ templ_no_ip = test_template.replace('ip_address: 11.11.11.11', '')
+ # create with default 'mac' parameter
+ stack_identifier = self.stack_create(template=templ_no_ip)
+ _id, _ip = self.get_port_id_and_ip(stack_identifier)
+
+ # Update with another 'mac' parameter
+ parameters = {'mac': '00-00-00-00-AA-AA'}
+ self.update_stack(stack_identifier, templ_no_ip,
+ parameters=parameters)
+
+ new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
+ # port id and ip should be different
+ self.assertNotEqual(_ip, new_ip)
+ self.assertNotEqual(_id, new_id)
+
+ @testcase.skip('Skipped until bug #1455100 is fixed.')
+ def test_stack_update_replace_with_ip(self):
+ # create with default 'mac' parameter
+ stack_identifier = self.stack_create(template=test_template)
+
+ _id, _ip = self.get_port_id_and_ip(stack_identifier)
+
+ # Update with another 'mac' parameter
+ parameters = {'mac': '00-00-00-00-AA-AA'}
+
+ # port should be replaced with same ip
+ self.update_stack(stack_identifier, test_template,
+ parameters=parameters)
+
+ new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
+ # port id should be different, ip should be the same
+ self.assertEqual(_ip, new_ip)
+ self.assertNotEqual(_id, new_id)
+
+ def test_stack_update_in_place_remove_ip(self):
+ # create with default 'mac' parameter and defined ip_address
+ stack_identifier = self.stack_create(template=test_template)
+ _id, _ip = self.get_port_id_and_ip(stack_identifier)
+
+ # remove ip_address property and update stack
+ templ_no_ip = test_template.replace('ip_address: 11.11.11.11', '')
+ self.update_stack(stack_identifier, templ_no_ip)
+
+ new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
+ # port should be updated with the same id, but different ip
+ self.assertNotEqual(_ip, new_ip)
+ self.assertEqual(_id, new_id)
diff --git a/functional/test_default_parameters.py b/functional/test_default_parameters.py
index 138a13e..00000fd 100644
--- a/functional/test_default_parameters.py
+++ b/functional/test_default_parameters.py
@@ -68,8 +68,6 @@
self.client = self.orchestration_client
def test_defaults(self):
- stack_name = self._stack_rand_name()
-
env = {'parameters': {}, 'parameter_defaults': {}}
if self.param:
env['parameters'] = {'length': self.param}
@@ -84,22 +82,13 @@
else:
nested_template = self.nested_template
- self.client.stacks.create(
- stack_name=stack_name,
+ stack_identifier = self.stack_create(
template=self.template,
files={'nested_random.yaml': nested_template},
- disable_rollback=True,
- parameters={},
environment=env
)
- self.addCleanup(self.client.stacks.delete, stack_name)
- stack = self.client.stacks.get(stack_name)
- stack_identifier = '%s/%s' % (stack_name, stack.id)
-
- self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- stack = self.client.stacks.get(stack_name)
+ stack = self.client.stacks.get(stack_identifier)
for out in stack.outputs:
if out['output_key'] == 'random1':
self.assertEqual(self.expect1, len(out['output_value']))
diff --git a/functional/test_heat_autoscaling.py b/functional/test_heat_autoscaling.py
index 0e6e0cb..dbe3e8f 100644
--- a/functional/test_heat_autoscaling.py
+++ b/functional/test_heat_autoscaling.py
@@ -132,4 +132,3 @@
'scaling_adjustment: 2')
self.update_stack(stack_identifier, template=new_template)
- self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
diff --git a/functional/test_instance_group.py b/functional/test_instance_group.py
index 5c88bed..1455d0b 100644
--- a/functional/test_instance_group.py
+++ b/functional/test_instance_group.py
@@ -174,7 +174,6 @@
'flavor': self.conf.instance_type}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
- self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 5)
@@ -345,7 +344,6 @@
# test stack update
self.update_stack(stack_identifier, updt_template,
environment=env, files=files)
- self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
updt_stack = self.client.stacks.get(stack_identifier)
# test that the launch configuration is replaced
diff --git a/functional/test_notifications.py b/functional/test_notifications.py
index a9b6cf1..a4c419c 100644
--- a/functional/test_notifications.py
+++ b/functional/test_notifications.py
@@ -141,17 +141,9 @@
return len(handler.notifications) == count
def test_basic_notifications(self):
- stack_identifier = self._stack_rand_name()
- # do this manually so we can call _stack_delete() directly.
- self.client.stacks.create(
- stack_name=stack_identifier,
- template=self.basic_template,
- files={},
- disable_rollback=True,
- parameters={},
- environment={}
- )
- self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
+ # disable cleanup so we can call _stack_delete() directly.
+ stack_identifier = self.stack_create(template=self.basic_template,
+ enable_cleanup=False)
self.update_stack(stack_identifier,
template=self.update_basic_template)
self.stack_suspend(stack_identifier)
diff --git a/functional/test_remote_stack.py b/functional/test_remote_stack.py
index 7579eb0..868f24e 100644
--- a/functional/test_remote_stack.py
+++ b/functional/test_remote_stack.py
@@ -140,17 +140,5 @@
def test_stack_suspend_resume(self):
files = {'remote_stack.yaml': self.remote_template}
stack_id = self.stack_create(files=files)
- rsrc = self.client.resources.get(stack_id, 'my_stack')
- remote_id = rsrc.physical_resource_id
-
- # suspend stack
- self.client.actions.suspend(stack_id)
- self._wait_for_stack_status(stack_id, 'SUSPEND_COMPLETE')
- rsrc = self.client.stacks.get(remote_id)
- self.assertEqual('SUSPEND_COMPLETE', rsrc.stack_status)
-
- # resume stack
- self.client.actions.resume(stack_id)
- self._wait_for_stack_status(stack_id, 'RESUME_COMPLETE')
- rsrc = self.client.stacks.get(remote_id)
- self.assertEqual('RESUME_COMPLETE', rsrc.stack_status)
+ self.stack_suspend(stack_id)
+ self.stack_resume(stack_id)
diff --git a/functional/test_resource_group.py b/functional/test_resource_group.py
index 322c99c..a41f841 100644
--- a/functional/test_resource_group.py
+++ b/functional/test_resource_group.py
@@ -98,7 +98,7 @@
# Prove validation works for non-zero create/update
template_two_nested = self.template.replace("count: 0", "count: 2")
- expected_err = "length Value 'BAD' is not an integer"
+ expected_err = "Value 'BAD' is not an integer"
ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack,
stack_identifier, template_two_nested,
environment=env, files=files)
diff --git a/functional/test_stack_tags.py b/functional/test_stack_tags.py
new file mode 100644
index 0000000..a183d25
--- /dev/null
+++ b/functional/test_stack_tags.py
@@ -0,0 +1,69 @@
+# 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.
+
+from heat_integrationtests.common import test
+
+
+class StackTagTest(test.HeatIntegrationTest):
+
+ template = '''
+heat_template_version: 2014-10-16
+description:
+ foo
+'''
+
+ def setUp(self):
+ super(StackTagTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_stack_tag(self):
+ # Stack create with stack tags
+ tags = ['foo', 'bar']
+ stack_identifier = self.stack_create(
+ template=self.template,
+ tags=tags
+ )
+
+ # Ensure property tag is populated and matches given tags
+ stack = self.client.stacks.get(stack_identifier)
+ self.assertEqual(tags, stack.tags)
+
+ # Update tags
+ updated_tags = ['tag1', 'tag2']
+ self.update_stack(
+ stack_identifier,
+ template=self.template,
+ tags=updated_tags)
+
+ # Ensure property tag is populated and matches updated tags
+ updated_stack = self.client.stacks.get(stack_identifier)
+ self.assertEqual(updated_tags, updated_stack.tags)
+
+ # Delete tags
+ self.update_stack(
+ stack_identifier,
+ template=self.template
+ )
+
+ # Ensure property tag is not populated
+ empty_tags_stack = self.client.stacks.get(stack_identifier)
+ self.assertIsNone(empty_tags_stack.tags)
+
+ def test_hidden_stack(self):
+ # Stack create with hidden stack tag
+ tags = ['foo', 'hidden']
+ self.stack_create(
+ template=self.template,
+ tags=tags)
+ # Ensure stack does not exist when we do a stack list
+ for stack in self.client.stacks.list():
+ self.assertNotIn('hidden', stack.tags, "Hidden stack can be seen")
diff --git a/functional/test_template_resource.py b/functional/test_template_resource.py
index 392bddd..5b189ac 100644
--- a/functional/test_template_resource.py
+++ b/functional/test_template_resource.py
@@ -102,17 +102,12 @@
This tests that if you manually delete a nested
stack, the parent stack is still deletable.
"""
- name = self._stack_rand_name()
- # do this manually so we can call _stack_delete() directly.
- self.client.stacks.create(
- stack_name=name,
+ # disable cleanup so we can call _stack_delete() directly.
+ stack_identifier = self.stack_create(
template=self.template,
files={'nested.yaml': self.nested_templ},
environment=self.env_templ,
- disable_rollback=True)
- stack = self.client.stacks.get(name)
- stack_identifier = '%s/%s' % (name, stack.id)
- self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
+ enable_cleanup=False)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
@@ -120,6 +115,36 @@
self._stack_delete(nested_ident)
self._stack_delete(stack_identifier)
+ def test_change_in_file_path(self):
+ stack_identifier = self.stack_create(
+ template=self.template,
+ files={'nested.yaml': self.nested_templ},
+ environment=self.env_templ)
+ stack = self.client.stacks.get(stack_identifier)
+ secret_out1 = self._stack_output(stack, 'secret-out')
+
+ nested_templ_2 = '''
+heat_template_version: 2013-05-23
+resources:
+ secret2:
+ type: OS::Heat::RandomString
+outputs:
+ value:
+ value: freddy
+'''
+ env_templ_2 = '''
+resource_registry:
+ "OS::Heat::RandomString": new/nested.yaml
+'''
+ self.update_stack(stack_identifier,
+ template=self.template,
+ files={'new/nested.yaml': nested_templ_2},
+ environment=env_templ_2)
+ stack = self.client.stacks.get(stack_identifier)
+ secret_out2 = self._stack_output(stack, 'secret-out')
+ self.assertNotEqual(secret_out1, secret_out2)
+ self.assertEqual('freddy', secret_out2)
+
class NestedAttributesTest(test.HeatIntegrationTest):
"""Prove that we can use the template resource references."""
@@ -226,6 +251,22 @@
Type: the.yaml
Properties:
one: my_name
+ two: your_name
+Outputs:
+ identifier:
+ Value: {Ref: the_nested}
+ value:
+ Value: {'Fn::GetAtt': [the_nested, the_str]}
+'''
+
+ main_template_change_prop = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_nested:
+ Type: the.yaml
+ Properties:
+ one: updated_name
+ two: your_name
Outputs:
identifier:
@@ -234,13 +275,30 @@
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
- main_template_2 = '''
+ main_template_add_prop = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
- one: updated_name
+ one: my_name
+ two: your_name
+ three: third_name
+
+Outputs:
+ identifier:
+ Value: {Ref: the_nested}
+ value:
+ Value: {'Fn::GetAtt': [the_nested, the_str]}
+'''
+
+ main_template_remove_prop = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_nested:
+ Type: the.yaml
+ Properties:
+ one: my_name
Outputs:
identifier:
@@ -255,6 +313,10 @@
one:
Default: foo
Type: String
+ two:
+ Default: bar
+ Type: String
+
Resources:
NestedResource:
Type: OS::Heat::RandomString
@@ -264,6 +326,7 @@
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
+
prop_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
@@ -277,17 +340,62 @@
NestedResource:
Type: OS::Heat::RandomString
Properties:
+ salt: {Ref: two}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+'''
+
+ prop_add_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: yikes
+ Type: String
+ two:
+ Default: foo
+ Type: String
+ three:
+ Default: bar
+ Type: String
+
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: {Ref: three}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+'''
+
+ prop_remove_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: yikes
+ Type: String
+
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
+
attr_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
+ two:
+ Default: bar
+ Type: String
+
Resources:
NestedResource:
Type: OS::Heat::RandomString
@@ -299,12 +407,17 @@
something_else:
Value: just_a_string
'''
+
content_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
+ two:
+ Default: bar
+ Type: String
+
Resources:
NestedResource:
Type: OS::Heat::RandomString
@@ -320,7 +433,7 @@
('no_changes', dict(template=main_template,
provider=initial_tmpl,
expect=NOCHANGE)),
- ('main_tmpl_change', dict(template=main_template_2,
+ ('main_tmpl_change', dict(template=main_template_change_prop,
provider=initial_tmpl,
expect=UPDATE)),
('provider_change', dict(template=main_template,
@@ -328,6 +441,12 @@
expect=UPDATE)),
('provider_props_change', dict(template=main_template,
provider=prop_change_tmpl,
+ expect=UPDATE)),
+ ('provider_props_add', dict(template=main_template_add_prop,
+ provider=prop_add_tmpl,
+ expect=UPDATE)),
+ ('provider_props_remove', dict(template=main_template_remove_prop,
+ provider=prop_remove_tmpl,
expect=NOCHANGE)),
('provider_attr_change', dict(template=main_template,
provider=attr_change_tmpl,
@@ -445,16 +564,11 @@
return yaml.load(yaml_templ)
def test_abandon(self):
- stack_name = self._stack_rand_name()
- self.client.stacks.create(
- stack_name=stack_name,
+ stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ},
- disable_rollback=True,
+ enable_cleanup=False
)
- stack = self.client.stacks.get(stack_name)
- stack_identifier = '%s/%s' % (stack_name, stack.id)
- self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
info = self.stack_abandon(stack_id=stack_identifier)
self.assertEqual(self._yaml_to_json(self.main_template),
@@ -527,16 +641,10 @@
self.client = self.orchestration_client
def test_check(self):
- stack_name = self._stack_rand_name()
- self.client.stacks.create(
- stack_name=stack_name,
+ stack_identifier = self.stack_create(
template=self.main_template,
- files={'the.yaml': self.nested_templ},
- disable_rollback=True,
+ files={'the.yaml': self.nested_templ}
)
- stack = self.client.stacks.get(stack_name)
- stack_identifier = '%s/%s' % (stack_name, stack.id)
- self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
self.client.actions.check(stack_id=stack_identifier)
self._wait_for_stack_status(stack_identifier, 'CHECK_COMPLETE')
diff --git a/functional/test_update.py b/functional/test_update.py
deleted file mode 100644
index b329ddc..0000000
--- a/functional/test_update.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# 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.
-
-
-from heat_integrationtests.common import test
-
-
-class UpdateStackTest(test.HeatIntegrationTest):
-
- template = '''
-heat_template_version: 2013-05-23
-resources:
- random1:
- type: OS::Heat::RandomString
-'''
- update_template = '''
-heat_template_version: 2013-05-23
-resources:
- random1:
- type: OS::Heat::RandomString
- random2:
- type: OS::Heat::RandomString
-'''
-
- provider_template = '''
-heat_template_version: 2013-05-23
-resources:
- random1:
- type: My::RandomString
-'''
-
- provider_group_template = '''
-heat_template_version: 2013-05-23
-resources:
- random_group:
- type: OS::Heat::ResourceGroup
- properties:
- count: 2
- resource_def:
- type: My::RandomString
-'''
-
- update_userdata_template = '''
-heat_template_version: 2014-10-16
-parameters:
- flavor:
- type: string
- user_data:
- type: string
- image:
- type: string
-
-resources:
- server:
- type: OS::Nova::Server
- properties:
- image: {get_param: image}
- flavor: {get_param: flavor}
- user_data_format: SOFTWARE_CONFIG
- user_data: {get_param: user_data}
-'''
-
- def setUp(self):
- super(UpdateStackTest, self).setUp()
- self.client = self.orchestration_client
-
- def test_stack_update_nochange(self):
- stack_identifier = self.stack_create()
- expected_resources = {'random1': 'OS::Heat::RandomString'}
- self.assertEqual(expected_resources,
- self.list_resources(stack_identifier))
-
- # Update with no changes, resources should be unchanged
- self.update_stack(stack_identifier, self.template)
- self.assertEqual(expected_resources,
- self.list_resources(stack_identifier))
-
- def test_stack_update_add_remove(self):
- stack_identifier = self.stack_create()
- initial_resources = {'random1': 'OS::Heat::RandomString'}
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
-
- # Add one resource via a stack update
- self.update_stack(stack_identifier, self.update_template)
- updated_resources = {'random1': 'OS::Heat::RandomString',
- 'random2': 'OS::Heat::RandomString'}
- self.assertEqual(updated_resources,
- self.list_resources(stack_identifier))
-
- # Then remove it by updating with the original template
- self.update_stack(stack_identifier, self.template)
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
-
- def test_stack_update_provider(self):
- files = {'provider.yaml': self.template}
- env = {'resource_registry':
- {'My::RandomString': 'provider.yaml'}}
- stack_identifier = self.stack_create(
- template=self.provider_template,
- files=files,
- environment=env
- )
-
- initial_resources = {'random1': 'My::RandomString'}
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
-
- # Prove the resource is backed by a nested stack, save the ID
- nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
- 'random1')
- nested_id = nested_identifier.split('/')[-1]
-
- # Then check the expected resources are in the nested stack
- nested_resources = {'random1': 'OS::Heat::RandomString'}
- self.assertEqual(nested_resources,
- self.list_resources(nested_identifier))
-
- # Add one resource via a stack update by changing the nested stack
- files['provider.yaml'] = self.update_template
- self.update_stack(stack_identifier, self.provider_template,
- environment=env, files=files)
-
- # Parent resources should be unchanged and the nested stack
- # should have been updated in-place without replacement
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
- rsrc = self.client.resources.get(stack_identifier, 'random1')
- self.assertEqual(rsrc.physical_resource_id, nested_id)
-
- # Then check the expected resources are in the nested stack
- nested_resources = {'random1': 'OS::Heat::RandomString',
- 'random2': 'OS::Heat::RandomString'}
- self.assertEqual(nested_resources,
- self.list_resources(nested_identifier))
-
- def test_stack_update_provider_group(self):
- '''Test two-level nested update.'''
- # Create a ResourceGroup (which creates a nested stack),
- # containing provider resources (which create a nested
- # stack), thus excercising an update which traverses
- # two levels of nesting.
- files = {'provider.yaml': self.template}
- env = {'resource_registry':
- {'My::RandomString': 'provider.yaml'}}
-
- stack_identifier = self.stack_create(
- template=self.provider_group_template,
- files=files,
- environment=env
- )
-
- initial_resources = {'random_group': 'OS::Heat::ResourceGroup'}
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
-
- # Prove the resource is backed by a nested stack, save the ID
- nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
- 'random_group')
-
- # Then check the expected resources are in the nested stack
- nested_resources = {'0': 'My::RandomString',
- '1': 'My::RandomString'}
- self.assertEqual(nested_resources,
- self.list_resources(nested_identifier))
-
- for n_rsrc in nested_resources:
- rsrc = self.client.resources.get(nested_identifier, n_rsrc)
- provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
- provider_identifier = '%s/%s' % (provider_stack.stack_name,
- provider_stack.id)
- provider_resources = {u'random1': u'OS::Heat::RandomString'}
- self.assertEqual(provider_resources,
- self.list_resources(provider_identifier))
-
- # Add one resource via a stack update by changing the nested stack
- files['provider.yaml'] = self.update_template
- self.update_stack(stack_identifier, self.provider_group_template,
- environment=env, files=files)
-
- # Parent resources should be unchanged and the nested stack
- # should have been updated in-place without replacement
- self.assertEqual(initial_resources,
- self.list_resources(stack_identifier))
-
- # Resource group stack should also be unchanged (but updated)
- nested_stack = self.client.stacks.get(nested_identifier)
- self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status)
- self.assertEqual(nested_resources,
- self.list_resources(nested_identifier))
-
- for n_rsrc in nested_resources:
- rsrc = self.client.resources.get(nested_identifier, n_rsrc)
- provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
- provider_identifier = '%s/%s' % (provider_stack.stack_name,
- provider_stack.id)
- provider_resources = {'random1': 'OS::Heat::RandomString',
- 'random2': 'OS::Heat::RandomString'}
- self.assertEqual(provider_resources,
- self.list_resources(provider_identifier))
-
- def test_stack_update_with_replacing_userdata(self):
- """Confirm that we can update userdata of instance during updating
- stack by the user of member role.
-
- Make sure that a resource that inherites from StackUser can be deleted
- during updating stack.
- """
- if not self.conf.minimal_image_ref:
- raise self.skipException("No minimal image configured to test")
- if not self.conf.minimal_instance_type:
- raise self.skipException("No flavor configured to test")
-
- parms = {'flavor': self.conf.minimal_instance_type,
- 'image': self.conf.minimal_image_ref,
- 'user_data': ''}
- name = self._stack_rand_name()
-
- stack_identifier = self.stack_create(
- stack_name=name,
- template=self.update_userdata_template,
- parameters=parms
- )
-
- parms_updated = parms
- parms_updated['user_data'] = 'two'
- self.update_stack(
- stack_identifier,
- template=self.update_userdata_template,
- parameters=parms_updated)
diff --git a/scenario/templates/test_ceilometer_alarm.yaml b/scenario/templates/test_ceilometer_alarm.yaml
new file mode 100644
index 0000000..01bc790
--- /dev/null
+++ b/scenario/templates/test_ceilometer_alarm.yaml
@@ -0,0 +1,33 @@
+heat_template_version: 2013-05-23
+resources:
+ asg:
+ type: OS::Heat::AutoScalingGroup
+ properties:
+ max_size: 5
+ min_size: 1
+ resource:
+ type: OS::Heat::RandomString
+ scaleup_policy:
+ type: OS::Heat::ScalingPolicy
+ properties:
+ adjustment_type: change_in_capacity
+ auto_scaling_group_id: {get_resource: asg}
+ cooldown: 0
+ scaling_adjustment: 1
+ alarm:
+ type: OS::Ceilometer::Alarm
+ properties:
+ description: Scale-up if the average CPU > 50% for 1 minute
+ meter_name: test_meter
+ statistic: count
+ comparison_operator: ge
+ threshold: 1
+ period: 60
+ evaluation_periods: 1
+ alarm_actions:
+ - {get_attr: [scaleup_policy, alarm_url]}
+ matching_metadata:
+ metadata.metering.stack_id: {get_param: "OS::stack_id"}
+outputs:
+ asg_size:
+ value: {get_attr: [asg, current_size]}
diff --git a/scenario/templates/test_neutron_autoscaling.yaml b/scenario/templates/test_neutron_autoscaling.yaml
index 59aad2c..a34ec43 100644
--- a/scenario/templates/test_neutron_autoscaling.yaml
+++ b/scenario/templates/test_neutron_autoscaling.yaml
@@ -11,7 +11,7 @@
type: string
label: Capacity
description: Auto-scaling group desired capacity
- fixed_subnet_name:
+ fixed_subnet:
type: string
label: fixed subnetwork ID
description: subnetwork ID used for autoscaling
@@ -28,7 +28,7 @@
lb_method: ROUND_ROBIN
name: test_pool
protocol: HTTP
- subnet: { get_param: fixed_subnet_name }
+ subnet: { get_param: fixed_subnet }
vip: {
"description": "Test VIP",
"protocol_port": 80,
@@ -49,6 +49,7 @@
properties:
AvailabilityZones : ["nova"]
LaunchConfigurationName : { get_resource : launch_config }
+ VPCZoneIdentifier: [{ get_param: fixed_subnet }]
MinSize : 1
MaxSize : 5
DesiredCapacity: { get_param: capacity }
diff --git a/scenario/templates/test_neutron_loadbalancer.yaml b/scenario/templates/test_neutron_loadbalancer.yaml
index a7e9f9c..dd659d0 100644
--- a/scenario/templates/test_neutron_loadbalancer.yaml
+++ b/scenario/templates/test_neutron_loadbalancer.yaml
@@ -10,6 +10,8 @@
type: string
image:
type: string
+ network:
+ type: string
private_subnet_id:
type: string
external_network_id:
@@ -17,6 +19,8 @@
port:
type: string
default: '80'
+ timeout:
+ type: number
resources:
sec_group:
@@ -32,6 +36,31 @@
- remote_ip_prefix: 0.0.0.0/0
protocol: icmp
+ wait_condition:
+ type: OS::Heat::WaitCondition
+ properties:
+ handle: { get_resource: wait_condition_handle }
+ count: 2
+ timeout: { get_param: timeout }
+
+ wait_condition_handle:
+ type: OS::Heat::WaitConditionHandle
+
+ config:
+ type: OS::Heat::SoftwareConfig
+ properties:
+ group: ungrouped
+ config:
+ str_replace:
+ template: |
+ #!/bin/bash -v
+ echo $(hostname) > index.html
+ python -m SimpleHTTPServer port &
+ wc_notify --data-binary '{"status": "SUCCESS"}'
+ params:
+ wc_notify: { get_attr: ['wait_condition_handle', 'curl_cli'] }
+ port: { get_param: port }
+
server1:
type: OS::Nova::Server
properties:
@@ -39,18 +68,10 @@
image: { get_param: image }
flavor: { get_param: flavor }
key_name: { get_param: key_name }
+ networks: [{network: {get_param: network} }]
security_groups: [{ get_resource: sec_group }]
- user_data:
- list_join:
- - ''
- - - '#!/bin/bash -v
-
- '
- - 'echo $(hostname) > index.html
-
- '
- - 'python -m SimpleHTTPServer '
- - { get_param: port }
+ user_data_format: SOFTWARE_CONFIG
+ user_data: { get_resource: config }
server2:
type: OS::Nova::Server
@@ -59,18 +80,10 @@
image: { get_param: image }
flavor: { get_param: flavor }
key_name: { get_param: key_name }
+ networks: [{network: {get_param: network} }]
security_groups: [{ get_resource: sec_group }]
- user_data:
- list_join:
- - ''
- - - '#!/bin/bash -v
-
- '
- - 'echo $(hostname) > index.html
-
- '
- - 'python -m SimpleHTTPServer '
- - { get_param: port }
+ user_data_format: SOFTWARE_CONFIG
+ user_data: { get_resource: config }
health_monitor:
type: OS::Neutron::HealthMonitor
@@ -97,9 +110,12 @@
floating_network: { get_param: external_network_id }
port_id:
{ get_attr: [test_pool, vip, 'port_id'] }
+ fixed_ip_address:
+ { get_attr: [test_pool, vip, 'address'] }
LBaaS:
type: OS::Neutron::LoadBalancer
+ depends_on: wait_condition
properties:
pool_id: { get_resource: test_pool }
protocol_port: { get_param: port }
@@ -108,9 +124,9 @@
outputs:
serv1_ip:
- value: {get_attr: [server1, networks, private, 0]}
+ value: {get_attr: [server1, networks, { get_param: network }, 0]}
serv2_ip:
- value: {get_attr: [server2, networks, private, 0]}
+ value: {get_attr: [server2, networks, { get_param: network }, 0]}
vip:
value: {get_attr: [test_pool, vip, address]}
fip:
diff --git a/scenario/test_ceilometer_alarm.py b/scenario/test_ceilometer_alarm.py
new file mode 100644
index 0000000..d8f21fd
--- /dev/null
+++ b/scenario/test_ceilometer_alarm.py
@@ -0,0 +1,57 @@
+# 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.
+
+from oslo_log import log as logging
+
+from heat_integrationtests.common import test
+
+LOG = logging.getLogger(__name__)
+
+
+class CeilometerAlarmTest(test.HeatIntegrationTest):
+ """Class is responsible for testing of ceilometer usage."""
+ def setUp(self):
+ super(CeilometerAlarmTest, self).setUp()
+ self.client = self.orchestration_client
+ self.template = self._load_template(__file__,
+ 'test_ceilometer_alarm.yaml',
+ 'templates')
+
+ def check_instance_count(self, stack_identifier, expected):
+ stack = self.client.stacks.get(stack_identifier)
+ actual = self._stack_output(stack, 'asg_size')
+ if actual != expected:
+ LOG.warn('check_instance_count exp:%d, act:%s' % (expected,
+ actual))
+ return actual == expected
+
+ def test_alarm(self):
+ """Confirm we can create an alarm and trigger it."""
+
+ # 1. create the stack
+ stack_identifier = self.stack_create(template=self.template)
+
+ # 2. send ceilometer a metric (should cause the alarm to fire)
+ sample = {}
+ sample['counter_type'] = 'gauge'
+ sample['counter_name'] = 'test_meter'
+ sample['counter_volume'] = 1
+ sample['counter_unit'] = 'count'
+ sample['resource_metadata'] = {'metering.stack_id':
+ stack_identifier.split('/')[-1]}
+ sample['resource_id'] = 'shouldnt_matter'
+ self.metering_client.samples.create(**sample)
+
+ # 3. confirm we get a scaleup.
+ # Note: there is little point waiting more than 60s+time to scale up.
+ self.assertTrue(test.call_until_true(
+ 120, 2, self.check_instance_count, stack_identifier, 2))
diff --git a/scenario/test_neutron_autoscaling.py b/scenario/test_neutron_autoscaling.py
index 9bbfbab..e7aae19 100644
--- a/scenario/test_neutron_autoscaling.py
+++ b/scenario/test_neutron_autoscaling.py
@@ -40,7 +40,7 @@
"image_id": self.conf.minimal_image_ref,
"capacity": "1",
"instance_type": self.conf.minimal_instance_type,
- "fixed_subnet_name": self.conf.fixed_subnet_name,
+ "fixed_subnet": self.net['subnets'][0],
}
# Launch stack
@@ -50,8 +50,10 @@
)
# Check number of members
- members = self.network_client.list_members()
- self.assertEqual(1, len(members["members"]))
+ pool_resource = self.client.resources.get(stack_id, 'test_pool')
+ pool_members = self.network_client.list_members(
+ pool_id=pool_resource.physical_resource_id)['members']
+ self.assertEqual(1, len(pool_members))
# Increase desired capacity and update the stack
template = self._load_template(
@@ -65,5 +67,6 @@
)
# Check number of members
- upd_members = self.network_client.list_members()
- self.assertEqual(2, len(upd_members["members"]))
+ pool_members = self.network_client.list_members(
+ pool_id=pool_resource.physical_resource_id)['members']
+ self.assertEqual(2, len(pool_members))
diff --git a/scenario/test_neutron_loadbalancer.py b/scenario/test_neutron_loadbalancer.py
index bbe85bc..d8e0197 100644
--- a/scenario/test_neutron_loadbalancer.py
+++ b/scenario/test_neutron_loadbalancer.py
@@ -12,7 +12,8 @@
# under the License.
import time
-import urllib
+
+from six.moves import urllib
from heat_integrationtests.scenario import scenario_base
@@ -31,7 +32,7 @@
resp = set()
for count in range(10):
time.sleep(1)
- resp.add(urllib.urlopen('http://%s/' % ip).read())
+ resp.add(urllib.request.urlopen('http://%s/' % ip).read())
self.assertEqual(expected_resp, resp)
@@ -54,8 +55,10 @@
'key_name': self.keypair_name,
'flavor': self.conf.minimal_instance_type,
'image': self.conf.image_ref,
+ 'network': self.net['name'],
'private_subnet_id': self.net['subnets'][0],
- 'external_network_id': self.public_net['id']
+ 'external_network_id': self.public_net['id'],
+ 'timeout': self.conf.build_timeout
}
# Launch stack
@@ -69,8 +72,7 @@
vip = self._stack_output(stack, 'vip')
server1_ip = self._stack_output(stack, 'serv1_ip')
server2_ip = self._stack_output(stack, 'serv2_ip')
-
- # Check connection and info about received responces
+ # Check connection and info about received responses
self.check_connectivity(server1_ip)
self.collect_responses(server1_ip, {'server1\n'})
diff --git a/scenario/test_server_cfn_init.py b/scenario/test_server_cfn_init.py
index a49606c..b5b1e67 100644
--- a/scenario/test_server_cfn_init.py
+++ b/scenario/test_server_cfn_init.py
@@ -47,9 +47,6 @@
# logs to be compared
self._log_console_output(servers=[server])
- # Check stack status
- self._wait_for_stack_status(sid, 'CREATE_COMPLETE')
-
stack = self.client.stacks.get(sid)
# This is an assert of great significance, as it means the following
@@ -94,11 +91,11 @@
via generated keypair.
"""
parameters = {
- "key_name": self.keypair_name,
- "flavor": self.conf.instance_type,
- "image": self.conf.image_ref,
- "timeout": self.conf.build_timeout,
- "subnet": self.net["subnets"][0],
+ 'key_name': self.keypair_name,
+ 'flavor': self.conf.instance_type,
+ 'image': self.conf.image_ref,
+ 'timeout': self.conf.build_timeout,
+ 'subnet': self.net['subnets'][0],
}
# Launch stack
diff --git a/scenario/test_server_software_config.py b/scenario/test_server_software_config.py
index 8b614c1..19fd1a8 100644
--- a/scenario/test_server_software_config.py
+++ b/scenario/test_server_software_config.py
@@ -84,9 +84,6 @@
# logs to be compared
self._log_console_output(servers=[server])
- # Check that stack was fully created
- self._wait_for_stack_status(sid, 'CREATE_COMPLETE')
-
complete_server_metadata = self.client.resources.metadata(
sid, 'server')
diff --git a/scenario/test_volumes.py b/scenario/test_volumes.py
index 79d4931..7562304 100644
--- a/scenario/test_volumes.py
+++ b/scenario/test_volumes.py
@@ -61,8 +61,7 @@
# Delete the stack and ensure a backup is created for volume_id
# but the volume itself is gone
- self.client.stacks.delete(stack_id)
- self._wait_for_stack_status(stack_id, 'DELETE_COMPLETE')
+ self._stack_delete(stack_id)
self.assertRaises(cinder_exceptions.NotFound,
self.volume_client.volumes.get,
volume_id)
@@ -97,8 +96,7 @@
testfile_data)
# Delete the stack and ensure the volume is gone
- self.client.stacks.delete(stack_identifier2)
- self._wait_for_stack_status(stack_identifier2, 'DELETE_COMPLETE')
+ self._stack_delete(stack_identifier2)
self.assertRaises(cinder_exceptions.NotFound,
self.volume_client.volumes.get,
volume_id2)