Merge "test_resource for functional tests"
This commit is contained in:
commit
af053589f7
@ -276,7 +276,8 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
||||
|
||||
def update_stack(self, stack_identifier, template, environment=None,
|
||||
files=None, parameters=None,
|
||||
expected_status='UPDATE_COMPLETE'):
|
||||
expected_status='UPDATE_COMPLETE',
|
||||
disable_rollback=True):
|
||||
env = environment or {}
|
||||
env_files = files or {}
|
||||
parameters = parameters or {}
|
||||
@ -286,11 +287,19 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
||||
stack_name=stack_name,
|
||||
template=template,
|
||||
files=env_files,
|
||||
disable_rollback=True,
|
||||
disable_rollback=disable_rollback,
|
||||
parameters=parameters,
|
||||
environment=env
|
||||
)
|
||||
self._wait_for_stack_status(stack_identifier, expected_status)
|
||||
kwargs = {'stack_identifier': stack_identifier,
|
||||
'status': expected_status}
|
||||
if expected_status in ['ROLLBACK_COMPLETE']:
|
||||
self.addCleanup(self.client.stacks.delete, stack_name)
|
||||
# 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):
|
||||
@ -334,7 +343,7 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
||||
|
||||
def stack_create(self, stack_name=None, template=None, files=None,
|
||||
parameters=None, environment=None,
|
||||
expected_status='CREATE_COMPLETE'):
|
||||
expected_status='CREATE_COMPLETE', disable_rollback=True):
|
||||
name = stack_name or self._stack_rand_name()
|
||||
templ = template or self.template
|
||||
templ_files = files or {}
|
||||
@ -344,16 +353,23 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
||||
stack_name=name,
|
||||
template=templ,
|
||||
files=templ_files,
|
||||
disable_rollback=True,
|
||||
disable_rollback=disable_rollback,
|
||||
parameters=params,
|
||||
environment=env
|
||||
)
|
||||
self.addCleanup(self.client.stacks.delete, name)
|
||||
if expected_status not in ['ROLLBACK_COMPLETE']:
|
||||
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,
|
||||
|
134
heat_integrationtests/common/test_resources/test_resource.py
Normal file
134
heat_integrationtests/common/test_resources/test_resource.py
Normal file
@ -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,
|
||||
}
|
386
heat_integrationtests/functional/test_create_update.py
Normal file
386
heat_integrationtests/functional/test_create_update.py
Normal file
@ -0,0 +1,386 @@
|
||||
# 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
|
||||
|
||||
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):
|
||||
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,
|
||||
'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)
|
@ -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)
|
@ -20,3 +20,4 @@ echo "HEAT_ENABLE_ADOPT_ABANDON=True" >> $localrc_path
|
||||
echo -e '[[post-config|$HEAT_CONF]]\n[DEFAULT]\n' >> $localconf
|
||||
echo -e 'notification_driver=messagingv2\n' >> $localconf
|
||||
echo -e 'num_engine_workers=2\n' >> $localconf
|
||||
echo -e 'plugin_dirs=$HEAT_DIR/heat_integrationtests/common/test_resources\n' >> $localconf
|
||||
|
Loading…
Reference in New Issue
Block a user