Clear heat inner functionaltests

This patch remove regression tests from heat tempest plugin. We
already maintain those tests in heat.

Also remove AutoscalingLoadBalancerTest and ReloadOnSighupTest
since we don't test anymore.

Depends-On: Ief31dc961bc108e2863119598dfb16581a38e9cf
Change-Id: I022077c92bc10e908c7fe549ed555ad0194e0704
This commit is contained in:
ricolin 2017-12-22 15:18:09 +08:00
parent 33f2850867
commit 17d868572d
32 changed files with 2 additions and 7332 deletions

View File

@ -122,9 +122,6 @@ HeatGroup = [
default=60,
help="Timeout in seconds to wait for output from ssh "
"channel."),
cfg.IntOpt('tenant_network_mask_bits',
default=28,
help="The mask bits for tenant ipv4 subnets"),
cfg.BoolOpt('skip_scenario_tests',
default=False,
help="Skip all scenario tests"),
@ -153,15 +150,6 @@ HeatGroup = [
default=120,
help="Timeout in seconds to wait for connectivity to "
"server."),
cfg.IntOpt('sighup_timeout',
default=120,
help="Timeout in seconds to wait for adding or removing child "
"process after receiving of sighup signal"),
cfg.IntOpt('sighup_config_edit_retries',
default=10,
help='Count of retries to edit config file during sighup. If '
'another worker already edit config file, file can be '
'busy, so need to wait and try edit file again.'),
cfg.StrOpt('heat_config_notify_script',
default=('heat-config-notify'),
help="Path to the script heat-config-notify"),

View File

@ -1,101 +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_tempest_plugin.tests.functional import functional_base
# Simple stack
test_template = {
'heat_template_version': '2013-05-23',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1'
}
}
}
}
# Nested stack
rsg_template = {
'heat_template_version': '2013-05-23',
'resources': {
'random_group': {
'type': 'OS::Heat::ResourceGroup',
'properties': {
'count': 2,
'resource_def': {
'type': 'OS::Heat::RandomString',
'properties': {
'length': 30,
'salt': 'initial'
}
}
}
}
}
}
class AdminActionsTest(functional_base.FunctionalTestsBase):
def setUp(self):
super(AdminActionsTest, self).setUp()
if not self.conf.admin_username or not self.conf.admin_password:
self.skipTest('No admin creds found, skipping')
def create_stack_setup_admin_client(self, template=test_template):
# Create the stack with the default user
self.stack_identifier = self.stack_create(template=template)
# Setup admin clients
self.setup_clients_for_admin()
def test_admin_simple_stack_actions(self):
self.create_stack_setup_admin_client()
updated_template = test_template.copy()
props = updated_template['resources']['test1']['properties']
props['value'] = 'new_value'
# Update, suspend and resume stack
self.update_stack(self.stack_identifier,
template=updated_template)
self.stack_suspend(self.stack_identifier)
self.stack_resume(self.stack_identifier)
# List stack resources
initial_resources = {'test1': 'OS::Heat::TestResource'}
self.assertEqual(initial_resources,
self.list_resources(self.stack_identifier))
# Delete stack
self._stack_delete(self.stack_identifier)
def test_admin_complex_stack_actions(self):
self.create_stack_setup_admin_client(template=rsg_template)
updated_template = rsg_template.copy()
props = updated_template['resources']['random_group']['properties']
props['count'] = 3
# Update, suspend and resume stack
self.update_stack(self.stack_identifier,
template=updated_template)
self.stack_suspend(self.stack_identifier)
self.stack_resume(self.stack_identifier)
# List stack resources
resources = {'random_group': 'OS::Heat::ResourceGroup'}
self.assertEqual(resources,
self.list_resources(self.stack_identifier))
# Delete stack
self._stack_delete(self.stack_identifier)

View File

@ -1,752 +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.
import copy
import json
from heatclient import exc
from oslo_log import log as logging
import six
from testtools import matchers
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
LOG = logging.getLogger(__name__)
class AutoscalingGroupTest(functional_base.FunctionalTestsBase):
template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to create multiple instances.",
"Parameters" : {"size": {"Type": "String", "Default": "1"},
"AZ": {"Type": "String", "Default": "nova"},
"image": {"Type": "String"},
"flavor": {"Type": "String"},
"user_data": {"Type": "String", "Default": "jsconfig data"}},
"Resources": {
"JobServerGroup": {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AvailabilityZones" : [{"Ref": "AZ"}],
"LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
"MinSize" : {"Ref": "size"},
"MaxSize" : "20"
}
},
"JobServerConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Metadata": {"foo": "bar"},
"Properties": {
"ImageId" : {"Ref": "image"},
"InstanceType" : {"Ref": "flavor"},
"SecurityGroups" : [ "sg-1" ],
"UserData" : {"Ref": "user_data"}
}
}
},
"Outputs": {
"InstanceList": {"Value": {
"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}},
"JobServerConfigRef": {"Value": {
"Ref": "JobServerConfig"}}
}
}
'''
instance_template = '''
heat_template_version: 2013-05-23
parameters:
ImageId: {type: string}
InstanceType: {type: string}
SecurityGroups: {type: comma_delimited_list}
UserData: {type: string}
Tags: {type: comma_delimited_list, default: "x,y"}
resources:
random1:
type: OS::Heat::RandomString
properties:
salt: {get_param: UserData}
outputs:
PublicIp: {value: {get_attr: [random1, value]}}
AvailabilityZone: {value: 'not-used11'}
PrivateDnsName: {value: 'not-used12'}
PublicDnsName: {value: 'not-used13'}
PrivateIp: {value: 'not-used14'}
'''
# This is designed to fail.
bad_instance_template = '''
heat_template_version: 2013-05-23
parameters:
ImageId: {type: string}
InstanceType: {type: string}
SecurityGroups: {type: comma_delimited_list}
UserData: {type: string}
Tags: {type: comma_delimited_list, default: "x,y"}
resources:
random1:
type: OS::Heat::RandomString
depends_on: waiter
ready_poster:
type: AWS::CloudFormation::WaitConditionHandle
waiter:
type: AWS::CloudFormation::WaitCondition
properties:
Handle: {get_resource: ready_poster}
Timeout: 1
outputs:
PublicIp:
value: {get_attr: [random1, value]}
'''
def setUp(self):
super(AutoscalingGroupTest, self).setUp()
if not self.conf.minimal_image_ref:
raise self.skipException("No minimal image configured to test")
if not self.conf.instance_type:
raise self.skipException("No flavor configured to test")
def assert_instance_count(self, stack, expected_count):
inst_list = self._stack_output(stack, 'InstanceList')
self.assertEqual(expected_count, len(inst_list.split(',')))
def _assert_instance_state(self, nested_identifier,
num_complete, num_failed):
for res in self.client.resources.list(nested_identifier):
if 'COMPLETE' in res.resource_status:
num_complete = num_complete - 1
elif 'FAILED' in res.resource_status:
num_failed = num_failed - 1
self.assertEqual(0, num_failed)
self.assertEqual(0, num_complete)
class AutoscalingGroupBasicTest(AutoscalingGroupTest):
def test_basic_create_works(self):
"""Make sure the working case is good.
Note this combines test_override_aws_ec2_instance into this test as
well, which is:
If AWS::EC2::Instance is overridden, AutoScalingGroup will
automatically use that overridden resource type.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 4,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files, environment=env)
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 4)
def test_size_updates_work(self):
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 2)
# Increase min size to 5
env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 5,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 5)
def test_update_group_replace(self):
"""Test case for ensuring non-updatable props case a replacement.
Make sure that during a group update the non-updatable
properties cause a replacement.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry':
{'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': '1',
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
orig_asg_id = rsrc.physical_resource_id
env2 = {'resource_registry':
{'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': '1',
'AZ': 'wibble',
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type,
'user_data': 'new data'}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
# replacement will cause the resource physical_resource_id to change.
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
def test_create_instance_error_causes_group_error(self):
"""Test create failing a resource in the instance group.
If a resource in an instance group fails to be created, the instance
group itself will fail and the broken inner resource will remain.
"""
stack_name = self._stack_rand_name()
files = {'provider.yaml': self.bad_instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
self.client.stacks.create(
stack_name=stack_name,
template=self.template,
files=files,
disable_rollback=True,
parameters={},
environment=env
)
self.addCleanup(self._stack_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_FAILED')
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
self._assert_instance_state(nested_ident, 0, 2)
def test_update_instance_error_causes_group_error(self):
"""Test update failing a resource in the instance group.
If a resource in an instance group fails to be created during an
update, the instance group itself will fail and the broken inner
resource will remain.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 2)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
self._assert_instance_state(nested_ident, 2, 0)
initial_list = [res.resource_name
for res in self.client.resources.list(nested_ident)]
env['parameters']['size'] = 3
files2 = {'provider.yaml': self.bad_instance_template}
self.client.stacks.update(
stack_id=stack_identifier,
template=self.template,
files=files2,
disable_rollback=True,
parameters={},
environment=env
)
self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
# assert that there are 3 bad instances
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# 2 resources should be in update failed, and one create failed.
for res in self.client.resources.list(nested_ident):
if res.resource_name in initial_list:
self._wait_for_resource_status(nested_ident,
res.resource_name,
'UPDATE_FAILED')
else:
self._wait_for_resource_status(nested_ident,
res.resource_name,
'CREATE_FAILED')
def test_group_suspend_resume(self):
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 4,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files, environment=env)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
self.stack_suspend(stack_identifier)
self._wait_for_all_resource_status(nested_ident, 'SUSPEND_COMPLETE')
self.stack_resume(stack_identifier)
self._wait_for_all_resource_status(nested_ident, 'RESUME_COMPLETE')
class AutoscalingGroupUpdatePolicyTest(AutoscalingGroupTest):
def ig_tmpl_with_updt_policy(self):
templ = json.loads(copy.deepcopy(self.template))
up = {"AutoScalingRollingUpdate": {
"MinInstancesInService": "1",
"MaxBatchSize": "2",
"PauseTime": "PT1S"}}
templ['Resources']['JobServerGroup']['UpdatePolicy'] = up
return templ
def update_instance_group(self, updt_template,
num_updates_expected_on_updt,
num_creates_expected_on_updt,
num_deletes_expected_on_updt):
# setup stack from the initial template
files = {'provider.yaml': self.instance_template}
size = 10
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': size,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_name = self._stack_rand_name()
stack_identifier = self.stack_create(
stack_name=stack_name,
template=self.ig_tmpl_with_updt_policy(),
files=files,
environment=env)
stack = self.client.stacks.get(stack_identifier)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# test that physical resource name of launch configuration is used
conf_name = self._stack_output(stack, 'JobServerConfigRef')
conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name
self.assertThat(conf_name,
matchers.MatchesRegex(conf_name_pattern))
# test the number of instances created
self.assert_instance_count(stack, size)
# saves info from initial list of instances for comparison later
init_instances = self.client.resources.list(nested_ident)
init_names = [inst.resource_name for inst in init_instances]
# test stack update
self.update_stack(stack_identifier, updt_template,
environment=env, files=files)
updt_stack = self.client.stacks.get(stack_identifier)
# test that the launch configuration is replaced
updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef')
self.assertThat(updt_conf_name,
matchers.MatchesRegex(conf_name_pattern))
self.assertNotEqual(conf_name, updt_conf_name)
# test that the group size are the same
updt_instances = self.client.resources.list(nested_ident)
updt_names = [inst.resource_name for inst in updt_instances]
self.assertEqual(len(init_names), len(updt_names))
for res in updt_instances:
self.assertEqual('UPDATE_COMPLETE', res.resource_status)
# test that the appropriate number of instance names are the same
matched_names = set(updt_names) & set(init_names)
self.assertEqual(num_updates_expected_on_updt, len(matched_names))
# test that the appropriate number of new instances are created
self.assertEqual(num_creates_expected_on_updt,
len(set(updt_names) - set(init_names)))
# test that the appropriate number of instances are deleted
self.assertEqual(num_deletes_expected_on_updt,
len(set(init_names) - set(updt_names)))
# test that the older instances are the ones being deleted
if num_deletes_expected_on_updt > 0:
deletes_expected = init_names[:num_deletes_expected_on_updt]
self.assertNotIn(deletes_expected, updt_names)
def test_instance_group_update_replace(self):
"""Test simple update replace.
Test update replace with no conflict in batch size and minimum
instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
grp = updt_template['Resources']['JobServerGroup']
policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '1'
policy['MaxBatchSize'] = '3'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=10,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0)
def test_instance_group_update_replace_with_adjusted_capacity(self):
"""Test update replace with capacity adjustment.
Test update replace with capacity adjustment due to conflict in batch
size and minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
grp = updt_template['Resources']['JobServerGroup']
policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '8'
policy['MaxBatchSize'] = '4'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=8,
num_creates_expected_on_updt=2,
num_deletes_expected_on_updt=2)
def test_instance_group_update_replace_huge_batch_size(self):
"""Test update replace with a huge batch size."""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '0'
policy['MaxBatchSize'] = '20'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=10,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0)
def test_instance_group_update_replace_huge_min_in_service(self):
"""Update replace with huge number of minimum instances in service."""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '20'
policy['MaxBatchSize'] = '1'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=9,
num_creates_expected_on_updt=1,
num_deletes_expected_on_updt=1)
def test_instance_group_update_no_replace(self):
"""Test simple update only and no replace.
Test simple update only and no replace (i.e. updated instance flavor
in Launch Configuration) with no conflict in batch size and
minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '1'
policy['MaxBatchSize'] = '3'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['InstanceType'] = self.conf.minimal_instance_type
self.update_instance_group(updt_template,
num_updates_expected_on_updt=10,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0)
def test_instance_group_update_no_replace_with_adjusted_capacity(self):
"""Test update only and no replace with capacity adjustment.
Test update only and no replace (i.e. updated instance flavor in
Launch Configuration) with capacity adjustment due to conflict in
batch size and minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
policy['MinInstancesInService'] = '8'
policy['MaxBatchSize'] = '4'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['InstanceType'] = self.conf.minimal_instance_type
self.update_instance_group(updt_template,
num_updates_expected_on_updt=8,
num_creates_expected_on_updt=2,
num_deletes_expected_on_updt=2)
class AutoScalingSignalTest(AutoscalingGroupTest):
template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to create multiple instances.",
"Parameters" : {"size": {"Type": "String", "Default": "1"},
"AZ": {"Type": "String", "Default": "nova"},
"image": {"Type": "String"},
"flavor": {"Type": "String"}},
"Resources": {
"custom_lb": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {"Ref": "image"},
"InstanceType": {"Ref": "flavor"},
"UserData": "foo",
"SecurityGroups": [ "sg-1" ],
"Tags": []
},
"Metadata": {
"IPs": {"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}
}
},
"JobServerGroup": {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AvailabilityZones" : [{"Ref": "AZ"}],
"LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
"DesiredCapacity" : {"Ref": "size"},
"MinSize" : "0",
"MaxSize" : "20"
}
},
"JobServerConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Metadata": {"foo": "bar"},
"Properties": {
"ImageId" : {"Ref": "image"},
"InstanceType" : {"Ref": "flavor"},
"SecurityGroups" : [ "sg-1" ],
"UserData" : "jsconfig data"
}
},
"ScaleUpPolicy" : {
"Type" : "AWS::AutoScaling::ScalingPolicy",
"Properties" : {
"AdjustmentType" : "ChangeInCapacity",
"AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
"Cooldown" : "0",
"ScalingAdjustment": "1"
}
},
"ScaleDownPolicy" : {
"Type" : "AWS::AutoScaling::ScalingPolicy",
"Properties" : {
"AdjustmentType" : "ChangeInCapacity",
"AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
"Cooldown" : "0",
"ScalingAdjustment" : "-2"
}
}
},
"Outputs": {
"InstanceList": {"Value": {
"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}
}
}
'''
lb_template = '''
heat_template_version: 2013-05-23
parameters:
ImageId: {type: string}
InstanceType: {type: string}
SecurityGroups: {type: comma_delimited_list}
UserData: {type: string}
Tags: {type: comma_delimited_list, default: "x,y"}
resources:
outputs:
PublicIp: {value: "not-used"}
AvailabilityZone: {value: 'not-used1'}
PrivateDnsName: {value: 'not-used2'}
PublicDnsName: {value: 'not-used3'}
PrivateIp: {value: 'not-used4'}
'''
def setUp(self):
super(AutoScalingSignalTest, self).setUp()
self.build_timeout = self.conf.build_timeout
self.build_interval = self.conf.build_interval
self.files = {'provider.yaml': self.instance_template,
'lb.yaml': self.lb_template}
self.env = {'resource_registry':
{'resources':
{'custom_lb': {'AWS::EC2::Instance': 'lb.yaml'}},
'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
def check_instance_count(self, stack_identifier, expected):
md = self.client.resources.metadata(stack_identifier, 'custom_lb')
actual_md = len(md['IPs'].split(','))
if actual_md != expected:
LOG.warning('check_instance_count exp:%d, meta:%s' % (expected,
md['IPs']))
return False
stack = self.client.stacks.get(stack_identifier)
inst_list = self._stack_output(stack, 'InstanceList')
actual = len(inst_list.split(','))
if actual != expected:
LOG.warning('check_instance_count exp:%d, act:%s' % (expected,
inst_list))
return actual == expected
def test_scaling_meta_update(self):
"""Use heatclient to signal the up and down policy.
Then confirm that the metadata in the custom_lb is updated each
time.
"""
stack_identifier = self.stack_create(template=self.template,
files=self.files,
environment=self.env)
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 2))
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# Scale up one, Trigger alarm
self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 3))
# Scale down two, Trigger alarm
self.client.resources.signal(stack_identifier, 'ScaleDownPolicy')
self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 1))
def test_signal_with_policy_update(self):
"""Prove that an updated policy is used in the next signal."""
stack_identifier = self.stack_create(template=self.template,
files=self.files,
environment=self.env)
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 2))
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# Scale up one, Trigger alarm
self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 3))
# increase the adjustment to "+2" and remove the DesiredCapacity
# so we don't go from 3 to 2.
new_template = self.template.replace(
'"ScalingAdjustment": "1"',
'"ScalingAdjustment": "2"').replace(
'"DesiredCapacity" : {"Ref": "size"},', '')
self.update_stack(stack_identifier, template=new_template,
environment=self.env, files=self.files)
# Scale up two, Trigger alarm
self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 5))
def test_signal_during_suspend(self):
"""Prove that a signal will fail when the stack is in suspend."""
stack_identifier = self.stack_create(template=self.template,
files=self.files,
environment=self.env)
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 2))
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# suspend the top level stack.
self.client.actions.suspend(stack_id=stack_identifier)
# Wait for stack to reach SUSPEND_COMPLETE
self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
# Send a signal and an exception will raise
ex = self.assertRaises(exc.BadRequest,
self.client.resources.signal,
stack_identifier, 'ScaleUpPolicy')
error_msg = 'Signal resource during SUSPEND is not supported'
self.assertIn(error_msg, six.text_type(ex))
ev = self.wait_for_event_with_reason(
stack_identifier,
reason='Cannot signal resource during SUSPEND',
rsrc_name='ScaleUpPolicy')
self.assertEqual('SUSPEND_COMPLETE', ev[0].resource_status)
# still SUSPEND_COMPLETE (not gone to UPDATE_COMPLETE)
self._wait_for_stack_status(nested_ident, 'SUSPEND_COMPLETE')
self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
# still 2 instances.
self.assertTrue(test.call_until_true(
self.build_timeout, self.build_interval,
self.check_instance_count, stack_identifier, 2))

