diff --git a/ceilometer/alarm/service.py b/ceilometer/alarm/service.py new file mode 100644 index 000000000..25f375ae3 --- /dev/null +++ b/ceilometer/alarm/service.py @@ -0,0 +1,90 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 Red Hat, Inc +# +# Author: Eoghan Glynn +# +# 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 oslo.config import cfg +from stevedore import extension + +from ceilometer.service import prepare_service +from ceilometer.openstack.common import log +from ceilometer.openstack.common import service as os_service +from ceilometerclient import client as ceiloclient + +OPTS = [ + cfg.IntOpt('threshold_evaluation_interval', + default=60, + help='Period of threshold evaluation cycle, should' + ' be >= than configured pipeline interval for' + ' collection of underlying metrics.'), +] + +cfg.CONF.register_opts(OPTS, group='alarm') + +LOG = log.getLogger(__name__) + + +class SingletonAlarmService(os_service.Service): + + ALARM_NAMESPACE = 'ceilometer.alarm' + + def __init__(self): + super(SingletonAlarmService, self).__init__() + self.extension_manager = extension.ExtensionManager( + namespace=self.ALARM_NAMESPACE, + invoke_on_load=True, + ) + + def start(self): + super(SingletonAlarmService, self).start() + for ext in self.extension_manager.extensions: + if ext.name == 'threshold_eval': + self.threshold_eval = ext.obj + interval = cfg.CONF.alarm.threshold_evaluation_interval + args = [ext.obj, self._client()] + self.tg.add_timer( + interval, + self._evaluate_all_alarms, + 0, + *args) + break + # Add a dummy thread to have wait() working + self.tg.add_timer(604800, lambda: None) + + @staticmethod + def _client(): + auth_config = cfg.CONF.service_credentials + creds = dict( + os_auth_url=auth_config.os_auth_url, + os_tenant_name=auth_config.os_tenant_name, + os_password=auth_config.os_password, + os_username=auth_config.os_username + ) + return ceiloclient.get_client(2, **creds) + + @staticmethod + def _evaluate_all_alarms(threshold_eval, api_client): + try: + alarms = api_client.alarms.list() + threshold_eval.assign_alarms(alarms) + threshold_eval.evaluate() + except Exception: + LOG.exception(_('threshold evaluation cycle failed')) + + +def singleton_alarm(): + prepare_service() + os_service.launch(SingletonAlarmService()).wait() diff --git a/ceilometer/alarm/threshold_evaluation.py b/ceilometer/alarm/threshold_evaluation.py index 38930b4fe..f5adf8750 100644 --- a/ceilometer/alarm/threshold_evaluation.py +++ b/ceilometer/alarm/threshold_evaluation.py @@ -53,7 +53,7 @@ class Evaluator(object): # avoid unknown state quorum = 1 - def __init__(self, notifier): + def __init__(self, notifier=None): self.alarms = [] self.notifier = notifier self.api_client = None diff --git a/etc/ceilometer/ceilometer.conf.sample b/etc/ceilometer/ceilometer.conf.sample index 127037f24..a90cd1463 100644 --- a/etc/ceilometer/ceilometer.conf.sample +++ b/etc/ceilometer/ceilometer.conf.sample @@ -526,6 +526,18 @@ #pool_timeout= +[alarm] + +# +# Options defined in ceilometer.alarm.service +# + +# Period of threshold evaluation cycle, should be >= than +# configured pipeline interval for collection of underlying +# metrics. (integer value) +#threshold_evaluation_interval=60 + + [rpc_notifier2] # @@ -612,4 +624,4 @@ #password= -# Total option count: 118 +# Total option count: 119 diff --git a/setup.cfg b/setup.cfg index dbfe2731e..3ad209abb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -83,6 +83,9 @@ ceilometer.publisher = rpc = ceilometer.publisher.rpc:RPCPublisher udp = ceilometer.publisher.udp:UDPPublisher +ceilometer.alarm = + threshold_eval = ceilometer.alarm.threshold_evaluation:Evaluator + paste.filter_factory = swift = ceilometer.objectstore.swift_middleware:filter_factory @@ -93,6 +96,7 @@ console_scripts = ceilometer-dbsync = ceilometer.storage:dbsync ceilometer-collector = ceilometer.collector.service:collector ceilometer-collector-udp = ceilometer.collector.service:udp_collector + ceilometer-alarm-singleton = ceilometer.alarm.service:singleton_alarm [build_sphinx] all_files = 1 diff --git a/tests/alarm/test_singleton_alarm_svc.py b/tests/alarm/test_singleton_alarm_svc.py new file mode 100644 index 000000000..56e18a262 --- /dev/null +++ b/tests/alarm/test_singleton_alarm_svc.py @@ -0,0 +1,85 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 Red Hat, Inc +# +# Author: Eoghan Glynn +# +# 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. +"""Tests for ceilometer/alarm/service.py +""" +import mock +import uuid + +from stevedore import extension +from stevedore.tests import manager as extension_tests + +from ceilometer.alarm import service +from ceilometer.storage import models +from ceilometer.tests import base + + +class TestSingletonAlarmService(base.TestCase): + def setUp(self): + super(TestSingletonAlarmService, self).setUp() + self.threshold_eval = mock.Mock() + self.extension_mgr = extension_tests.TestExtensionManager( + [ + extension.Extension( + 'threshold_eval', + None, + None, + self.threshold_eval, ), + ]) + self.api_client = mock.MagicMock() + self.singleton = service.SingletonAlarmService() + self.singleton.tg = mock.Mock() + self.singleton.extension_manager = self.extension_mgr + + def test_start(self): + with mock.patch('ceilometerclient.client.get_client', + return_value=self.api_client): + self.singleton.start() + expected = [ + mock.call(60, + self.singleton._evaluate_all_alarms, + 0, + self.threshold_eval, + self.api_client), + mock.call(604800, mock.ANY), + ] + actual = self.singleton.tg.add_timer.call_args_list + self.assertEqual(actual, expected) + + def test_evaluation_cycle(self): + alarms = [ + models.Alarm(name='instance_running_hot', + counter_name='cpu_util', + comparison_operator='gt', + threshold=80.0, + evaluation_periods=5, + statistic='avg', + user_id='foobar', + project_id='snafu', + period=60, + alarm_id=str(uuid.uuid4())), + ] + self.api_client.alarms.list.return_value = alarms + with mock.patch('ceilometerclient.client.get_client', + return_value=self.api_client): + self.singleton.start() + self.singleton._evaluate_all_alarms( + self.threshold_eval, + self.api_client + ) + self.threshold_eval.assign_alarms.assert_called_once_with(alarms) + self.threshold_eval.evaluate.assert_called_once_with()