blob: 33367fc349041f4e5541354ea1fbe9c7bc674939 [file] [log] [blame]
Angus Salkeldebf15d72014-12-10 17:03:15 +10001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
Angus Salkeld8c5b8552014-12-15 11:22:30 +100013import copy
Angus Salkeldebf15d72014-12-10 17:03:15 +100014import logging
Angus Salkeld8c5b8552014-12-15 11:22:30 +100015import yaml
16
17from heatclient import exc
Angus Salkeldebf15d72014-12-10 17:03:15 +100018
19from heat_integrationtests.common import test
20
21
22LOG = logging.getLogger(__name__)
23
24
25class InstanceGroupTest(test.HeatIntegrationTest):
26
27 template = '''
28{
29 "AWSTemplateFormatVersion" : "2010-09-09",
30 "Description" : "Template to create multiple instances.",
31 "Parameters" : {"size": {"Type": "String", "Default": "1"},
32 "AZ": {"Type": "String", "Default": "nova"},
33 "image": {"Type": "String"},
34 "flavor": {"Type": "String"},
35 "keyname": {"Type": "String"}},
36 "Resources": {
37 "JobServerGroup": {
38 "Type": "OS::Heat::InstanceGroup",
39 "Properties": {
40 "LaunchConfigurationName" : {"Ref": "JobServerConfig"},
41 "Size" : {"Ref": "size"},
42 "AvailabilityZones" : [{"Ref": "AZ"}]
43 }
44 },
45
46 "JobServerConfig" : {
47 "Type" : "AWS::AutoScaling::LaunchConfiguration",
48 "Metadata": {"foo": "bar"},
49 "Properties": {
50 "ImageId" : {"Ref": "image"},
51 "InstanceType" : {"Ref": "flavor"},
52 "KeyName" : {"Ref": "keyname"},
53 "SecurityGroups" : [ "sg-1" ],
54 "UserData" : "jsconfig data",
55 }
56 }
57 },
58 "Outputs": {
59 "InstanceList": {"Value": {
60 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}
61 }
62}
63'''
64
65 instance_template = '''
66heat_template_version: 2013-05-23
67parameters:
68 ImageId: {type: string}
69 InstanceType: {type: string}
70 KeyName: {type: string}
71 SecurityGroups: {type: comma_delimited_list}
72 UserData: {type: string}
73 Tags: {type: comma_delimited_list}
74
75resources:
76 random1:
77 type: OS::Heat::RandomString
78
79outputs:
80 PublicIp:
81 value: {get_attr: [random1, value]}
82'''
83
84 def setUp(self):
85 super(InstanceGroupTest, self).setUp()
86 self.client = self.orchestration_client
87 if not self.conf.image_ref:
88 raise self.skipException("No image configured to test")
89 if not self.conf.keypair_name:
90 raise self.skipException("No keyname configured to test")
91 if not self.conf.instance_type:
92 raise self.skipException("No flavor configured to test")
93
Angus Salkeldbfc7e932014-12-15 11:15:45 +100094 def assert_instance_count(self, stack, expected_count):
95 inst_list = self._stack_output(stack, 'InstanceList')
96 self.assertEqual(expected_count, len(inst_list.split(',')))
97
Angus Salkeldebf15d72014-12-10 17:03:15 +100098 def test_basic_create_works(self):
99 """Make sure the working case is good.
100 Note this combines test_override_aws_ec2_instance into this test as
101 well, which is:
102 If AWS::EC2::Instance is overridden, InstanceGroup will automatically
103 use that overridden resource type.
104 """
105
Angus Salkeldebf15d72014-12-10 17:03:15 +1000106 files = {'provider.yaml': self.instance_template}
107 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
108 'parameters': {'size': 4,
109 'image': self.conf.image_ref,
110 'keyname': self.conf.keypair_name,
111 'flavor': self.conf.instance_type}}
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000112 stack_identifier = self.stack_create(template=self.template,
113 files=files, environment=env)
Angus Salkeldebf15d72014-12-10 17:03:15 +1000114 initial_resources = {
115 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
116 'JobServerGroup': 'OS::Heat::InstanceGroup'}
117 self.assertEqual(initial_resources,
118 self.list_resources(stack_identifier))
119
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000120 stack = self.client.stacks.get(stack_identifier)
121 self.assert_instance_count(stack, 4)
122
Angus Salkeld8c5b8552014-12-15 11:22:30 +1000123 def test_create_config_prop_validation(self):
124 """Make sure that during a group create the instance
125 properties are validated. And an error causes the group to fail.
126 """
127 stack_name = self._stack_rand_name()
128
129 # add a property without a default and don't provide a value.
130 # we use this to make the instance fail on a property validation
131 # error.
132 broken = yaml.load(copy.copy(self.instance_template))
133 broken['parameters']['no_default'] = {'type': 'string'}
134 files = {'provider.yaml': yaml.dump(broken)}
135 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
136 'parameters': {'size': 4,
137 'image': self.conf.image_ref,
138 'keyname': self.conf.keypair_name,
139 'flavor': self.conf.instance_type}}
140
141 # now with static nested stack validation, this gets raised quickly.
142 excp = self.assertRaises(exc.HTTPBadRequest, self.client.stacks.create,
143 stack_name=stack_name, template=self.template,
144 files=files, disable_rollback=True,
145 parameters={}, environment=env)
146 self.assertIn('Property no_default not assigned', str(excp))
147
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000148 def test_size_updates_work(self):
149 files = {'provider.yaml': self.instance_template}
150 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
151 'parameters': {'size': 2,
152 'image': self.conf.image_ref,
153 'keyname': self.conf.keypair_name,
154 'flavor': self.conf.instance_type}}
155
156 stack_identifier = self.stack_create(template=self.template,
157 files=files,
158 environment=env)
159 stack = self.client.stacks.get(stack_identifier)
160 self.assert_instance_count(stack, 2)
161
162 # Increase min size to 5
163 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
164 'parameters': {'size': 5,
165 'image': self.conf.image_ref,
166 'keyname': self.conf.keypair_name,
167 'flavor': self.conf.instance_type}}
168 self.update_stack(stack_identifier, self.template,
169 environment=env2, files=files)
170 self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
171 stack = self.client.stacks.get(stack_identifier)
172 self.assert_instance_count(stack, 5)
Angus Salkeldcd21b1b2014-12-15 11:27:04 +1000173
174 def test_update_group_replace(self):
175 """Make sure that during a group update the non updatable
176 properties cause a replacement.
177 """
178 files = {'provider.yaml': self.instance_template}
179 env = {'resource_registry':
180 {'AWS::EC2::Instance': 'provider.yaml'},
181 'parameters': {'size': 1,
182 'image': self.conf.image_ref,
183 'keyname': self.conf.keypair_name,
184 'flavor': self.conf.instance_type}}
185
186 stack_identifier = self.stack_create(template=self.template,
187 files=files,
188 environment=env)
189 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
190 orig_asg_id = rsrc.physical_resource_id
191
192 env2 = {'resource_registry':
193 {'AWS::EC2::Instance': 'provider.yaml'},
194 'parameters': {'size': '2',
195 'AZ': 'wibble',
196 'image': self.conf.image_ref,
197 'keyname': self.conf.keypair_name,
198 'flavor': self.conf.instance_type}}
199 self.update_stack(stack_identifier, self.template,
200 environment=env2, files=files)
201
202 # replacement will cause the resource physical_resource_id to change.
203 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
204 self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)