View File

@ -1,201 +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.
import hashlib
import json
import random
from six.moves.urllib import parse
from swiftclient import utils as swiftclient_utils
import yaml
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
class AwsStackTest(functional_base.FunctionalTestsBase):
test_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: the.yaml
Parameters:
KeyName: foo
Outputs:
output_foo:
Value: {"Fn::GetAtt": [the_nested, Outputs.Foo]}
'''
nested_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
KeyName:
Type: String
Outputs:
Foo:
Value: bar
'''
update_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
KeyName:
Type: String
Outputs:
Foo:
Value: foo
'''
nested_with_res_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
KeyName:
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Outputs:
Foo:
Value: {"Fn::GetAtt": [NestedResource, value]}
'''
def setUp(self):
super(AwsStackTest, self).setUp()
if not self.is_service_available('object-store'):
self.skipTest('object-store service not available, skipping')
self.object_container_name = test.rand_name()
self.project_id = self.identity_client.project_id
self.swift_key = hashlib.sha224(
str(random.getrandbits(256)).encode('ascii')).hexdigest()[:32]
key_header = 'x-container-meta-temp-url-key'
self.object_client.put_container(self.object_container_name,
{key_header: self.swift_key})
self.addCleanup(self.object_client.delete_container,
self.object_container_name)
def publish_template(self, contents, cleanup=True):
oc = self.object_client
# post the object
oc.put_object(self.object_container_name, 'template.yaml', contents)
if cleanup:
self.addCleanup(oc.delete_object,
self.object_container_name,
'template.yaml')
path = '/v1/AUTH_%s/%s/%s' % (self.project_id,
self.object_container_name,
'template.yaml')
timeout = self.conf.build_timeout * 10
tempurl = swiftclient_utils.generate_temp_url(path, timeout,
self.swift_key, 'GET')
sw_url = parse.urlparse(oc.url)
return '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl)
def test_nested_stack_create(self):
url = self.publish_template(self.nested_template)
self.template = self.test_template.replace('the.yaml', url)
stack_identifier = self.stack_create(template=self.template)
stack = self.client.stacks.get(stack_identifier)
self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
def test_nested_stack_create_with_timeout(self):
url = self.publish_template(self.nested_template)
self.template = self.test_template.replace('the.yaml', url)
timeout_template = yaml.safe_load(self.template)
props = timeout_template['Resources']['the_nested']['Properties']
props['TimeoutInMinutes'] = '50'
stack_identifier = self.stack_create(
template=timeout_template)
stack = self.client.stacks.get(stack_identifier)
self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
def test_nested_stack_adopt_ok(self):
url = self.publish_template(self.nested_with_res_template)
self.template = self.test_template.replace('the.yaml', url)
adopt_data = {
"resources": {
"the_nested": {
"resource_id": "test-res-id",
"resources": {
"NestedResource": {
"type": "OS::Heat::RandomString",
"resource_id": "test-nested-res-id",
"resource_data": {"value": "goopie"}
}
}
}
},
"environment": {"parameters": {}},
"template": yaml.safe_load(self.template)
}
stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data))
self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('goopie', self._stack_output(stack, 'output_foo'))
def test_nested_stack_adopt_fail(self):
url = self.publish_template(self.nested_with_res_template)
self.template = self.test_template.replace('the.yaml', url)
adopt_data = {
"resources": {
"the_nested": {
"resource_id": "test-res-id",
"resources": {
}
}
},
"environment": {"parameters": {}},
"template": yaml.safe_load(self.template)
}
stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data),
wait_for_status='ADOPT_FAILED')
rsrc = self.client.resources.get(stack_identifier, 'the_nested')
self.assertEqual('ADOPT_FAILED', rsrc.resource_status)
def test_nested_stack_update(self):
url = self.publish_template(self.nested_template)
self.template = self.test_template.replace('the.yaml', url)
stack_identifier = self.stack_create(template=self.template)
original_nested_id = self.assert_resource_is_a_stack(
stack_identifier, 'the_nested')
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
new_template = yaml.safe_load(self.template)
props = new_template['Resources']['the_nested']['Properties']
props['TemplateURL'] = self.publish_template(self.update_template,
cleanup=False)
self.update_stack(stack_identifier, new_template)
# Expect the physical resource name staying the same after update,
# so that the nested was actually updated instead of replaced.
new_nested_id = self.assert_resource_is_a_stack(
stack_identifier, 'the_nested')
self.assertEqual(original_nested_id, new_nested_id)
updt_stack = self.client.stacks.get(stack_identifier)
self.assertEqual('foo', self._stack_output(updt_stack, 'output_foo'))
def test_nested_stack_suspend_resume(self):
url = self.publish_template(self.nested_template)
self.template = self.test_template.replace('the.yaml', url)
stack_identifier = self.stack_create(template=self.template)
self.stack_suspend(stack_identifier)
self.stack_resume(stack_identifier)

View File

