From c1b71616e54e5bd5e1922f0071b6185488c62113 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Mon, 6 May 2013 11:08:19 +1000 Subject: [PATCH] Add Alarm DB API and models blueprint alarm-api Change-Id: I3c6e1edace3197e67788c2ee968b002645fea82f --- ceilometer/storage/base.py | 16 ++++++ ceilometer/storage/impl_hbase.py | 17 ++++++ ceilometer/storage/impl_log.py | 15 ++++++ ceilometer/storage/impl_mongodb.py | 16 ++++++ ceilometer/storage/impl_sqlalchemy.py | 16 ++++++ ceilometer/storage/models.py | 75 +++++++++++++++++++++++++++ tests/storage/base.py | 63 ++++++++++++++++++++++ 7 files changed, 218 insertions(+) diff --git a/ceilometer/storage/base.py b/ceilometer/storage/base.py index 842f5aadf0..5c8881ad3b 100644 --- a/ceilometer/storage/base.py +++ b/ceilometer/storage/base.py @@ -137,6 +137,22 @@ class Connection(object): The filter must have a meter value set. """ + @abc.abstractmethod + def get_alarms(self, name=None, user=None, + project=None, enabled=True, alarm_id=None): + """Yields a lists of alarms that match filters + """ + + @abc.abstractmethod + def update_alarm(self, alarm): + """update alarm + """ + + @abc.abstractmethod + def delete_alarm(self, alarm_id): + """Delete a alarm + """ + @abc.abstractmethod def clear(self): """Clear database.""" diff --git a/ceilometer/storage/impl_hbase.py b/ceilometer/storage/impl_hbase.py index d7ea87a8e9..33b4c82f55 100644 --- a/ceilometer/storage/impl_hbase.py +++ b/ceilometer/storage/impl_hbase.py @@ -494,6 +494,23 @@ class Connection(base.Connection): self._update_meter_stats(results[-1], meter) return results + def get_alarms(self, name=None, user=None, + project=None, enabled=True, alarm_id=None): + """Yields a lists of alarms that match filters + raise NotImplementedError('metaquery not implemented') + """ + raise NotImplementedError('Alarms not implemented') + + def update_alarm(self, alarm): + """update alarm + """ + raise NotImplementedError('Alarms not implemented') + + def delete_alarm(self, alarm_id): + """Delete a alarm + """ + raise NotImplementedError('Alarms not implemented') + ############### # This is a very crude version of "in-memory HBase", which implements just diff --git a/ceilometer/storage/impl_log.py b/ceilometer/storage/impl_log.py index e922a6a6e6..0a6193d6ba 100644 --- a/ceilometer/storage/impl_log.py +++ b/ceilometer/storage/impl_log.py @@ -145,3 +145,18 @@ class Connection(base.Connection): """ return [] + + def get_alarms(self, name=None, user=None, + project=None, enabled=True, alarm_id=None): + """Yields a lists of alarms that match filters + """ + return [] + + def update_alarm(self, alarm): + """update alarm + """ + return alarm + + def delete_alarm(self, alarm_id): + """Delete a alarm + """ diff --git a/ceilometer/storage/impl_mongodb.py b/ceilometer/storage/impl_mongodb.py index ecd0fe729a..cfd4edcabe 100644 --- a/ceilometer/storage/impl_mongodb.py +++ b/ceilometer/storage/impl_mongodb.py @@ -506,6 +506,22 @@ class Connection(base.Connection): a_max.valueOf() // 1000) return (a_min, a_max) + def get_alarms(self, name=None, user=None, + project=None, enabled=True, alarm_id=None): + """Yields a lists of alarms that match filters + """ + raise NotImplementedError('Alarms not implemented') + + def update_alarm(self, alarm): + """update alarm + """ + raise NotImplementedError('Alarms not implemented') + + def delete_alarm(self, alarm_id): + """Delete a alarm + """ + raise NotImplementedError('Alarms not implemented') + def require_map_reduce(conn): """Raises SkipTest if the connection is using mim. diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py index 455e378a1a..efd1b0997f 100644 --- a/ceilometer/storage/impl_sqlalchemy.py +++ b/ceilometer/storage/impl_sqlalchemy.py @@ -411,3 +411,19 @@ class Connection(base.Connection): period_start=period_start, period_end=period_end, ) + + def get_alarms(self, name=None, user=None, + project=None, enabled=True, alarm_id=None): + """Yields a lists of alarms that match filters + """ + raise NotImplementedError('Alarms not implemented') + + def update_alarm(self, alarm): + """update alarm + """ + raise NotImplementedError('Alarms not implemented') + + def delete_alarm(self, alarm_id): + """Delete a alarm + """ + raise NotImplementedError('Alarms not implemented') diff --git a/ceilometer/storage/models.py b/ceilometer/storage/models.py index 4129d07840..0467f14451 100644 --- a/ceilometer/storage/models.py +++ b/ceilometer/storage/models.py @@ -174,3 +174,78 @@ class Statistics(Model): period_end=period_end, duration=duration, duration_start=duration_start, duration_end=duration_end) + + +class Alarm(Model): + ALARM_INSUFFICIENT_DATA = 'insufficient data' + ALARM_OK = 'ok' + ALARM_ALARM = 'alarm' + """ + An alarm to monitor. + + :param alarm_id: UUID of the alarm + :param name: The Alarm name + :param description: User friendly description of the alarm + :param enabled: Is the alarm enabled + :param state: Alarm state (alarm/nodata/ok) + :param counter_name: The counter that the alarm is based on + :param comparison_operator: How to compare the samples and the threshold + :param threshold: the value to compare to the samples + :param statistic: the function from Statistic (min/max/avg/count) + :param user_id: the owner/creator of the alarm + :param project_id: the project_id of the creator + :param evaluation_periods: the number of periods + :param period: the time period in seconds + :param timestamp: the timestamp when the alarm was last updated + :param state_timestamp: the timestamp of the last state change + :param ok_actions: the list of webhooks to call when entering the ok state + :param alarm_actions: the list of webhooks to call when entering the + alarm state + :param insufficient_data_actions: the list of webhooks to call when + entering the insufficient data state + :param matching_metadata: the key/values of metadata to match on. + """ + def __init__(self, name, counter_name, + comparison_operator, threshold, statistic, + user_id, project_id, + evaluation_periods=1, + period=60, + alarm_id=None, + enabled=True, + description='', + timestamp=None, + state=ALARM_INSUFFICIENT_DATA, + state_timestamp=None, + ok_actions=[], + alarm_actions=[], + insufficient_data_actions=[], + matching_metadata={} + ): + if not description: + # make a nice user friendly description by default + description = 'Alarm when %s is %s a %s of %s over %s seconds' % ( + counter_name, comparison_operator, + statistic, threshold, period) + + Model.__init__( + self, + alarm_id=alarm_id, + enabled=enabled, + name=name, + description=description, + timestamp=timestamp, + counter_name=counter_name, + user_id=user_id, + project_id=project_id, + comparison_operator=comparison_operator, + threshold=threshold, + statistic=statistic, + evaluation_periods=evaluation_periods, + period=period, + state=state, + state_timestamp=state_timestamp, + ok_actions=ok_actions, + alarm_actions=alarm_actions, + insufficient_data_actions= + insufficient_data_actions, + matching_metadata=matching_metadata) diff --git a/tests/storage/base.py b/tests/storage/base.py index 5d218e9994..574a9bb8cb 100644 --- a/tests/storage/base.py +++ b/tests/storage/base.py @@ -583,3 +583,66 @@ class CounterDataTypeTest(DBTestBase): ) results = list(self.conn.get_samples(f)) self.assertEqual(results[0].counter_volume, 1938495037.53697) + + +class AlarmTest(DBTestBase): + + def test_empty(self): + alarms = list(self.conn.get_alarms()) + self.assertEquals([], alarms) + + def add_some_alarms(self): + alarms = [models.Alarm('red-alert', + 'test.one', 'eq', 36, 'count', + 'me', 'and-da-boys', + evaluation_periods=1, + period=60, + alarm_actions=['http://nowhere/alarms']), + models.Alarm('orange-alert', + 'test.fourty', 'gt', 75, 'avg', + 'me', 'and-da-boys', + period=60, + alarm_actions=['http://nowhere/alarms']), + models.Alarm('yellow-alert', + 'test.five', 'lt', 10, 'min', + 'me', 'and-da-boys', + alarm_actions=['http://nowhere/alarms'])] + for a in alarms: + self.conn.update_alarm(a) + + def test_add(self): + self.add_some_alarms() + alarms = list(self.conn.get_alarms()) + self.assertEquals(len(alarms), 3) + + def test_defaults(self): + self.add_some_alarms() + yellow = list(self.conn.get_alarms(name='yellow-alert'))[0] + + self.assertEquals(yellow.evaluation_periods, 1) + self.assertEquals(yellow.period, 60) + self.assertEquals(yellow.enabled, True) + self.assertEquals(yellow.description, + 'Alarm when test.five is lt %s' % + 'a min of 10 over 60 seconds') + self.assertEquals(yellow.state, models.Alarm.ALARM_INSUFFICIENT_DATA) + self.assertEquals(yellow.ok_actions, []) + self.assertEquals(yellow.insufficient_data_actions, []) + + def test_update(self): + self.add_some_alarms() + orange = list(self.conn.get_alarms(name='orange-alert'))[0] + orange.enabled = False + orange.state = models.Alarm.ALARM_INSUFFICIENT_DATA + updated = self.conn.update_alarm(orange) + self.assertEquals(updated.enabled, False) + self.assertEquals(updated.state, models.Alarm.ALARM_INSUFFICIENT_DATA) + + def test_delete(self): + self.add_some_alarms() + victim = list(self.conn.get_alarms(name='orange-alert'))[0] + self.conn.delete_alarm(victim.alarm_id) + survivors = list(self.conn.get_alarms()) + self.assertEquals(len(survivors), 2) + for s in survivors: + self.assertNotEquals(victim.name, s.name)