test_resource for functional tests
Introduces new resource plugin to be able to test various functional test cases which includes Rollback, Update In Place, Concurrent Update and can be customised furthur based on the needs. Co-Authored-by: Anant Patil <anant.patil@hp.com> Change-Id: I3b8a1d2928553c87abaac81ee687e0faa85c9c5e
This commit is contained in:
parent
45043709fd
commit
5ea948ce9f
@ -276,7 +276,8 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
|||||||
|
|
||||||
def update_stack(self, stack_identifier, template, environment=None,
|
def update_stack(self, stack_identifier, template, environment=None,
|
||||||
files=None, parameters=None,
|
files=None, parameters=None,
|
||||||
expected_status='UPDATE_COMPLETE'):
|
expected_status='UPDATE_COMPLETE',
|
||||||
|
disable_rollback=True):
|
||||||
env = environment or {}
|
env = environment or {}
|
||||||
env_files = files or {}
|
env_files = files or {}
|
||||||
parameters = parameters or {}
|
parameters = parameters or {}
|
||||||
@ -286,11 +287,19 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
|||||||
stack_name=stack_name,
|
stack_name=stack_name,
|
||||||
template=template,
|
template=template,
|
||||||
files=env_files,
|
files=env_files,
|
||||||
disable_rollback=True,
|
disable_rollback=disable_rollback,
|
||||||
parameters=parameters,
|
parameters=parameters,
|
||||||
environment=env
|
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,
|
def assert_resource_is_a_stack(self, stack_identifier, res_name,
|
||||||
wait=False):
|
wait=False):
|
||||||
@ -334,7 +343,7 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
|||||||
|
|
||||||
def stack_create(self, stack_name=None, template=None, files=None,
|
def stack_create(self, stack_name=None, template=None, files=None,
|
||||||
parameters=None, environment=None,
|
parameters=None, environment=None,
|
||||||
expected_status='CREATE_COMPLETE'):
|
expected_status='CREATE_COMPLETE', disable_rollback=True):
|
||||||
name = stack_name or self._stack_rand_name()
|
name = stack_name or self._stack_rand_name()
|
||||||
templ = template or self.template
|
templ = template or self.template
|
||||||
templ_files = files or {}
|
templ_files = files or {}
|
||||||
@ -344,16 +353,23 @@ class HeatIntegrationTest(testscenarios.WithScenarios,
|
|||||||
stack_name=name,
|
stack_name=name,
|
||||||
template=templ,
|
template=templ,
|
||||||
files=templ_files,
|
files=templ_files,
|
||||||
disable_rollback=True,
|
disable_rollback=disable_rollback,
|
||||||
parameters=params,
|
parameters=params,
|
||||||
environment=env
|
environment=env
|
||||||
)
|
)
|
||||||
|
if expected_status not in ['ROLLBACK_COMPLETE']:
|
||||||
self.addCleanup(self.client.stacks.delete, name)
|
self.addCleanup(self.client.stacks.delete, name)
|
||||||
|
|
||||||
stack = self.client.stacks.get(name)
|
stack = self.client.stacks.get(name)
|
||||||
stack_identifier = '%s/%s' % (name, stack.id)
|
stack_identifier = '%s/%s' % (name, stack.id)
|
||||||
|
kwargs = {'stack_identifier': stack_identifier,
|
||||||
|
'status': expected_status}
|
||||||
if 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
|
return stack_identifier
|
||||||
|
|
||||||
def stack_adopt(self, stack_name=None, files=None,
|
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 '[[post-config|$HEAT_CONF]]\n[DEFAULT]\n' >> $localconf
|
||||||
echo -e 'notification_driver=messagingv2\n' >> $localconf
|
echo -e 'notification_driver=messagingv2\n' >> $localconf
|
||||||
echo -e 'num_engine_workers=2\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