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
This commit is contained in:
Angus Salkeld 2013-07-31 21:24:26 +10:00
parent 31e53d4fe7
commit af68b6168c
4 changed files with 81 additions and 13 deletions

View 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)

View 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)

View 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:
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]

View 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 = (