@ -1,61 +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_tempest_plugin.tests.functional import functional_base
class CancelUpdateTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: '2013-05-23'
parameters:
InstanceType:
type: string
ImageId:
type: string
network:
type: string
resources:
port:
type: OS::Neutron::Port
properties:
network: {get_param: network}
Server:
type: OS::Nova::Server
properties:
flavor_update_policy: REPLACE
image: {get_param: ImageId}
flavor: {get_param: InstanceType}
networks:
- port: {get_resource: port}
'''
def setUp(self):
super(CancelUpdateTest, self).setUp()
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 minimal flavor configured to test.")
def test_cancel_update_server_with_port(self):
parameters = {'InstanceType': self.conf.minimal_instance_type,
'ImageId': self.conf.minimal_image_ref,
'network': self.conf.fixed_network_name}
stack_identifier = self.stack_create(template=self.template,
parameters=parameters)
parameters['InstanceType'] = self.conf.instance_type
self.update_stack(stack_identifier, self.template,
parameters=parameters,
expected_status='UPDATE_IN_PROGRESS')
self.cancel_update_stack(stack_identifier)

View File

@ -1,157 +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 heatclient import exc
import keystoneclient
from heat_tempest_plugin.tests.functional import functional_base
class ServiceBasedExposureTest(functional_base.FunctionalTestsBase):
# NOTE(pas-ha) if we ever decide to install Sahara on Heat
# functional gate, this must be changed to other not-installed
# but in principle supported service
unavailable_service = 'Sahara'
unavailable_template = """
heat_template_version: 2015-10-15
parameters:
instance_type:
type: string
resources:
not_available:
type: OS::Sahara::NodeGroupTemplate
properties:
plugin_name: fake
hadoop_version: 0.1
flavor: {get_param: instance_type}
node_processes: []
"""
def setUp(self):
super(ServiceBasedExposureTest, self).setUp()
# check that Sahara endpoint is available
if self._is_sahara_deployed():
self.skipTest("Sahara is actually deployed, "
"can not run negative tests on "
"Sahara resources availability.")
def _is_sahara_deployed(self):
try:
self.identity_client.get_endpoint_url('data-processing',
self.conf.region)
except keystoneclient.exceptions.EndpointNotFound:
return False
return True
def test_unavailable_resources_not_listed(self):
resources = self.client.resource_types.list()
self.assertFalse(any(self.unavailable_service in r.resource_type
for r in resources))
def test_unavailable_resources_not_created(self):
stack_name = self._stack_rand_name()
parameters = {'instance_type': self.conf.minimal_instance_type}
ex = self.assertRaises(exc.HTTPBadRequest,
self.client.stacks.create,
stack_name=stack_name,
parameters=parameters,
template=self.unavailable_template)
self.assertIn('ResourceTypeUnavailable', ex.message.decode('utf-8'))
self.assertIn('OS::Sahara::NodeGroupTemplate',
ex.message.decode('utf-8'))
class RoleBasedExposureTest(functional_base.FunctionalTestsBase):
fl_tmpl = """
heat_template_version: 2015-10-15
resources:
not4everyone:
type: OS::Nova::Flavor
properties:
ram: 20000
vcpus: 10
"""
cvt_tmpl = """
heat_template_version: 2015-10-15
resources:
cvt:
type: OS::Cinder::VolumeType
properties:
name: cvt_test
"""
host_aggr_tmpl = """
heat_template_version: 2015-10-15
parameters:
az:
type: string
default: nova
resources:
cvt:
type: OS::Nova::HostAggregate
properties:
name: aggregate_test
availability_zone: {get_param: az}
"""
scenarios = [
('r_nova_flavor', dict(
stack_name='s_nova_flavor',
template=fl_tmpl,
forbidden_r_type="OS::Nova::Flavor",
test_creation=True)),
('r_nova_host_aggregate', dict(
stack_name='s_nova_ost_aggregate',
template=host_aggr_tmpl,
forbidden_r_type="OS::Nova::HostAggregate",
test_creation=True)),
('r_cinder_vtype', dict(
stack_name='s_cinder_vtype',
template=cvt_tmpl,
forbidden_r_type="OS::Cinder::VolumeType",
test_creation=True)),
('r_cinder_vtype_encrypt', dict(
forbidden_r_type="OS::Cinder::EncryptedVolumeType",
test_creation=False)),
('r_neutron_qos', dict(
forbidden_r_type="OS::Neutron::QoSPolicy",
test_creation=False)),
('r_neutron_qos_bandwidth_limit', dict(
forbidden_r_type="OS::Neutron::QoSBandwidthLimitRule",
test_creation=False)),
('r_manila_share_type', dict(
forbidden_r_type="OS::Manila::ShareType",
test_creation=False))
]
def test_non_admin_forbidden_create_resources(self):
"""Fail to create resource w/o admin role.
Integration tests job runs as normal OpenStack user,
and the resources above are configured to require
admin role in default policy file of Heat.
"""
if self.test_creation:
ex = self.assertRaises(exc.Forbidden,
self.client.stacks.create,
stack_name=self.stack_name,
template=self.template)
self.assertIn(self.forbidden_r_type, ex.message.decode('utf-8'))
def test_forbidden_resource_not_listed(self):
resources = self.client.resource_types.list()
self.assertNotIn(self.forbidden_r_type,
(r.resource_type for r in resources))

View File

@ -1,619 +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_tempest_plugin.tests.functional import functional_base
cfn_template = '''
AWSTemplateFormatVersion: 2010-09-09
Parameters:
env_type:
Default: test
Type: String
AllowedValues: [prod, test]
zone:
Type: String
Default: beijing
Conditions:
Prod: {"Fn::Equals" : [{Ref: env_type}, "prod"]}
Test:
Fn::Not:
- Fn::Equals:
- Ref: env_type
- prod
Beijing_Prod:
Fn::And:
- Fn::Equals:
- Ref: env_type
- prod
- Fn::Equals:
- Ref: zone
- beijing
Xian_Zone:
Fn::Equals:
- Ref: zone
- xian
Xianyang_Zone:
Fn::Equals:
- Ref: zone
- xianyang
Fujian_Zone:
Fn::Or:
- Fn::Equals:
- Ref: zone
- fuzhou
- Fn::Equals:
- Ref: zone
- xiamen
Fujian_Prod:
Fn::And:
- Fujian_Zone
- Prod
Shannxi_Provice:
Fn::Or:
- Xian_Zone
- Xianyang_Zone
Not_Shannxi:
Fn::Not: [Shannxi_Provice]
Resources:
test_res:
Type: OS::Heat::TestResource
Properties:
value: {"Fn::If": ["Prod", "env_is_prod", "env_is_test"]}
prod_res:
Type: OS::Heat::TestResource
Properties:
value: prod_res
Condition: Prod
test_res1:
Type: OS::Heat::TestResource
Properties:
value: just in test env
Condition: Test
beijing_prod_res:
Type: OS::Heat::TestResource
Properties:
value: beijing_prod_res
Condition: Beijing_Prod
fujian_res:
Type: OS::Heat::TestResource
Condition: Fujian_Zone
Properties:
value: fujian_res
fujian_prod_res:
Type: OS::Heat::TestResource
Condition: Fujian_Prod
Properties:
value: fujian_prod_res
shannxi_res:
Type: OS::Heat::TestResource
Condition: Shannxi_Provice
Properties:
value: shannxi_res
not_shannxi_res:
Type: OS::Heat::TestResource
Condition: Not_Shannxi
Properties:
value: not_shannxi_res
Outputs:
res_value:
Value: {"Fn::GetAtt": [prod_res, output]}
Condition: Prod
test_res_value:
Value: {"Fn::GetAtt": [test_res, output]}
prod_resource:
Value: {"Fn::If": [Prod, {Ref: prod_res}, 'no_prod_res']}
test_res1_value:
Value: {"Fn::If": [Test, {"Fn::GetAtt": [test_res1, output]},
'no_test_res1']}
beijing_prod_res:
Value: {"Fn::If": [Beijing_Prod, {Ref: beijing_prod_res}, 'no_prod_res']}
'''
hot_template = '''
heat_template_version: 2016-10-14
parameters:
env_type:
default: test
type: string
constraints:
- allowed_values: [prod, test]
zone:
type: string
default: beijing
conditions:
prod: {equals : [{get_param: env_type}, "prod"]}
test:
not:
equals:
- get_param: env_type
- prod
beijing_prod:
and:
- equals:
- get_param: zone
- beijing
- equals:
- get_param: env_type
- prod
xian_zone:
equals:
- get_param: zone
- xian
xianyang_zone:
equals:
- get_param: zone
- xianyang
fujian_zone:
or:
- equals:
- get_param: zone
- fuzhou
- equals:
- get_param: zone
- xiamen
fujian_prod:
and:
- fujian_zone
- prod
shannxi_provice:
or:
- xian_zone
- xianyang_zone
not_shannxi:
not: shannxi_provice
resources:
test_res:
type: OS::Heat::TestResource
properties:
value: {if: ["prod", "env_is_prod", "env_is_test"]}
prod_res:
type: OS::Heat::TestResource
properties:
value: prod_res
condition: prod
test_res1:
type: OS::Heat::TestResource
properties:
value: just in test env
condition: test
beijing_prod_res:
type: OS::Heat::TestResource
properties:
value: beijing_prod_res
condition: beijing_prod
fujian_res:
type: OS::Heat::TestResource
condition: fujian_zone
properties:
value: fujian_res
fujian_prod_res:
type: OS::Heat::TestResource
condition: fujian_prod
properties:
value: fujian_prod_res
shannxi_res:
type: OS::Heat::TestResource
condition: shannxi_provice
properties:
value: shannxi_res
not_shannxi_res:
type: OS::Heat::TestResource
condition: not_shannxi
properties:
value: not_shannxi_res
outputs:
res_value:
value: {get_attr: [prod_res, output]}
condition: prod
test_res_value:
value: {get_attr: [test_res, output]}
prod_resource:
value: {if: [prod, {get_resource: prod_res}, 'no_prod_res']}
test_res1_value:
value: {if: [test, {get_attr: [test_res1, output]}, 'no_test_res1']}
beijing_prod_res:
value: {if: [beijing_prod, {get_resource: beijing_prod_res},
'no_prod_res']}
'''
before_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
env_type:
default: test
type: string
conditions:
cd1: {equals : [{get_param: env_type}, "prod"]}
resources:
test:
type: OS::Heat::TestResource
properties:
value: {if: [cd1, 'prod', 'test']}
'''
after_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
env_type:
default: prod
type: string
conditions:
cd2: {equals : [{get_param: env_type}, "prod"]}
resources:
test:
type: OS::Heat::TestResource
properties:
value: {if: [cd2, 'prod', 'test']}
test2:
type: OS::Heat::TestResource
properties:
value: {if: [cd2, 'prod', 'test']}
'''
fail_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
env_type:
default: prod
type: string
conditions:
cd3: {equals : [{get_param: env_type}, "prod"]}
resources:
test:
type: OS::Heat::TestResource
properties:
value: {if: [cd3, 'prod', 'test']}
test2:
type: OS::Heat::TestResource
properties:
value: {if: [cd3, 'prod', 'test']}
test_fail:
type: OS::Heat::TestResource
properties:
fail: True
depends_on: [test, test2]
'''
recover_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
env_type:
default: prod
type: string
conditions:
cd3: {equals : [{get_param: env_type}, "prod"]}
resources:
test2:
type: OS::Heat::TestResource
properties:
value: {if: [cd3, 'prod', 'test']}
test_fail:
type: OS::Heat::TestResource
properties:
fail: False
depends_on: [test2]
'''
class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
def res_assert_for_prod(self, resources, bj_prod=True, fj_zone=False,
shannxi_provice=False):
res_names = [res.resource_name for res in resources]
if bj_prod:
self.assertEqual(4, len(resources))
self.assertIn('beijing_prod_res', res_names)
self.assertIn('not_shannxi_res', res_names)
elif fj_zone:
self.assertEqual(5, len(resources))
self.assertIn('fujian_res', res_names)
self.assertNotIn('beijing_prod_res', res_names)
self.assertIn('not_shannxi_res', res_names)
self.assertIn('fujian_prod_res', res_names)
elif shannxi_provice:
self.assertEqual(3, len(resources))
self.assertIn('shannxi_res', res_names)
else:
self.assertEqual(3, len(resources))
self.assertIn('not_shannxi_res', res_names)
self.assertIn('prod_res', res_names)
self.assertIn('test_res', res_names)
def res_assert_for_test(self, resources, fj_zone=False,
shannxi_provice=False):
res_names = [res.resource_name for res in resources]
if fj_zone:
self.assertEqual(4, len(resources))
self.assertIn('fujian_res', res_names)
self.assertIn('not_shannxi_res', res_names)
elif shannxi_provice:
self.assertEqual(3, len(resources))
self.assertNotIn('fujian_res', res_names)
self.assertIn('shannxi_res', res_names)
else:
self.assertEqual(3, len(resources))
self.assertIn('not_shannxi_res', res_names)
self.assertIn('test_res', res_names)
self.assertIn('test_res1', res_names)
self.assertNotIn('prod_res', res_names)
def output_assert_for_prod(self, stack_id, bj_prod=True):
output = self.client.stacks.output_show(stack_id,
'res_value')['output']
self.assertEqual('prod_res', output['output_value'])
test_res_value = self.client.stacks.output_show(
stack_id, 'test_res_value')['output']
self.assertEqual('env_is_prod', test_res_value['output_value'])
prod_resource = self.client.stacks.output_show(
stack_id, 'prod_resource')['output']
self.assertNotEqual('no_prod_res', prod_resource['output_value'])
test_res_output = self.client.stacks.output_show(
stack_id, 'test_res1_value')['output']
self.assertEqual('no_test_res1', test_res_output['output_value'])
beijing_prod_res = self.client.stacks.output_show(
stack_id, 'beijing_prod_res')['output']
if bj_prod:
self.assertNotEqual('no_prod_res',
beijing_prod_res['output_value'])
else:
self.assertEqual('no_prod_res', beijing_prod_res['output_value'])
def output_assert_for_test(self, stack_id):
output = self.client.stacks.output_show(stack_id,
'res_value')['output']
self.assertIsNone(output['output_value'])
test_res_value = self.client.stacks.output_show(
stack_id, 'test_res_value')['output']
self.assertEqual('env_is_test', test_res_value['output_value'])
prod_resource = self.client.stacks.output_show(
stack_id, 'prod_resource')['output']
self.assertEqual('no_prod_res', prod_resource['output_value'])
test_res_output = self.client.stacks.output_show(
stack_id, 'test_res1_value')['output']
self.assertEqual('just in test env',
test_res_output['output_value'])
beijing_prod_res = self.client.stacks.output_show(
stack_id, 'beijing_prod_res')['output']
self.assertEqual('no_prod_res', beijing_prod_res['output_value'])
def test_stack_create_update_cfn_template_test_to_prod(self):
stack_identifier = self.stack_create(template=cfn_template)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'fuzhou'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'env_type': 'prod',
'zone': 'shanghai'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'prod',
'zone': 'xiamen'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'prod',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=False,
shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, False)
def test_stack_create_update_cfn_template_prod_to_test(self):
parms = {'env_type': 'prod'}
stack_identifier = self.stack_create(template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'zone': 'xiamen',
'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'zone': 'xianyang',
'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=False,
shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'zone': 'shanghai',
'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=False,
shannxi_provice=False)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'env_type': 'test'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'fuzhou'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=False,
shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
def test_stack_create_update_hot_template_test_to_prod(self):
stack_identifier = self.stack_create(template=hot_template)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'prod'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'env_type': 'prod',
'zone': 'shanghai'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'prod',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False, shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, False)
def test_stack_create_update_hot_template_prod_to_test(self):
parms = {'env_type': 'prod'}
stack_identifier = self.stack_create(template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'env_type': 'prod',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False, shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'test'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=False,
shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
def test_condition_rename(self):
stack_identifier = self.stack_create(template=before_rename_tmpl)
self.update_stack(stack_identifier, template=after_rename_tmpl)
self.update_stack(stack_identifier, template=fail_rename_tmpl,
expected_status='UPDATE_FAILED')
self.update_stack(stack_identifier, template=recover_rename_tmpl)

View File

@ -1,710 +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.
import copy
import json
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
test_template_one_resource = {
'heat_template_version': 'pike',
'description': 'Test template to create one instance.',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1',
'fail': False,
'update_replace': False,
'wait_secs': 1,
'action_wait_secs': {'create': 1},
'client_name': 'nova',
'entity_name': 'servers',
}
}
}
}
test_template_two_resource = {
'heat_template_version': 'pike',
'description': 'Test template to create two instance.',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1',
'fail': False,
'update_replace': False,
'wait_secs': 0,
'action_wait_secs': {'update': 1}
}
},
'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(functional_base.FunctionalTestsBase):
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(functional_base.FunctionalTestsBase):
provider_template = {
'heat_template_version': '2013-05-23',
'description': 'foo',
'resources': {
'test1': {
'type': 'My::TestResource'
}
}
}
provider_group_template = '''
heat_template_version: 2013-05-23
parameters:
count:
type: number
default: 2
resources:
test_group:
type: OS::Heat::ResourceGroup
properties:
count: {get_param: count}
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}
'''
fail_param_template = '''
heat_template_version: 2014-10-16
parameters:
do_fail:
type: boolean
default: False
resources:
aresource:
type: OS::Heat::TestResource
properties:
value: Test
fail: {get_param: do_fail}
wait_secs: 1
'''
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_from_failed(self):
# Prove it's possible to update from an UPDATE_FAILED state
template = _change_rsrc_properties(test_template_one_resource,
['test1'],
{'value': 'test_update_failed'})
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_one_resource, ['test1'], {'fail': True})
# Update with bad template, we should fail
self.update_stack(stack_identifier, tmpl_update,
expected_status='UPDATE_FAILED')
# but then passing a good template should succeed
self.update_stack(stack_identifier, test_template_two_resource)
updated_resources = {'test1': 'OS::Heat::TestResource',
'test2': '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_alias_type(self):
env = {'resource_registry':
{'My::TestResource': 'OS::Heat::RandomString',
'My::TestResource2': 'OS::Heat::RandomString'}}
stack_identifier = self.stack_create(
template=self.provider_template,
environment=env
)
p_res = self.client.resources.get(stack_identifier, 'test1')
self.assertEqual('My::TestResource', p_res.resource_type)
initial_resources = {'test1': 'My::TestResource'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
res = self.client.resources.get(stack_identifier, 'test1')
# Modify the type of the resource alias to My::TestResource2
tmpl_update = copy.deepcopy(self.provider_template)
tmpl_update['resources']['test1']['type'] = 'My::TestResource2'
self.update_stack(stack_identifier, tmpl_update, environment=env)
res_a = self.client.resources.get(stack_identifier, 'test1')
self.assertEqual(res.physical_resource_id, res_a.physical_resource_id)
self.assertEqual(res.attributes['value'], res_a.attributes['value'])
def test_stack_update_alias_changes(self):
env = {'resource_registry':
{'My::TestResource': 'OS::Heat::RandomString'}}
stack_identifier = self.stack_create(
template=self.provider_template,
environment=env
)
p_res = self.client.resources.get(stack_identifier, 'test1')
self.assertEqual('My::TestResource', p_res.resource_type)
initial_resources = {'test1': 'My::TestResource'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
res = self.client.resources.get(stack_identifier, 'test1')
# Modify the resource alias to point to a different type
env = {'resource_registry':
{'My::TestResource': 'OS::Heat::TestResource'}}
self.update_stack(stack_identifier, template=self.provider_template,
environment=env)
res_a = self.client.resources.get(stack_identifier, 'test1')
self.assertNotEqual(res.physical_resource_id,
res_a.physical_resource_id)
def test_stack_update_provider_type(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',
'My::TestResource2': 'provider.template'}}
stack_identifier = self.stack_create(
template=self.provider_template,
files=files,
environment=env
)
p_res = self.client.resources.get(stack_identifier, 'test1')
self.assertEqual('My::TestResource', p_res.resource_type)
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))
n_res = self.client.resources.get(nested_identifier, 'test1')
# Modify the type of the provider resource to My::TestResource2
tmpl_update = copy.deepcopy(self.provider_template)
tmpl_update['resources']['test1']['type'] = 'My::TestResource2'
self.update_stack(stack_identifier, tmpl_update,
environment=env, files=files)
p_res = self.client.resources.get(stack_identifier, 'test1')
self.assertEqual('My::TestResource2', p_res.resource_type)
# Parent resources should be unchanged and the nested stack
# should have been updated in-place without replacement
self.assertEqual({u'test1': u'My::TestResource2'},
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
self.assertEqual(nested_resources,
self.list_resources(nested_identifier))
n_res2 = self.client.resources.get(nested_identifier, 'test1')
self.assertEqual(n_res.physical_resource_id,
n_res2.physical_resource_id)
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 exercising 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):
"""Test case for updating userdata of instance.
Confirm that we can update userdata of instance during updating stack
by the user of member role.
Make sure that a resource that inherits 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': ''}
stack_identifier = self.stack_create(
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)
def test_stack_update_provider_group_patch(self):
'''Test two-level nested update with PATCH'''
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))
# increase the count, pass only the paramter, no env or template
params = {'count': 3}
self.update_stack(stack_identifier, parameters=params, existing=True)
# 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)
# Add a resource, as we should have added one
nested_resources['2'] = 'My::TestResource'
self.assertEqual(nested_resources,
self.list_resources(nested_identifier))
def test_stack_update_from_failed_patch(self):
'''Test PATCH update from a failed state.'''
# Start with empty template
stack_identifier = self.stack_create(
template='heat_template_version: 2014-10-16')
# Update with a good template, but bad parameter
self.update_stack(stack_identifier,
template=self.fail_param_template,
parameters={'do_fail': True},
expected_status='UPDATE_FAILED')
# PATCH update, only providing the parameter
self.update_stack(stack_identifier,
parameters={'do_fail': False},
existing=True)
self.assertEqual({u'aresource': u'OS::Heat::TestResource'},
self.list_resources(stack_identifier))
def test_stack_update_with_new_env(self):
"""Update handles new resource types in the environment.
If a resource type appears during an update and the update fails,
retrying the update is able to find the type properly in the
environment.
"""
stack_identifier = self.stack_create(
template=test_template_one_resource)
# Update with a new resource and make the update fails
template = _change_rsrc_properties(test_template_one_resource,
['test1'], {'fail': True})
template['resources']['test2'] = {'type': 'My::TestResource'}
template['resources']['test1']['depends_on'] = 'test2'
env = {'resource_registry':
{'My::TestResource': 'OS::Heat::TestResource'}}
self.update_stack(stack_identifier,
template=template,
environment=env,
expected_status='UPDATE_FAILED')
# Fixing the template should fix the stack
template = _change_rsrc_properties(template,
['test1'], {'fail': False})
self.update_stack(stack_identifier,
template=template,
environment=env)
self.assertEqual({'test1': 'OS::Heat::TestResource',
'test2': 'My::TestResource'},
self.list_resources(stack_identifier))
def test_stack_update_with_new_version(self):
"""Update handles new template version in failure.
If a stack update fails while changing the template version, update is
able to handle the new version fine.
"""
stack_identifier = self.stack_create(
template=test_template_one_resource)
# Update with a new function and make the update fails
template = _change_rsrc_properties(test_template_two_resource,
['test1'], {'fail': True})
template['heat_template_version'] = '2015-10-15'
template['resources']['test2']['properties']['value'] = {
'list_join': [',', ['a'], ['b']]}
self.update_stack(stack_identifier,
template=template,
expected_status='UPDATE_FAILED')
template = _change_rsrc_properties(template,
['test2'], {'value': 'Test2'})
self.update_stack(stack_identifier,
template=template,
expected_status='UPDATE_FAILED')
self._stack_delete(stack_identifier)
def test_stack_update_with_old_version(self):
"""Update handles old template version in failure.
If a stack update fails while changing the template version, update is
able to handle the old version fine.
"""
template = _change_rsrc_properties(
test_template_one_resource,
['test1'], {'value': {'list_join': [',', ['a'], ['b']]}})
template['heat_template_version'] = '2015-10-15'
stack_identifier = self.stack_create(
template=template)
# Update with a new function and make the update fails
template = _change_rsrc_properties(test_template_one_resource,
['test1'], {'fail': True})
self.update_stack(stack_identifier,
template=template,
expected_status='UPDATE_FAILED')
self._stack_delete(stack_identifier)
def test_stack_update_with_conditions(self):
"""Update manages new conditions added.
When a new resource is added during updates, the stacks handles the new
conditions correctly, and doesn't fail to load them while the update is
still in progress.
"""
stack_identifier = self.stack_create(
template=test_template_one_resource)
updated_template = copy.deepcopy(test_template_two_resource)
updated_template['conditions'] = {'cond1': True}
updated_template['resources']['test3'] = {
'type': 'OS::Heat::TestResource',
'properties': {
'value': {'if': ['cond1', 'val3', 'val4']}
}
}
test2_props = updated_template['resources']['test2']['properties']
test2_props['action_wait_secs'] = {'create': 30}
self.update_stack(stack_identifier,
template=updated_template,
expected_status='UPDATE_IN_PROGRESS')
def check_resources():
resources = self.list_resources(stack_identifier)
if len(resources) < 2:
return False
self.assertIn('test3', resources)
return True
self.assertTrue(test.call_until_true(20, 2, check_resources))

View File

@ -1,92 +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.
import yaml
from heat_tempest_plugin.tests.functional import functional_base
class DefaultParametersTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: 40
resources:
random1:
type: nested_random.yaml
random2:
type: OS::Heat::RandomString
properties:
length: {get_param: length}
outputs:
random1:
value: {get_attr: [random1, random1_value]}
random2:
value: {get_resource: random2}
'''
nested_template = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: 50
resources:
random1:
type: OS::Heat::RandomString
properties:
length: {get_param: length}
outputs:
random1_value:
value: {get_resource: random1}
'''
scenarios = [
('none', dict(param=None, default=None, temp_def=True,
expect1=50, expect2=40)),
('default', dict(param=None, default=12, temp_def=True,
expect1=12, expect2=12)),
('both', dict(param=15, default=12, temp_def=True,
expect1=12, expect2=15)),
('no_temp_default', dict(param=None, default=12, temp_def=False,
expect1=12, expect2=12)),
]
def test_defaults(self):
env = {'parameters': {}, 'parameter_defaults': {}}
if self.param:
env['parameters'] = {'length': self.param}
if self.default:
env['parameter_defaults'] = {'length': self.default}
if not self.temp_def:
# remove the default from the parameter in the nested template.
ntempl = yaml.safe_load(self.nested_template)
del ntempl['parameters']['length']['default']
nested_template = yaml.safe_dump(ntempl)
else:
nested_template = self.nested_template
stack_identifier = self.stack_create(
template=self.template,
files={'nested_random.yaml': nested_template},
environment=env
)
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']))
if out['output_key'] == 'random2':
self.assertEqual(self.expect2, len(out['output_value']))

View File

@ -1,42 +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.
import time
from heat_tempest_plugin.tests.functional import functional_base
class DeleteInProgressTest(functional_base.FunctionalTestsBase):
root_template = '''
heat_template_version: 2013-05-23
resources:
rg:
type: OS::Heat::ResourceGroup
properties:
count: 125
resource_def:
type: empty.yaml
'''
empty_template = '''
heat_template_version: 2013-05-23
resources:
'''
def test_delete_nested_stacks_create_in_progress(self):
files = {'empty.yaml': self.empty_template}
identifier = self.stack_create(template=self.root_template,
files=files,
expected_status='CREATE_IN_PROGRESS')
time.sleep(20)
self._stack_delete(identifier)

View File

@ -1,95 +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_tempest_plugin.tests.functional import functional_base
TEMPLATE = '''
heat_template_version: 2015-04-30
parameters:
p0:
type: string
default: CORRECT
p1:
type: string
default: INCORRECT
p2:
type: string
default: INCORRECT
resources:
r1:
type: test::R1
r2:
type: test::R2
r3a:
type: test::R3
r3b:
type: test::R3
'''
ENV_1 = '''
parameters:
p1: CORRECT
p2: INCORRECT-E1
resource_registry:
test::R1: OS::Heat::RandomString
test::R2: BROKEN
test::R3: OS::Heat::None
'''
ENV_2 = '''
parameters:
p2: CORRECT
resource_registry:
test::R2: OS::Heat::RandomString
resources:
r3b:
test::R3: OS::Heat::RandomString
'''
class EnvironmentMergingTests(functional_base.FunctionalTestsBase):
def test_server_environment_merging(self):
# Setup
files = {'env1.yaml': ENV_1, 'env2.yaml': ENV_2}
environment_files = ['env1.yaml', 'env2.yaml']
# Test
stack_id = self.stack_create(stack_name='env_merge',
template=TEMPLATE,
files=files,
environment_files=environment_files)
# Verify
# Since there is no environment show, the registry overriding
# is partially verified by there being no error. If it wasn't
# working, test::R2 would remain mapped to BROKEN in env1.
# Sanity check
resources = self.list_resources(stack_id)
self.assertEqual(4, len(resources))
# Verify the parameters are correctly set
stack = self.client.stacks.get(stack_id)
self.assertEqual('CORRECT', stack.parameters['p0'])
self.assertEqual('CORRECT', stack.parameters['p1'])
self.assertEqual('CORRECT', stack.parameters['p2'])
# Verify that r3b has been overridden into a RandomString
# by checking to see that it has a value
r3b = self.client.resources.get(stack_id, 'r3b')
r3b_attrs = r3b.attributes
self.assertIn('value', r3b_attrs)

View File

@ -1,215 +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_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
class HeatAutoscalingTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2014-10-16
resources:
random_group:
type: OS::Heat::AutoScalingGroup
properties:
cooldown: 0
desired_capacity: 3
max_size: 5
min_size: 2
resource:
type: OS::Heat::RandomString
scale_up_policy:
type: OS::Heat::ScalingPolicy
properties:
adjustment_type: change_in_capacity
auto_scaling_group_id: { get_resource: random_group }
scaling_adjustment: 1
scale_down_policy:
type: OS::Heat::ScalingPolicy
properties:
adjustment_type: change_in_capacity
auto_scaling_group_id: { get_resource: random_group }
scaling_adjustment: -1
outputs:
all_values:
value: {get_attr: [random_group, outputs_list, value]}
value_0:
value: {get_attr: [random_group, resource.0.value]}
value_1:
value: {get_attr: [random_group, resource.1.value]}
value_2:
value: {get_attr: [random_group, resource.2.value]}
asg_size:
value: {get_attr: [random_group, current_size]}
'''
template_nested = '''
heat_template_version: 2014-10-16
resources:
random_group:
type: OS::Heat::AutoScalingGroup
properties:
desired_capacity: 3
max_size: 5
min_size: 2
resource:
type: randomstr.yaml
outputs:
all_values:
value: {get_attr: [random_group, outputs_list, random_str]}
value_0:
value: {get_attr: [random_group, resource.0.random_str]}
value_1:
value: {get_attr: [random_group, resource.1.random_str]}
value_2:
value: {get_attr: [random_group, resource.2.random_str]}
'''
template_randomstr = '''
heat_template_version: 2013-05-23
resources:
random_str:
type: OS::Heat::RandomString
outputs:
random_str:
value: {get_attr: [random_str, value]}
'''
def _assert_output_values(self, stack_id):
stack = self.client.stacks.get(stack_id)
all_values = self._stack_output(stack, 'all_values')
self.assertEqual(3, len(all_values))
self.assertEqual(all_values[0], self._stack_output(stack, 'value_0'))
self.assertEqual(all_values[1], self._stack_output(stack, 'value_1'))
self.assertEqual(all_values[2], self._stack_output(stack, 'value_2'))
def test_asg_scale_up_max_size(self):
stack_id = self.stack_create(template=self.template,
expected_status='CREATE_COMPLETE')
stack = self.client.stacks.get(stack_id)
asg_size = self._stack_output(stack, 'asg_size')
# Ensure that initial desired capacity is met
self.assertEqual(3, asg_size)
# send scale up signals and ensure that asg honors max_size
asg = self.client.resources.get(stack_id, 'random_group')
max_size = 5
for num in range(asg_size+1, max_size+2):
expected_resources = num if num <= max_size else max_size
self.client.resources.signal(stack_id, 'scale_up_policy')
self.assertTrue(
test.call_until_true(self.conf.build_timeout,
self.conf.build_interval,
self.check_autoscale_complete,
asg.physical_resource_id,
expected_resources, stack_id,
'scale_up_policy'))
def test_asg_scale_down_min_size(self):
stack_id = self.stack_create(template=self.template,
expected_status='CREATE_COMPLETE')
stack = self.client.stacks.get(stack_id)
asg_size = self._stack_output(stack, 'asg_size')
# Ensure that initial desired capacity is met
self.assertEqual(3, asg_size)
# send scale down signals and ensure that asg honors min_size
asg = self.client.resources.get(stack_id, 'random_group')
min_size = 2
for num in range(asg_size-1, 0, -1):
expected_resources = num if num >= min_size else min_size
self.client.resources.signal(stack_id, 'scale_down_policy')
self.assertTrue(
test.call_until_true(self.conf.build_timeout,
self.conf.build_interval,
self.check_autoscale_complete,
asg.physical_resource_id,
expected_resources, stack_id,
'scale_down_policy'))
def test_asg_cooldown(self):
cooldown_tmpl = self.template.replace('cooldown: 0',
'cooldown: 60')
stack_id = self.stack_create(template=cooldown_tmpl,
expected_status='CREATE_COMPLETE')
stack = self.client.stacks.get(stack_id)
asg_size = self._stack_output(stack, 'asg_size')
# Ensure that initial desired capacity is met
self.assertEqual(3, asg_size)
# send scale up signal.
# Since cooldown is in effect, number of resources should not change
asg = self.client.resources.get(stack_id, 'random_group')
expected_resources = 3
self.client.resources.signal(stack_id, 'scale_up_policy')
self.assertTrue(
test.call_until_true(self.conf.build_timeout,
self.conf.build_interval,
self.check_autoscale_complete,
asg.physical_resource_id,
expected_resources, stack_id,
'scale_up_policy'))
def test_path_attrs(self):
stack_id = self.stack_create(template=self.template)
expected_resources = {'random_group': 'OS::Heat::AutoScalingGroup',
'scale_up_policy': 'OS::Heat::ScalingPolicy',
'scale_down_policy': 'OS::Heat::ScalingPolicy'}
self.assertEqual(expected_resources, self.list_resources(stack_id))
self._assert_output_values(stack_id)
def test_path_attrs_nested(self):
files = {'randomstr.yaml': self.template_randomstr}
stack_id = self.stack_create(template=self.template_nested,
files=files)
expected_resources = {'random_group': 'OS::Heat::AutoScalingGroup'}
self.assertEqual(expected_resources, self.list_resources(stack_id))
self._assert_output_values(stack_id)
class AutoScalingGroupUpdateWithNoChanges(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2013-05-23
resources:
test_group:
type: OS::Heat::AutoScalingGroup
properties:
desired_capacity: 0
max_size: 0
min_size: 0
resource:
type: OS::Heat::RandomString
test_policy:
type: OS::Heat::ScalingPolicy
properties:
adjustment_type: change_in_capacity
auto_scaling_group_id: { get_resource: test_group }
scaling_adjustment: 1
'''
def test_as_group_update_without_resource_changes(self):
stack_identifier = self.stack_create(template=self.template)
new_template = self.template.replace(
'scaling_adjustment: 1',
'scaling_adjustment: 2')
self.update_stack(stack_identifier, template=new_template)

View File

@ -1,141 +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_tempest_plugin.tests.functional import functional_base
from heatclient import exc as heat_exceptions
class ImmutableParametersTest(functional_base.FunctionalTestsBase):
template_param_has_no_immutable_field = '''
heat_template_version: 2014-10-16
parameters:
param1:
type: string
default: default_value
outputs:
param1_output:
description: 'parameter 1 details'
value: { get_param: param1 }
'''
template_param_has_immutable_field = '''
heat_template_version: 2014-10-16
parameters:
param1:
type: string
default: default_value
immutable: false
outputs:
param1_output:
description: 'parameter 1 details'
value: { get_param: param1 }
'''
def test_no_immutable_param_field(self):
param1_create_value = 'value1'
create_parameters = {"param1": param1_create_value}
stack_identifier = self.stack_create(
template=self.template_param_has_no_immutable_field,
parameters=create_parameters
)
stack = self.client.stacks.get(stack_identifier)
# Verify the value of the parameter
self.assertEqual(param1_create_value,
self._stack_output(stack, 'param1_output'))
param1_update_value = 'value2'
update_parameters = {"param1": param1_update_value}
self.update_stack(
stack_identifier,
template=self.template_param_has_no_immutable_field,
parameters=update_parameters)
stack = self.client.stacks.get(stack_identifier)
# Verify the value of the updated parameter
self.assertEqual(param1_update_value,
self._stack_output(stack, 'param1_output'))
def test_immutable_param_field_allowed(self):
param1_create_value = 'value1'
create_parameters = {"param1": param1_create_value}
stack_identifier = self.stack_create(
template=self.template_param_has_immutable_field,
parameters=create_parameters
)
stack = self.client.stacks.get(stack_identifier)
# Verify the value of the parameter
self.assertEqual(param1_create_value,
self._stack_output(stack, 'param1_output'))
param1_update_value = 'value2'
update_parameters = {"param1": param1_update_value}
self.update_stack(
stack_identifier,
template=self.template_param_has_immutable_field,
parameters=update_parameters)
stack = self.client.stacks.get(stack_identifier)
# Verify the value of the updated parameter
self.assertEqual(param1_update_value,
self._stack_output(stack, 'param1_output'))
# Ensure stack is not in a failed state
self.assertEqual('UPDATE_COMPLETE', stack.stack_status)
def test_immutable_param_field_error(self):
param1_create_value = 'value1'
create_parameters = {"param1": param1_create_value}
# Toggle the immutable field to preclude updating
immutable_true = self.template_param_has_immutable_field.replace(
'immutable: false', 'immutable: true')
stack_identifier = self.stack_create(
template=immutable_true,
parameters=create_parameters
)
stack = self.client.stacks.get(stack_identifier)
param1_update_value = 'value2'
update_parameters = {"param1": param1_update_value}
# Verify the value of the parameter
self.assertEqual(param1_create_value,
self._stack_output(stack, 'param1_output'))
# Attempt to update the stack with a new parameter value
try:
self.update_stack(
stack_identifier,
template=immutable_true,
parameters=update_parameters)
except heat_exceptions.HTTPBadRequest as exc:
exp = ('The following parameters are immutable and may not be '
'updated: param1')
self.assertIn(exp, str(exc))
stack = self.client.stacks.get(stack_identifier)
# Ensure stack is not in a failed state
self.assertEqual('CREATE_COMPLETE', stack.stack_status)
# Ensure immutable parameter has not changed
self.assertEqual(param1_create_value,
self._stack_output(stack, 'param1_output'))

View File

@ -1,500 +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.
import copy
import json
from testtools import matchers
from heat_tempest_plugin.tests.functional import functional_base
class InstanceGroupTest(functional_base.FunctionalTestsBase):
template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to create multiple instances.",
"Parameters" : {"size": {"Type": "String", "Default": "1"},
"AZ": {"Type": "String", "Default": "nova"},
"image": {"Type": "String"},
"flavor": {"Type": "String"},
"user_data": {"Type": "String", "Default": "jsconfig data"}},
"Resources": {
"JobServerGroup": {
"Type": "OS::Heat::InstanceGroup",
"Properties": {
"LaunchConfigurationName" : {"Ref": "JobServerConfig"},
"Size" : {"Ref": "size"},
"AvailabilityZones" : [{"Ref": "AZ"}]
}
},
"JobServerConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Metadata": {"foo": "bar"},
"Properties": {
"ImageId" : {"Ref": "image"},
"InstanceType" : {"Ref": "flavor"},
"SecurityGroups" : [ "sg-1" ],
"UserData" : {"Ref": "user_data"}
}
}
},
"Outputs": {
"InstanceList": {"Value": {
"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}},
"JobServerConfigRef": {"Value": {
"Ref": "JobServerConfig"}}
}
}
'''
instance_template = '''
heat_template_version: 2013-05-23
parameters:
ImageId: {type: string}
InstanceType: {type: string}
SecurityGroups: {type: comma_delimited_list}
UserData: {type: string}
Tags: {type: comma_delimited_list}
resources:
random1:
type: OS::Heat::RandomString
properties:
salt: {get_param: UserData}
outputs:
PublicIp:
value: {get_attr: [random1, value]}
'''
# This is designed to fail.
bad_instance_template = '''
heat_template_version: 2013-05-23
parameters:
ImageId: {type: string}
InstanceType: {type: string}
SecurityGroups: {type: comma_delimited_list}
UserData: {type: string}
Tags: {type: comma_delimited_list}
resources:
random1:
type: OS::Heat::RandomString
depends_on: waiter
ready_poster:
type: AWS::CloudFormation::WaitConditionHandle
waiter:
type: AWS::CloudFormation::WaitCondition
properties:
Handle: {Ref: ready_poster}
Timeout: 1
outputs:
PublicIp:
value: {get_attr: [random1, value]}
'''
def setUp(self):
super(InstanceGroupTest, self).setUp()
if not self.conf.minimal_image_ref:
raise self.skipException("No minimal image configured to test")
if not self.conf.instance_type:
raise self.skipException("No flavor configured to test")
def assert_instance_count(self, stack, expected_count):
inst_list = self._stack_output(stack, 'InstanceList')
self.assertEqual(expected_count, len(inst_list.split(',')))
def _assert_instance_state(self, nested_identifier,
num_complete, num_failed):
for res in self.client.resources.list(nested_identifier):
if 'COMPLETE' in res.resource_status:
num_complete = num_complete - 1
elif 'FAILED' in res.resource_status:
num_failed = num_failed - 1
self.assertEqual(0, num_failed)
self.assertEqual(0, num_complete)
class InstanceGroupBasicTest(InstanceGroupTest):
def test_basic_create_works(self):
"""Make sure the working case is good.
Note this combines test_override_aws_ec2_instance into this test as
well, which is:
If AWS::EC2::Instance is overridden, InstanceGroup will automatically
use that overridden resource type.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 4,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files, environment=env)
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'OS::Heat::InstanceGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 4)
def test_size_updates_work(self):
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 2)
# Increase min size to 5
env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 5,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 5)
def test_update_group_replace(self):
"""Test case for ensuring non-updatable props case a replacement.
Make sure that during a group update the non-updatable properties cause
a replacement.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry':
{'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 1,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
orig_asg_id = rsrc.physical_resource_id
env2 = {'resource_registry':
{'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': '2',
'AZ': 'wibble',
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type,
'user_data': 'new data'}}
self.update_stack(stack_identifier, self.template,
environment=env2, files=files)
# replacement will cause the resource physical_resource_id to change.
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
def test_create_instance_error_causes_group_error(self):
"""Test create failing a resource in the instance group.
If a resource in an instance group fails to be created, the instance
group itself will fail and the broken inner resource will remain.
"""
stack_name = self._stack_rand_name()
files = {'provider.yaml': self.bad_instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
self.client.stacks.create(
stack_name=stack_name,
template=self.template,
files=files,
disable_rollback=True,
parameters={},
environment=env
)
self.addCleanup(self._stack_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_FAILED')
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'OS::Heat::InstanceGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
self._assert_instance_state(nested_ident, 0, 2)
def test_update_instance_error_causes_group_error(self):
"""Test update failing a resource in the instance group.
If a resource in an instance group fails to be created during an
update, the instance group itself will fail and the broken inner
resource will remain.
"""
files = {'provider.yaml': self.instance_template}
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': 2,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.instance_type}}
stack_identifier = self.stack_create(template=self.template,
files=files,
environment=env)
initial_resources = {
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
'JobServerGroup': 'OS::Heat::InstanceGroup'}
self.assertEqual(initial_resources,
self.list_resources(stack_identifier))
stack = self.client.stacks.get(stack_identifier)
self.assert_instance_count(stack, 2)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
self._assert_instance_state(nested_ident, 2, 0)
initial_list = [res.resource_name
for res in self.client.resources.list(nested_ident)]
env['parameters']['size'] = 3
files2 = {'provider.yaml': self.bad_instance_template}
self.client.stacks.update(
stack_id=stack_identifier,
template=self.template,
files=files2,
disable_rollback=True,
parameters={},
environment=env
)
self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# assert that there are 3 bad instances
# 2 resources should be in update failed, and one create failed.
for res in self.client.resources.list(nested_ident):
if res.resource_name in initial_list:
self._wait_for_resource_status(nested_ident,
res.resource_name,
'UPDATE_FAILED')
else:
self._wait_for_resource_status(nested_ident,
res.resource_name,
'CREATE_FAILED')
class InstanceGroupUpdatePolicyTest(InstanceGroupTest):
def ig_tmpl_with_updt_policy(self):
templ = json.loads(copy.deepcopy(self.template))
up = {"RollingUpdate": {
"MinInstancesInService": "1",
"MaxBatchSize": "2",
"PauseTime": "PT1S"}}
templ['Resources']['JobServerGroup']['UpdatePolicy'] = up
return templ
def update_instance_group(self, updt_template,
num_updates_expected_on_updt,
num_creates_expected_on_updt,
num_deletes_expected_on_updt,
update_replace):
# setup stack from the initial template
files = {'provider.yaml': self.instance_template}
size = 5
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
'parameters': {'size': size,
'image': self.conf.minimal_image_ref,
'flavor': self.conf.minimal_instance_type}}
stack_name = self._stack_rand_name()
stack_identifier = self.stack_create(
stack_name=stack_name,
template=self.ig_tmpl_with_updt_policy(),
files=files,
environment=env)
stack = self.client.stacks.get(stack_identifier)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'JobServerGroup')
# test that physical resource name of launch configuration is used
conf_name = self._stack_output(stack, 'JobServerConfigRef')
conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name
self.assertThat(conf_name,
matchers.MatchesRegex(conf_name_pattern))
# test the number of instances created
self.assert_instance_count(stack, size)
# saves info from initial list of instances for comparison later
init_instances = self.client.resources.list(nested_ident)
init_names = [inst.resource_name for inst in init_instances]
# test stack update
self.update_stack(stack_identifier, updt_template,
environment=env, files=files)
updt_stack = self.client.stacks.get(stack_identifier)
# test that the launch configuration is replaced
updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef')
self.assertThat(updt_conf_name,
matchers.MatchesRegex(conf_name_pattern))
self.assertNotEqual(conf_name, updt_conf_name)
# test that the group size are the same
updt_instances = self.client.resources.list(nested_ident)
updt_names = [inst.resource_name for inst in updt_instances]
self.assertEqual(len(init_names), len(updt_names))
for res in updt_instances:
self.assertEqual('UPDATE_COMPLETE', res.resource_status)
# test that the appropriate number of instance names are the same
matched_names = set(updt_names) & set(init_names)
self.assertEqual(num_updates_expected_on_updt, len(matched_names))
# test that the appropriate number of new instances are created
self.assertEqual(num_creates_expected_on_updt,
len(set(updt_names) - set(init_names)))
# test that the appropriate number of instances are deleted
self.assertEqual(num_deletes_expected_on_updt,
len(set(init_names) - set(updt_names)))
# test that the older instances are the ones being deleted
if num_deletes_expected_on_updt > 0:
deletes_expected = init_names[:num_deletes_expected_on_updt]
self.assertNotIn(deletes_expected, updt_names)
def test_instance_group_update_replace(self):
"""Test simple update replace with no conflict.
Test simple update replace with no conflict in batch size and
minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
grp = updt_template['Resources']['JobServerGroup']
policy = grp['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '1'
policy['MaxBatchSize'] = '3'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=5,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0,
update_replace=True)
def test_instance_group_update_replace_with_adjusted_capacity(self):
"""Test update replace with capacity adjustment.
Test update replace with capacity adjustment due to conflict in
batch size and minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
grp = updt_template['Resources']['JobServerGroup']
policy = grp['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '4'
policy['MaxBatchSize'] = '4'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=2,
num_creates_expected_on_updt=3,
num_deletes_expected_on_updt=3,
update_replace=True)
def test_instance_group_update_replace_huge_batch_size(self):
"""Test update replace with a huge batch size."""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '0'
policy['MaxBatchSize'] = '20'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=5,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0,
update_replace=True)
def test_instance_group_update_replace_huge_min_in_service(self):
"""Update replace with huge number of minimum instances in service."""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '20'
policy['MaxBatchSize'] = '2'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['UserData'] = 'new data'
self.update_instance_group(updt_template,
num_updates_expected_on_updt=3,
num_creates_expected_on_updt=2,
num_deletes_expected_on_updt=2,
update_replace=True)
def test_instance_group_update_no_replace(self):
"""Test simple update only and no replace with no conflict.
Test simple update only and no replace (i.e. updated instance flavor
in Launch Configuration) with no conflict in batch size and
minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '1'
policy['MaxBatchSize'] = '3'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['InstanceType'] = self.conf.instance_type
self.update_instance_group(updt_template,
num_updates_expected_on_updt=5,
num_creates_expected_on_updt=0,
num_deletes_expected_on_updt=0,
update_replace=False)
def test_instance_group_update_no_replace_with_adjusted_capacity(self):
"""Test update only and no replace with capacity adjustment.
Test update only and no replace (i.e. updated instance flavor in
Launch Configuration) with capacity adjustment due to conflict in
batch size and minimum instances in service.
"""
updt_template = self.ig_tmpl_with_updt_policy()
group = updt_template['Resources']['JobServerGroup']
policy = group['UpdatePolicy']['RollingUpdate']
policy['MinInstancesInService'] = '4'
policy['MaxBatchSize'] = '4'
policy['PauseTime'] = 'PT0S'
config = updt_template['Resources']['JobServerConfig']
config['Properties']['InstanceType'] = self.conf.instance_type
self.update_instance_group(updt_template,
num_updates_expected_on_updt=2,
num_creates_expected_on_updt=3,
num_deletes_expected_on_updt=3,
update_replace=False)

View File

@ -1,165 +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.
# Using nested get_attr functions isn't a good idea - in particular, this
# actually working depends on correct dependencies between the two resources
# whose attributes are being fetched, and these dependencies are non-local to
# where the get_attr calls are used. Nevertheless, it did sort-of work, and
# this test will help keep it that way.
from heat_tempest_plugin.tests.functional import functional_base
initial_template = '''
heat_template_version: ocata
resources:
dict_resource:
type: OS::Heat::Value
properties:
value:
blarg: wibble
foo: bar
baz: quux
fred: barney
# These dependencies are required because we only want to read the
# attribute values for a given resource once, and therefore we do so in
# dependency order. This is necessarily true for a convergence traversal,
# but also happens when we're fetching the resource attributes e.g. to show
# the output values. The key1/key2 attribute values must be stored before
# we attempt to calculate the dep_attrs for dict_resource in order to
# correctly determine which attributes of dict_resource are used.
depends_on:
- key1
- key2
- indirect_key3_dep
key1:
type: OS::Heat::Value
properties:
value: blarg
key2:
type: OS::Heat::Value
properties:
value: foo
key3:
type: OS::Heat::Value
properties:
value: fred
value1:
type: OS::Heat::Value
properties:
value:
get_attr:
- dict_resource
- value
- {get_attr: [key1, value]}
indirect_key3_dep:
type: OS::Heat::Value
properties:
value: ignored
depends_on: key3
outputs:
value1:
value: {get_attr: [value1, value]}
value2:
value: {get_attr: [dict_resource, value, {get_attr: [key2, value]}]}
value3:
value: {get_attr: [dict_resource, value, {get_attr: [key3, value]}]}
'''
update_template = '''
heat_template_version: ocata
resources:
dict_resource:
type: OS::Heat::Value
properties:
value:
blarg: wibble
foo: bar
baz: quux
fred: barney
depends_on:
- key1
- key2
- indirect_key3_dep
- key4
key1:
type: OS::Heat::Value
properties:
value: foo
key2:
type: OS::Heat::Value
properties:
value: fred
key3:
type: OS::Heat::Value
properties:
value: blarg
key4:
type: OS::Heat::Value
properties:
value: baz
value1:
type: OS::Heat::Value
properties:
value:
get_attr:
- dict_resource
- value
- {get_attr: [key1, value]}
value4:
type: OS::Heat::Value
properties:
value:
get_attr:
- dict_resource
- value
- {get_attr: [key4, value]}
indirect_key3_dep:
type: OS::Heat::Value
properties:
value: ignored
depends_on: key3
outputs:
value1:
value: {get_attr: [value1, value]}
value2:
value: {get_attr: [dict_resource, value, {get_attr: [key2, value]}]}
value3:
value: {get_attr: [dict_resource, value, {get_attr: [key3, value]}]}
value4:
value: {get_attr: [value4, value]}
'''
class NestedGetAttrTest(functional_base.FunctionalTestsBase):
def assertOutput(self, value, stack_identifier, key):
op = self.client.stacks.output_show(stack_identifier, key)['output']
self.assertEqual(key, op['output_key'])
if 'output_error' in op:
raise Exception(op['output_error'])
self.assertEqual(value, op['output_value'])
def test_nested_get_attr_create(self):
stack_identifier = self.stack_create(template=initial_template)
self.assertOutput('wibble', stack_identifier, 'value1')
self.assertOutput('bar', stack_identifier, 'value2')
self.assertOutput('barney', stack_identifier, 'value3')
def test_nested_get_attr_update(self):
stack_identifier = self.stack_create(template=initial_template)
self.update_stack(stack_identifier, template=update_template)
self.assertOutput('bar', stack_identifier, 'value1')
self.assertOutput('barney', stack_identifier, 'value2')
self.assertOutput('wibble', stack_identifier, 'value3')
self.assertOutput('quux', stack_identifier, 'value4')

View File

@ -1,194 +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.
import kombu
from oslo_config import cfg
from oslo_messaging._drivers import common
from oslo_messaging import transport
import requests
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
BASIC_NOTIFICATIONS = [
'orchestration.stack.create.start',
'orchestration.stack.create.end',
'orchestration.stack.update.start',
'orchestration.stack.update.end',
'orchestration.stack.suspend.start',
'orchestration.stack.suspend.end',
'orchestration.stack.resume.start',
'orchestration.stack.resume.end',
'orchestration.stack.delete.start',
'orchestration.stack.delete.end'
]
ASG_NOTIFICATIONS = [
'orchestration.autoscaling.start',
'orchestration.autoscaling.end'
]
def get_url(conf):
conf = conf.oslo_messaging_rabbit
return 'amqp://%s:%s@%s:%s/' % (conf.rabbit_userid,
conf.rabbit_password,
conf.rabbit_host,
conf.rabbit_port)
class NotificationHandler(object):
def __init__(self, stack_id, events=None):
self._notifications = []
self.stack_id = stack_id
self.events = events
def process_message(self, body, message):
notification = common.deserialize_msg(body)
if notification['payload']['stack_name'] == self.stack_id:
if self.events is not None:
if notification['event_type'] in self.events:
self.notifications.append(notification['event_type'])
else:
self.notifications.append(notification['event_type'])
message.ack()
def clear(self):
self._notifications = []
@property
def notifications(self):
return self._notifications
class NotificationTest(functional_base.FunctionalTestsBase):
basic_template = '''
heat_template_version: 2013-05-23
resources:
random1:
type: OS::Heat::RandomString
'''
update_basic_template = '''
heat_template_version: 2013-05-23
resources:
random1:
type: OS::Heat::RandomString
random2:
type: OS::Heat::RandomString
'''
asg_template = '''
heat_template_version: 2013-05-23
resources:
asg:
type: OS::Heat::AutoScalingGroup
properties:
resource:
type: OS::Heat::RandomString
min_size: 1
desired_capacity: 2
max_size: 3
scale_up_policy:
type: OS::Heat::ScalingPolicy
properties:
adjustment_type: change_in_capacity
auto_scaling_group_id: {get_resource: asg}
cooldown: 0
scaling_adjustment: 1
scale_down_policy:
type: OS::Heat::ScalingPolicy
properties:
adjustment_type: change_in_capacity
auto_scaling_group_id: {get_resource: asg}
cooldown: 0
scaling_adjustment: '-1'
outputs:
scale_up_url:
value: {get_attr: [scale_up_policy, alarm_url]}
scale_dn_url:
value: {get_attr: [scale_down_policy, alarm_url]}
'''
def setUp(self):
super(NotificationTest, self).setUp()
self.exchange = kombu.Exchange('heat', 'topic', durable=False)
queue = kombu.Queue(exchange=self.exchange,
routing_key='notifications.info',
exclusive=True)
self.conn = kombu.Connection(get_url(
transport.get_transport(cfg.CONF).conf))
self.ch = self.conn.channel()
self.queue = queue(self.ch)
self.queue.declare()
def consume_events(self, handler, count):
self.conn.drain_events()
return len(handler.notifications) == count
def test_basic_notifications(self):
# 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)
self.stack_resume(stack_identifier)
self._stack_delete(stack_identifier)
handler = NotificationHandler(stack_identifier.split('/')[0])
with self.conn.Consumer(self.queue,
callbacks=[handler.process_message],
auto_declare=False):
try:
while True:
self.conn.drain_events(timeout=1)
except Exception:
pass
for n in BASIC_NOTIFICATIONS:
self.assertIn(n, handler.notifications)
def test_asg_notifications(self):
stack_identifier = self.stack_create(template=self.asg_template)
for output in self.client.stacks.get(stack_identifier).outputs:
if output['output_key'] == 'scale_dn_url':
scale_down_url = output['output_value']
else:
scale_up_url = output['output_value']
notifications = []
handler = NotificationHandler(stack_identifier.split('/')[0],
ASG_NOTIFICATIONS)
with self.conn.Consumer(self.queue,
callbacks=[handler.process_message],
auto_declare=False):
requests.post(scale_up_url, verify=self.verify_cert)
self.assertTrue(
test.call_until_true(20, 0, self.consume_events, handler, 2))
notifications += handler.notifications
handler.clear()
requests.post(scale_down_url, verify=self.verify_cert)
self.assertTrue(
test.call_until_true(20, 0, self.consume_events, handler, 2))
notifications += handler.notifications
self.assertEqual(2, notifications.count(ASG_NOTIFICATIONS[0]))
self.assertEqual(2, notifications.count(ASG_NOTIFICATIONS[1]))

View File

@ -1,298 +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_tempest_plugin.tests.functional import functional_base
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
}
}
}
}
class UpdatePreviewBase(functional_base.FunctionalTestsBase):
def assert_empty_sections(self, changes, empty_sections):
for section in empty_sections:
self.assertEqual([], changes[section])
class UpdatePreviewStackTest(UpdatePreviewBase):
def test_add_resource(self):
self.stack_identifier = self.stack_create(
template=test_template_one_resource)
result = self.preview_update_stack(self.stack_identifier,
test_template_two_resource)
changes = result['resource_changes']
unchanged = changes['unchanged'][0]['resource_name']
self.assertEqual('test1', unchanged)
added = changes['added'][0]['resource_name']
self.assertEqual('test2', added)
self.assert_empty_sections(changes, ['updated', 'replaced', 'deleted'])
def test_no_change(self):
self.stack_identifier = self.stack_create(
template=test_template_one_resource)
result = self.preview_update_stack(self.stack_identifier,
test_template_one_resource)
changes = result['resource_changes']
unchanged = changes['unchanged'][0]['resource_name']
self.assertEqual('test1', unchanged)
self.assert_empty_sections(
changes, ['updated', 'replaced', 'deleted', 'added'])
def test_update_resource(self):
self.stack_identifier = self.stack_create(
template=test_template_one_resource)
test_template_updated_resource = {
'heat_template_version': '2013-05-23',
'description': 'Test template to create one instance.',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1 foo',
'fail': False,
'update_replace': False,
'wait_secs': 0
}
}
}
}
result = self.preview_update_stack(self.stack_identifier,
test_template_updated_resource)
changes = result['resource_changes']
updated = changes['updated'][0]['resource_name']
self.assertEqual('test1', updated)
self.assert_empty_sections(
changes, ['added', 'unchanged', 'replaced', 'deleted'])
def test_replaced_resource(self):
self.stack_identifier = self.stack_create(
template=test_template_one_resource)
new_template = {
'heat_template_version': '2013-05-23',
'description': 'Test template to create one instance.',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'update_replace': True,
}
}
}
}
result = self.preview_update_stack(self.stack_identifier, new_template)
changes = result['resource_changes']
replaced = changes['replaced'][0]['resource_name']
self.assertEqual('test1', replaced)
self.assert_empty_sections(
changes, ['added', 'unchanged', 'updated', 'deleted'])
def test_delete_resource(self):
self.stack_identifier = self.stack_create(
template=test_template_two_resource)
result = self.preview_update_stack(self.stack_identifier,
test_template_one_resource)
changes = result['resource_changes']
unchanged = changes['unchanged'][0]['resource_name']
self.assertEqual('test1', unchanged)
deleted = changes['deleted'][0]['resource_name']
self.assertEqual('test2', deleted)
self.assert_empty_sections(changes, ['updated', 'replaced', 'added'])
class UpdatePreviewStackTestNested(UpdatePreviewBase):
template_nested_parent = '''
heat_template_version: 2016-04-08
resources:
nested1:
type: nested1.yaml
'''
template_nested1 = '''
heat_template_version: 2016-04-08
resources:
nested2:
type: nested2.yaml
'''
template_nested2 = '''
heat_template_version: 2016-04-08
resources:
random:
type: OS::Heat::RandomString
'''
template_nested2_2 = '''
heat_template_version: 2016-04-08
resources:
random:
type: OS::Heat::RandomString
random2:
type: OS::Heat::RandomString
'''
def _get_by_resource_name(self, changes, name, action):
filtered_l = [x for x in changes[action]
if x['resource_name'] == name]
self.assertEqual(1, len(filtered_l))
return filtered_l[0]
def test_nested_resources_nochange(self):
files = {'nested1.yaml': self.template_nested1,
'nested2.yaml': self.template_nested2}
self.stack_identifier = self.stack_create(
template=self.template_nested_parent, files=files)
result = self.preview_update_stack(
self.stack_identifier,
template=self.template_nested_parent,
files=files, show_nested=True)
changes = result['resource_changes']
# The nested random resource should be unchanged, but we always
# update nested stacks even when there are no changes
self.assertEqual(1, len(changes['unchanged']))
self.assertEqual('random', changes['unchanged'][0]['resource_name'])
self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
self.assertEqual(2, len(changes['updated']))
u_nested1 = self._get_by_resource_name(changes, 'nested1', 'updated')
self.assertNotIn('parent_resource', u_nested1)
u_nested2 = self._get_by_resource_name(changes, 'nested2', 'updated')
self.assertEqual('nested1', u_nested2['parent_resource'])
self.assert_empty_sections(changes, ['replaced', 'deleted', 'added'])
def test_nested_resources_add(self):
files = {'nested1.yaml': self.template_nested1,
'nested2.yaml': self.template_nested2}
self.stack_identifier = self.stack_create(
template=self.template_nested_parent, files=files)
files['nested2.yaml'] = self.template_nested2_2
result = self.preview_update_stack(
self.stack_identifier,
template=self.template_nested_parent,
files=files, show_nested=True)
changes = result['resource_changes']
# The nested random resource should be unchanged, but we always
# update nested stacks even when there are no changes
self.assertEqual(1, len(changes['unchanged']))
self.assertEqual('random', changes['unchanged'][0]['resource_name'])
self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
self.assertEqual(1, len(changes['added']))
self.assertEqual('random2', changes['added'][0]['resource_name'])
self.assertEqual('nested2', changes['added'][0]['parent_resource'])
self.assert_empty_sections(changes, ['replaced', 'deleted'])
def test_nested_resources_delete(self):
files = {'nested1.yaml': self.template_nested1,
'nested2.yaml': self.template_nested2_2}
self.stack_identifier = self.stack_create(
template=self.template_nested_parent, files=files)
files['nested2.yaml'] = self.template_nested2
result = self.preview_update_stack(
self.stack_identifier,
template=self.template_nested_parent,
files=files, show_nested=True)
changes = result['resource_changes']
# The nested random resource should be unchanged, but we always
# update nested stacks even when there are no changes
self.assertEqual(1, len(changes['unchanged']))
self.assertEqual('random', changes['unchanged'][0]['resource_name'])
self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
self.assertEqual(1, len(changes['deleted']))
self.assertEqual('random2', changes['deleted'][0]['resource_name'])
self.assertEqual('nested2', changes['deleted'][0]['parent_resource'])
self.assert_empty_sections(changes, ['replaced', 'added'])
def test_nested_resources_replace(self):
files = {'nested1.yaml': self.template_nested1,
'nested2.yaml': self.template_nested2}
self.stack_identifier = self.stack_create(
template=self.template_nested_parent, files=files)
parent_none = self.template_nested_parent.replace(
'nested1.yaml', 'OS::Heat::None')
result = self.preview_update_stack(
self.stack_identifier,
template=parent_none,
show_nested=True)
changes = result['resource_changes']
# The nested random resource should be unchanged, but we always
# update nested stacks even when there are no changes
self.assertEqual(1, len(changes['replaced']))
self.assertEqual('nested1', changes['replaced'][0]['resource_name'])
self.assertEqual(2, len(changes['deleted']))
d_random = self._get_by_resource_name(changes, 'random', 'deleted')
self.assertEqual('nested2', d_random['parent_resource'])
d_nested2 = self._get_by_resource_name(changes, 'nested2', 'deleted')
self.assertEqual('nested1', d_nested2['parent_resource'])
self.assert_empty_sections(changes, ['updated', 'unchanged', 'added'])

View File

@ -1,51 +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.
import time
from oslo_concurrency import processutils
from heat_tempest_plugin.tests.functional import functional_base
class PurgeTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2014-10-16
parameters:
resources:
test_resource:
type: OS::Heat::TestResource
'''
def test_purge(self):
stack_identifier = self.stack_create(template=self.template)
self._stack_delete(stack_identifier)
stacks = dict((stack.id, stack) for stack in
self.client.stacks.list(show_deleted=True))
self.assertIn(stack_identifier.split('/')[1], stacks)
time.sleep(1)
cmd = "heat-manage purge_deleted 0"
processutils.execute(cmd, shell=True)
stacks = dict((stack.id, stack) for stack in
self.client.stacks.list(show_deleted=True))
self.assertNotIn(stack_identifier.split('/')[1], stacks)
# Test with tags
stack_identifier = self.stack_create(template=self.template,
tags="foo,bar")
self._stack_delete(stack_identifier)
time.sleep(1)
cmd = "heat-manage purge_deleted 0"
processutils.execute(cmd, shell=True)
stacks = dict((stack.id, stack) for stack in
self.client.stacks.list(show_deleted=True))
self.assertNotIn(stack_identifier.split('/')[1], stacks)

View File

@ -1,142 +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.
import re
import subprocess
import time
import eventlet
from oslo_concurrency import processutils
from six.moves import configparser
from heat_tempest_plugin.tests.functional import functional_base
class ReloadOnSighupTest(functional_base.FunctionalTestsBase):
def setUp(self):
self.config_file = "/etc/heat/heat.conf"
super(ReloadOnSighupTest, self).setUp()
def _is_mod_wsgi_daemon(self, service):
process = ''.join(['wsgi:',
service[:9]]).replace('_', '-').encode('utf-8')
s = subprocess.Popen(["ps", "ax"], stdout=subprocess.PIPE)
for x in s.stdout:
if re.search(process, x):
return True
def _set_config_value(self, service, key, value):
config = configparser.ConfigParser()
# NOTE(prazumovsky): If there are several workers, there can be
# situation, when one thread opens self.config_file for writing
# (so config_file erases with opening), in that moment other thread
# intercepts to this file and try to set config option value, i.e.
# write to file, which is already erased by first thread, so,
# NoSectionError raised. So, should wait until first thread writes to
# config_file.
retries_count = self.conf.sighup_config_edit_retries
while True:
config.read(self.config_file)
try:
config.set(service, key, str(value))
except configparser.NoSectionError:
if retries_count <= 0:
raise
retries_count -= 1
eventlet.sleep(1)
else:
break
with open(self.config_file, 'w') as f:
config.write(f)
def _get_config_value(self, service, key):
config = configparser.ConfigParser()
config.read(self.config_file)
val = config.get(service, key)
return val
def _get_heat_api_pids(self, service):
# get the pids of all heat-api processes
if service == "heat_api":
process = "heat-api|grep -Ev 'grep|cloudwatch|cfn'"
else:
process = "%s|grep -Ev 'grep'" % service.replace('_', '-')
cmd = "ps -ef|grep %s|awk '{print $2}'" % process
out, err = processutils.execute(cmd, shell=True)
self.assertIsNotNone(out, "heat-api service not running. %s" % err)
pids = filter(None, out.split('\n'))
# get the parent pids of all heat-api processes
cmd = "ps -ef|grep %s|awk '{print $3}'" % process
out, _ = processutils.execute(cmd, shell=True)
parent_pids = filter(None, out.split('\n'))
heat_api_parent = list(set(pids) & set(parent_pids))[0]
heat_api_children = list(set(pids) - set(parent_pids))
return heat_api_parent, heat_api_children
def _change_config(self, service, old_workers, new_workers):
pre_reload_parent, pre_reload_children = self._get_heat_api_pids(
service)
self.assertEqual(old_workers, len(pre_reload_children))
# change the config values
self._set_config_value(service, 'workers', new_workers)
cmd = "kill -HUP %s" % pre_reload_parent
processutils.execute(cmd, shell=True)
# wait till heat-api reloads
start_time = time.time()
while time.time() - start_time < self.conf.sighup_timeout:
post_reload_parent, post_reload_children = self._get_heat_api_pids(
service)
intersect = set(post_reload_children) & set(pre_reload_children)
if (new_workers == len(post_reload_children)
and pre_reload_parent == post_reload_parent
and intersect == set()):
break
eventlet.sleep(1)
self.assertEqual(pre_reload_parent, post_reload_parent)
self.assertEqual(new_workers, len(post_reload_children))
# test if all child processes are newly created
self.assertEqual(set(post_reload_children) & set(pre_reload_children),
set())
def _reload(self, service):
old_workers = int(self._get_config_value(service, 'workers'))
new_workers = old_workers + 1
self.addCleanup(self._set_config_value, service, 'workers',
old_workers)
self._change_config(service, old_workers, new_workers)
# revert all the changes made
self._change_config(service, new_workers, old_workers)
def _reload_on_sighup(self, service):
if not self._is_mod_wsgi_daemon(service):
self._reload(service)
else:
self.skipTest('Skipping Test, Service running under httpd.')
def test_api_reload_on_sighup(self):
self._reload_on_sighup('heat_api')
def test_api_cfn_reload_on_sighup(self):
self._reload_on_sighup('heat_api_cfn')
def test_api_cloudwatch_on_sighup(self):
self._reload_on_sighup('heat_api_cloudwatch')

View File

@ -1,92 +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.
import yaml
from heat_tempest_plugin.tests.functional import functional_base
class ReplaceDeprecatedResourceTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: "2013-05-23"
parameters:
flavor:
type: string
image:
type: string
network:
type: string
resources:
config:
type: OS::Heat::SoftwareConfig
properties:
config: xxxx
server:
type: OS::Nova::Server
properties:
image: {get_param: image}
flavor: {get_param: flavor}
networks: [{network: {get_param: network} }]
user_data_format: SOFTWARE_CONFIG
dep:
type: OS::Heat::SoftwareDeployments
properties:
config: {get_resource: config}
servers: {'0': {get_resource: server}}
signal_transport: NO_SIGNAL
outputs:
server:
value: {get_resource: server}
'''
deployment_group_snippet = '''
type: OS::Heat::SoftwareDeploymentGroup
properties:
config: {get_resource: config}
servers: {'0': {get_resource: server}}
signal_transport: NO_SIGNAL
'''
enable_cleanup = True
def test_replace_software_deployments(self):
parms = {'flavor': self.conf.minimal_instance_type,
'network': self.conf.fixed_network_name,
'image': self.conf.minimal_image_ref
}
deployments_template = yaml.safe_load(self.template)
stack_identifier = self.stack_create(
parameters=parms,
template=deployments_template,
enable_cleanup=self.enable_cleanup)
expected_resources = {'config': 'OS::Heat::SoftwareConfig',
'dep': 'OS::Heat::SoftwareDeployments',
'server': 'OS::Nova::Server'}
resource = self.client.resources.get(stack_identifier, 'server')
self.assertEqual(expected_resources,
self.list_resources(stack_identifier))
initial_phy_id = resource.physical_resource_id
resources = deployments_template['resources']
resources['dep'] = yaml.safe_load(self.deployment_group_snippet)
self.update_stack(
stack_identifier,
deployments_template,
parameters=parms)
resource = self.client.resources.get(stack_identifier, 'server')
self.assertEqual(initial_phy_id,
resource.physical_resource_id)
expected_new_resources = {'config': 'OS::Heat::SoftwareConfig',
'dep': 'OS::Heat::SoftwareDeploymentGroup',
'server': 'OS::Nova::Server'}
self.assertEqual(expected_new_resources,
self.list_resources(stack_identifier))

View File

@ -1,167 +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_tempest_plugin.tests.functional import functional_base
TEMPLATE_SIMPLE = '''
heat_template_version: 2016-04-08
parameters:
string-length:
type: number
resources:
my-chain:
type: OS::Heat::ResourceChain
properties:
resources: ['OS::Heat::RandomString', 'OS::Heat::RandomString']
resource_properties:
length: { get_param: string-length }
outputs:
resource-ids:
value: { get_attr: [my-chain, refs] }
resource-0-value:
value: { get_attr: [my-chain, resource.0, value] }
all-resource-attrs:
value: { get_attr: [my-chain, attributes, value] }
'''
TEMPLATE_PARAM_DRIVEN = '''
heat_template_version: 2016-04-08
parameters:
chain-types:
type: comma_delimited_list
resources:
my-chain:
type: OS::Heat::ResourceChain
properties:
resources: { get_param: chain-types }
'''
class ResourceChainTests(functional_base.FunctionalTestsBase):
def test_create(self):
# Test
params = {'string-length': 8}
stack_id = self.stack_create(template=TEMPLATE_SIMPLE,
parameters=params)
# Verify
stack = self.client.stacks.get(stack_id)
self.assertIsNotNone(stack)
# Top-level resource for chain
expected = {'my-chain': 'OS::Heat::ResourceChain'}
found = self.list_resources(stack_id)
self.assertEqual(expected, found)
# Nested stack exists and has two resources
nested_id = self.group_nested_identifier(stack_id, 'my-chain')
expected = {'0': 'OS::Heat::RandomString',
'1': 'OS::Heat::RandomString'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)
# Outputs
resource_ids = self._stack_output(stack, 'resource-ids')
self.assertIsNotNone(resource_ids)
self.assertEqual(2, len(resource_ids))
resource_value = self._stack_output(stack, 'resource-0-value')
self.assertIsNotNone(resource_value)
self.assertEqual(8, len(resource_value)) # from parameter
resource_attrs = self._stack_output(stack, 'all-resource-attrs')
self.assertIsNotNone(resource_attrs)
self.assertIsInstance(resource_attrs, dict)
self.assertEqual(2, len(resource_attrs))
self.assertEqual(8, len(resource_attrs['0']))
self.assertEqual(8, len(resource_attrs['1']))
def test_update(self):
# Setup
params = {'string-length': 8}
stack_id = self.stack_create(template=TEMPLATE_SIMPLE,
parameters=params)
update_tmpl = '''
heat_template_version: 2016-04-08
parameters:
string-length:
type: number
resources:
my-chain:
type: OS::Heat::ResourceChain
properties:
resources: ['OS::Heat::None']
'''
# Test
self.update_stack(stack_id, template=update_tmpl, parameters=params)
# Verify
# Nested stack only has the None resource
nested_id = self.group_nested_identifier(stack_id, 'my-chain')
expected = {'0': 'OS::Heat::None'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)
def test_update_resources(self):
params = {'chain-types': 'OS::Heat::None'}
stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN,
parameters=params)
nested_id = self.group_nested_identifier(stack_id, 'my-chain')
expected = {'0': 'OS::Heat::None'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)
params = {'chain-types': 'OS::Heat::None,OS::Heat::None'}
self.update_stack(stack_id, template=TEMPLATE_PARAM_DRIVEN,
parameters=params)
expected = {'0': 'OS::Heat::None', '1': 'OS::Heat::None'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)
def test_resources_param_driven(self):
# Setup
params = {'chain-types':
'OS::Heat::None,OS::Heat::RandomString,OS::Heat::None'}
# Test
stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN,
parameters=params)
# Verify
nested_id = self.group_nested_identifier(stack_id, 'my-chain')
expected = {'0': 'OS::Heat::None',
'1': 'OS::Heat::RandomString',
'2': 'OS::Heat::None'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)
def test_resources_env_defined(self):
# Setup
env = {'parameters': {'chain-types': 'OS::Heat::None'}}
# Test
stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN,
environment=env)
# Verify
nested_id = self.group_nested_identifier(stack_id, 'my-chain')
expected = {'0': 'OS::Heat::None'}
found = self.list_resources(nested_id)
self.assertEqual(expected, found)

View File

@ -1,695 +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.
import copy
import json
from heatclient import exc
import six
import yaml
from heat_tempest_plugin.tests.functional import functional_base
class ResourceGroupTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2013-05-23
resources:
random_group:
type: OS::Heat::ResourceGroup
properties:
count: 0
resource_def:
type: My::RandomString
properties:
length: 30
salt: initial
outputs:
random1:
value: {get_attr: [random_group, resource.0.value]}
random2:
value: {get_attr: [random_group, resource.1.value]}
all_values:
value: {get_attr: [random_group, value]}
'''
def test_resource_group_zero_novalidate(self):
# Nested resources should be validated only when size > 0
# This allows features to be disabled via size=0 without
# triggering validation of nested resource custom constraints
# e.g images etc in the nested schema.
nested_template_fail = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: 50
salt:
type: string
default: initial
resources:
random:
type: OS::Heat::RandomString
properties:
length: BAD
'''
files = {'provider.yaml': nested_template_fail}
env = {'resource_registry':
{'My::RandomString': 'provider.yaml'}}
stack_identifier = self.stack_create(
template=self.template,
files=files,
environment=env
)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
# Check we created an empty nested stack
nested_identifier = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual({}, self.list_resources(nested_identifier))
# Prove validation works for non-zero create/update
template_two_nested = self.template.replace("count: 0", "count: 2")
expected_err = ("resources.random_group<nested_stack>.resources."
"0<provider.yaml>.resources.random: : "
"Value 'BAD' is not an integer")
ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack,
stack_identifier, template_two_nested,
environment=env, files=files)
self.assertIn(expected_err, six.text_type(ex))
ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create,
template=template_two_nested,
environment=env, files=files)
self.assertIn(expected_err, six.text_type(ex))
def _validate_resources(self, stack_identifier, expected_count):
resources = self.list_group_resources(stack_identifier,
'random_group')
self.assertEqual(expected_count, len(resources))
expected_resources = dict(
(str(idx), 'My::RandomString')
for idx in range(expected_count))
self.assertEqual(expected_resources, resources)
def test_create(self):
def validate_output(stack, output_key, length):
output_value = self._stack_output(stack, output_key)
self.assertEqual(length, len(output_value))
return output_value
# verify that the resources in resource group are identically
# configured, resource names and outputs are appropriate.
env = {'resource_registry':
{'My::RandomString': 'OS::Heat::RandomString'}}
create_template = self.template.replace("count: 0", "count: 2")
stack_identifier = self.stack_create(template=create_template,
environment=env)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
# validate count, type and name of resources in a resource group.
self._validate_resources(stack_identifier, 2)
# validate outputs
stack = self.client.stacks.get(stack_identifier)
outputs = []
outputs.append(validate_output(stack, 'random1', 30))
outputs.append(validate_output(stack, 'random2', 30))
self.assertEqual(outputs, self._stack_output(stack, 'all_values'))
def test_update_increase_decrease_count(self):
# create stack with resource group count 2
env = {'resource_registry':
{'My::RandomString': 'OS::Heat::RandomString'}}
create_template = self.template.replace("count: 0", "count: 2")
stack_identifier = self.stack_create(template=create_template,
environment=env)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
# verify that the resource group has 2 resources
self._validate_resources(stack_identifier, 2)
# increase the resource group count to 5
update_template = self.template.replace("count: 0", "count: 5")
self.update_stack(stack_identifier, update_template, environment=env)
# verify that the resource group has 5 resources
self._validate_resources(stack_identifier, 5)
# decrease the resource group count to 3
update_template = self.template.replace("count: 0", "count: 3")
self.update_stack(stack_identifier, update_template, environment=env)
# verify that the resource group has 3 resources
self._validate_resources(stack_identifier, 3)
def test_update_removal_policies(self):
rp_template = '''
heat_template_version: 2014-10-16
resources:
random_group:
type: OS::Heat::ResourceGroup
properties:
count: 5
removal_policies: []
resource_def:
type: OS::Heat::RandomString
'''
# create stack with resource group, initial count 5
stack_identifier = self.stack_create(template=rp_template)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
group_resources = self.list_group_resources(stack_identifier,
'random_group')
expected_resources = {u'0': u'OS::Heat::RandomString',
u'1': u'OS::Heat::RandomString',
u'2': u'OS::Heat::RandomString',
u'3': u'OS::Heat::RandomString',
u'4': u'OS::Heat::RandomString'}
self.assertEqual(expected_resources, group_resources)
# Remove three, specifying the middle resources to be removed
update_template = rp_template.replace(
'removal_policies: []',
'removal_policies: [{resource_list: [\'1\', \'2\', \'3\']}]')
self.update_stack(stack_identifier, update_template)
group_resources = self.list_group_resources(stack_identifier,
'random_group')
expected_resources = {u'0': u'OS::Heat::RandomString',
u'4': u'OS::Heat::RandomString',
u'5': u'OS::Heat::RandomString',
u'6': u'OS::Heat::RandomString',
u'7': u'OS::Heat::RandomString'}
self.assertEqual(expected_resources, group_resources)
def test_props_update(self):
"""Test update of resource_def properties behaves as expected."""
env = {'resource_registry':
{'My::RandomString': 'OS::Heat::RandomString'}}
template_one = self.template.replace("count: 0", "count: 1")
stack_identifier = self.stack_create(template=template_one,
environment=env)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
initial_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual({'0': 'My::RandomString'},
self.list_resources(initial_nested_ident))
# get the resource id
res = self.client.resources.get(initial_nested_ident, '0')
initial_res_id = res.physical_resource_id
# change the salt (this should replace the RandomString but
# not the nested stack or resource group.
template_salt = template_one.replace("salt: initial", "salt: more")
self.update_stack(stack_identifier, template_salt, environment=env)
updated_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual(initial_nested_ident, updated_nested_ident)
# compare the resource id, we expect a change.
res = self.client.resources.get(updated_nested_ident, '0')
updated_res_id = res.physical_resource_id
self.assertNotEqual(initial_res_id, updated_res_id)
def test_update_nochange(self):
"""Test update with no properties change."""
env = {'resource_registry':
{'My::RandomString': 'OS::Heat::RandomString'}}
template_one = self.template.replace("count: 0", "count: 2")
stack_identifier = self.stack_create(template=template_one,
environment=env)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
initial_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
self.list_resources(initial_nested_ident))
# get the output
stack0 = self.client.stacks.get(stack_identifier)
initial_rand = self._stack_output(stack0, 'random1')
template_copy = copy.deepcopy(template_one)
self.update_stack(stack_identifier, template_copy, environment=env)
updated_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual(initial_nested_ident, updated_nested_ident)
# compare the random number, we expect no change.
stack1 = self.client.stacks.get(stack_identifier)
updated_rand = self._stack_output(stack1, 'random1')
self.assertEqual(initial_rand, updated_rand)
def test_update_nochange_resource_needs_update(self):
"""Test update when the resource definition has changed.
Test the scenario when the ResourceGroup update happens without
any changed properties, this can happen if the definition of
a contained provider resource changes (files map changes), then
the group and underlying nested stack should end up updated.
"""
random_templ1 = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: not-used
salt:
type: string
default: not-used
resources:
random1:
type: OS::Heat::RandomString
properties:
salt: initial
outputs:
value:
value: {get_attr: [random1, value]}
'''
files1 = {'my_random.yaml': random_templ1}
random_templ2 = random_templ1.replace('salt: initial',
'salt: more')
files2 = {'my_random.yaml': random_templ2}
env = {'resource_registry':
{'My::RandomString': 'my_random.yaml'}}
template_one = self.template.replace("count: 0", "count: 2")
stack_identifier = self.stack_create(template=template_one,
environment=env,
files=files1)
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
self.list_resources(stack_identifier))
self.assertEqual(files1, self.client.stacks.files(stack_identifier))
initial_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
self.list_resources(initial_nested_ident))
# get the output
stack0 = self.client.stacks.get(stack_identifier)
initial_rand = self._stack_output(stack0, 'random1')
# change the environment so we use a different TemplateResource.
# note "files2".
self.update_stack(stack_identifier, template_one,
environment=env, files=files2)
updated_nested_ident = self.group_nested_identifier(stack_identifier,
'random_group')
self.assertEqual(initial_nested_ident, updated_nested_ident)
self.assertEqual(files2, self.client.stacks.files(stack_identifier))
# compare the output, we expect a change.
stack1 = self.client.stacks.get(stack_identifier)
updated_rand = self._stack_output(stack1, 'random1')
self.assertNotEqual(initial_rand, updated_rand)
class ResourceGroupTestNullParams(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: 2013-05-23
parameters:
param:
type: empty
resources:
random_group:
type: OS::Heat::ResourceGroup
properties:
count: 1
resource_def:
type: My::RandomString
properties:
param: {get_param: param}
outputs:
val:
value: {get_attr: [random_group, val]}
'''
nested_template_file = '''
heat_template_version: 2013-05-23
parameters:
param:
type: empty
outputs:
val:
value: {get_param: param}
'''
scenarios = [
('string_empty', dict(
param='',
p_type='string',
)),
('boolean_false', dict(
param=False,
p_type='boolean',
)),
('number_zero', dict(
param=0,
p_type='number',
)),
('comma_delimited_list', dict(
param=[],
p_type='comma_delimited_list',
)),
('json_empty', dict(
param={},
p_type='json',
)),
]
def test_create_pass_zero_parameter(self):
templ = self.template.replace('type: empty',
'type: %s' % self.p_type)
n_t_f = self.nested_template_file.replace('type: empty',
'type: %s' % self.p_type)
files = {'provider.yaml': n_t_f}
env = {'resource_registry':
{'My::RandomString': 'provider.yaml'}}
stack_identifier = self.stack_create(
template=templ,
files=files,
environment=env,
parameters={'param': self.param}
)
stack = self.client.stacks.get(stack_identifier)
self.assertEqual(self.param, self._stack_output(stack, 'val')[0])
class ResourceGroupAdoptTest(functional_base.FunctionalTestsBase):
"""Prove that we can do resource group adopt."""
main_template = '''
heat_template_version: "2013-05-23"
resources:
group1:
type: OS::Heat::ResourceGroup
properties:
count: 2
resource_def:
type: OS::Heat::RandomString
outputs:
test0:
value: {get_attr: [group1, resource.0.value]}
test1:
value: {get_attr: [group1, resource.1.value]}
'''
def _yaml_to_json(self, yaml_templ):
return yaml.safe_load(yaml_templ)
def test_adopt(self):
data = {
"resources": {
"group1": {
"status": "COMPLETE",
"name": "group1",
"resource_data": {},
"metadata": {},
"resource_id": "test-group1-id",
"action": "CREATE",
"type": "OS::Heat::ResourceGroup",
"resources": {
"0": {
"status": "COMPLETE",
"name": "0",
"resource_data": {"value": "goopie"},
"resource_id": "ID-0",
"action": "CREATE",
"type": "OS::Heat::RandomString",
"metadata": {}
},
"1": {
"status": "COMPLETE",
"name": "1",
"resource_data": {"value": "different"},
"resource_id": "ID-1",
"action": "CREATE",
"type": "OS::Heat::RandomString",
"metadata": {}
}
}
}
},
"environment": {"parameters": {}},
"template": yaml.safe_load(self.main_template)
}
stack_identifier = self.stack_adopt(
adopt_data=json.dumps(data))
self.assert_resource_is_a_stack(stack_identifier, 'group1')
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('goopie', self._stack_output(stack, 'test0'))
self.assertEqual('different', self._stack_output(stack, 'test1'))
class ResourceGroupErrorResourceTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: "2013-05-23"
resources:
group1:
type: OS::Heat::ResourceGroup
properties:
count: 2
resource_def:
type: fail.yaml
'''
nested_templ = '''
heat_template_version: "2013-05-23"
resources:
oops:
type: OS::Heat::TestResource
properties:
fail: true
wait_secs: 2
'''
def test_fail(self):
stack_identifier = self.stack_create(
template=self.template,
files={'fail.yaml': self.nested_templ},
expected_status='CREATE_FAILED',
enable_cleanup=False)
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('CREATE_FAILED', stack.stack_status)
self.client.stacks.delete(stack_identifier)
self._wait_for_stack_status(
stack_identifier, 'DELETE_COMPLETE',
success_on_not_found=True)
class ResourceGroupUpdatePolicyTest(functional_base.FunctionalTestsBase):
template = '''
heat_template_version: '2015-04-30'
resources:
random_group:
type: OS::Heat::ResourceGroup
update_policy:
rolling_update:
min_in_service: 1
max_batch_size: 2
pause_time: 1
properties:
count: 10
resource_def:
type: OS::Heat::TestResource
properties:
value: initial
update_replace: False
'''
def update_resource_group(self, update_template,
updated, created, deleted):
stack_identifier = self.stack_create(template=self.template)
group_resources = self.list_group_resources(stack_identifier,
'random_group',
minimal=False)
init_names = [res.physical_resource_id for res in group_resources]
self.update_stack(stack_identifier, update_template)
group_resources = self.list_group_resources(stack_identifier,
'random_group',
minimal=False)
updt_names = [res.physical_resource_id for res in group_resources]
matched_names = set(updt_names) & set(init_names)
self.assertEqual(updated, len(matched_names))
self.assertEqual(created, len(set(updt_names) - set(init_names)))
self.assertEqual(deleted, len(set(init_names) - set(updt_names)))
def test_resource_group_update(self):
"""Test rolling update with no conflict.
Simple rolling update with no conflict in batch size
and minimum instances in service.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '1'
policy['max_batch_size'] = '3'
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=10,
created=0,
deleted=0)
def test_resource_group_update_replace(self):
"""Test rolling update(replace)with no conflict.
Simple rolling update replace with no conflict in batch size
and minimum instances in service.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '1'
policy['max_batch_size'] = '3'
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
res_def['properties']['update_replace'] = True
self.update_resource_group(updt_template,
updated=0,
created=10,
deleted=10)
def test_resource_group_update_scaledown(self):
"""Test rolling update with scaledown.
Simple rolling update with reduced size.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '1'
policy['max_batch_size'] = '3'
grp['properties']['count'] = 6
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=6,
created=0,
deleted=4)
def test_resource_group_update_scaleup(self):
"""Test rolling update with scaleup.
Simple rolling update with increased size.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '1'
policy['max_batch_size'] = '3'
grp['properties']['count'] = 12
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=10,
created=2,
deleted=0)
def test_resource_group_update_adjusted(self):
"""Test rolling update with enough available resources
Update with capacity adjustment with enough resources.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '8'
policy['max_batch_size'] = '4'
grp['properties']['count'] = 6
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=6,
created=0,
deleted=4)
def test_resource_group_update_with_adjusted_capacity(self):
"""Test rolling update with capacity adjustment.
Rolling update with capacity adjustment due to conflict in
batch size and minimum instances in service.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '8'
policy['max_batch_size'] = '4'
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=10,
created=0,
deleted=0)
def test_resource_group_update_huge_batch_size(self):
"""Test rolling update with huge batch size.
Rolling Update with a huge batch size(more than
current size).
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '0'
policy['max_batch_size'] = '20'
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=10,
created=0,
deleted=0)
def test_resource_group_update_huge_min_in_service(self):
"""Test rolling update with huge minimum capacity.
Rolling Update with a huge number of minimum instances
in service.
"""
updt_template = yaml.safe_load(copy.deepcopy(self.template))
grp = updt_template['resources']['random_group']
policy = grp['update_policy']['rolling_update']
policy['min_in_service'] = '20'
policy['max_batch_size'] = '1'
res_def = grp['properties']['resource_def']
res_def['properties']['value'] = 'updated'
self.update_resource_group(updt_template,
updated=10,
created=0,
deleted=0)

