From 8914fbc52d9a6bdf84f3e0ce29f7a5371ad606cf Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Fri, 3 May 2013 14:25:57 +1000 Subject: [PATCH] Allow non-replacement updates of Alarms blueprint cloudwatch-update-stack Change-Id: I58982caf33bb5b326797472ec6c3dab8eba3a668 --- heat/engine/resources/cloud_watch.py | 37 ++++++++++++++- heat/tests/test_autoscaling.py | 71 +++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/heat/engine/resources/cloud_watch.py b/heat/engine/resources/cloud_watch.py index e4d6f725..95110943 100644 --- a/heat/engine/resources/cloud_watch.py +++ b/heat/engine/resources/cloud_watch.py @@ -16,6 +16,7 @@ from heat.common import exception from heat.engine import watchrule from heat.engine import resource +from heat.engine.properties import Properties from heat.db import api as db_api from heat.openstack.common import log as logging @@ -73,6 +74,14 @@ class CloudWatchAlarm(resource.Resource): 'Count/Second', None]}} strict_dependency = False + update_allowed_keys = ('Properties',) + # allow the properties that affect the watch calculation. + # note: when using in-instance monitoring you can only change the + # metric name if you re-configure the instance too. + update_allowed_properties = ('ComparisonOperator', 'AlarmDescription', + 'EvaluationPeriods', 'Period', 'Statistic', + 'AlarmActions', 'OKActions', 'Units' + 'InsufficientDataActions', 'Threshold') def handle_create(self): wr = watchrule.WatchRule(context=self.context, @@ -82,7 +91,33 @@ class CloudWatchAlarm(resource.Resource): wr.store() def handle_update(self, json_snippet): - return self.UPDATE_REPLACE + try: + self.update_template_diff(json_snippet) + except NotImplementedError: + logger.error("Could not update %s, invalid key" % self.name) + return self.UPDATE_REPLACE + + try: + prop_diff = self.update_template_diff_properties(json_snippet) + except NotImplementedError: + logger.error("Could not update %s, invalid Property" % self.name) + return self.UPDATE_REPLACE + + # 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', {}), + self.stack.resolve_runtime_data, + self.name) + loader = watchrule.WatchRule.load + wr = loader(self.context, + watch_name=self.physical_resource_name()) + + wr.rule = self.parsed_template('Properties') + wr.store() + + return self.UPDATE_COMPLETE def handle_delete(self): try: diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index 8a88ede5..30c9a4fd 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -25,6 +25,7 @@ from heat.common import template_format from heat.engine.resources import autoscaling as asc from heat.engine.resources import loadbalancer from heat.engine.resources import instance +from heat.engine.resources import cloud_watch from heat.engine import clients from heat.engine import parser from heat.engine import scheduler @@ -84,6 +85,16 @@ class AutoScalingTest(HeatTestCase): resource.state) return resource + def create_alarm(self, t, stack, resource_name): + resource = cloud_watch.CloudWatchAlarm(resource_name, + t['Resources'][resource_name], + stack) + self.assertEqual(None, resource.validate()) + scheduler.TaskRunner(resource.create)() + self.assertEqual(cloud_watch.CloudWatchAlarm.CREATE_COMPLETE, + resource.state) + return resource + def _stub_create(self, num): self.m.StubOutWithMock(eventlet, 'sleep') @@ -266,7 +277,6 @@ class AutoScalingTest(HeatTestCase): self.assertEqual('WebServerGroup', resource.FnGetRefId()) self.assertEqual('WebServerGroup-0', resource.resource_id) update_snippet = copy.deepcopy(resource.parsed_template()) - old_cd = update_snippet['Properties']['Cooldown'] update_snippet['Properties']['Cooldown'] = '61' self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, resource.handle_update(update_snippet)) @@ -274,6 +284,65 @@ class AutoScalingTest(HeatTestCase): resource.delete() self.m.VerifyAll() + def test_mem_alarm_high_update_no_replace(self): + ''' + Make sure that we can change the update-able properties + without replacing the Alarm resource. + ''' + t = self.load_template() + + #short circuit the alarm's references + properties = t['Resources']['MEMAlarmHigh']['Properties'] + properties['AlarmActions'] = ['a'] + properties['Dimensions'] = [{'a': 'v'}] + + stack = self.parse_stack(t) + # the watch rule needs a valid stack_id + stack.store() + + self.m.ReplayAll() + resource = self.create_alarm(t, stack, 'MEMAlarmHigh') + snippet = copy.deepcopy(resource.parsed_template()) + snippet['Properties']['ComparisonOperator'] = 'LessThanThreshold' + snippet['Properties']['AlarmDescription'] = 'fruity' + snippet['Properties']['EvaluationPeriods'] = '2' + snippet['Properties']['Period'] = '90' + snippet['Properties']['Statistic'] = 'Maximum' + snippet['Properties']['Threshold'] = '39' + + self.assertEqual(cloud_watch.CloudWatchAlarm.UPDATE_COMPLETE, + resource.handle_update(snippet)) + + resource.delete() + self.m.VerifyAll() + + def test_mem_alarm_high_update_replace(self): + ''' + Make sure that the Alarm resource IS replaced when non-update-able + properties are changed. + ''' + t = self.load_template() + + #short circuit the alarm's references + properties = t['Resources']['MEMAlarmHigh']['Properties'] + properties['AlarmActions'] = ['a'] + properties['Dimensions'] = [{'a': 'v'}] + + stack = self.parse_stack(t) + # the watch rule needs a valid stack_id + stack.store() + + self.m.ReplayAll() + resource = self.create_alarm(t, stack, 'MEMAlarmHigh') + snippet = copy.deepcopy(resource.parsed_template()) + snippet['Properties']['MetricName'] = 'temp' + + self.assertEqual(cloud_watch.CloudWatchAlarm.UPDATE_REPLACE, + resource.handle_update(snippet)) + + resource.delete() + self.m.VerifyAll() + def test_scaling_group_adjust(self): t = self.load_template() stack = self.parse_stack(t) -- 2.45.2