From f9fa8da05cf10034faf70340105bbd4f1d07f008 Mon Sep 17 00:00:00 2001 From: Mehdi Abaakouk Date: Thu, 5 Feb 2015 11:11:41 +0100 Subject: [PATCH] ceilometer: new Gnocchi Alarm resources Ceilometer offers two new kind of alarms. This change adds the associated resources to Heat. Implements blueprint ceilometer-gnocchi-alarm Change-Id: I0d3dfcbda7d09361bb90fd16122b903bf4c03fbe --- contrib/heat_gnocchi/README.md | 18 + contrib/heat_gnocchi/heat_gnocchi/__init__.py | 0 .../heat_gnocchi/resources/__init__.py | 0 .../heat_gnocchi/resources/gnocchi_alarm.py | 132 +++++++ .../heat_gnocchi/tests/__init__.py | 0 .../heat_gnocchi/tests/test_gnocchi_alarm.py | 366 ++++++++++++++++++ contrib/heat_gnocchi/setup.cfg | 29 ++ contrib/heat_gnocchi/setup.py | 30 ++ heat/engine/resources/ceilometer/alarm.py | 67 ++-- 9 files changed, 610 insertions(+), 32 deletions(-) create mode 100644 contrib/heat_gnocchi/README.md create mode 100644 contrib/heat_gnocchi/heat_gnocchi/__init__.py create mode 100644 contrib/heat_gnocchi/heat_gnocchi/resources/__init__.py create mode 100644 contrib/heat_gnocchi/heat_gnocchi/resources/gnocchi_alarm.py create mode 100644 contrib/heat_gnocchi/heat_gnocchi/tests/__init__.py create mode 100644 contrib/heat_gnocchi/heat_gnocchi/tests/test_gnocchi_alarm.py create mode 100644 contrib/heat_gnocchi/setup.cfg create mode 100644 contrib/heat_gnocchi/setup.py diff --git a/contrib/heat_gnocchi/README.md b/contrib/heat_gnocchi/README.md new file mode 100644 index 0000000000..38941ed531 --- /dev/null +++ b/contrib/heat_gnocchi/README.md @@ -0,0 +1,18 @@ +Gnocchi plugin for OpenStack Heat +================================= + +This plugin adds Ceilometer Gnocchi Alarm resources in a Heat template. + + +### 1. Install the Gnocchi plugin in Heat + +NOTE: These instructions assume the value of heat.conf plugin_dirs includes the +default directory /usr/lib/heat. + +To install the plugin, from this directory run: + sudo python ./setup.py install + +### 2. Restart heat + +Only the process "heat-engine" needs to be restarted to load the newly installed +plugin. diff --git a/contrib/heat_gnocchi/heat_gnocchi/__init__.py b/contrib/heat_gnocchi/heat_gnocchi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/heat_gnocchi/heat_gnocchi/resources/__init__.py b/contrib/heat_gnocchi/heat_gnocchi/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/heat_gnocchi/heat_gnocchi/resources/gnocchi_alarm.py b/contrib/heat_gnocchi/heat_gnocchi/resources/gnocchi_alarm.py new file mode 100644 index 0000000000..9db5de6fbf --- /dev/null +++ b/contrib/heat_gnocchi/heat_gnocchi/resources/gnocchi_alarm.py @@ -0,0 +1,132 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from heat.common.i18n import _ +from heat.engine import constraints +from heat.engine import properties +from heat.engine.resources.ceilometer import alarm +from heat.engine import support + + +COMMON_GNOCCHI_PROPERTIES = ( + COMPARISON_OPERATOR, EVALUATION_PERIODS, GRANULARITY, + AGGREGATION_METHOD, THRESHOLD, +) = ( + 'comparison_operator', 'evaluation_periods', 'granularity', + 'aggregation_method', 'threshold', +) + + +common_gnocchi_properties_schema = { + COMPARISON_OPERATOR: properties.Schema( + properties.Schema.STRING, + _('Operator used to compare specified statistic with threshold.'), + constraints=[ + constraints.AllowedValues(['ge', 'gt', 'eq', 'ne', 'lt', + 'le']), + ], + update_allowed=True + ), + EVALUATION_PERIODS: properties.Schema( + properties.Schema.INTEGER, + _('Number of periods to evaluate over.'), + update_allowed=True + ), + AGGREGATION_METHOD: properties.Schema( + properties.Schema.STRING, + _('The aggregation method to compare to the threshold'), + constraints=[ + constraints.AllowedValues(['mean', 'sum', 'last', 'max', 'min', + 'std', 'median', 'first', 'count']), + ], + update_allowed=True + ), + GRANULARITY: properties.Schema( + properties.Schema.INTEGER, + _('The time range in seconds.'), + update_allowed=True + ), + THRESHOLD: properties.Schema( + properties.Schema.NUMBER, + _('Threshold to evaluate against.'), + required=True, + update_allowed=True + ), +} + + +class CeilometerGnocchiResourcesAlarm(alarm.BaseCeilometerAlarm): + + support_status = support.SupportStatus(version='2015.1') + + PROPERTIES = ( + METRIC, RESOURCE_CONSTRAINT, RESOURCE_TYPE + ) = ( + 'metric', 'resource_constraint', 'resource_type' + ) + PROPERTIES += COMMON_GNOCCHI_PROPERTIES + + properties_schema = { + METRIC: properties.Schema( + properties.Schema.STRING, + _('Metric name watched by the alarm.'), + required=True, + update_allowed=True + ), + RESOURCE_CONSTRAINT: properties.Schema( + properties.Schema.STRING, + _('Id of a resource or expression to select multiple resources'), + required=True, + update_allowed=True + ), + RESOURCE_TYPE: properties.Schema( + properties.Schema.STRING, + _('Resource type'), + required=True, + update_allowed=True + ), + } + properties_schema.update(common_gnocchi_properties_schema) + properties_schema.update(alarm.common_properties_schema) + + ceilometer_alarm_type = 'gnocchi_resources_threshold' + + +class CeilometerGnocchiMetricsAlarm(CeilometerGnocchiResourcesAlarm): + + support_status = support.SupportStatus(version='2015.1') + + PROPERTIES = (METRICS,) = ('metrics',) + PROPERTIES += COMMON_GNOCCHI_PROPERTIES + + properties_schema = { + METRICS: properties.Schema( + properties.Schema.LIST, + _('A list of metric ids.'), + required=True, + update_allowed=True, + ), + } + properties_schema.update(common_gnocchi_properties_schema) + properties_schema.update(alarm.common_properties_schema) + + ceilometer_alarm_type = 'gnocchi_metrics_threshold' + + +def resource_mapping(): + return { + 'OS::Ceilometer::GnocchiResourcesAlarm': + CeilometerGnocchiResourcesAlarm, + 'OS::Ceilometer::GnocchiMetricslarm': CeilometerGnocchiMetricsAlarm, + } diff --git a/contrib/heat_gnocchi/heat_gnocchi/tests/__init__.py b/contrib/heat_gnocchi/heat_gnocchi/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/heat_gnocchi/heat_gnocchi/tests/test_gnocchi_alarm.py b/contrib/heat_gnocchi/heat_gnocchi/tests/test_gnocchi_alarm.py new file mode 100644 index 0000000000..3b1c4efdaa --- /dev/null +++ b/contrib/heat_gnocchi/heat_gnocchi/tests/test_gnocchi_alarm.py @@ -0,0 +1,366 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from ceilometerclient import exc as ceilometerclient_exc +import mock +import mox + +from heat.common import exception +from heat.common import template_format +from heat.engine.clients.os import ceilometer +from heat.engine import resource +from heat.engine import scheduler +from heat.tests import common +from heat.tests import utils + +from ..resources import gnocchi_alarm # noqa + +gnocchi_resources_alarm_template = ''' +heat_template_version: 2013-05-23 +description: Gnocchi Resources Alarm Test +resources: + GnoResAlarm: + type: OS::Ceilometer::GnocchiResourcesAlarm + properties: + description: Do stuff with gnocchi + metric: cpu_util + aggregation_method: mean + granularity: 60 + evaluation_periods: 1 + threshold: 50 + alarm_actions: [] + resource_type: instance + resource_constraint: server_group=mystack + comparison_operator: gt +''' + + +gnocchi_metrics_alarm_template = ''' +heat_template_version: 2013-05-23 +description: Gnocchi Metrics Alarm Test +resources: + GnoMetricsAlarm: + type: OS::Ceilometer::GnocchiMetricsAlarm + properties: + description: Do stuff with gnocchi metrics + metrics: ["911fce07-e0d7-4210-8c8c-4a9d811fcabc", + "2543d435-fe93-4443-9351-fb0156930f94"] + aggregation_method: mean + granularity: 60 + evaluation_periods: 1 + threshold: 50 + alarm_actions: [] + comparison_operator: gt +''' + + +class FakeCeilometerAlarm(object): + alarm_id = 'foo' + + +class FakeCeilometerAlarms(object): + def create(self, **kwargs): + pass + + def update(self, **kwargs): + pass + + def delete(self, alarm_id): + pass + + +class FakeCeilometerClient(object): + alarms = FakeCeilometerAlarms() + + +class GnocchiResourcesAlarmTest(common.HeatTestCase): + + def setUp(self): + super(GnocchiResourcesAlarmTest, self).setUp() + self.fc = FakeCeilometerClient() + resource._register_class("OS::Ceilometer::GnocchiResourcesAlarm", + gnocchi_alarm.CeilometerGnocchiResourcesAlarm) + self.m.StubOutWithMock(ceilometer.CeilometerClientPlugin, '_create') + + def create_alarm(self): + ceilometer.CeilometerClientPlugin._create().AndReturn( + self.fc) + self.m.StubOutWithMock(self.fc.alarms, 'create') + self.fc.alarms.create( + alarm_actions=[], + description=u'Do stuff with gnocchi', + enabled=True, + insufficient_data_actions=None, + ok_actions=None, + name=mox.IgnoreArg(), type='gnocchi_resources_threshold', + repeat_actions=True, + gnocchi_resources_threshold_rule={ + "metric": "cpu_util", + "aggregation_method": "mean", + "granularity": 60, + "evaluation_periods": 1, + "threshold": 50, + "resource_type": "instance", + "resource_constraint": "server_group=mystack", + "comparison_operator": "gt", + } + ).AndReturn(FakeCeilometerAlarm()) + snippet = template_format.parse(gnocchi_resources_alarm_template) + stack = utils.parse_stack(snippet) + resource_defns = stack.t.resource_definitions(stack) + return gnocchi_alarm.CeilometerGnocchiResourcesAlarm( + 'GnoResAlarm', resource_defns['GnoResAlarm'], stack) + + def test_create(self): + rsrc = self.create_alarm() + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + self.assertEqual('foo', rsrc.resource_id) + self.m.VerifyAll() + + def test_update(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update( + alarm_id='foo', + gnocchi_resources_threshold_rule={ + 'resource_constraint': 'd3d6c642-921e-4fc2-9c5f-15d9a5afb598'}) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + + update_template = copy.deepcopy(rsrc.t) + update_template['Properties']['resource_constraint'] = ( + 'd3d6c642-921e-4fc2-9c5f-15d9a5afb598') + scheduler.TaskRunner(rsrc.update, update_template)() + self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_suspend(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update(alarm_id='foo', enabled=False) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + + scheduler.TaskRunner(rsrc.suspend)() + self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_resume(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update(alarm_id='foo', enabled=True) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE) + + scheduler.TaskRunner(rsrc.resume)() + self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_delete(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'delete') + self.fc.alarms.delete('foo') + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + scheduler.TaskRunner(rsrc.delete)() + self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_delete_not_found(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'delete') + self.fc.alarms.delete('foo').AndRaise( + ceilometerclient_exc.HTTPNotFound()) + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + scheduler.TaskRunner(rsrc.delete)() + self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def _prepare_check_resource(self): + snippet = template_format.parse(gnocchi_resources_alarm_template) + stack = utils.parse_stack(snippet) + res = stack['GnoResAlarm'] + res.ceilometer = mock.Mock() + mock_alarm = mock.Mock(enabled=True, state='ok') + res.ceilometer().alarms.get.return_value = mock_alarm + return res + + def test_check(self): + res = self._prepare_check_resource() + scheduler.TaskRunner(res.check)() + self.assertEqual((res.CHECK, res.COMPLETE), res.state) + + def test_check_failure(self): + res = self._prepare_check_resource() + res.ceilometer().alarms.get.side_effect = Exception('Boom') + + self.assertRaises(exception.ResourceFailure, + scheduler.TaskRunner(res.check)) + self.assertEqual((res.CHECK, res.FAILED), res.state) + self.assertIn('Boom', res.status_reason) + + +class GnocchiMetricsAlarmTest(common.HeatTestCase): + + def setUp(self): + super(GnocchiMetricsAlarmTest, self).setUp() + self.fc = FakeCeilometerClient() + resource._register_class("OS::Ceilometer::GnocchiMetricsAlarm", + gnocchi_alarm.CeilometerGnocchiMetricsAlarm) + self.m.StubOutWithMock(ceilometer.CeilometerClientPlugin, '_create') + + def create_alarm(self): + ceilometer.CeilometerClientPlugin._create().AndReturn( + self.fc) + self.m.StubOutWithMock(self.fc.alarms, 'create') + self.fc.alarms.create( + alarm_actions=[], + description=u'Do stuff with gnocchi metrics', + enabled=True, + insufficient_data_actions=None, + ok_actions=None, + name=mox.IgnoreArg(), type='gnocchi_metrics_threshold', + repeat_actions=True, + gnocchi_metrics_threshold_rule={ + "aggregation_method": "mean", + "granularity": 60, + "evaluation_periods": 1, + "threshold": 50, + "comparison_operator": "gt", + "metrics": ["911fce07-e0d7-4210-8c8c-4a9d811fcabc", + "2543d435-fe93-4443-9351-fb0156930f94"], + } + ).AndReturn(FakeCeilometerAlarm()) + snippet = template_format.parse(gnocchi_metrics_alarm_template) + stack = utils.parse_stack(snippet) + resource_defns = stack.t.resource_definitions(stack) + return gnocchi_alarm.CeilometerGnocchiMetricsAlarm( + 'GnoMetricsAlarm', resource_defns['GnoMetricsAlarm'], stack) + + def test_create(self): + rsrc = self.create_alarm() + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + self.assertEqual('foo', rsrc.resource_id) + self.m.VerifyAll() + + def test_update(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update( + alarm_id='foo', + gnocchi_metrics_threshold_rule={ + 'metrics': ['d3d6c642-921e-4fc2-9c5f-15d9a5afb598', + 'bc60f822-18a0-4a0c-94e7-94c554b00901']}) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + + update_template = copy.deepcopy(rsrc.t) + update_template['Properties']['metrics'] = [ + 'd3d6c642-921e-4fc2-9c5f-15d9a5afb598', + 'bc60f822-18a0-4a0c-94e7-94c554b00901'] + scheduler.TaskRunner(rsrc.update, update_template)() + self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_suspend(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update(alarm_id='foo', enabled=False) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + + scheduler.TaskRunner(rsrc.suspend)() + self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_resume(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'update') + self.fc.alarms.update(alarm_id='foo', enabled=True) + + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE) + + scheduler.TaskRunner(rsrc.resume)() + self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_delete(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'delete') + self.fc.alarms.delete('foo') + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + scheduler.TaskRunner(rsrc.delete)() + self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def test_delete_not_found(self): + rsrc = self.create_alarm() + self.m.StubOutWithMock(self.fc.alarms, 'delete') + self.fc.alarms.delete('foo').AndRaise( + ceilometerclient_exc.HTTPNotFound()) + self.m.ReplayAll() + scheduler.TaskRunner(rsrc.create)() + scheduler.TaskRunner(rsrc.delete)() + self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) + + self.m.VerifyAll() + + def _prepare_check_resource(self): + snippet = template_format.parse(gnocchi_metrics_alarm_template) + stack = utils.parse_stack(snippet) + res = stack['GnoMetricsAlarm'] + res.ceilometer = mock.Mock() + mock_alarm = mock.Mock(enabled=True, state='ok') + res.ceilometer().alarms.get.return_value = mock_alarm + return res + + def test_check(self): + res = self._prepare_check_resource() + scheduler.TaskRunner(res.check)() + self.assertEqual((res.CHECK, res.COMPLETE), res.state) + + def test_check_failure(self): + res = self._prepare_check_resource() + res.ceilometer().alarms.get.side_effect = Exception('Boom') + + self.assertRaises(exception.ResourceFailure, + scheduler.TaskRunner(res.check)) + self.assertEqual((res.CHECK, res.FAILED), res.state) + self.assertIn('Boom', res.status_reason) diff --git a/contrib/heat_gnocchi/setup.cfg b/contrib/heat_gnocchi/setup.cfg new file mode 100644 index 0000000000..d545527c0a --- /dev/null +++ b/contrib/heat_gnocchi/setup.cfg @@ -0,0 +1,29 @@ +[metadata] +name = heat-contrib-gnocchi +summary = Heat resources for working gnocchi queues +description-file = + README.md +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[files] +packages = + heat_gnocchi +# Copy to /usr/lib/heat for plugin loading +data_files = + lib/heat/gnocchi = heat_gnocchi/resources/* + +[global] +setup-hooks = + pbr.hooks.setup_hook diff --git a/contrib/heat_gnocchi/setup.py b/contrib/heat_gnocchi/setup.py new file mode 100644 index 0000000000..736375744d --- /dev/null +++ b/contrib/heat_gnocchi/setup.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/heat/engine/resources/ceilometer/alarm.py b/heat/engine/resources/ceilometer/alarm.py index e9388aa69c..7585fbc966 100644 --- a/heat/engine/resources/ceilometer/alarm.py +++ b/heat/engine/resources/ceilometer/alarm.py @@ -313,51 +313,26 @@ class CeilometerAlarm(resource.Resource): self.ceilometer().alarms.get(self.resource_id) -class CombinationAlarm(resource.Resource): - - support_status = support.SupportStatus(version='2014.1') - - PROPERTIES = ( - ALARM_IDS, OPERATOR, - ) = ( - 'alarm_ids', 'operator', - ) - - properties_schema = { - ALARM_IDS: properties.Schema( - properties.Schema.LIST, - _('List of alarm identifiers to combine.'), - required=True, - constraints=[constraints.Length(min=1)], - update_allowed=True), - OPERATOR: properties.Schema( - properties.Schema.STRING, - _('Operator used to combine the alarms.'), - constraints=[constraints.AllowedValues(['and', 'or'])], - update_allowed=True) - } - properties_schema.update(common_properties_schema) - +class BaseCeilometerAlarm(resource.Resource): default_client_name = 'ceilometer' def handle_create(self): properties = actions_to_urls(self.stack, self.properties) properties['name'] = self.physical_resource_name() - properties['type'] = 'combination' - + properties['type'] = self.ceilometer_alarm_type alarm = self.ceilometer().alarms.create( **self._reformat_properties(properties)) self.resource_id_set(alarm.alarm_id) def _reformat_properties(self, properties): - combination_rule = {} - for name in [self.ALARM_IDS, self.OPERATOR]: + rule = {} + for name in self.PROPERTIES: value = properties.pop(name, None) if value: - combination_rule[name] = value - if combination_rule: - properties['combination_rule'] = combination_rule + rule[name] = value + if rule: + properties['%s_rule' % self.ceilometer_alarm_type] = rule return properties def handle_update(self, json_snippet, tmpl_diff, prop_diff): @@ -386,6 +361,34 @@ class CombinationAlarm(resource.Resource): self.ceilometer().alarms.get(self.resource_id) +class CombinationAlarm(BaseCeilometerAlarm): + + support_status = support.SupportStatus(version='2014.1') + + PROPERTIES = ( + ALARM_IDS, OPERATOR, + ) = ( + 'alarm_ids', 'operator', + ) + + properties_schema = { + ALARM_IDS: properties.Schema( + properties.Schema.LIST, + _('List of alarm identifiers to combine.'), + required=True, + constraints=[constraints.Length(min=1)], + update_allowed=True), + OPERATOR: properties.Schema( + properties.Schema.STRING, + _('Operator used to combine the alarms.'), + constraints=[constraints.AllowedValues(['and', 'or'])], + update_allowed=True) + } + properties_schema.update(common_properties_schema) + + ceilometer_alarm_type = 'combination' + + def resource_mapping(): return { 'OS::Ceilometer::Alarm': CeilometerAlarm,