View File

@ -1,93 +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.
import copy
import time
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
_test_template = {
'heat_template_version': 'pike',
'description': 'Test template to create two resources.',
'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,
'action_wait_secs': {
'create': 30,
}
},
'depends_on': ['test1']
}
}
}
def get_templates(fail=False, delay_s=None):
before = copy.deepcopy(_test_template)
after = copy.deepcopy(before)
for r in after['resources'].values():
r['properties']['value'] = 'Test2'
before_props = before['resources']['test2']['properties']
before_props['fail'] = fail
if delay_s is not None:
before_props['action_wait_secs']['create'] = delay_s
return before, after
class SimultaneousUpdateStackTest(functional_base.FunctionalTestsBase):
@test.requires_convergence
def test_retrigger_success(self):
before, after = get_templates()
stack_id = self.stack_create(template=before,
expected_status='CREATE_IN_PROGRESS')
time.sleep(10)
self.update_stack(stack_id, after)
@test.requires_convergence
def test_retrigger_failure(self):
before, after = get_templates(fail=True)
stack_id = self.stack_create(template=before,
expected_status='CREATE_IN_PROGRESS')
time.sleep(10)
self.update_stack(stack_id, after)
@test.requires_convergence
def test_retrigger_timeout(self):
before, after = get_templates(delay_s=70)
stack_id = self.stack_create(template=before,
expected_status='CREATE_IN_PROGRESS',
timeout=1)
time.sleep(50)
self.update_stack(stack_id, after)

