From af68b6168c2748bb3ec8104aee84c12a4684162e Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Wed, 31 Jul 2013 21:24:26 +1000 Subject: [PATCH] Allow the Ceilometer Alarm to be used with cfn-push-stats This is for when: - Ceilometer does not support the desired custom metric - Easier migration to Ceilometer Alarms (reuse most of old templates) A watchrule is create within the Ceilometer alarm, but it is marked as belonging to Ceilometer. This is used in the follow situation: - So we can figure out that we don't need to run the watch periodic task - when we receive new sample data we can forward the data to Ceilometer blueprint watch-ceilometer Change-Id: I0d672e69a522a23158805d75378f4bfe631b4bf3 --- heat/engine/resources/ceilometer/alarm.py | 20 ++++++++ heat/engine/service.py | 12 +++-- heat/engine/watchrule.py | 58 +++++++++++++++++++---- heat/rpc/api.py | 4 +- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/heat/engine/resources/ceilometer/alarm.py b/heat/engine/resources/ceilometer/alarm.py index 85a567757..1fa014e0e 100644 --- a/heat/engine/resources/ceilometer/alarm.py +++ b/heat/engine/resources/ceilometer/alarm.py @@ -13,7 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.common import exception from heat.engine import resource +from heat.engine import watchrule class CeilometerAlarm(resource.Resource): @@ -85,6 +87,17 @@ class CeilometerAlarm(resource.Resource): alarm = self.ceilometer().alarms.create(**props) self.resource_id_set(alarm.alarm_id) + # the watchrule below is for backwards compatibility. + # 1) so we don't create watch tasks unneccessarly + # 2) to support CW stats post, we will redirect the request + # to ceilometer. + wr = watchrule.WatchRule(context=self.context, + watch_name=self.physical_resource_name(), + rule=self.parsed_template('Properties'), + stack_id=self.stack.id) + wr.state = wr.CEILOMETER_CONTROLLED + wr.store() + def handle_update(self, json_snippet, tmpl_diff, prop_diff): if prop_diff: kwargs = {'alarm_id': self.resource_id} @@ -102,6 +115,13 @@ class CeilometerAlarm(resource.Resource): enabled=True) def handle_delete(self): + try: + wr = watchrule.WatchRule.load( + self.context, watch_name=self.physical_resource_name()) + wr.destroy() + except exception.WatchRuleNotFound: + pass + if self.resource_id is not None: self.ceilometer().alarms.delete(self.resource_id) diff --git a/heat/engine/service.py b/heat/engine/service.py index cf36e6aa7..56eb5a17c 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -100,13 +100,17 @@ class EngineService(service.Service): wrs = db_api.watch_rule_get_all_by_stack(cnxt, stack_id) - # reset the last_evaluated so we don't fire off alarms when - # the engine has not been running. now = timeutils.utcnow() + start_watch_thread = False for wr in wrs: + # reset the last_evaluated so we don't fire off alarms when + # the engine has not been running. db_api.watch_rule_update(cnxt, wr.id, {'last_evaluated': now}) - if len(wrs) > 0: + if wr.state != rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED: + start_watch_thread = True + + if start_watch_thread: self._timer_in_thread(stack_id, self._periodic_watcher_task, sid=stack_id) @@ -736,6 +740,8 @@ class EngineService(service.Service): arg3 -> State (must be one defined in WatchRule class ''' wr = watchrule.WatchRule.load(cnxt, watch_name) + if wr.state == rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED: + return actions = wr.set_watch_state(state) for action in actions: self._start_in_thread(wr.stack_id, action) diff --git a/heat/engine/watchrule.py b/heat/engine/watchrule.py index 3f2f82de6..e791088e2 100644 --- a/heat/engine/watchrule.py +++ b/heat/engine/watchrule.py @@ -31,12 +31,14 @@ class WatchRule(object): ALARM, NORMAL, NODATA, - SUSPENDED + SUSPENDED, + CEILOMETER_CONTROLLED, ) = ( rpc_api.WATCH_STATE_ALARM, rpc_api.WATCH_STATE_OK, rpc_api.WATCH_STATE_NODATA, - rpc_api.WATCH_STATE_SUSPENDED + rpc_api.WATCH_STATE_SUSPENDED, + rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED, ) ACTION_MAP = {ALARM: 'AlarmActions', NORMAL: 'OKActions', @@ -54,7 +56,12 @@ class WatchRule(object): self.state = state self.rule = rule self.stack_id = stack_id - self.timeperiod = datetime.timedelta(seconds=int(rule['Period'])) + period = 0 + if 'Period' in rule: + period = int(rule['Period']) + elif 'period' in rule: + period = int(rule['period']) + self.timeperiod = datetime.timedelta(seconds=period) self.id = wid self.watch_data = watch_data self.last_evaluated = last_evaluated @@ -257,7 +264,34 @@ class WatchRule(object): new_state) return actions + def _to_ceilometer(self, data): + from heat.engine import clients + clients = clients.Clients(self.context) + sample = {} + sample['counter_type'] = 'gauge' + + for k, d in iter(data.items()): + if k == 'Namespace': + continue + sample['counter_name'] = k + sample['counter_volume'] = d['Value'] + sample['counter_unit'] = d['Unit'] + dims = d.get('Dimensions', {}) + if isinstance(dims, list): + dims = dims[0] + sample['resource_metadata'] = dims + sample['resource_id'] = dims.get('InstanceId') + logger.debug('new sample:%s data:%s' % (k, sample)) + clients.ceilometer().samples.create(**sample) + def create_watch_data(self, data): + if self.state == self.CEILOMETER_CONTROLLED: + # this is a short term measure for those that have cfn-push-stats + # within their templates, but want to use Ceilometer alarms. + + self._to_ceilometer(data) + return + if self.state == self.SUSPENDED: logger.debug('Ignoring metric data for %s, SUSPENDED state' % self.name) @@ -322,16 +356,24 @@ def rule_can_use_sample(wr, stats_data): if wr.state == WatchRule.SUSPENDED: return False - if wr.rule['MetricName'] not in stats_data: - return False + if wr.state == WatchRule.CEILOMETER_CONTROLLED: + metric = wr.rule['counter_name'] + rule_dims = {} + for k, v in iter(wr.rule.get('matching_metadata', {}).items()): + name = k.split('.')[-1] + rule_dims[name] = v + else: + metric = wr.rule['MetricName'] + rule_dims = dict((d['Name'], d['Value']) + for d in wr.rule.get('Dimensions', [])) - rule_dims = dict((d['Name'], d['Value']) - for d in wr.rule.get('Dimensions', [])) + if metric not in stats_data: + return False for k, v in iter(stats_data.items()): if k == 'Namespace': continue - if k == wr.rule['MetricName']: + if k == metric: data_dims = v.get('Dimensions', {}) if isinstance(data_dims, list): data_dims = data_dims[0] diff --git a/heat/rpc/api.py b/heat/rpc/api.py index 529e2e90e..f8df6a531 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -118,9 +118,9 @@ WATCH_RULE_KEYS = ( WATCH_STATES = ( WATCH_STATE_OK, WATCH_STATE_ALARM, WATCH_STATE_NODATA, - WATCH_STATE_SUSPENDED + WATCH_STATE_SUSPENDED, WATCH_STATE_CEILOMETER_CONTROLLED ) = ( - 'NORMAL', 'ALARM', 'NODATA', 'SUSPENDED' + 'NORMAL', 'ALARM', 'NODATA', 'SUSPENDED', 'CEILOMETER_CONTROLLED' ) WATCH_DATA_KEYS = (