diff --git a/ceilometerclient/common/utils.py b/ceilometerclient/common/utils.py index 685a8bb0..aa0511a0 100644 --- a/ceilometerclient/common/utils.py +++ b/ceilometerclient/common/utils.py @@ -140,6 +140,17 @@ def args_array_to_dict(kwargs, key_to_convert): return kwargs +def key_with_slash_to_nested_dict(kwargs): + nested_kwargs = {} + for k in kwargs.keys(): + keys = k.split('/', 1) + if len(keys) == 2: + nested_kwargs.setdefault(keys[0], {})[keys[1]] = kwargs[k] + del kwargs[k] + kwargs.update(nested_kwargs) + return kwargs + + def exit(msg=''): if msg: print >> sys.stderr, msg diff --git a/ceilometerclient/tests/test_utils.py b/ceilometerclient/tests/test_utils.py index 046ebf1b..ab355fba 100644 --- a/ceilometerclient/tests/test_utils.py +++ b/ceilometerclient/tests/test_utils.py @@ -57,3 +57,20 @@ class UtilsTest(test_utils.BaseTestCase): 'matching_metadata': {'metadata.key': 'metadata_value'}, 'other': 'value' }) + + def test_key_with_slash_to_nested_dict(self): + my_args = { + 'combination_rule/alarm_ids': ['id1', 'id2'], + 'combination_rule/operator': 'and', + 'threshold_rule/threshold': 400, + 'threshold_rule/statictic': 'avg', + 'threshold_rule/comparison_operator': 'or', + } + nested_dict = utils.key_with_slash_to_nested_dict(my_args) + self.assertEqual(nested_dict, { + 'combination_rule': {'alarm_ids': ['id1', 'id2'], + 'operator': 'and'}, + 'threshold_rule': {'threshold': 400, + 'statictic': 'avg', + 'comparison_operator': 'or'}, + }) diff --git a/ceilometerclient/tests/v2/test_alarms.py b/ceilometerclient/tests/v2/test_alarms.py index 727dcc08..82279f50 100644 --- a/ceilometerclient/tests/v2/test_alarms.py +++ b/ceilometerclient/tests/v2/test_alarms.py @@ -22,37 +22,91 @@ import testtools from ceilometerclient.tests import utils import ceilometerclient.v2.alarms - AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], u'ok_actions': [u'http://site:8000/ok'], u'description': u'An alarm', - u'matching_metadata': {u'key_name': u'key_value'}, - u'evaluation_periods': 2, + u'type': u'threshold', + u'threshold_rule': { + u'meter_name': u'storage.objects', + u'query': [{u'field': u'key_name', + u'op': u'eq', + u'value': u'key_value'}], + u'evaluation_periods': 2, + u'period': 240.0, + u'statistic': u'avg', + u'threshold': 200.0, + u'comparison_operator': 'gt', + }, u'timestamp': u'2013-05-09T13:41:23.085000', u'enabled': True, - u'meter_name': u'storage.objects', - u'period': 240.0, u'alarm_id': u'alarm-id', u'state': u'ok', u'insufficient_data_actions': [u'http://site:8000/nodata'], - u'statistic': u'avg', - u'threshold': 200.0, u'user_id': u'user-id', u'project_id': u'project-id', u'state_timestamp': u'2013-05-09T13:41:23.085000', - u'comparison_operator': 'gt', u'repeat_actions': False, u'name': 'SwiftObjectAlarm'} CREATE_ALARM = copy.deepcopy(AN_ALARM) del CREATE_ALARM['timestamp'] del CREATE_ALARM['state_timestamp'] del CREATE_ALARM['alarm_id'] -DELTA_ALARM = {u'alarm_actions': ['url1', 'url2'], - u'comparison_operator': u'lt', - u'meter_name': u'foobar', - u'threshold': 42.1} +DELTA_ALARM = {u'alarm_actions': ['url1', 'url2']} +DELTA_ALARM_RULE = {u'comparison_operator': u'lt', + u'threshold': 42.1, + u'meter_name': u'foobar', + u'query': [{u'field': u'key_name', + u'op': u'eq', + u'value': u'key_value'}]} UPDATED_ALARM = copy.deepcopy(AN_ALARM) UPDATED_ALARM.update(DELTA_ALARM) +UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) +UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) +del UPDATE_ALARM['user_id'] +del UPDATE_ALARM['project_id'] +del UPDATE_ALARM['name'] +del UPDATE_ALARM['alarm_id'] +del UPDATE_ALARM['timestamp'] +del UPDATE_ALARM['state_timestamp'] + +AN_LEGACY_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], + u'ok_actions': [u'http://site:8000/ok'], + u'description': u'An alarm', + u'matching_metadata': {u'key_name': u'key_value'}, + u'evaluation_periods': 2, + u'timestamp': u'2013-05-09T13:41:23.085000', + u'enabled': True, + u'meter_name': u'storage.objects', + u'period': 240.0, + u'alarm_id': u'alarm-id', + u'state': u'ok', + u'insufficient_data_actions': [u'http://site:8000/nodata'], + u'statistic': u'avg', + u'threshold': 200.0, + u'user_id': u'user-id', + u'project_id': u'project-id', + u'state_timestamp': u'2013-05-09T13:41:23.085000', + u'comparison_operator': 'gt', + u'repeat_actions': False, + u'name': 'SwiftObjectAlarm'} +CREATE_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) +del CREATE_LEGACY_ALARM['timestamp'] +del CREATE_LEGACY_ALARM['state_timestamp'] +del CREATE_LEGACY_ALARM['alarm_id'] +DELTA_LEGACY_ALARM = {u'alarm_actions': ['url1', 'url2'], + u'comparison_operator': u'lt', + u'meter_name': u'foobar', + u'threshold': 42.1} +UPDATED_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) +UPDATED_LEGACY_ALARM.update(DELTA_LEGACY_ALARM) +UPDATE_LEGACY_ALARM = copy.deepcopy(UPDATED_LEGACY_ALARM) +del UPDATE_LEGACY_ALARM['user_id'] +del UPDATE_LEGACY_ALARM['project_id'] +del UPDATE_LEGACY_ALARM['name'] +del UPDATE_LEGACY_ALARM['alarm_id'] +del UPDATE_LEGACY_ALARM['timestamp'] +del UPDATE_LEGACY_ALARM['state_timestamp'] + fixtures = { '/v2/alarms': @@ -77,6 +131,18 @@ fixtures = { UPDATED_ALARM, ), }, + '/v2/alarms/alarm-id/state': + { + 'PUT': ( + {}, + 'alarm' + ), + 'GET': ( + {}, + 'alarm' + ), + + }, '/v2/alarms?q.op=&q.op=&q.value=project-id&q.value=SwiftObjectAlarm' '&q.field=project_id&q.field=name': { @@ -136,6 +202,7 @@ class AlarmManagerTest(testtools.TestCase): self.assertEqual(self.api.calls, expect) self.assertTrue(alarm) self.assertEqual(alarm.alarm_id, 'alarm-id') + self.assertEqual(alarm.rule, alarm.threshold_rule) def test_create(self): alarm = self.mgr.create(**CREATE_ALARM) @@ -145,10 +212,61 @@ class AlarmManagerTest(testtools.TestCase): self.assertEqual(self.api.calls, expect) self.assertTrue(alarm) - def test_create_legacy(self): + def test_update(self): + alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) + expect = [ + ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), + ] + self.assertEqual(self.api.calls, expect) + self.assertTrue(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in UPDATED_ALARM.iteritems(): + self.assertEqual(getattr(alarm, key), value) + + def test_set_state(self): + state = self.mgr.set_state(alarm_id='alarm-id', state='alarm') + expect = [ + ('PUT', '/v2/alarms/alarm-id/state', {}, 'alarm'), + ] + self.assertEqual(self.api.calls, expect) + self.assertEqual(state, 'alarm') + + def test_get_state(self): + state = self.mgr.get_state(alarm_id='alarm-id') + expect = [ + ('GET', '/v2/alarms/alarm-id/state', {}, None), + ] + self.assertEqual(self.api.calls, expect) + self.assertEqual(state, 'alarm') + + def test_delete(self): + deleted = self.mgr.delete(alarm_id='victim-id') + expect = [ + ('DELETE', '/v2/alarms/victim-id', {}, None), + ] + self.assertEqual(self.api.calls, expect) + self.assertTrue(deleted is None) + + +class AlarmLegacyManagerTest(testtools.TestCase): + + def setUp(self): + super(AlarmLegacyManagerTest, self).setUp() + self.api = utils.FakeAPI(fixtures) + self.mgr = ceilometerclient.v2.alarms.AlarmManager(self.api) + + def test_create(self): + alarm = self.mgr.create(**CREATE_LEGACY_ALARM) + expect = [ + ('POST', '/v2/alarms', {}, CREATE_ALARM), + ] + self.assertEqual(self.api.calls, expect) + self.assertTrue(alarm) + + def test_create_counter_name(self): create = {} - create.update(CREATE_ALARM) - create['counter_name'] = CREATE_ALARM['meter_name'] + create.update(CREATE_LEGACY_ALARM) + create['counter_name'] = CREATE_LEGACY_ALARM['meter_name'] del create['meter_name'] alarm = self.mgr.create(**create) expect = [ @@ -158,35 +276,27 @@ class AlarmManagerTest(testtools.TestCase): self.assertTrue(alarm) def test_update(self): - alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) + alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_LEGACY_ALARM) expect = [ - ('PUT', '/v2/alarms/alarm-id', {}, DELTA_ALARM), + ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), ] self.assertEqual(self.api.calls, expect) self.assertTrue(alarm) self.assertEqual(alarm.alarm_id, 'alarm-id') - for (key, value) in DELTA_ALARM.iteritems(): + for (key, value) in UPDATED_ALARM.iteritems(): self.assertEqual(getattr(alarm, key), value) - def test_update_legacy(self): - delta = {} - delta.update(DELTA_ALARM) - delta['counter_name'] = DELTA_ALARM['meter_name'] - del delta['meter_name'] - alarm = self.mgr.update(alarm_id='alarm-id', **delta) + def test_update_counter_name(self): + updated = {} + updated.update(UPDATE_LEGACY_ALARM) + updated['counter_name'] = UPDATED_LEGACY_ALARM['meter_name'] + del updated['meter_name'] + alarm = self.mgr.update(alarm_id='alarm-id', **updated) expect = [ - ('PUT', '/v2/alarms/alarm-id', {}, DELTA_ALARM), + ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), ] self.assertEqual(self.api.calls, expect) self.assertTrue(alarm) self.assertEqual(alarm.alarm_id, 'alarm-id') - for (key, value) in DELTA_ALARM.iteritems(): + for (key, value) in UPDATED_ALARM.iteritems(): self.assertEqual(getattr(alarm, key), value) - - def test_delete(self): - deleted = self.mgr.delete(alarm_id='victim-id') - expect = [ - ('DELETE', '/v2/alarms/victim-id', {}, None), - ] - self.assertEqual(self.api.calls, expect) - self.assertTrue(deleted is None) diff --git a/ceilometerclient/v2/alarms.py b/ceilometerclient/v2/alarms.py index 97323066..d1ce7c68 100644 --- a/ceilometerclient/v2/alarms.py +++ b/ceilometerclient/v2/alarms.py @@ -23,28 +23,32 @@ from ceilometerclient.v2 import options UPDATABLE_ATTRIBUTES = [ + 'name', 'description', - 'period', - 'evaluation_periods', + 'type', 'state', 'enabled', - 'meter_name', - 'statistic', - 'comparison_operator', - 'threshold', 'alarm_actions', 'ok_actions', 'insufficient_data_actions', 'repeat_actions', - 'matching_metadata', + 'threshold_rule', + 'combination_rule', ] -CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['name', 'project_id', 'user_id'] +CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['project_id', 'user_id'] class Alarm(base.Resource): def __repr__(self): return "" % self._info + def __getattr__(self, k): + # Alias to have the Alarm client object + # that look like the Alarm storage object + if k == 'rule': + k = '%s_rule' % self.type + return super(Alarm, self).__getattr__(k) + class AlarmManager(base.Manager): resource_class = Alarm @@ -62,6 +66,11 @@ class AlarmManager(base.Manager): except IndexError: return None + @classmethod + def _compat_legacy_alarm_kwargs(cls, kwargs): + cls._compat_counter_rename_kwargs(kwargs) + cls._compat_alarm_before_rule_type_kwargs(kwargs) + @staticmethod def _compat_counter_rename_kwargs(kwargs): # NOTE(jd) Compatibility with Havana-2 API @@ -70,17 +79,53 @@ class AlarmManager(base.Manager): DeprecationWarning) kwargs['meter_name'] = kwargs['counter_name'] + @staticmethod + def _compat_alarm_before_rule_type_kwargs(kwargs): + # NOTE(sileht) Compatibility with Havana-3 API + if kwargs.get('type'): + return + warnings.warn("alarm without type set is deprecated", + DeprecationWarning) + + kwargs['type'] = 'threshold' + kwargs['threshold_rule'] = {} + for field in ['period', 'evaluation_periods', 'threshold', + 'statistic', 'comparison_operator', 'meter_name']: + if field in kwargs: + kwargs['threshold_rule'][field] = kwargs[field] + del kwargs[field] + + query = [] + if 'matching_metadata' in kwargs: + for key in kwargs['matching_metadata']: + query.append({'field': key, + 'op': 'eq', + 'value': kwargs['matching_metadata'][key]}) + del kwargs['matching_metadata'] + kwargs['threshold_rule']['query'] = query + def create(self, **kwargs): - self._compat_counter_rename_kwargs(kwargs) + self._compat_legacy_alarm_kwargs(kwargs) new = dict((key, value) for (key, value) in kwargs.items() if key in CREATION_ATTRIBUTES) return self._create(self._path(), new) def update(self, alarm_id, **kwargs): - self._compat_counter_rename_kwargs(kwargs) + self._compat_legacy_alarm_kwargs(kwargs) updated = dict((key, value) for (key, value) in kwargs.items() if key in UPDATABLE_ATTRIBUTES) return self._update(self._path(alarm_id), updated) def delete(self, alarm_id): return self._delete(self._path(alarm_id)) + + def set_state(self, alarm_id, state): + resp, body = self.api.json_request('PUT', + "%s/state" % self._path(alarm_id), + body=state) + return body + + def get_state(self, alarm_id): + resp, body = self.api.json_request('GET', + "%s/state" % self._path(alarm_id)) + return body diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py index c69a59e7..05f7f963 100644 --- a/ceilometerclient/v2/shell.py +++ b/ceilometerclient/v2/shell.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import json from ceilometerclient.common import utils @@ -25,6 +26,7 @@ from ceilometerclient.v2 import options ALARM_STATES = ['ok', 'alarm', 'insufficient_data'] ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] +ALARM_COMBINATION_OPERATORS = ['and', 'or'] STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] @@ -133,23 +135,19 @@ def do_alarm_list(cc, args={}): alarms = cc.alarms.list(q=options.cli_to_array(args.query)) # omit action initially to keep output width sane # (can switch over to vertical formatting when available from CLIFF) - field_labels = ['Name', 'Description', 'Metric', 'Period', 'Count', - 'Threshold', 'Comparison', 'State', 'Enabled', 'Alarm ID', - 'User ID', 'Project ID'] - fields = ['name', 'description', 'meter_name', 'period', - 'evaluation_periods', 'threshold', 'comparison_operator', - 'state', 'enabled', 'alarm_id', 'user_id', 'project_id'] + field_labels = ['Name', 'Description', 'State', 'Enabled', 'Continuous', + 'Alarm ID', 'User ID', 'Project ID'] + fields = ['name', 'description', 'state', 'enabled', 'repeat_actions', + 'alarm_id', 'user_id', 'project_id'] utils.print_list(alarms, fields, field_labels, sortby=0) def _display_alarm(alarm): - fields = ['name', 'description', 'meter_name', 'period', - 'evaluation_periods', 'statistic', 'threshold', - 'comparison_operator', 'state', 'enabled', 'alarm_id', 'user_id', - 'project_id', 'alarm_actions', 'ok_actions', - 'insufficient_data_actions', 'repeat_actions', - 'matching_metadata'] + fields = ['name', 'description', 'type', 'rule', + 'state', 'enabled', 'alarm_id', 'user_id', 'project_id', + 'alarm_actions', 'ok_actions', 'insufficient_data_actions', + 'repeat_actions'] data = dict([(f, getattr(alarm, f, '')) for f in fields]) utils.print_dict(data, wrap=72) @@ -168,96 +166,136 @@ def do_alarm_show(cc, args={}): _display_alarm(alarm) -@utils.arg('--name', metavar='', - help='Name of the alarm (must be unique per tenant)') -@utils.arg('--project-id', metavar='', - help='Tenant to associate with alarm ' - '(only settable by admin users)') -@utils.arg('--user-id', metavar='', - help='User to associate with alarm ' - '(only settable by admin users)') -@utils.arg('--description', metavar='', - help='Free text description of the alarm') +def common_alarm_arguments(func): + @utils.arg('--name', metavar='', required=True, + help='Name of the alarm (must be unique per tenant)') + @utils.arg('--project-id', metavar='', + help='Tenant to associate with alarm ' + '(only settable by admin users)') + @utils.arg('--user-id', metavar='', + help='User to associate with alarm ' + '(only settable by admin users)') + @utils.arg('--description', metavar='', + help='Free text description of the alarm') + @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('--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.')) + @utils.arg('--repeat-actions', dest='repeat_actions', + metavar='{True|False}', type=utils.string_to_bool, + default=False, + help=('True if actions should be repeatedly notified ' + 'while alarm remains in target state')) + @functools.wraps(func) + def _wrapper(*args, **kwargs): + return func(*args, **kwargs) + return _wrapper + + +@common_alarm_arguments @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('--meter-name', metavar='', +@utils.arg('--meter-name', metavar='', required=True, 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='', +@utils.arg('--threshold', type=float, metavar='', required=True, 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.')) -@utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', - type=utils.string_to_bool, default=False, - help=('True if actions should be repeatedly notified ' - 'while alarm remains in target state')) @utils.arg('--matching-metadata', dest='matching_metadata', metavar='', action='append', default=None, help=('A meter should match this resource metadata (key=value) ' 'additionally to the meter_name')) def do_alarm_create(cc, args={}): - '''Create a new alarm.''' + '''Create a new alarm (Deprecated).''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.args_array_to_dict(fields, "matching_metadata") alarm = cc.alarms.create(**fields) _display_alarm(alarm) -@utils.arg('-a', '--alarm_id', metavar='', +@common_alarm_arguments +@utils.arg('--meter-name', metavar='', required=True, + dest='threshold_rule/meter_name', + help='Metric to evaluate against') +@utils.arg('--period', type=int, metavar='', + dest='threshold_rule/period', + help='Length of each period (seconds) to evaluate over') +@utils.arg('--evaluation-periods', type=int, metavar='', + dest='threshold_rule/evaluation_periods', + help='Number of periods to evaluate over') +@utils.arg('--statistic', metavar='', + dest='threshold_rule/statistic', + help='Statistic to evaluate, one of: ' + str(STATISTICS)) +@utils.arg('--comparison-operator', metavar='', + dest='threshold_rule/comparison_operator', + help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) +@utils.arg('--threshold', type=float, metavar='', required=True, + dest='threshold_rule/threshold', + help='Threshold to evaluate against') +@utils.arg('-q', '--query', metavar='', + dest='threshold_rule/query', + help='The query to find the data for computing statistics ' + '(key[op]value; list.)') +def do_alarm_threshold_create(cc, args={}): + '''Create a new alarm based on computed statistics.''' + fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) + fields = utils.key_with_slash_to_nested_dict(fields) + fields['type'] = 'threshold' + if 'query' in fields['threshold_rule']: + fields['threshold_rule']['query'] = options.cli_to_array( + fields['threshold_rule']['query']) + alarm = cc.alarms.create(**fields) + _display_alarm(alarm) + + +@common_alarm_arguments +@utils.arg('--alarm_ids', action='append', metavar='', + required=True, dest='combination_rule/alarm_ids', + help='List of alarm id') +@utils.arg('--operator', metavar='', + dest='combination_rule/operator', + help='Operator to compare with, one of: ' + str( + ALARM_COMBINATION_OPERATORS)) +def do_alarm_combination_create(cc, args={}): + '''Create a new alarm based on state of other alarms.''' + fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) + fields = utils.key_with_slash_to_nested_dict(fields) + fields['type'] = 'combination' + alarm = cc.alarms.create(**fields) + _display_alarm(alarm) + + +@utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to update.') -@utils.arg('--description', metavar='', - help='Free text description of the alarm') +@common_alarm_arguments @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('--meter-name', metavar='', +@utils.arg('--meter-name', metavar='', required=True, 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='', +@utils.arg('--threshold', type=float, metavar='', required=True, 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.')) -@utils.arg('--repeat-actions', dest='repeat_actions', - metavar='{True|False}', type=utils.string_to_bool, - help=('True if actions should be repeatedly notified ' - 'while alarm remains in target state')) @utils.arg('--matching-metadata', dest='matching_metadata', metavar='', action='append', default=None, help=('A meter should match this resource metadata (key=value) ' @@ -267,12 +305,79 @@ def do_alarm_update(cc, args={}): fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.args_array_to_dict(fields, "matching_metadata") fields.pop('alarm_id') - alarm = cc.alarms.update(args.alarm_id, **fields) + try: + alarm = cc.alarms.update(args.alarm_id, **fields) + except exc.HTTPNotFound: + raise exc.CommandError('Alarm not found: %s' % args.alarm_id) _display_alarm(alarm) -@utils.arg('-a', '--alarm_id', metavar='', - help='ID of the alarm to show.') +@utils.arg('-a', '--alarm_id', metavar='', required=True, + help='ID of the alarm to update.') +@common_alarm_arguments +@utils.arg('--meter-name', metavar='', + dest='threshold_rule/meter_name', required=True, + help='Metric to evaluate against') +@utils.arg('--period', type=int, metavar='', + dest='threshold_rule/period', + help='Length of each period (seconds) to evaluate over') +@utils.arg('--evaluation-periods', type=int, metavar='', + dest='threshold_rule/evaluation_periods', + help='Number of periods to evaluate over') +@utils.arg('--statistic', metavar='', + dest='threshold_rule/statistic', + help='Statistic to evaluate, one of: ' + str(STATISTICS)) +@utils.arg('--comparison-operator', metavar='', + dest='threshold_rule/comparison_operator', + help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) +@utils.arg('--threshold', type=float, metavar='', required=True, + dest='threshold_rule/threshold', + help='Threshold to evaluate against') +@utils.arg('-q', '--query', metavar='', + dest='threshold_rule/query', + help='The query to find the data for computing statistics ' + '(key[op]value; list.)') +def do_alarm_threshold_update(cc, args={}): + '''Update an existing alarm based on computed statistics.''' + fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) + fields = utils.key_with_slash_to_nested_dict(fields) + fields.pop('alarm_id') + fields['type'] = 'threshold' + if 'query' in fields['threshold_rule']: + fields['threshold_rule']['query'] = options.cli_to_array( + fields['threshold_rule']['query']) + try: + alarm = cc.alarms.update(args.alarm_id, **fields) + except exc.HTTPNotFound: + raise exc.CommandError('Alarm not found: %s' % args.alarm_id) + _display_alarm(alarm) + + +@utils.arg('-a', '--alarm_id', metavar='', required=True, + help='ID of the alarm to update.') +@common_alarm_arguments +@utils.arg('--alarm_ids', action='append', metavar='', + dest='combination_rule/alarm_ids', required=True, + help='List of alarm id') +@utils.arg('---operator', metavar='', + dest='combination_rule/operator', + help='Operator to compare with, one of: ' + str( + ALARM_COMBINATION_OPERATORS)) +def do_alarm_combination_update(cc, args={}): + '''Update an existing alarm based on state of other alarms.''' + fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) + fields = utils.key_with_slash_to_nested_dict(fields) + fields.pop('alarm_id') + fields['type'] = 'combination' + try: + alarm = cc.alarms.update(args.alarm_id, **fields) + except exc.HTTPNotFound: + raise exc.CommandError('Alarm not found: %s' % args.alarm_id) + _display_alarm(alarm) + + +@utils.arg('-a', '--alarm_id', metavar='', required=True, + help='ID of the alarm to delete.') def do_alarm_delete(cc, args={}): '''Delete an alarm.''' if args.alarm_id is None: @@ -283,6 +388,32 @@ def do_alarm_delete(cc, args={}): raise exc.CommandError('Alarm not found: %s' % args.alarm_id) +@utils.arg('-a', '--alarm_id', metavar='', required=True, + help='ID of the alarm state to set.') +@utils.arg('--state', metavar='', required=True, + help='State of the alarm, one of: ' + str(ALARM_STATES)) +def do_alarm_set_state(cc, args={}): + '''Set the state of an alarm.''' + try: + cc.alarms.set_state(args.alarm_id, args.state) + except exc.HTTPNotFound: + raise exc.CommandError('Alarm not found: %s' % args.alarm_id) + state = cc.alarms.get_state(args.alarm_id) + utils.print_dict({'state': state}, wrap=72) + + +@utils.arg('-a', '--alarm_id', metavar='', required=True, + help='ID of the alarm state to show.') +def do_alarm_get_state(cc, args={}): + '''Get the state of an alarm.''' + try: + cc.alarms.set_state(args.alarm_id, args.state) + except exc.HTTPNotFound: + raise exc.CommandError('Alarm not found: %s' % args.alarm_id) + state = cc.alarms.get_state(args.alarm_id) + utils.print_dict({'state': state}, wrap=72) + + @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') def do_resource_list(cc, args={}):