View File

@ -1,76 +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_tempest_plugin.tests.functional import functional_base
class StackSnapshotRestoreTest(functional_base.FunctionalTestsBase):
def setUp(self):
super(StackSnapshotRestoreTest, self).setUp()
if not self.conf.minimal_image_ref:
raise self.skipException("No image configured to test")
if not self.conf.minimal_instance_type:
raise self.skipException(
"No minimal_instance_type configured to test")
self.assign_keypair()
def test_stack_snapshot_restore(self):
template = '''
heat_template_version: ocata
parameters:
keyname:
type: string
flavor:
type: string
image:
type: string
network:
type: string
resources:
my_port:
type: OS::Neutron::Port
properties:
network: {get_param: network}
my_server:
type: OS::Nova::Server
properties:
image: {get_param: image}
flavor: {get_param: flavor}
key_name: {get_param: keyname}
networks: [{port: {get_resource: my_port} }]
'''
def get_server_image(server_id):
server = self.compute_client.servers.get(server_id)
return server.image['id']
parameters = {'keyname': self.keypair_name,
'flavor': self.conf.minimal_instance_type,
'image': self.conf.minimal_image_ref,
'network': self.conf.fixed_network_name}
stack_identifier = self.stack_create(template=template,
parameters=parameters)
server_resource = self.client.resources.get(
stack_identifier, 'my_server')
server_id = server_resource.physical_resource_id
prev_image_id = get_server_image(server_id)
# Do snapshot and restore
snapshot_id = self.stack_snapshot(stack_identifier)
self.stack_restore(stack_identifier, snapshot_id)
self.assertNotEqual(prev_image_id, get_server_image(server_id))

