# License for the specific language governing permissions and limitations
# under the License.
+import copy
from heat.common import exception
from heat.engine import resource
from heat.engine import signal_responder
'Schema': tags_schema}}
}
update_allowed_keys = ('Properties',)
- update_allowed_properties = ('Size',)
+ update_allowed_properties = ('Size', 'LaunchConfigurationName',)
attributes_schema = {
"InstanceList": ("A comma-delimited list of server ip addresses. "
"(Heat extension)")
launch configuration.
"""
conf_name = self.properties['LaunchConfigurationName']
- instance_definition = self.stack.t['Resources'][conf_name].copy()
+ conf = self.stack.resource_by_refid(conf_name)
+ instance_definition = copy.deepcopy(conf.t)
instance_definition['Type'] = 'AWS::EC2::Instance'
instance_definition['Properties']['Tags'] = self._tags()
# resolve references within the context of this stack.
- static_parsed = self.stack.resolve_static_data(instance_definition)
- fully_parsed = self.stack.resolve_runtime_data(static_parsed)
+ fully_parsed = self.stack.resolve_runtime_data(instance_definition)
resources = {}
for i in range(num_instances):
# template keys and properties supported for handle_update,
# note trailing comma is required for a single item to get a tuple
update_allowed_keys = ('Properties',)
- update_allowed_properties = ('MaxSize', 'MinSize',
+ update_allowed_properties = ('LaunchConfigurationName',
+ 'MaxSize', 'MinSize',
'Cooldown', 'DesiredCapacity',)
def handle_create(self):
'Schema': tags_schema}},
}
+ def FnGetRefId(self):
+ return unicode(self.physical_resource_name())
+
class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin):
properties_schema = {
self.fc = fakes.FakeKeystoneClient()
def create_scaling_group(self, t, stack, resource_name):
- rsrc = asc.AutoScalingGroup(resource_name,
- t['Resources'][resource_name],
- stack)
+ # create the launch configuration resource
+ conf = stack.resources['LaunchConfig']
+ self.assertEqual(None, conf.validate())
+ scheduler.TaskRunner(conf.create)()
+ self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+ # create the group resource
+ rsrc = stack.resources[resource_name]
self.assertEqual(None, rsrc.validate())
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
return rsrc
def create_scaling_policy(self, t, stack, resource_name):
- rsrc = asc.ScalingPolicy(resource_name,
- t['Resources'][resource_name],
- stack)
+ rsrc = stack.resources[resource_name]
self.assertEqual(None, rsrc.validate())
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('WebServerGroup', rsrc.FnGetRefId())
self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names())
update_snippet = copy.deepcopy(rsrc.parsed_template())
- update_snippet['Properties']['LaunchConfigurationName'] = 'foo'
+ update_snippet['Properties']['AvailabilityZones'] = ['foo']
self.assertRaises(resource.UpdateReplace,
rsrc.update, update_snippet)
instance.Instance.handle_create().AndRaise(Exception)
self.m.ReplayAll()
- rsrc = asc.AutoScalingGroup('WebServerGroup',
- t['Resources']['WebServerGroup'],
- stack)
+
+ conf = stack.resources['LaunchConfig']
+ self.assertEqual(None, conf.validate())
+ scheduler.TaskRunner(conf.create)()
+ self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+ rsrc = stack.resources['WebServerGroup']
self.assertEqual(None, rsrc.validate())
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
from heat.common import exception
from heat.common import template_format
-from heat.engine.resources import autoscaling as asc
from heat.engine.resources import instance
from heat.engine import resource
from heat.engine import resources
instance_class.check_create_complete(
cookie).MultipleTimes().AndReturn(True)
- def create_instance_group(self, t, stack, resource_name):
- rsrc = asc.InstanceGroup(resource_name,
- t['Resources'][resource_name],
- stack)
+ def create_resource(self, t, stack, resource_name):
+ # subsequent resources may need to reference previous created resources
+ # use the stack's resource objects instead of instantiating new ones
+ rsrc = stack.resources[resource_name]
self.assertEqual(None, rsrc.validate())
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
instance.Instance.FnGetAtt('PublicIp').AndReturn('1.2.3.4')
self.m.ReplayAll()
- rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
-
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = self.create_resource(t, stack, 'JobServerGroup')
self.assertEqual('JobServerGroup', rsrc.FnGetRefId())
self.assertEqual('1.2.3.4', rsrc.FnGetAtt('InstanceList'))
self._stub_create(1, instance_class=MyInstance)
self.m.ReplayAll()
-
- rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = self.create_resource(t, stack, 'JobServerGroup')
self.assertEqual('JobServerGroup', rsrc.FnGetRefId())
rsrc.delete()
self.m.VerifyAll()
t = template_format.parse(ig_template)
stack = utils.parse_stack(t)
- rsrc = asc.InstanceGroup('JobServerGroup',
- t['Resources']['JobServerGroup'],
- stack)
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = stack.resources['JobServerGroup']
self.m.StubOutWithMock(instance.Instance, 'handle_create')
not_found = exception.ImageNotFound(image_name='bla')
self._stub_create(2)
self.m.ReplayAll()
- rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = self.create_resource(t, stack, 'JobServerGroup')
self.m.VerifyAll()
self.m.UnsetStubs()
self._stub_create(2)
self.m.ReplayAll()
- rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = self.create_resource(t, stack, 'JobServerGroup')
self.m.ReplayAll()
self._stub_create(2)
self.m.ReplayAll()
- rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+ conf = self.create_resource(t, stack, 'JobServerConfig')
+ rsrc = self.create_resource(t, stack, 'JobServerGroup')
self.m.ReplayAll()
update_snippet = copy.deepcopy(rsrc.parsed_template())
- update_snippet['Properties']['LaunchConfigurationName'] = 'wibble'
+ update_snippet['Properties']['AvailabilityZones'] = ['wibble']
self.assertRaises(resource.UpdateReplace,
rsrc.update, update_snippet)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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
+
+from heat.common import template_format
+from heat.engine.resources import instance
+from heat.engine import parser
+from heat.tests.common import HeatTestCase
+from heat.tests.utils import setup_dummy_db
+from heat.tests.utils import parse_stack
+
+
+ig_template_before = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create multiple instances.",
+ "Parameters" : {},
+ "Resources" : {
+ "JobServerGroup" : {
+ "Type" : "OS::Heat::InstanceGroup",
+ "Properties" : {
+ "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
+ "Size" : "8",
+ "AvailabilityZones" : ["nova"]
+ }
+ },
+ "JobServerConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "InstanceType" : "m1.medium",
+ "KeyName" : "test",
+ "SecurityGroups" : [ "sg-1" ],
+ "UserData" : "jsconfig data"
+ }
+ }
+ }
+}
+'''
+
+ig_template_after = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create multiple instances.",
+ "Parameters" : {},
+ "Resources" : {
+ "JobServerGroup" : {
+ "Type" : "OS::Heat::InstanceGroup",
+ "Properties" : {
+ "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
+ "Size" : "8",
+ "AvailabilityZones" : ["nova"]
+ }
+ },
+ "JobServerConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "InstanceType" : "m1.large",
+ "KeyName" : "test",
+ "SecurityGroups" : [ "sg-1" ],
+ "UserData" : "jsconfig data"
+ }
+ }
+ }
+}
+'''
+
+
+class InstanceGroupTest(HeatTestCase):
+ def setUp(self):
+ super(InstanceGroupTest, self).setUp()
+ setup_dummy_db()
+
+ def _stub_create(self, num, instance_class=instance.Instance):
+ """
+ Expect creation of C{num} number of Instances.
+
+ :param instance_class: The resource class to expect to be created
+ instead of instance.Instance.
+ """
+
+ self.m.StubOutWithMock(parser.Stack, 'validate')
+ parser.Stack.validate()
+
+ self.m.StubOutWithMock(instance_class, 'handle_create')
+ self.m.StubOutWithMock(instance_class, 'check_create_complete')
+ cookie = object()
+ for x in range(num):
+ instance_class.handle_create().AndReturn(cookie)
+ instance_class.check_create_complete(cookie).AndReturn(False)
+ instance_class.check_create_complete(
+ cookie).MultipleTimes().AndReturn(True)
+
+ def get_launch_conf_name(self, stack, ig_name):
+ return stack.resources[ig_name].properties['LaunchConfigurationName']
+
+ def test_instance_group(self):
+
+ # setup stack from the initial template
+ tmpl = template_format.parse(ig_template_before)
+ stack = parse_stack(tmpl)
+
+ # test stack create
+ # test the number of instance creation
+ # test that physical resource name of launch configuration is used
+ size = int(stack.resources['JobServerGroup'].properties['Size'])
+ self._stub_create(size)
+ self.m.ReplayAll()
+ stack.create()
+ self.m.VerifyAll()
+ self.assertEqual(stack.status, stack.COMPLETE)
+ conf = stack.resources['JobServerConfig']
+ conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack.name
+ regex_pattern = re.compile(conf_name_pattern)
+ self.assertTrue(regex_pattern.match(conf.FnGetRefId()))
+
+ # test stack update
+ # test that launch configuration is replaced
+ conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
+ updated_tmpl = template_format.parse(ig_template_after)
+ updated_stack = parse_stack(updated_tmpl)
+ stack.update(updated_stack)
+ updated_conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
+ self.assertNotEqual(conf_name, updated_conf_name)
from heat.engine import environment
from heat.tests.v1_1 import fakes
-from heat.engine.resources import autoscaling
from heat.engine.resources import instance as instances
from heat.engine.resources import nova_utils
from heat.common import template_format
"Type": "OS::Heat::InstanceGroup",
"Properties": {
"AvailabilityZones" : ["nova"],
- "LaunchConfigurationName": "Config",
+ "LaunchConfigurationName": { "Ref": "Config" },
"Size" : "1"
}
}
stack_id=uuidutils.generate_uuid())
t['Resources']['WebServer']['Properties']['Tags'] = intags
- group = autoscaling.InstanceGroup('WebServer',
- t['Resources']['WebServer'],
- stack)
+
+ # create the launch configuration
+ conf = stack.resources['Config']
+ self.assertEqual(None, conf.validate())
+ scheduler.TaskRunner(conf.create)()
+ self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+ group = stack.resources['WebServer']
self.m.StubOutWithMock(instances.Instance, 'nova')
instances.Instance.nova().MultipleTimes().AndReturn(self.fc)