]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Allow the Ceilometer Alarm to be used with cfn-push-stats
authorAngus Salkeld <asalkeld@redhat.com>
Wed, 31 Jul 2013 11:24:26 +0000 (21:24 +1000)
committerAngus Salkeld <asalkeld@redhat.com>
Thu, 1 Aug 2013 10:49:34 +0000 (20:49 +1000)
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
heat/engine/service.py
heat/engine/watchrule.py
heat/rpc/api.py

index 85a5677575e002292d8f835b1f5da34f31e60f86..1fa014e0e35e6709c068372f4a25d1786953a8ce 100644 (file)
@@ -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)
 
index cf36e6aa7c8802af1bfc8e91aade7acb1a15bc95..56eb5a17c0cc37e872eb933b7de6ef22de465109 100644 (file)
@@ -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)
index 3f2f82de6b68e294726f2948ee3c5fa497089372..e791088e22d90e61064b48083a409f9e1a0c551b 100644 (file)
@@ -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:
+    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', []))
+
+    if metric not in stats_data:
         return False
 
-    rule_dims = dict((d['Name'], d['Value'])
-                     for d in wr.rule.get('Dimensions', []))
-
     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]
index 529e2e90e68417ff7e3466c3db5a7834abb80976..f8df6a5318396d350e97e02ada637325ee983bbd 100644 (file)
@@ -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 = (