View File

@ -1,150 +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_tempest_plugin.tests.functional import functional_base
class SoftwareDeploymentGroupTest(functional_base.FunctionalTestsBase):
sd_template = '''
heat_template_version: 2016-10-14
parameters:
input:
type: string
default: foo_input
resources:
config:
type: OS::Heat::SoftwareConfig
properties:
group: script
inputs:
- name: foo
deployment:
type: OS::Heat::SoftwareDeploymentGroup
properties:
config: {get_resource: config}
input_values:
foo: {get_param: input}
servers:
'0': dummy0
'1': dummy1
'2': dummy2
'3': dummy3
'''
sd_template_with_upd_policy = '''
heat_template_version: 2016-10-14
parameters:
input:
type: string
default: foo_input
resources:
config:
type: OS::Heat::SoftwareConfig
properties:
group: script
inputs:
- name: foo
deployment:
type: OS::Heat::SoftwareDeploymentGroup
update_policy:
rolling_update:
max_batch_size: 2
pause_time: 1
properties:
config: {get_resource: config}
input_values:
foo: {get_param: input}
servers:
'0': dummy0
'1': dummy1
'2': dummy2
'3': dummy3
'''
enable_cleanup = True
def deployment_crud(self, template):
stack_identifier = self.stack_create(
template=template,
enable_cleanup=self.enable_cleanup,
expected_status='CREATE_IN_PROGRESS')
self._wait_for_resource_status(
stack_identifier, 'deployment', 'CREATE_IN_PROGRESS')
# Wait for all deployment resources to become IN_PROGRESS, since only
# IN_PROGRESS resources get signalled
nested_identifier = self.assert_resource_is_a_stack(
stack_identifier, 'deployment')
self._wait_for_stack_status(nested_identifier, 'CREATE_IN_PROGRESS')
self._wait_for_all_resource_status(nested_identifier,
'CREATE_IN_PROGRESS')
group_resources = self.list_group_resources(
stack_identifier, 'deployment', minimal=False)
self.assertEqual(4, len(group_resources))
self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE',
signal_required=True,
resources_to_signal=group_resources)
created_group_resources = self.list_group_resources(
stack_identifier, 'deployment', minimal=False)
self.assertEqual(4, len(created_group_resources))
self.check_input_values(created_group_resources, 'foo', 'foo_input')
self.update_stack(stack_identifier,
template=template,
environment={'parameters': {'input': 'input2'}},
expected_status='UPDATE_IN_PROGRESS')
nested_identifier = self.assert_resource_is_a_stack(
stack_identifier, 'deployment')
self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE',
signal_required=True,
resources_to_signal=group_resources)
self.check_input_values(created_group_resources, 'foo', 'input2')
# We explicitly test delete here, vs just via cleanup and check
# the nested stack is gone
self._stack_delete(stack_identifier)
self._wait_for_stack_status(
nested_identifier, 'DELETE_COMPLETE',
success_on_not_found=True)
def test_deployment_crud(self):
self.deployment_crud(self.sd_template)
def test_deployment_crud_with_rolling_update(self):
self.deployment_crud(self.sd_template_with_upd_policy)
def test_deployments_create_delete_in_progress(self):
stack_identifier = self.stack_create(
template=self.sd_template,
enable_cleanup=self.enable_cleanup,
expected_status='CREATE_IN_PROGRESS')
self._wait_for_resource_status(
stack_identifier, 'deployment', 'CREATE_IN_PROGRESS')
nested_identifier = self.assert_resource_is_a_stack(
stack_identifier, 'deployment')
group_resources = self.list_group_resources(
stack_identifier, 'deployment', minimal=False)
self.assertEqual(4, len(group_resources))
# Now test delete while the stacks are still IN_PROGRESS
self._stack_delete(stack_identifier)
self._wait_for_stack_status(
nested_identifier, 'DELETE_COMPLETE',
success_on_not_found=True)

