Merge "Clear heat inner functionaltests"
This commit is contained in:
commit
ead585ed66
|
@ -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"),
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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)
|
|
@ -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))
|
|
@ -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']))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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'))
|
|
@ -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)
|
|
@ -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')
|
|
@ -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]))
|
|
@ -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'])
|
|
@ -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)
|
|
@ -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')
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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)
|
|
@ -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'))
|
|
@ -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'))
|
|
@ -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)
|
|
@ -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"
|
||||
|
|
|
@ -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})
|
|
@ -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))
|
|
@ -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')
|
Loading…
Reference in New Issue