diff --git a/ceilometerclient/common/base.py b/ceilometerclient/common/base.py index 337ce60..8979c70 100644 --- a/ceilometerclient/common/base.py +++ b/ceilometerclient/common/base.py @@ -72,7 +72,7 @@ class Manager(object): resp, body = self.api.json_request('PUT', url, body=body) # PUT requests may not return a body if body: - return self.resource_class(self, body[response_key]) + return self.resource_class(self, body) class Resource(object): diff --git a/ceilometerclient/v2/alarms.py b/ceilometerclient/v2/alarms.py index d0c4442..40b51f4 100644 --- a/ceilometerclient/v2/alarms.py +++ b/ceilometerclient/v2/alarms.py @@ -20,6 +20,22 @@ from ceilometerclient.common import base from ceilometerclient.v2 import options +UPDATABLE_ATTRIBUTES = [ + 'description', + 'period', + 'evaluation_periods', + 'state', + 'enabled', + 'counter_name', + 'statistic', + 'comparison_operator', + 'threshold', + 'alarm_actions', + 'ok_actions', + 'insufficient_data_actions', + ] + + class Alarm(base.Resource): def __repr__(self): return "" % self._info @@ -41,5 +57,13 @@ class AlarmManager(base.Manager): except IndexError: return None + def update(self, alarm_id, **kwargs): + existing = self.get(alarm_id) + updated = existing.to_dict() + for (key, value) in kwargs.items(): + if key in updated and key in UPDATABLE_ATTRIBUTES: + updated[key] = value + return self._update(self._path(alarm_id), updated) + def delete(self, alarm_id): return self._delete(self._path(alarm_id)) diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py index 5f7f129..a69cb1b 100644 --- a/ceilometerclient/v2/shell.py +++ b/ceilometerclient/v2/shell.py @@ -21,6 +21,11 @@ from ceilometerclient import exc from ceilometerclient.v2 import options +ALARM_STATES = ['ok', 'alarm', 'insufficient_data'] +ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] +STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] + + @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') @utils.arg('-m', '--meter', metavar='', @@ -98,6 +103,15 @@ def do_alarm_list(cc, args={}): sortby=0) +def _display_alarm(alarm): + fields = ['name', 'description', 'counter_name', 'period', + 'evaluation_periods', 'threshold', 'comparison_operator', + 'state', 'enabled', 'alarm_id', 'user_id', 'project_id', + 'alarm_actions', 'ok_actions', 'insufficient_data_actions'] + data = dict([(f, getattr(alarm, f, '')) for f in fields]) + utils.print_dict(data, wrap=72) + + @utils.arg('-a', '--alarm_id', metavar='', help='ID of the alarm to show.') def do_alarm_show(cc, args={}): @@ -105,16 +119,51 @@ def do_alarm_show(cc, args={}): if args.alarm_id is None: raise exc.CommandError('Alarm ID not provided (-a )') try: - resource = cc.alarms.get(args.alarm_id) + alarm = cc.alarms.get(args.alarm_id) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) else: - fields = ['name', 'description', 'counter_name', 'period', - 'evaluation_periods', 'threshold', 'comparison_operator', - 'state', 'enabled', 'alarm_id', 'user_id', 'project_id', - 'alarm_actions', 'ok_actions', 'insufficient_data_actions'] - data = dict([(f, getattr(resource, f, '')) for f in fields]) - utils.print_dict(data, wrap=72) + _display_alarm(alarm) + + +@utils.arg('-a', '--alarm_id', metavar='', + help='ID of the alarm to update.') +@utils.arg('--description', metavar='', + help='Free text description of the alarm') +@utils.arg('--period', type=int, metavar='', + help='Length of each period (seconds) to evaluate over') +@utils.arg('--evaluation-periods', type=int, metavar='', + help='Number of periods to evaluate over') +@utils.arg('--state', metavar='', + help='State of the alarm, one of: ' + str(ALARM_STATES)) +@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}', + help='True if alarm evaluation/actioning is enabled') +@utils.arg('--counter-name', metavar='', + help='Metric to evaluate against') +@utils.arg('--statistic', metavar='', + help='Statistic to evaluate, one of: ' + str(STATISTICS)) +@utils.arg('--comparison-operator', metavar='', + help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) +@utils.arg('--threshold', type=float, metavar='', + help='Threshold to evaluate against') +@utils.arg('--alarm-action', dest='alarm_actions', + metavar='', action='append', default=None, + help=('URL to invoke when state transitions to alarm. ' + 'May be used multiple times.')) +@utils.arg('--ok-action', dest='ok_actions', + metavar='', action='append', default=None, + help=('URL to invoke when state transitions to OK. ' + 'May be used multiple times.')) +@utils.arg('--insufficient-data-action', dest='insufficient_data_actions', + metavar='', action='append', default=None, + help=('URL to invoke when state transitions to unkown. ' + 'May be used multiple times.')) +def do_alarm_update(cc, args={}): + '''Update an existing alarm.''' + fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) + fields.pop('alarm_id') + alarm = cc.alarms.update(args.alarm_id, **fields) + _display_alarm(alarm) @utils.arg('-a', '--alarm_id', metavar='', diff --git a/tests/v2/test_alarms.py b/tests/v2/test_alarms.py index 8d68b22..d39bb2c 100644 --- a/tests/v2/test_alarms.py +++ b/tests/v2/test_alarms.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import unittest import ceilometerclient.v2.alarms @@ -41,6 +42,11 @@ AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], u'state_timestamp': u'2013-05-09T13:41:23.085000', u'comparison_operator': 'gt', u'name': 'SwiftObjectAlarm'} +DELTA_ALARM = {u'alarm_actions': ['url1', 'url2'], + u'comparison_operator': u'lt', + u'threshold': 42.1} +UPDATED_ALARM = copy.deepcopy(AN_ALARM) +UPDATED_ALARM.update(DELTA_ALARM) fixtures = { '/v2/alarms': @@ -56,6 +62,10 @@ fixtures = { {}, AN_ALARM, ), + 'PUT': ( + {}, + UPDATED_ALARM, + ), }, '/v2/alarms?q.op=&q.op=&q.value=project-id&q.value=SwiftObjectAlarm' '&q.field=project_id&q.field=name': @@ -116,6 +126,18 @@ class AlarmManagerTest(unittest.TestCase): self.assertTrue(alarm) self.assertEqual(alarm.alarm_id, 'alarm-id') + def test_update(self): + alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) + expect = [ + ('GET', '/v2/alarms/alarm-id', {}, None), + ('PUT', '/v2/alarms/alarm-id', {}, UPDATED_ALARM), + ] + self.assertEqual(self.api.calls, expect) + self.assertTrue(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in DELTA_ALARM.iteritems(): + self.assertEqual(getattr(alarm, key), value) + def test_delete(self): deleted = self.mgr.delete(alarm_id='victim-id') expect = [