View File

@ -1,92 +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.
import copy
import eventlet
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
template = {
'heat_template_version': 'pike',
'resources': {
'test1': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1',
'action_wait_secs': {
'update': 30,
}
}
},
'test2': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': 'Test1',
},
'depends_on': ['test1']
},
}
}
def get_templates(delay_s=None):
before = copy.deepcopy(template)
after = copy.deepcopy(before)
for r in after['resources'].values():
r['properties']['value'] = 'Test2'
if delay_s:
before_props = before['resources']['test1']['properties']
before_props['action_wait_secs']['create'] = delay_s
return before, after
class StackCancelTest(functional_base.FunctionalTestsBase):
def _test_cancel_update(self, rollback=True,
expected_status='ROLLBACK_COMPLETE'):
before, after = get_templates()
stack_id = self.stack_create(template=before)
self.update_stack(stack_id, template=after,
expected_status='UPDATE_IN_PROGRESS')
self._wait_for_resource_status(stack_id, 'test1', 'UPDATE_IN_PROGRESS')
self.cancel_update_stack(stack_id, rollback, expected_status)
return stack_id
def test_cancel_update_with_rollback(self):
self._test_cancel_update()
def test_cancel_update_without_rollback(self):
stack_id = self._test_cancel_update(rollback=False,
expected_status='UPDATE_FAILED')
self.assertTrue(test.call_until_true(
60, 2, self.verify_resource_status,
stack_id, 'test1', 'UPDATE_COMPLETE'))
eventlet.sleep(2)
self.assertTrue(self.verify_resource_status(stack_id, 'test2',
'CREATE_COMPLETE'))
def test_cancel_create_without_rollback(self):
before, after = get_templates(delay_s=30)
stack_id = self.stack_create(template=before,
expected_status='CREATE_IN_PROGRESS')
self._wait_for_resource_status(stack_id, 'test1', 'CREATE_IN_PROGRESS')
self.cancel_update_stack(stack_id, rollback=False,
expected_status='CREATE_FAILED')
self.assertTrue(test.call_until_true(
60, 2, self.verify_resource_status,
stack_id, 'test1', 'CREATE_COMPLETE'))
eventlet.sleep(2)
self.assertTrue(self.verify_resource_status(stack_id, 'test2',
'INIT_COMPLETE'))

View File

@ -1,46 +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_tempest_plugin.tests.functional import functional_base
test_template = '''
heat_template_version: 2014-10-16
resources:
signal_handle:
type: "OS::Heat::SwiftSignalHandle"
outputs:
signal_curl:
value: { get_attr: ['signal_handle', 'curl_cli'] }
description: Swift signal cURL
signal_url:
value: { get_attr: ['signal_handle', 'endpoint'] }
description: Swift signal URL
'''
class SwiftSignalHandleUpdateTest(functional_base.FunctionalTestsBase):
def test_stack_update_same_template_replace_no_url(self):
if not self.is_service_available('object-store'):
self.skipTest('object-store service not available, skipping')
stack_identifier = self.stack_create(template=test_template)
stack = self.client.stacks.get(stack_identifier)
orig_url = self._stack_output(stack, 'signal_url')
orig_curl = self._stack_output(stack, 'signal_curl')
self.update_stack(stack_identifier, test_template)
stack = self.client.stacks.get(stack_identifier)
self.assertEqual(orig_url, self._stack_output(stack, 'signal_url'))
self.assertEqual(orig_curl, self._stack_output(stack, 'signal_curl'))

View File

