From c6e82c4a38d04ce257a1e10fada34f89c04d527f Mon Sep 17 00:00:00 2001 From: Christopher Armstrong Date: Mon, 22 Jul 2013 16:35:13 -0500 Subject: [PATCH] Refactor InstanceGroup to use a nested stack Much of the code of InstanceGroup was able to be deleted in preference for using the native functionality of StackResource. InstanceGroup now generates a template and updates the nested stack when it needs to be created or resized. blueprint instance-group-nested-stack Fixes bug 1189278 Change-Id: Ic08a55cad1ac34d69080c0ef2dae4877f1fefd29 --- heat/engine/resources/autoscaling.py | 287 +++++++++------------------ heat/engine/stack_resource.py | 21 ++ heat/tests/test_autoscaling.py | 223 +++++++++++---------- heat/tests/test_instance_group.py | 14 +- heat/tests/test_stack_resource.py | 39 ++++ 5 files changed, 281 insertions(+), 303 deletions(-) diff --git a/heat/engine/resources/autoscaling.py b/heat/engine/resources/autoscaling.py index 5a8c48f0..54b15e05 100644 --- a/heat/engine/resources/autoscaling.py +++ b/heat/engine/resources/autoscaling.py @@ -16,11 +16,11 @@ from heat.common import exception from heat.engine import resource from heat.engine import signal_responder -from heat.engine import scheduler from heat.openstack.common import log as logging from heat.openstack.common import timeutils from heat.engine.properties import Properties +from heat.engine import stack_resource logger = logging.getLogger(__name__) @@ -55,7 +55,7 @@ class CooldownMixin(object): self.metadata = metadata -class InstanceGroup(resource.Resource): +class InstanceGroup(stack_resource.StackResource): tags_schema = {'Key': {'Type': 'String', 'Required': True}, 'Value': {'Type': 'String', @@ -79,22 +79,46 @@ class InstanceGroup(resource.Resource): "(Heat extension)") } - def handle_create(self): - return self.resize(int(self.properties['Size']), raise_on_error=True) + def get_instance_names(self): + """Get a list of resource names of the instances in this InstanceGroup. + + Deleted resources will be ignored. + """ + return sorted(x.name for x in self.get_instances()) - def check_create_complete(self, creator): - if creator is None: - return True + def get_instances(self): + """Get a set of all the instance resources managed by this group.""" + return [resource for resource in self.nested() + if resource.state[0] != resource.DELETE] + + def handle_create(self): + """Create a nested stack and add the initial resources to it.""" + num_instances = int(self.properties['Size']) + initial_template = self._create_template(num_instances) + return self.create_with_template(initial_template, {}) - return creator.step() + def check_create_complete(self, task): + """ + When stack creation is done, update the load balancer. - def _wait_for_activation(self, creator): - if creator is not None: - creator.run_to_completion() + If any instances failed to be created, delete them. + """ + try: + done = super(InstanceGroup, self).check_create_complete(task) + except exception.Error as exc: + for resource in self.nested(): + if resource.state == ('CREATE', 'FAILED'): + resource.destroy() + raise + if done and len(self.get_instances()): + self._lb_reload() + return done def handle_update(self, json_snippet, tmpl_diff, prop_diff): - # If Properties has changed, update self.properties, so we - # get the new values during any subsequent adjustment + """ + If Properties has changed, update self.properties, so we + get the new values during any subsequent adjustment. + """ if prop_diff: self.properties = Properties(self.properties_schema, json_snippet.get('Properties', {}), @@ -104,39 +128,9 @@ class InstanceGroup(resource.Resource): # Get the current capacity, we may need to adjust if # Size has changed if 'Size' in prop_diff: - inst_list = [] - if self.resource_id is not None: - inst_list = sorted(self.resource_id.split(',')) - + inst_list = self.get_instances() if len(inst_list) != int(self.properties['Size']): - creator = self.resize(int(self.properties['Size']), - raise_on_error=True) - self._wait_for_activation(creator) - - def _make_instance(self, name): - # We look up and subclass the class for AWS::EC2::Instance instead of - # just importing Instance, so that if someone overrides that resource - # we'll use the custom one. - Instance = resource.get_class('AWS::EC2::Instance', - resource_name=name, - environment=self.stack.env) - - class GroupedInstance(Instance): - ''' - Subclass Instance to suppress event transitions, since the - scaling-group instances are not "real" resources, ie defined in the - template, which causes problems for event handling since we can't - look up the resources via parser.Stack - ''' - def state_set(self, action, status, reason="state changed"): - self._store_or_update(action, status, reason) - - conf = self.properties['LaunchConfigurationName'] - instance_definition = self.stack.t['Resources'][conf] - - # honour the Tags property in the InstanceGroup and AutoScalingGroup - instance_definition['Properties']['Tags'] = self._tags() - return GroupedInstance(name, instance_definition, self.stack) + self.resize(int(self.properties['Size'])) def _tags(self): """ @@ -151,130 +145,47 @@ class InstanceGroup(resource.Resource): return tags + [{'Key': 'metering.groupname', 'Value': self.FnGetRefId()}] - def _instances(self): - ''' - Convert the stored instance list into a list of GroupedInstance objects - ''' - gi_list = [] - if self.resource_id is not None: - inst_list = self.resource_id.split(',') - for i in inst_list: - gi_list.append(self._make_instance(i)) - return gi_list - def handle_delete(self): - for inst in self._instances(): - logger.debug('handle_delete %s' % inst.name) - inst.destroy() - - def handle_suspend(self): - cookie_list = [] - for inst in self._instances(): - logger.debug('handle_suspend %s' % inst.name) - inst_cookie = inst.handle_suspend() - cookie_list.append((inst, inst_cookie)) - return cookie_list - - def check_suspend_complete(self, cookie_list): - for inst, inst_cookie in cookie_list: - if not inst.check_suspend_complete(inst_cookie): - return False - return True - - def handle_resume(self): - cookie_list = [] - for inst in self._instances(): - logger.debug('handle_resume %s' % inst.name) - inst_cookie = inst.handle_resume() - cookie_list.append((inst, inst_cookie)) - return cookie_list - - def check_resume_complete(self, cookie_list): - for inst, inst_cookie in cookie_list: - if not inst.check_resume_complete(inst_cookie): - return False - return True - - @scheduler.wrappertask - def _scale(self, instance_task, indices): - group = scheduler.PollingTaskGroup.from_task_with_args(instance_task, - indices) - yield group() - - # When all instance tasks are complete, reload the LB config - self._lb_reload() + return self.delete_nested() - def resize(self, new_capacity, raise_on_error=False): - inst_list = [] - if self.resource_id is not None: - inst_list = sorted(self.resource_id.split(',')) + def _create_template(self, num_instances): + """ + Create a template with a number of instance definitions based on the + launch configuration. + """ + conf_name = self.properties['LaunchConfigurationName'] + instance_definition = self.stack.t['Resources'][conf_name].copy() + instance_definition['Type'] = 'AWS::EC2::Instance' + instance_definition['Properties']['Tags'] = self._tags() + resources = {} + for i in range(num_instances): + resources["%s-%d" % (self.name, i)] = instance_definition + return {"Resources": resources} - capacity = len(inst_list) - if new_capacity == capacity: - logger.debug('no change in capacity %d' % capacity) - return - logger.debug('adjusting capacity from %d to %d' % (capacity, - new_capacity)) - - @scheduler.wrappertask - def create_instance(index): - name = '%s-%d' % (self.name, index) - inst = self._make_instance(name) - - logger.debug('Creating %s instance %d' % (str(self), index)) - - try: - yield inst.create() - except exception.ResourceFailure as ex: - if raise_on_error: - raise - # Handle instance creation failure locally by destroying the - # failed instance to avoid orphaned instances costing user - # extra memory - logger.warn('Creating %s instance %d failed %s, destroying' - % (str(self), index, str(ex))) - inst.destroy() - else: - inst_list.append(name) - self.resource_id_set(','.join(inst_list)) - - if new_capacity > capacity: - # grow - creator = scheduler.TaskRunner(self._scale, - create_instance, - xrange(capacity, new_capacity)) - creator.start() - return creator - else: - # shrink (kill largest numbered first) - del_list = inst_list[new_capacity:] - for victim in reversed(del_list): - inst = self._make_instance(victim) - inst.destroy() - inst_list.remove(victim) - # If we shrink to zero, set resource_id back to None - self.resource_id_set(','.join(inst_list) or None) + def resize(self, new_capacity): + """ + Resize the instance group to the new capacity. - self._lb_reload() + When shrinking, the newest instances will be removed. + """ + new_template = self._create_template(new_capacity) + result = self.update_with_template(new_template, {}) + for resource in self.nested(): + if resource.state == ('CREATE', 'FAILED'): + resource.destroy() + self._lb_reload() + return result def _lb_reload(self): ''' - Notify the LoadBalancer to reload it's config to include + Notify the LoadBalancer to reload its config to include the changes in instances we have just made. This must be done after activation (instance in ACTIVE state), otherwise the instances' IP addresses may not be available. ''' if self.properties['LoadBalancerNames']: - inst_list = [] - if self.resource_id is not None: - inst_list = sorted(self.resource_id.split(',')) - # convert the list of instance names into a list of instance id's - id_list = [] - for inst_name in inst_list: - inst = self._make_instance(inst_name) - id_list.append(inst.FnGetRefId()) - + id_list = [inst.FnGetRefId() for inst in self.get_instances()] for lb in self.properties['LoadBalancerNames']: self.stack[lb].json_snippet['Properties']['Instances'] = \ id_list @@ -291,14 +202,10 @@ class InstanceGroup(resource.Resource): ip addresses. ''' if name == 'InstanceList': - if self.resource_id is None: - return None - name_list = sorted(self.resource_id.split(',')) - inst_list = [] - for name in name_list: - inst = self._make_instance(name) - inst_list.append(inst.FnGetAtt('PublicIp')) - return unicode(','.join(inst_list)) + ips = [inst.FnGetAtt('PublicIp') + for inst in self._nested.resources.values()] + if ips: + return unicode(','.join(ips)) class AutoScalingGroup(InstanceGroup, CooldownMixin): @@ -338,12 +245,22 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): num_to_create = int(self.properties['DesiredCapacity']) else: num_to_create = int(self.properties['MinSize']) + initial_template = self._create_template(num_to_create) + return self.create_with_template(initial_template, {}) - return self._adjust(num_to_create) + def check_create_complete(self, task): + """Invoke the cooldown after creation succeeds.""" + done = super(AutoScalingGroup, self).check_create_complete(task) + if done: + self._cooldown_timestamp( + "%s : %s" % ('ExactCapacity', len(self.get_instances()))) + return done def handle_update(self, json_snippet, tmpl_diff, prop_diff): - # If Properties has changed, update self.properties, so we - # get the new values during any subsequent adjustment + """ + If Properties has changed, update self.properties, so we get the new + values during any subsequent adjustment. + """ if prop_diff: self.properties = Properties(self.properties_schema, json_snippet.get('Properties', {}), @@ -352,11 +269,7 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): # Get the current capacity, we may need to adjust if # MinSize or MaxSize has changed - inst_list = [] - if self.resource_id is not None: - inst_list = sorted(self.resource_id.split(',')) - - capacity = len(inst_list) + capacity = len(self.get_instances()) # Figure out if an adjustment is required new_capacity = None @@ -367,30 +280,22 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): if capacity > int(self.properties['MaxSize']): new_capacity = int(self.properties['MaxSize']) if 'DesiredCapacity' in prop_diff: - if self.properties['DesiredCapacity']: - new_capacity = int(self.properties['DesiredCapacity']) + if self.properties['DesiredCapacity']: + new_capacity = int(self.properties['DesiredCapacity']) if new_capacity is not None: - creator = self._adjust(new_capacity) - self._wait_for_activation(creator) + self.adjust(new_capacity, adjustment_type='ExactCapacity') def adjust(self, adjustment, adjustment_type='ChangeInCapacity'): - creator = self._adjust(adjustment, adjustment_type, False) - self._wait_for_activation(creator) - - def _adjust(self, adjustment, adjustment_type='ExactCapacity', - raise_on_error=True): - + """ + Adjust the size of the scaling group if the cooldown permits. + """ if self._cooldown_inprogress(): logger.info("%s NOT performing scaling adjustment, cooldown %s" % (self.name, self.properties['Cooldown'])) return - inst_list = [] - if self.resource_id is not None: - inst_list = sorted(self.resource_id.split(',')) - - capacity = len(inst_list) + capacity = len(self.get_instances()) if adjustment_type == 'ChangeInCapacity': new_capacity = capacity + adjustment elif adjustment_type == 'ExactCapacity': @@ -410,7 +315,7 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): logger.debug('no change in capacity %d' % capacity) return - result = self.resize(new_capacity, raise_on_error=raise_on_error) + result = self.resize(new_capacity) self._cooldown_timestamp("%s : %s" % (adjustment_type, adjustment)) @@ -479,8 +384,10 @@ class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin): } def handle_update(self, json_snippet, tmpl_diff, prop_diff): - # If Properties has changed, update self.properties, so we - # get the new values during any subsequent adjustment + """ + If Properties has changed, update self.properties, so we get the new + values during any subsequent adjustment. + """ if prop_diff: self.properties = Properties(self.properties_schema, json_snippet.get('Properties', {}), diff --git a/heat/engine/stack_resource.py b/heat/engine/stack_resource.py index e322b240..5c436361 100644 --- a/heat/engine/stack_resource.py +++ b/heat/engine/stack_resource.py @@ -93,6 +93,27 @@ class StackResource(resource.Resource): return done + def update_with_template(self, child_template, user_params, + timeout_mins=None): + """Update the nested stack with the new template.""" + template = parser.Template(child_template) + # Note that there is no call to self._outputs_to_attribs here. + # If we have a use case for updating attributes of the resource based + # on updated templates we should make sure it's optional because not + # all subclasses want that behavior, since they may offer custom + # attributes. + + # Note we disable rollback for nested stacks, since they + # should be rolled back by the parent stack on failure + stack = parser.Stack(self.context, + self.physical_resource_name(), + template, + environment.Environment(user_params), + timeout_mins=timeout_mins, + disable_rollback=True, + parent_resource=self) + return self._nested.update(stack) + def delete_nested(self): ''' Delete the nested stack. diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index b0ff2d0f..a3169b54 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -178,7 +178,7 @@ class AutoScalingTest(HeatTestCase): now = timeutils.utcnow() self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual(None, rsrc.resource_id) + self.assertEqual(None, rsrc.FnGetAtt("InstanceList")) rsrc.delete() self.m.VerifyAll() @@ -195,21 +195,22 @@ class AutoScalingTest(HeatTestCase): self._stub_meta_expected(now, 'ExactCapacity : 1') self._stub_create(1) self.m.ReplayAll() + rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Reduce the min size to 0, should complete without adjusting update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['MinSize'] = '0' self.assertEqual(None, rsrc.update(update_snippet)) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) - # trigger adjustment to reduce to 0, resource_id should be None + # trigger adjustment to reduce to 0, there should be no more instances self._stub_lb_reload(0) self._stub_meta_expected(now, 'ChangeInCapacity : -1') self.m.ReplayAll() rsrc.adjust(-1) - self.assertEqual(None, rsrc.resource_id) + self.assertEqual([], rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -226,7 +227,7 @@ class AutoScalingTest(HeatTestCase): rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['LaunchConfigurationName'] = 'foo' self.assertRaises(resource.UpdateReplace, @@ -246,7 +247,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -277,7 +278,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -292,6 +293,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE) + for i in rsrc.nested().resources.values(): + i.state_set(rsrc.SUSPEND, rsrc.COMPLETE) scheduler.TaskRunner(rsrc.resume)() self.assertEqual(rsrc.state, (rsrc.RESUME, rsrc.COMPLETE)) @@ -312,7 +315,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -347,7 +351,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -364,6 +369,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE) + for i in rsrc.nested().resources.values(): + i.state_set(rsrc.SUSPEND, rsrc.COMPLETE) scheduler.TaskRunner(rsrc.resume)() self.assertEqual(rsrc.state, (rsrc.RESUME, rsrc.COMPLETE)) @@ -382,7 +389,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -397,7 +404,8 @@ class AutoScalingTest(HeatTestCase): sus_task = scheduler.TaskRunner(rsrc.suspend) self.assertRaises(exception.ResourceFailure, sus_task, ()) self.assertEqual(rsrc.state, (rsrc.SUSPEND, rsrc.FAILED)) - self.assertEqual(rsrc.status_reason, 'Exception: oops') + self.assertEqual(rsrc.status_reason, + 'Error: Resource suspend failed: Exception: oops') rsrc.delete() self.m.VerifyAll() @@ -413,7 +421,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.assertEqual(rsrc.state, (rsrc.CREATE, rsrc.COMPLETE)) self.m.VerifyAll() @@ -426,11 +434,14 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE) + for i in rsrc.nested().resources.values(): + i.state_set(rsrc.SUSPEND, rsrc.COMPLETE) sus_task = scheduler.TaskRunner(rsrc.resume) self.assertRaises(exception.ResourceFailure, sus_task, ()) self.assertEqual(rsrc.state, (rsrc.RESUME, rsrc.FAILED)) - self.assertEqual(rsrc.status_reason, 'Exception: oops') + self.assertEqual(rsrc.status_reason, + 'Error: Resource resume failed: Exception: oops') rsrc.delete() self.m.VerifyAll() @@ -452,7 +463,7 @@ class AutoScalingTest(HeatTestCase): scheduler.TaskRunner(rsrc.create)) self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state) - self.assertEqual(None, rsrc.resource_id) + self.assertEqual([], rsrc.get_instance_names()) self.m.VerifyAll() @@ -469,13 +480,13 @@ class AutoScalingTest(HeatTestCase): self._stub_create(1) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Reduce the max size to 2, should complete without adjusting update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['MaxSize'] = '2' self.assertEqual(None, rsrc.update(update_snippet)) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.assertEqual('2', rsrc.properties['MaxSize']) @@ -495,7 +506,7 @@ class AutoScalingTest(HeatTestCase): self._stub_create(1) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Increase min size to 2, should trigger an ExactCapacity adjust self._stub_lb_reload(2) @@ -506,8 +517,8 @@ class AutoScalingTest(HeatTestCase): update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['MinSize'] = '2' self.assertEqual(None, rsrc.update(update_snippet)) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.assertEqual('2', rsrc.properties['MinSize']) rsrc.delete() @@ -526,7 +537,7 @@ class AutoScalingTest(HeatTestCase): self._stub_create(1) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Increase min size to 2 via DesiredCapacity, should adjust self._stub_lb_reload(2) @@ -537,8 +548,8 @@ class AutoScalingTest(HeatTestCase): update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['DesiredCapacity'] = '2' self.assertEqual(None, rsrc.update(update_snippet)) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.assertEqual('2', rsrc.properties['DesiredCapacity']) @@ -557,16 +568,16 @@ class AutoScalingTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Remove DesiredCapacity from the updated template, which should # have no effect, it's an optional parameter update_snippet = copy.deepcopy(rsrc.parsed_template()) del(update_snippet['Properties']['DesiredCapacity']) self.assertEqual(None, rsrc.update(update_snippet)) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.assertEqual(None, rsrc.properties['DesiredCapacity']) @@ -587,7 +598,7 @@ class AutoScalingTest(HeatTestCase): rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['Cooldown'] = '61' self.assertEqual(None, rsrc.update(update_snippet)) @@ -623,7 +634,7 @@ class AutoScalingTest(HeatTestCase): rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') self.assertEqual('WebServerGroup', rsrc.FnGetRefId()) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) update_snippet = copy.deepcopy(rsrc.parsed_template()) update_snippet['Properties']['Cooldown'] = '61' self.assertEqual(None, rsrc.update(update_snippet)) @@ -644,15 +655,16 @@ class AutoScalingTest(HeatTestCase): self._stub_create(3) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) # reduce to 1 self._stub_lb_reload(1) self._stub_meta_expected(now, 'ChangeInCapacity : -2') self.m.ReplayAll() rsrc.adjust(-2) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # raise to 3 self._stub_lb_reload(3) @@ -660,16 +672,17 @@ class AutoScalingTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc.adjust(2) - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) # set to 2 self._stub_lb_reload(2) self._stub_meta_expected(now, 'ExactCapacity : 2') self.m.ReplayAll() rsrc.adjust(2, 'ExactCapacity') - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) self.m.VerifyAll() def test_scaling_group_scale_up_failure(self): @@ -683,20 +696,18 @@ class AutoScalingTest(HeatTestCase): self._stub_create(1) self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.m.VerifyAll() self.m.UnsetStubs() # Scale up one 1 instance with resource failure self.m.StubOutWithMock(instance.Instance, 'handle_create') instance.Instance.handle_create().AndRaise(Exception) - self.m.StubOutWithMock(instance.Instance, 'destroy') - instance.Instance.destroy() self._stub_lb_reload(1, unset=False, nochange=True) self.m.ReplayAll() rsrc.adjust(1) - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) self.m.VerifyAll() @@ -714,23 +725,23 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # raise above the max rsrc.adjust(4) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # lower below the min rsrc.adjust(-2) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # no change rsrc.adjust(0) - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -748,16 +759,16 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], + rsrc.get_instance_names()) # raise by 200% self._stub_lb_reload(3) @@ -765,8 +776,9 @@ class AutoScalingTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc.adjust(200, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() @@ -785,16 +797,16 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], + rsrc.get_instance_names()) # Now move time on 10 seconds - Cooldown in template is 60 # so this should not update the policy metadata, and the @@ -819,7 +831,7 @@ class AutoScalingTest(HeatTestCase): # raise by 200%, too soon for Cooldown so there should be no change rsrc.adjust(200, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) rsrc.delete() @@ -838,16 +850,16 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], + rsrc.get_instance_names()) # Now move time on 61 seconds - Cooldown in template is 60 # so this should update the policy metadata, and the @@ -870,8 +882,9 @@ class AutoScalingTest(HeatTestCase): self._stub_meta_expected(now, 'PercentChangeInCapacity : 200') self.m.ReplayAll() rsrc.adjust(200, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() @@ -890,16 +903,16 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], + rsrc.get_instance_names()) # Don't move time, since cooldown is zero, it should work previous_meta = {timeutils.strtime(now): @@ -918,8 +931,9 @@ class AutoScalingTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc.adjust(200, 'PercentChangeInCapacity') - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -937,7 +951,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Scale up one self._stub_lb_reload(2) @@ -955,8 +969,8 @@ class AutoScalingTest(HeatTestCase): alarm_url = up_policy.FnGetAtt('AlarmUrl') self.assertNotEqual(None, alarm_url) up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -975,8 +989,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Scale down one self._stub_lb_reload(1) @@ -990,7 +1004,7 @@ class AutoScalingTest(HeatTestCase): down_policy = self.create_scaling_policy(t, stack, 'WebServerScaleDownPolicy') down_policy.signal() - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -1007,7 +1021,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Scale up one self._stub_lb_reload(2) @@ -1022,8 +1036,8 @@ class AutoScalingTest(HeatTestCase): up_policy = self.create_scaling_policy(t, stack, 'WebServerScaleUpPolicy') up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Now move time on 10 seconds - Cooldown in template is 60 # so this should not update the policy metadata, and the @@ -1045,8 +1059,8 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -1063,7 +1077,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Scale up one self._stub_lb_reload(2) @@ -1078,8 +1092,8 @@ class AutoScalingTest(HeatTestCase): up_policy = self.create_scaling_policy(t, stack, 'WebServerScaleUpPolicy') up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Now move time on 61 seconds - Cooldown in template is 60 # so this should trigger a scale-up @@ -1100,8 +1114,9 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -1118,7 +1133,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Create the scaling policy (with Cooldown=0) and scale up one properties = t['Resources']['WebServerScaleUpPolicy']['Properties'] @@ -1135,8 +1150,8 @@ class AutoScalingTest(HeatTestCase): up_policy = self.create_scaling_policy(t, stack, 'WebServerScaleUpPolicy') up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Now trigger another scale-up without changing time, should work previous_meta = {timeutils.strtime(now): 'ChangeInCapacity : 1'} @@ -1155,8 +1170,9 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -1173,7 +1189,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Create the scaling policy no Cooldown property, should behave the # same as when Cooldown==0 @@ -1192,8 +1208,8 @@ class AutoScalingTest(HeatTestCase): up_policy = self.create_scaling_policy(t, stack, 'WebServerScaleUpPolicy') up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Now trigger another scale-up without changing time, should work previous_meta = {timeutils.strtime(now): 'ChangeInCapacity : 1'} @@ -1212,8 +1228,9 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1,WebServerGroup-2', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() @@ -1235,7 +1252,7 @@ class AutoScalingTest(HeatTestCase): self.m.ReplayAll() rsrc = self.create_scaling_group(t, stack, 'WebServerGroup') stack.resources['WebServerGroup'] = rsrc - self.assertEqual('WebServerGroup-0', rsrc.resource_id) + self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names()) # Create initial scaling policy up_policy = self.create_scaling_policy(t, stack, @@ -1249,8 +1266,8 @@ class AutoScalingTest(HeatTestCase): # Trigger alarm up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1'], + rsrc.get_instance_names()) # Update scaling policy update_snippet = copy.deepcopy(up_policy.parsed_template()) @@ -1280,9 +1297,9 @@ class AutoScalingTest(HeatTestCase): # Trigger alarm up_policy.signal() - self.assertEqual('WebServerGroup-0,WebServerGroup-1,' - 'WebServerGroup-2,WebServerGroup-3', - rsrc.resource_id) + self.assertEqual(['WebServerGroup-0', 'WebServerGroup-1', + 'WebServerGroup-2', 'WebServerGroup-3'], + rsrc.get_instance_names()) rsrc.delete() self.m.VerifyAll() diff --git a/heat/tests/test_instance_group.py b/heat/tests/test_instance_group.py index e387975e..23c0cfc6 100644 --- a/heat/tests/test_instance_group.py +++ b/heat/tests/test_instance_group.py @@ -25,6 +25,7 @@ from heat.tests.common import HeatTestCase from heat.tests.utils import setup_dummy_db from heat.tests.utils import parse_stack + ig_template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", @@ -101,7 +102,9 @@ class InstanceGroupTest(HeatTestCase): self.assertEqual('JobServerGroup', rsrc.FnGetRefId()) self.assertEqual('1.2.3.4', rsrc.FnGetAtt('InstanceList')) - self.assertEqual('JobServerGroup-0', rsrc.resource_id) + + nested = rsrc.nested() + self.assertEqual(nested.id, rsrc.resource_id) rsrc.delete() self.m.VerifyAll() @@ -163,8 +166,6 @@ class InstanceGroupTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc = self.create_instance_group(t, stack, 'JobServerGroup') - self.assertEqual('JobServerGroup-0,JobServerGroup-1', - rsrc.resource_id) self.m.VerifyAll() self.m.UnsetStubs() @@ -186,9 +187,6 @@ class InstanceGroupTest(HeatTestCase): prop_diff = {'Size': '5'} self.assertEqual(None, rsrc.handle_update(update_snippet, tmpl_diff, prop_diff)) - assert_str = ','.join(['JobServerGroup-%s' % x for x in range(5)]) - self.assertEqual(assert_str, - rsrc.resource_id) self.assertEqual('10.0.0.2,10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6', rsrc.FnGetAtt('InstanceList')) @@ -204,8 +202,6 @@ class InstanceGroupTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc = self.create_instance_group(t, stack, 'JobServerGroup') - self.assertEqual('JobServerGroup-0,JobServerGroup-1', - rsrc.resource_id) self.m.ReplayAll() @@ -226,8 +222,6 @@ class InstanceGroupTest(HeatTestCase): self._stub_create(2) self.m.ReplayAll() rsrc = self.create_instance_group(t, stack, 'JobServerGroup') - self.assertEqual('JobServerGroup-0,JobServerGroup-1', - rsrc.resource_id) self.m.ReplayAll() diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index 0961d8db..346adbce 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -59,6 +59,21 @@ wp_template = ''' ''' +generic_template = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "WordPress", + "Parameters" : {}, + "Resources" : { + "WebServer": { + "Type": "GenericResource", + "Properties": {} + } + } +} +''' + + class MyStackResource(stack_resource.StackResource, generic_rsrc.GenericResource): def physical_resource_name(self): @@ -83,6 +98,8 @@ class StackResourceTest(HeatTestCase): setup_dummy_db() resource._register_class('some_magic_type', MyStackResource) + resource._register_class('GenericResource', + generic_rsrc.GenericResource) t = parser.Template({template.RESOURCES: {"provider_resource": ws_res_snippet}}) self.parent_stack = parser.Stack(dummy_context(), 'test_stack', t, @@ -91,6 +108,7 @@ class StackResourceTest(HeatTestCase): ws_res_snippet, self.parent_stack) self.templ = template_format.parse(wp_template) + self.generic_template = template_format.parse(generic_template) @stack_delete_after def test_create_with_template_ok(self): @@ -104,6 +122,27 @@ class StackResourceTest(HeatTestCase): self.assertEqual(self.templ, self.stack.t.t) self.assertEqual(self.stack.id, self.parent_resource.resource_id) + @stack_delete_after + def test_update_with_template_ok(self): + """ + The update_with_template method updates the nested stack with the + given template and user parameters. + """ + create_result = self.parent_resource.create_with_template( + self.generic_template, {}) + while not create_result.step(): + pass + self.stack = self.parent_resource.nested() + + new_templ = self.generic_template.copy() + inst_snippet = new_templ["Resources"]["WebServer"].copy() + new_templ["Resources"]["WebServer2"] = inst_snippet + update_result = self.parent_resource.update_with_template( + new_templ, {}) + self.assertEqual(self.stack.state, ('UPDATE', 'COMPLETE')) + self.assertEqual(set(self.stack.resources.keys()), + set(["WebServer", "WebServer2"])) + @stack_delete_after def test_load_nested_ok(self): self.parent_resource.create_with_template(self.templ, -- 2.45.2