@ -1,982 +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.
import json
from heatclient import exc as heat_exceptions
import six
import yaml
from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
class TemplateResourceTest(functional_base.FunctionalTestsBase):
"""Prove that we can use the registry in a nested provider."""
template = '''
heat_template_version: 2013-05-23
resources:
secret1:
type: OS::Heat::RandomString
outputs:
secret-out:
value: { get_attr: [secret1, value] }
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
secret2:
type: OS::Heat::RandomString
outputs:
value:
value: { get_attr: [secret2, value] }
'''
env_templ = '''
resource_registry:
"OS::Heat::RandomString": nested.yaml
'''
def test_nested_env(self):
main_templ = '''
heat_template_version: 2013-05-23
resources:
secret1:
type: My::NestedSecret
outputs:
secret-out:
value: { get_attr: [secret1, value] }
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
secret2:
type: My::Secret
outputs:
value:
value: { get_attr: [secret2, value] }
'''
env_templ = '''
resource_registry:
"My::Secret": "OS::Heat::RandomString"
"My::NestedSecret": nested.yaml
'''
stack_identifier = self.stack_create(
template=main_templ,
files={'nested.yaml': nested_templ},
environment=env_templ)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
# prove that resource.parent_resource is populated.
sec2 = self.client.resources.get(nested_ident, 'secret2')
self.assertEqual('secret1', sec2.parent_resource)
def test_no_infinite_recursion(self):
"""Prove that we can override a python resource.
And use that resource within the template resource.
"""
stack_identifier = self.stack_create(
template=self.template,
files={'nested.yaml': self.nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret1')
def test_nested_stack_delete_then_delete_parent_stack(self):
"""Check the robustness of stack deletion.
This tests that if you manually delete a nested
stack, the parent stack is still deletable.
"""
# 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,
enable_cleanup=False)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
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(functional_base.FunctionalTestsBase):
"""Prove that we can use the template resource references."""
main_templ = '''
heat_template_version: 2014-10-16
resources:
secret2:
type: My::NestedSecret
outputs:
old_way:
value: { get_attr: [secret2, nested_str]}
test_attr1:
value: { get_attr: [secret2, resource.secret1, value]}
test_attr2:
value: { get_attr: [secret2, resource.secret1.value]}
test_ref:
value: { get_resource: secret2 }
'''
env_templ = '''
resource_registry:
"My::NestedSecret": nested.yaml
'''
def test_stack_ref(self):
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
test_ref = self._stack_output(stack, 'test_ref')
self.assertIn('arn:openstack:heat:', test_ref)
def test_transparent_ref(self):
"""Test using nested resource more transparently.
With the addition of OS::stack_id we can now use the nested resource
more transparently.
"""
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
OS::stack_id:
value: {get_resource: secret1}
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
test_ref = self._stack_output(stack, 'test_ref')
test_attr = self._stack_output(stack, 'old_way')
self.assertNotIn('arn:openstack:heat', test_ref)
self.assertEqual(test_attr, test_ref)
def test_nested_attributes(self):
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
old_way = self._stack_output(stack, 'old_way')
test_attr1 = self._stack_output(stack, 'test_attr1')
test_attr2 = self._stack_output(stack, 'test_attr2')
self.assertEqual(old_way, test_attr1)
self.assertEqual(old_way, test_attr2)
class TemplateResourceFacadeTest(functional_base.FunctionalTestsBase):
"""Prove that we can use ResourceFacade in a HOT template."""
main_template = '''
heat_template_version: 2013-05-23
resources:
the_nested:
type: the.yaml
metadata:
foo: bar
outputs:
value:
value: {get_attr: [the_nested, output]}
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
test:
type: OS::Heat::TestResource
properties:
value: {"Fn::Select": [foo, {resource_facade: metadata}]}
outputs:
output:
value: {get_attr: [test, output]}
'''
def test_metadata(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ})
stack = self.client.stacks.get(stack_identifier)
value = self._stack_output(stack, 'value')
self.assertEqual('bar', value)
class TemplateResourceUpdateTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource updates."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
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:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
main_template_add_prop = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
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:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
initial_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
two:
Default: bar
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
prop_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: yikes
Type: String
two:
Default: foo
Type: String
Resources:
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
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
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
Properties:
salt: yum
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange')
scenarios = [
('no_changes', dict(template=main_template,
provider=initial_tmpl,
expect=NOCHANGE)),
('main_tmpl_change', dict(template=main_template_change_prop,
provider=initial_tmpl,
expect=UPDATE)),
('provider_change', dict(template=main_template,
provider=content_change_tmpl,
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,
expect=NOCHANGE)),
]
def test_template_resource_update_template_schema(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.initial_tmpl})
stack = self.client.stacks.get(stack_identifier)
initial_id = self._stack_output(stack, 'identifier')
initial_val = self._stack_output(stack, 'value')
self.update_stack(stack_identifier,
self.template,
files={'the.yaml': self.provider})
stack = self.client.stacks.get(stack_identifier)
self.assertEqual(initial_id,
self._stack_output(stack, 'identifier'))
if self.expect == self.NOCHANGE:
self.assertEqual(initial_val,
self._stack_output(stack, 'value'))
else:
self.assertNotEqual(initial_val,
self._stack_output(stack, 'value'))
class TemplateResourceUpdateFailedTest(functional_base.FunctionalTestsBase):
"""Prove that we can do updates on a nested stack to fix a stack."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
keypair:
Type: OS::Nova::KeyPair
Properties:
name: replace-this
save_private_key: false
server:
Type: server_fail.yaml
DependsOn: keypair
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
RealRandom:
Type: OS::Heat::RandomString
'''
def setUp(self):
super(TemplateResourceUpdateFailedTest, self).setUp()
self.assign_keypair()
def test_update_on_failed_create(self):
# create a stack with "server" dependent on "keypair", but
# keypair fails, so "server" is not created properly.
# We then fix the template and it should succeed.
broken_templ = self.main_template.replace('replace-this',
self.keypair_name)
stack_identifier = self.stack_create(
template=broken_templ,
files={'server_fail.yaml': self.nested_templ},
expected_status='CREATE_FAILED')
fixed_templ = self.main_template.replace('replace-this',
test.rand_name())
self.update_stack(stack_identifier,
fixed_templ,
files={'server_fail.yaml': self.nested_templ})
class TemplateResourceAdoptTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource adopt/abandon."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
Resources:
RealRandom:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [RealRandom, value]}
'''
def _yaml_to_json(self, yaml_templ):
return yaml.safe_load(yaml_templ)
def test_abandon(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ},
enable_cleanup=False
)
info = self.stack_abandon(stack_id=stack_identifier)
self.assertEqual(self._yaml_to_json(self.main_template),
info['template'])
self.assertEqual(self._yaml_to_json(self.nested_templ),
info['resources']['the_nested']['template'])
# TODO(james combs): Implement separate test cases for export
# once export REST API is available. Also test reverse order
# of invocation: export -> abandon AND abandon -> export
def test_adopt(self):
data = {
'resources': {
'the_nested': {
"type": "the.yaml",
"resources": {
"RealRandom": {
"type": "OS::Heat::RandomString",
'resource_data': {'value': 'goopie'},
'resource_id': 'froggy'
}
}
}
},
"environment": {"parameters": {}},
"template": yaml.safe_load(self.main_template)
}
stack_identifier = self.stack_adopt(
adopt_data=json.dumps(data),
files={'the.yaml': self.nested_templ})
self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('goopie', self._stack_output(stack, 'value'))
class TemplateResourceCheckTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource check."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
Resources:
RealRandom:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [RealRandom, value]}
'''
def test_check(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ}
)
self.client.actions.check(stack_id=stack_identifier)
self._wait_for_stack_status(stack_identifier, 'CHECK_COMPLETE')
class TemplateResourceErrorMessageTest(functional_base.FunctionalTestsBase):
"""Prove that nested stack errors don't suck."""
template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
victim:
Type: fail.yaml
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
oops:
Type: OS::Heat::TestResource
Properties:
fail: true
wait_secs: 2
'''
def test_fail(self):
stack_identifier = self.stack_create(
template=self.template,
files={'fail.yaml': self.nested_templ},
expected_status='CREATE_FAILED')
stack = self.client.stacks.get(stack_identifier)
exp_path = 'resources.victim.resources.oops'
exp_msg = 'Test Resource failed oops'
exp = 'Resource CREATE failed: ValueError: %s: %s' % (exp_path,
exp_msg)
self.assertEqual(exp, stack.stack_status_reason)
class TemplateResourceSuspendResumeTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource suspend/resume."""
main_template = '''
heat_template_version: 2014-10-16
parameters:
resources:
the_nested:
type: the.yaml
'''
nested_templ = '''
heat_template_version: 2014-10-16
resources:
test_random_string:
type: OS::Heat::RandomString
'''
def test_suspend_resume(self):
"""Basic test for template resource suspend resume."""
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ}
)
self.stack_suspend(stack_identifier=stack_identifier)
self.stack_resume(stack_identifier=stack_identifier)
class ValidateFacadeTest(functional_base.FunctionalTestsBase):
"""Prove that nested stack errors don't suck."""
template = '''
heat_template_version: 2015-10-15
resources:
thisone:
type: OS::Thingy
properties:
one: pre
two: post
outputs:
one:
value: {get_attr: [thisone, here-it-is]}
'''
templ_facade = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
outputs:
here-it-is:
value: noop
'''
env = '''
resource_registry:
OS::Thingy: facade.yaml
resources:
thisone:
OS::Thingy: concrete.yaml
'''
def setUp(self):
super(ValidateFacadeTest, self).setUp()
self.client = self.orchestration_client
def test_missing_param(self):
templ_missing_parameter = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
resources:
str:
type: OS::Heat::RandomString
outputs:
here-it-is:
value:
not-important
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_parameter},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Required property two for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))
def test_missing_output(self):
templ_missing_output = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
resources:
str:
type: OS::Heat::RandomString
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_output},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Attribute here-it-is for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))
class TemplateResourceNewParamTest(functional_base.FunctionalTestsBase):
main_template = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
properties:
value1: foo
'''
nested_templ = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
value: {get_param: value1}
'''
main_template_update = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
properties:
value1: foo
value2: foo
'''
nested_templ_update_fail = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
value2:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
fail: True
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
nested_templ_update = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
value2:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
def test_update(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'resource.yaml': self.nested_templ})
# Make the update fails with the new parameter inserted.
self.update_stack(
stack_identifier,
self.main_template_update,
files={'resource.yaml': self.nested_templ_update_fail},
expected_status='UPDATE_FAILED')
# Fix the update, it should succeed now.
self.update_stack(
stack_identifier,
self.main_template_update,
files={'resource.yaml': self.nested_templ_update})
class TemplateResourceRemovedParamTest(functional_base.FunctionalTestsBase):
main_template = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
resources:
my_resource:
type: resource.yaml
properties:
value1: {get_param: value1}
'''
nested_templ = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
resources:
test:
type: OS::Heat::TestResource
properties:
value: {get_param: value1}
'''
main_template_update = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
'''
nested_templ_update = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
value2:
type: string
default: bar
resources:
test:
type: OS::Heat::TestResource
properties:
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
def test_update(self):
stack_identifier = self.stack_create(
template=self.main_template,
environment={'parameters': {'value1': 'spam'}},
files={'resource.yaml': self.nested_templ})
self.update_stack(
stack_identifier,
self.main_template_update,
environment={'parameter_defaults': {'value2': 'egg'}},
files={'resource.yaml': self.nested_templ_update}, existing=True)

View File

@ -18,9 +18,8 @@ class TemplateAPITest(functional_base.FunctionalTestsBase):
"""This will test the following template calls:
1. Get the template content for the specific stack
2. List template versions
3. List resource types
4. Show resource details for OS::Heat::TestResource
2. List resource types
3. Show resource details for OS::Heat::TestResource
"""
template = {
@ -46,19 +45,6 @@ class TemplateAPITest(functional_base.FunctionalTestsBase):
template_from_client = self.client.stacks.template(stack_identifier)
self.assertEqual(self.template, template_from_client)
def test_template_version(self):
template_versions = self.client.template_versions.list()
supported_template_versions = ["2013-05-23", "2014-10-16",
"2015-04-30", "2015-10-15",
"2012-12-12", "2010-09-09",
"2016-04-08", "2016-10-14", "newton",
"2017-02-24", "ocata",
"2017-09-01", "pike",
"2018-03-02", "queens"]
for template in template_versions:
self.assertIn(template.version.split(".")[1],
supported_template_versions)
def test_resource_types(self):
resource_types = self.client.resource_types.list()
self.assertTrue(any(resource.resource_type == "OS::Heat::TestResource"

View File

@ -1,117 +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_tempest_plugin.tests.functional import functional_base
template_subnet_old_network = """
heat_template_version: 2016-10-14
parameters:
net_cidr:
type: string
resources:
net:
type: OS::Neutron::Net
subnet:
type: OS::Neutron::Subnet
properties:
cidr: { get_param: net_cidr }
network_id: { get_resource: net }
"""
template_with_get_attr = """
heat_template_version: 2016-10-14
description: Test template to create/update subnet with translation
parameters:
net_cidr:
type: string
resources:
net:
type: OS::Neutron::Net
net_value:
type: OS::Heat::Value
properties:
value: { get_resource: net }
subnet:
type: OS::Neutron::Subnet
properties:
network: { get_attr: [net_value, value] }
cidr: { get_param: net_cidr }
"""
template_value_from_nested_stack_main = """
heat_template_version: 2016-10-14
parameters:
flavor:
type: string
image:
type: string
public_net:
type: string
resources:
network_settings:
type: network.yaml
properties:
public_net: { get_param: public_net }
server:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
image: { get_param: image }
networks: { get_attr: [network_settings, networks] }
"""
template_value_from_nested_stack_network = """
heat_template_version: 2016-10-14
parameters:
public_net:
type: string
outputs:
networks:
value:
- uuid: { get_param: public_net }
"""
class TestTranslation(functional_base.FunctionalTestsBase):
def test_create_update_subnet_old_network(self):
# Just create and update where network is translated properly.
env = {'parameters': {'net_cidr': '11.11.11.0/24'}}
stack_identifier = self.stack_create(
template=template_subnet_old_network,
environment=env)
env = {'parameters': {'net_cidr': '11.11.12.0/24'}}
self.update_stack(stack_identifier,
template=template_subnet_old_network,
environment=env)
def test_create_update_translation_with_get_attr(self):
# Check create and update successful for translation function value.
env = {'parameters': {'net_cidr': '11.11.11.0/24'}}
stack_identifier = self.stack_create(
template=template_with_get_attr,
environment=env)
env = {'parameters': {'net_cidr': '11.11.12.0/24'}}
self.update_stack(stack_identifier,
template=template_with_get_attr,
environment=env)
def test_value_from_nested_stack(self):
env = {'parameters': {
'flavor': self.conf.minimal_instance_type,
'image': self.conf.minimal_image_ref,
'public_net': self.conf.fixed_network_name
}}
self.stack_create(
template=template_value_from_nested_stack_main,
environment=env,
files={'network.yaml': template_value_from_nested_stack_network})

View File

@ -1,166 +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.
import time
from heat_tempest_plugin.tests.functional import functional_base
test_template = {
'heat_template_version': '2013-05-23',
'description': 'Test template to create one instance.',
'resources': {
'bar': {
'type': 'OS::Heat::TestResource',
'properties': {
'value': '1234',
'update_replace': False,
}
}
}
}
env_both_restrict = {u'resource_registry': {
u'resources': {
'bar': {'restricted_actions': ['update', 'replace']}
}
}
}
env_replace_restrict = {u'resource_registry': {
u'resources': {
'*ar': {'restricted_actions': 'replace'}
}
}
}
reason_update_restrict = 'update is restricted for resource.'
reason_replace_restrict = 'replace is restricted for resource.'
class UpdateRestrictedStackTest(functional_base.FunctionalTestsBase):
def _check_for_restriction_reason(self, events,
reason, num_expected=1):
matched = [e for e in events
if e.resource_status_reason == reason]
return len(matched) == num_expected
def test_update(self):
stack_identifier = self.stack_create(template=test_template)
update_template = test_template.copy()
props = update_template['resources']['bar']['properties']
props['value'] = '4567'
# check update fails - with 'both' restricted
self.update_stack(stack_identifier, update_template,
env_both_restrict,
expected_status='UPDATE_FAILED')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'CREATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_update_restrict))
# Ensure the timestamp changes, since this will be very quick
time.sleep(1)
# check update succeeds - with only 'replace' restricted
self.update_stack(stack_identifier, update_template,
env_replace_restrict,
expected_status='UPDATE_COMPLETE')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'UPDATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertFalse(
self._check_for_restriction_reason(resource_events,
reason_update_restrict, 2))
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_replace_restrict, 0))
def test_replace(self):
stack_identifier = self.stack_create(template=test_template)
update_template = test_template.copy()
props = update_template['resources']['bar']['properties']
props['update_replace'] = True
# check replace fails - with 'both' restricted
self.update_stack(stack_identifier, update_template,
env_both_restrict,
expected_status='UPDATE_FAILED')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'CREATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_replace_restrict))
# Ensure the timestamp changes, since this will be very quick
time.sleep(1)
# check replace fails - with only 'replace' restricted
self.update_stack(stack_identifier, update_template,
env_replace_restrict,
expected_status='UPDATE_FAILED')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'CREATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_replace_restrict, 2))
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_update_restrict, 0))
def test_update_type_changed(self):
stack_identifier = self.stack_create(template=test_template)
update_template = test_template.copy()
rsrc = update_template['resources']['bar']
rsrc['type'] = 'OS::Heat::None'
# check replace fails - with 'both' restricted
self.update_stack(stack_identifier, update_template,
env_both_restrict,
expected_status='UPDATE_FAILED')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'CREATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_replace_restrict))
# Ensure the timestamp changes, since this will be very quick
time.sleep(1)
# check replace fails - with only 'replace' restricted
self.update_stack(stack_identifier, update_template,
env_replace_restrict,
expected_status='UPDATE_FAILED')
self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
'CREATE_COMPLETE'))
resource_events = self.client.events.list(stack_identifier, 'bar')
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_replace_restrict, 2))
self.assertTrue(
self._check_for_restriction_reason(resource_events,
reason_update_restrict, 0))

View File

@ -1,92 +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_tempest_plugin.tests.functional import functional_base
class StackValidationTest(functional_base.FunctionalTestsBase):
def setUp(self):
super(StackValidationTest, self).setUp()
if not self.conf.minimal_image_ref:
raise self.skipException("No image configured to test")
if not self.conf.minimal_instance_type:
raise self.skipException(
"No minimal_instance_type configured to test")
self.assign_keypair()
def test_stack_validate_provider_references_parent_resource(self):
template = '''
heat_template_version: 2014-10-16
parameters:
keyname:
type: string
flavor:
type: string
image:
type: string
network:
type: string
resources:
config:
type: My::Config
properties:
server: {get_resource: server}
server:
type: OS::Nova::Server
properties:
image: {get_param: image}
flavor: {get_param: flavor}
key_name: {get_param: keyname}
networks: [{network: {get_param: network} }]
user_data_format: SOFTWARE_CONFIG
'''
config_template = '''
heat_template_version: 2014-10-16
parameters:
server:
type: string
resources:
config:
type: OS::Heat::SoftwareConfig
deployment:
type: OS::Heat::SoftwareDeployment
properties:
config:
get_resource: config
server:
get_param: server
'''
files = {'provider.yaml': config_template}
env = {'resource_registry':
{'My::Config': 'provider.yaml'}}
parameters = {'keyname': self.keypair_name,
'flavor': self.conf.minimal_instance_type,
'image': self.conf.minimal_image_ref,
'network': self.conf.fixed_network_name}
# Note we don't wait for CREATE_COMPLETE, because we're using a
# minimal image without the tools to apply the config.
# The point of the test is just to prove that validation won't
# falsely prevent stack creation starting, ref bug #1407100
# Note that we can be sure My::Config will stay IN_PROGRESS as
# there's no signal sent to the deployment
self.stack_create(template=template,
files=files,
environment=env,
parameters=parameters,
expected_status='CREATE_IN_PROGRESS')