Merge tag '1.0.6' into debian/unstable
python-ceilometerclient 1.0.6
Includes the following changes:
     * Add support for new alarm-history command
     * Use standard CLI object-verb ordering for alarm-{g|s}set-state
     * Fix shell.do_alarm_get_state to get as opposed to set
     * Updated from global requirements
     * Allow to update an alarm partially
     * Added support to --os-cacert
     * Help messages: specify which options are required
     * Improve the CM shell client alarm visualisation
			
			
This commit is contained in:
		| @@ -21,6 +21,7 @@ def _get_ksclient(**kwargs): | |||||||
|             * username: name of user |             * username: name of user | ||||||
|             * password: user's password |             * password: user's password | ||||||
|             * auth_url: endpoint to authenticate against |             * auth_url: endpoint to authenticate against | ||||||
|  |             * cacert: path of CA TLS certificate | ||||||
|             * insecure: allow insecure SSL (no cert verification) |             * insecure: allow insecure SSL (no cert verification) | ||||||
|             * tenant_{name|id}: name or ID of tenant |             * tenant_{name|id}: name or ID of tenant | ||||||
|     """ |     """ | ||||||
| @@ -30,6 +31,7 @@ def _get_ksclient(**kwargs): | |||||||
|                            tenant_name=kwargs.get('tenant_name'), |                            tenant_name=kwargs.get('tenant_name'), | ||||||
|                            auth_url=kwargs.get('auth_url'), |                            auth_url=kwargs.get('auth_url'), | ||||||
|                            region_name=kwargs.get('region_name'), |                            region_name=kwargs.get('region_name'), | ||||||
|  |                            cacert=kwargs.get('cacert'), | ||||||
|                            insecure=kwargs.get('insecure')) |                            insecure=kwargs.get('insecure')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -52,6 +54,7 @@ def get_client(api_version, **kwargs): | |||||||
|             * os_username: name of user |             * os_username: name of user | ||||||
|             * os_password: user's password |             * os_password: user's password | ||||||
|             * os_auth_url: endpoint to authenticate against |             * os_auth_url: endpoint to authenticate against | ||||||
|  |             * os_cacert: path of CA TLS certificate | ||||||
|             * insecure: allow insecure SSL (no cert verification) |             * insecure: allow insecure SSL (no cert verification) | ||||||
|             * os_tenant_{name|id}: name or ID of tenant |             * os_tenant_{name|id}: name or ID of tenant | ||||||
|     """ |     """ | ||||||
| @@ -72,6 +75,7 @@ def get_client(api_version, **kwargs): | |||||||
|             'region_name': kwargs.get('os_region_name'), |             'region_name': kwargs.get('os_region_name'), | ||||||
|             'service_type': kwargs.get('os_service_type'), |             'service_type': kwargs.get('os_service_type'), | ||||||
|             'endpoint_type': kwargs.get('os_endpoint_type'), |             'endpoint_type': kwargs.get('os_endpoint_type'), | ||||||
|  |             'cacert': kwargs.get('os_cacert'), | ||||||
|             'insecure': kwargs.get('insecure'), |             'insecure': kwargs.get('insecure'), | ||||||
|         } |         } | ||||||
|         _ksclient = _get_ksclient(**ks_kwargs) |         _ksclient = _get_ksclient(**ks_kwargs) | ||||||
| @@ -86,7 +90,7 @@ def get_client(api_version, **kwargs): | |||||||
|         'token': token, |         'token': token, | ||||||
|         'insecure': kwargs.get('insecure'), |         'insecure': kwargs.get('insecure'), | ||||||
|         'timeout': kwargs.get('timeout'), |         'timeout': kwargs.get('timeout'), | ||||||
|         'ca_file': kwargs.get('ca_file'), |         'cacert': kwargs.get('cacert'), | ||||||
|         'cert_file': kwargs.get('cert_file'), |         'cert_file': kwargs.get('cert_file'), | ||||||
|         'key_file': kwargs.get('key_file'), |         'key_file': kwargs.get('key_file'), | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ class HTTPClient(object): | |||||||
|  |  | ||||||
|         if parts.scheme == 'https': |         if parts.scheme == 'https': | ||||||
|             _class = VerifiedHTTPSConnection |             _class = VerifiedHTTPSConnection | ||||||
|             _kwargs['ca_file'] = kwargs.get('ca_file', None) |             _kwargs['ca_cert'] = kwargs.get('cacert', None) | ||||||
|             _kwargs['cert_file'] = kwargs.get('cert_file', None) |             _kwargs['cert_file'] = kwargs.get('cert_file', None) | ||||||
|             _kwargs['key_file'] = kwargs.get('key_file', None) |             _kwargs['key_file'] = kwargs.get('key_file', None) | ||||||
|             _kwargs['insecure'] = kwargs.get('insecure', False) |             _kwargs['insecure'] = kwargs.get('insecure', False) | ||||||
| @@ -93,7 +93,7 @@ class HTTPClient(object): | |||||||
|         conn_params_fmt = [ |         conn_params_fmt = [ | ||||||
|             ('key_file', '--key %s'), |             ('key_file', '--key %s'), | ||||||
|             ('cert_file', '--cert %s'), |             ('cert_file', '--cert %s'), | ||||||
|             ('ca_file', '--cacert %s'), |             ('cacert', '--cacert %s'), | ||||||
|         ] |         ] | ||||||
|         for (key, fmt) in conn_params_fmt: |         for (key, fmt) in conn_params_fmt: | ||||||
|             value = self.connection_params[2].get(key) |             value = self.connection_params[2].get(key) | ||||||
| @@ -215,21 +215,21 @@ class VerifiedHTTPSConnection(httplib.HTTPSConnection): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, host, port, key_file=None, cert_file=None, |     def __init__(self, host, port, key_file=None, cert_file=None, | ||||||
|                  ca_file=None, timeout=None, insecure=False): |                  cacert=None, timeout=None, insecure=False): | ||||||
|         httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file, |         httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file, | ||||||
|                                          cert_file=cert_file) |                                          cert_file=cert_file) | ||||||
|         self.key_file = key_file |         self.key_file = key_file | ||||||
|         self.cert_file = cert_file |         self.cert_file = cert_file | ||||||
|         if ca_file is not None: |         if cacert is not None: | ||||||
|             self.ca_file = ca_file |             self.cacert = cacert | ||||||
|         else: |         else: | ||||||
|             self.ca_file = self.get_system_ca_file() |             self.cacert = self.get_system_ca_file() | ||||||
|         self.timeout = timeout |         self.timeout = timeout | ||||||
|         self.insecure = insecure |         self.insecure = insecure | ||||||
|  |  | ||||||
|     def connect(self): |     def connect(self): | ||||||
|         """Connect to a host on a given (SSL) port. |         """Connect to a host on a given (SSL) port. | ||||||
|         If ca_file is pointing somewhere, use it to check Server Certificate. |         If cacert is pointing somewhere, use it to check Server Certificate. | ||||||
|  |  | ||||||
|         Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). |         Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). | ||||||
|         This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to |         This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to | ||||||
| @@ -245,7 +245,7 @@ class VerifiedHTTPSConnection(httplib.HTTPSConnection): | |||||||
|         if self.insecure is True: |         if self.insecure is True: | ||||||
|             kwargs = {'cert_reqs': ssl.CERT_NONE} |             kwargs = {'cert_reqs': ssl.CERT_NONE} | ||||||
|         else: |         else: | ||||||
|             kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file} |             kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.cacert} | ||||||
|  |  | ||||||
|         if self.cert_file: |         if self.cert_file: | ||||||
|             kwargs['certfile'] = self.cert_file |             kwargs['certfile'] = self.cert_file | ||||||
|   | |||||||
| @@ -27,6 +27,13 @@ from ceilometerclient.openstack.common import importutils | |||||||
| # Decorator for cli-args | # Decorator for cli-args | ||||||
| def arg(*args, **kwargs): | def arg(*args, **kwargs): | ||||||
|     def _decorator(func): |     def _decorator(func): | ||||||
|  |         if 'help' in kwargs: | ||||||
|  |             if 'default' in kwargs: | ||||||
|  |                 kwargs['help'] += " Defaults to %s." % kwargs['default'] | ||||||
|  |             required = kwargs.get('required', False) | ||||||
|  |             if required: | ||||||
|  |                 kwargs['help'] += " Required." | ||||||
|  |  | ||||||
|         # Because of the sematics of decorator composition if we just append |         # Because of the sematics of decorator composition if we just append | ||||||
|         # to the options list positional options will appear to be backwards. |         # to the options list positional options will appear to be backwards. | ||||||
|         func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) |         func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) | ||||||
| @@ -63,17 +70,19 @@ def print_dict(d, dict_property="Property", wrap=0): | |||||||
|         # convert dict to str to check length |         # convert dict to str to check length | ||||||
|         if isinstance(v, dict): |         if isinstance(v, dict): | ||||||
|             v = str(v) |             v = str(v) | ||||||
|         if wrap > 0: |  | ||||||
|             v = textwrap.fill(str(v), wrap) |  | ||||||
|         # if value has a newline, add in multiple rows |         # if value has a newline, add in multiple rows | ||||||
|         # e.g. fault with stacktrace |         # e.g. fault with stacktrace | ||||||
|         if v and isinstance(v, basestring) and r'\n' in v: |         if v and isinstance(v, basestring) and r'\n' in v: | ||||||
|             lines = v.strip().split(r'\n') |             lines = v.strip().split(r'\n') | ||||||
|             col1 = k |             col1 = k | ||||||
|             for line in lines: |             for line in lines: | ||||||
|  |                 if wrap > 0: | ||||||
|  |                     line = textwrap.fill(str(line), wrap) | ||||||
|                 pt.add_row([col1, line]) |                 pt.add_row([col1, line]) | ||||||
|                 col1 = '' |                 col1 = '' | ||||||
|         else: |         else: | ||||||
|  |             if wrap > 0: | ||||||
|  |                 v = textwrap.fill(str(v), wrap) | ||||||
|             pt.add_row([k, v]) |             pt.add_row([k, v]) | ||||||
|     print pt.get_string() |     print pt.get_string() | ||||||
|  |  | ||||||
| @@ -151,6 +160,15 @@ def key_with_slash_to_nested_dict(kwargs): | |||||||
|     return kwargs |     return kwargs | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def merge_nested_dict(dest, source, depth=0): | ||||||
|  |     for (key, value) in source.iteritems(): | ||||||
|  |         if isinstance(value, dict) and depth: | ||||||
|  |             merge_nested_dict(dest[key], value, | ||||||
|  |                               depth=(depth - 1)) | ||||||
|  |         else: | ||||||
|  |             dest[key] = value | ||||||
|  |  | ||||||
|  |  | ||||||
| def exit(msg=''): | def exit(msg=''): | ||||||
|     if msg: |     if msg: | ||||||
|         print >> sys.stderr, msg |         print >> sys.stderr, msg | ||||||
|   | |||||||
| @@ -76,11 +76,17 @@ class CeilometerShell(object): | |||||||
|                             ' This option is not necessary if your key is ' |                             ' This option is not necessary if your key is ' | ||||||
|                             'prepended to your cert file.') |                             'prepended to your cert file.') | ||||||
|  |  | ||||||
|         parser.add_argument('--ca-file', |         parser.add_argument('--os-cacert', | ||||||
|                             help='Path of CA SSL certificate(s) used to verify' |                             metavar='<ca-certificate-file>', | ||||||
|                             ' the remote server certificate. Without this ' |                             dest='os_cacert', | ||||||
|  |                             default=utils.env('OS_CACERT'), | ||||||
|  |                             help='Path of CA TLS certificate(s) used to verify' | ||||||
|  |                             'the remote server\'s certificate. Without this ' | ||||||
|                             'option ceilometer looks for the default system ' |                             'option ceilometer looks for the default system ' | ||||||
|                             'CA certificates.') |                             'CA certificates.') | ||||||
|  |         parser.add_argument('--ca-file', | ||||||
|  |                             dest='os_cacert', | ||||||
|  |                             help='DEPRECATED! Use --os-cacert.') | ||||||
|  |  | ||||||
|         parser.add_argument('--timeout', |         parser.add_argument('--timeout', | ||||||
|                             default=600, |                             default=600, | ||||||
|   | |||||||
| @@ -74,3 +74,53 @@ class UtilsTest(test_utils.BaseTestCase): | |||||||
|                                'statictic': 'avg', |                                'statictic': 'avg', | ||||||
|                                'comparison_operator': 'or'}, |                                'comparison_operator': 'or'}, | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  |     def test_arg(self): | ||||||
|  |         @utils.arg(help="not_required_no_default.") | ||||||
|  |         def not_required_no_default(): | ||||||
|  |             pass | ||||||
|  |         _, args = not_required_no_default.__dict__['arguments'][0] | ||||||
|  |         self.assertEqual(args['help'], "not_required_no_default.") | ||||||
|  |  | ||||||
|  |         @utils.arg(required=True, help="required_no_default.") | ||||||
|  |         def required_no_default(): | ||||||
|  |             pass | ||||||
|  |         _, args = required_no_default.__dict__['arguments'][0] | ||||||
|  |         self.assertEqual(args['help'], "required_no_default. Required.") | ||||||
|  |  | ||||||
|  |         @utils.arg(default=42, help="not_required_default.") | ||||||
|  |         def not_required_default(): | ||||||
|  |             pass | ||||||
|  |         _, args = not_required_default.__dict__['arguments'][0] | ||||||
|  |         self.assertEqual(args['help'], "not_required_default. Defaults to 42.") | ||||||
|  |  | ||||||
|  |     def test_merge_nested_dict(self): | ||||||
|  |         dest = {'key': 'value', | ||||||
|  |                 'nested': {'key2': 'value2', | ||||||
|  |                            'key3': 'value3', | ||||||
|  |                            'nested2': {'key': 'value', | ||||||
|  |                                        'some': 'thing'}}} | ||||||
|  |         source = {'key': 'modified', | ||||||
|  |                   'nested': {'key3': 'modified3', | ||||||
|  |                              'nested2': {'key5': 'value5'}}} | ||||||
|  |         utils.merge_nested_dict(dest, source, depth=1) | ||||||
|  |  | ||||||
|  |         self.assertEqual(dest, {'key': 'modified', | ||||||
|  |                                 'nested': {'key2': 'value2', | ||||||
|  |                                            'key3': 'modified3', | ||||||
|  |                                            'nested2': {'key5': 'value5'}}}) | ||||||
|  |  | ||||||
|  |     def test_merge_nested_dict_no_depth(self): | ||||||
|  |         dest = {'key': 'value', | ||||||
|  |                 'nested': {'key2': 'value2', | ||||||
|  |                            'key3': 'value3', | ||||||
|  |                            'nested2': {'key': 'value', | ||||||
|  |                                        'some': 'thing'}}} | ||||||
|  |         source = {'key': 'modified', | ||||||
|  |                   'nested': {'key3': 'modified3', | ||||||
|  |                              'nested2': {'key5': 'value5'}}} | ||||||
|  |         utils.merge_nested_dict(dest, source) | ||||||
|  |  | ||||||
|  |         self.assertEqual(dest, {'key': 'modified', | ||||||
|  |                                 'nested': {'key3': 'modified3', | ||||||
|  |                                            'nested2': {'key5': 'value5'}}}) | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ import copy | |||||||
| import testtools | import testtools | ||||||
|  |  | ||||||
| from ceilometerclient.tests import utils | from ceilometerclient.tests import utils | ||||||
| import ceilometerclient.v2.alarms | from ceilometerclient.v2 import alarms | ||||||
|  |  | ||||||
| AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], | AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], | ||||||
|             u'ok_actions': [u'http://site:8000/ok'], |             u'ok_actions': [u'http://site:8000/ok'], | ||||||
| @@ -61,6 +61,7 @@ DELTA_ALARM_RULE = {u'comparison_operator': u'lt', | |||||||
| UPDATED_ALARM = copy.deepcopy(AN_ALARM) | UPDATED_ALARM = copy.deepcopy(AN_ALARM) | ||||||
| UPDATED_ALARM.update(DELTA_ALARM) | UPDATED_ALARM.update(DELTA_ALARM) | ||||||
| UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) | UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) | ||||||
|  | DELTA_ALARM['threshold_rule'] = DELTA_ALARM_RULE | ||||||
| UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) | UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) | ||||||
| del UPDATE_ALARM['user_id'] | del UPDATE_ALARM['user_id'] | ||||||
| del UPDATE_ALARM['project_id'] | del UPDATE_ALARM['project_id'] | ||||||
| @@ -107,6 +108,47 @@ del UPDATE_LEGACY_ALARM['alarm_id'] | |||||||
| del UPDATE_LEGACY_ALARM['timestamp'] | del UPDATE_LEGACY_ALARM['timestamp'] | ||||||
| del UPDATE_LEGACY_ALARM['state_timestamp'] | del UPDATE_LEGACY_ALARM['state_timestamp'] | ||||||
|  |  | ||||||
|  | FULL_DETAIL = ('{"alarm_actions": [], ' | ||||||
|  |                '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' | ||||||
|  |                '"name": "scombo", ' | ||||||
|  |                '"timestamp": "2013-10-03T08:58:33.647912", ' | ||||||
|  |                '"enabled": true, ' | ||||||
|  |                '"state_timestamp": "2013-10-03T08:58:33.647912", ' | ||||||
|  |                '"rule": {"operator": "or", "alarm_ids": ' | ||||||
|  |                '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' | ||||||
|  |                '"alarm_id": "alarm-id, ' | ||||||
|  |                '"state": "insufficient data", ' | ||||||
|  |                '"insufficient_data_actions": [], ' | ||||||
|  |                '"repeat_actions": false, ' | ||||||
|  |                '"ok_actions": [], ' | ||||||
|  |                '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' | ||||||
|  |                '"type": "combination", ' | ||||||
|  |                '"description": "Combined state of alarms ' | ||||||
|  |                '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') | ||||||
|  | ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                   'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', | ||||||
|  |                   'timestamp': '2013-10-03T08:59:28.326000', | ||||||
|  |                   'detail': '{"state": "alarm"}', | ||||||
|  |                   'alarm_id': 'alarm-id', | ||||||
|  |                   'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'type': 'state transition'}, | ||||||
|  |                  {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                   'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', | ||||||
|  |                   'timestamp': '2013-10-03T08:59:28.326000', | ||||||
|  |                   'detail': '{"description": "combination of one"}', | ||||||
|  |                   'alarm_id': 'alarm-id', | ||||||
|  |                   'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'type': 'rule change'}, | ||||||
|  |                  {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                   'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', | ||||||
|  |                   'timestamp': '2013-10-03T08:58:33.647000', | ||||||
|  |                   'detail': FULL_DETAIL, | ||||||
|  |                   'alarm_id': 'alarm-id', | ||||||
|  |                   'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                   'type': 'creation'}] | ||||||
|  |  | ||||||
| fixtures = { | fixtures = { | ||||||
|     '/v2/alarms': |     '/v2/alarms': | ||||||
| @@ -158,6 +200,20 @@ fixtures = { | |||||||
|             None, |             None, | ||||||
|         ), |         ), | ||||||
|     }, |     }, | ||||||
|  |     '/v2/alarms/alarm-id/history': | ||||||
|  |     { | ||||||
|  |         'GET': ( | ||||||
|  |             {}, | ||||||
|  |             ALARM_HISTORY, | ||||||
|  |         ), | ||||||
|  |     }, | ||||||
|  |     '/v2/alarms/alarm-id/history?q.op=&q.value=NOW&q.field=timestamp': | ||||||
|  |     { | ||||||
|  |         'GET': ( | ||||||
|  |             {}, | ||||||
|  |             ALARM_HISTORY, | ||||||
|  |         ), | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -166,7 +222,7 @@ class AlarmManagerTest(testtools.TestCase): | |||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super(AlarmManagerTest, self).setUp() |         super(AlarmManagerTest, self).setUp() | ||||||
|         self.api = utils.FakeAPI(fixtures) |         self.api = utils.FakeAPI(fixtures) | ||||||
|         self.mgr = ceilometerclient.v2.alarms.AlarmManager(self.api) |         self.mgr = alarms.AlarmManager(self.api) | ||||||
|  |  | ||||||
|     def test_list_all(self): |     def test_list_all(self): | ||||||
|         alarms = list(self.mgr.list()) |         alarms = list(self.mgr.list()) | ||||||
| @@ -215,7 +271,20 @@ class AlarmManagerTest(testtools.TestCase): | |||||||
|     def test_update(self): |     def test_update(self): | ||||||
|         alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) |         alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) | ||||||
|         expect = [ |         expect = [ | ||||||
|             ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), |             ('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 UPDATED_ALARM.iteritems(): | ||||||
|  |             self.assertEqual(getattr(alarm, key), value) | ||||||
|  |  | ||||||
|  |     def test_update_delta(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.assertEqual(self.api.calls, expect) | ||||||
|         self.assertTrue(alarm) |         self.assertTrue(alarm) | ||||||
| @@ -247,13 +316,33 @@ class AlarmManagerTest(testtools.TestCase): | |||||||
|         self.assertEqual(self.api.calls, expect) |         self.assertEqual(self.api.calls, expect) | ||||||
|         self.assertTrue(deleted is None) |         self.assertTrue(deleted is None) | ||||||
|  |  | ||||||
|  |     def _do_test_get_history(self, q, url): | ||||||
|  |         history = self.mgr.get_history(q=q, alarm_id='alarm-id') | ||||||
|  |         expect = [('GET', url, {}, None)] | ||||||
|  |         self.assertEqual(self.api.calls, expect) | ||||||
|  |         for i in xrange(len(history)): | ||||||
|  |             change = history[i] | ||||||
|  |             self.assertTrue(isinstance(change, alarms.AlarmChange)) | ||||||
|  |             for k, v in ALARM_HISTORY[i].iteritems(): | ||||||
|  |                 self.assertEqual(getattr(change, k), v) | ||||||
|  |  | ||||||
|  |     def test_get_all_history(self): | ||||||
|  |         url = '/v2/alarms/alarm-id/history' | ||||||
|  |         self._do_test_get_history(None, url) | ||||||
|  |  | ||||||
|  |     def test_get_constrained_history(self): | ||||||
|  |         q = [dict(field='timestamp', value='NOW')] | ||||||
|  |         url = ('/v2/alarms/alarm-id/history' | ||||||
|  |                '?q.op=&q.value=NOW&q.field=timestamp') | ||||||
|  |         self._do_test_get_history(q, url) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AlarmLegacyManagerTest(testtools.TestCase): | class AlarmLegacyManagerTest(testtools.TestCase): | ||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super(AlarmLegacyManagerTest, self).setUp() |         super(AlarmLegacyManagerTest, self).setUp() | ||||||
|         self.api = utils.FakeAPI(fixtures) |         self.api = utils.FakeAPI(fixtures) | ||||||
|         self.mgr = ceilometerclient.v2.alarms.AlarmManager(self.api) |         self.mgr = alarms.AlarmManager(self.api) | ||||||
|  |  | ||||||
|     def test_create(self): |     def test_create(self): | ||||||
|         alarm = self.mgr.create(**CREATE_LEGACY_ALARM) |         alarm = self.mgr.create(**CREATE_LEGACY_ALARM) | ||||||
| @@ -276,9 +365,10 @@ class AlarmLegacyManagerTest(testtools.TestCase): | |||||||
|         self.assertTrue(alarm) |         self.assertTrue(alarm) | ||||||
|  |  | ||||||
|     def test_update(self): |     def test_update(self): | ||||||
|         alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_LEGACY_ALARM) |         alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_LEGACY_ALARM) | ||||||
|         expect = [ |         expect = [ | ||||||
|             ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), |             ('GET', '/v2/alarms/alarm-id', {}, None), | ||||||
|  |             ('PUT', '/v2/alarms/alarm-id', {}, UPDATED_ALARM), | ||||||
|         ] |         ] | ||||||
|         self.assertEqual(self.api.calls, expect) |         self.assertEqual(self.api.calls, expect) | ||||||
|         self.assertTrue(alarm) |         self.assertTrue(alarm) | ||||||
| @@ -293,7 +383,8 @@ class AlarmLegacyManagerTest(testtools.TestCase): | |||||||
|         del updated['meter_name'] |         del updated['meter_name'] | ||||||
|         alarm = self.mgr.update(alarm_id='alarm-id', **updated) |         alarm = self.mgr.update(alarm_id='alarm-id', **updated) | ||||||
|         expect = [ |         expect = [ | ||||||
|             ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM), |             ('GET', '/v2/alarms/alarm-id', {}, None), | ||||||
|  |             ('PUT', '/v2/alarms/alarm-id', {}, UPDATED_ALARM), | ||||||
|         ] |         ] | ||||||
|         self.assertEqual(self.api.calls, expect) |         self.assertEqual(self.api.calls, expect) | ||||||
|         self.assertTrue(alarm) |         self.assertTrue(alarm) | ||||||
|   | |||||||
							
								
								
									
										137
									
								
								ceilometerclient/tests/v2/test_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								ceilometerclient/tests/v2/test_shell.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | #   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 cStringIO | ||||||
|  | import mock | ||||||
|  | import re | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from testtools import matchers | ||||||
|  |  | ||||||
|  | from ceilometerclient.tests import utils | ||||||
|  | from ceilometerclient.v2 import alarms | ||||||
|  | from ceilometerclient.v2 import shell as ceilometer_shell | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ShellAlarmStateCommandsTest(utils.BaseTestCase): | ||||||
|  |  | ||||||
|  |     ALARM_ID = 'foobar' | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super(ShellAlarmStateCommandsTest, self).setUp() | ||||||
|  |         self.cc = mock.Mock() | ||||||
|  |         self.cc.alarms = mock.Mock() | ||||||
|  |         self.args = mock.Mock() | ||||||
|  |         self.args.alarm_id = self.ALARM_ID | ||||||
|  |  | ||||||
|  |     def test_alarm_state_get(self): | ||||||
|  |         ceilometer_shell.do_alarm_state_get(self.cc, self.args) | ||||||
|  |         self.cc.alarms.get_state.assert_called_once_with(self.ALARM_ID) | ||||||
|  |         self.assertFalse(self.cc.alarms.set_state.called) | ||||||
|  |  | ||||||
|  |     def test_alarm_state_set(self): | ||||||
|  |         self.args.state = 'ok' | ||||||
|  |         ceilometer_shell.do_alarm_state_set(self.cc, self.args) | ||||||
|  |         self.cc.alarms.set_state.assert_called_once_with(self.ALARM_ID, 'ok') | ||||||
|  |         self.assertFalse(self.cc.alarms.get_state.called) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ShellAlarmHistoryCommandTest(utils.BaseTestCase): | ||||||
|  |  | ||||||
|  |     ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' | ||||||
|  |     FULL_DETAIL = ('{"alarm_actions": [], ' | ||||||
|  |                    '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' | ||||||
|  |                    '"name": "scombo", ' | ||||||
|  |                    '"timestamp": "2013-10-03T08:58:33.647912", ' | ||||||
|  |                    '"enabled": true, ' | ||||||
|  |                    '"state_timestamp": "2013-10-03T08:58:33.647912", ' | ||||||
|  |                    '"rule": {"operator": "or", "alarm_ids": ' | ||||||
|  |                    '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' | ||||||
|  |                    '"alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", ' | ||||||
|  |                    '"state": "insufficient data", ' | ||||||
|  |                    '"insufficient_data_actions": [], ' | ||||||
|  |                    '"repeat_actions": false, ' | ||||||
|  |                    '"ok_actions": [], ' | ||||||
|  |                    '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' | ||||||
|  |                    '"type": "combination", ' | ||||||
|  |                    '"description": "Combined state of alarms ' | ||||||
|  |                    '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') | ||||||
|  |     ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                       'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', | ||||||
|  |                       'timestamp': '2013-10-03T08:59:28.326000', | ||||||
|  |                       'detail': '{"state": "alarm"}', | ||||||
|  |                       'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', | ||||||
|  |                       'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'type': 'state transition'}, | ||||||
|  |                      {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                       'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', | ||||||
|  |                       'timestamp': '2013-10-03T08:59:28.326000', | ||||||
|  |                       'detail': '{"description": "combination of one"}', | ||||||
|  |                       'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', | ||||||
|  |                       'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'type': 'rule change'}, | ||||||
|  |                      {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'user_id': '8185aa72421a4fd396d4122cba50e1b5', | ||||||
|  |                       'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', | ||||||
|  |                       'timestamp': '2013-10-03T08:58:33.647000', | ||||||
|  |                       'detail': FULL_DETAIL, | ||||||
|  |                       'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', | ||||||
|  |                       'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', | ||||||
|  |                       'type': 'creation'}] | ||||||
|  |     TIMESTAMP_RE = (' +\| (\d{4})-(\d{2})-(\d{2})T' | ||||||
|  |                     '(\d{2})\:(\d{2})\:(\d{2})\.(\d{6}) \| +') | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super(ShellAlarmHistoryCommandTest, self).setUp() | ||||||
|  |         self.cc = mock.Mock() | ||||||
|  |         self.cc.alarms = mock.Mock() | ||||||
|  |         self.args = mock.Mock() | ||||||
|  |         self.args.alarm_id = self.ALARM_ID | ||||||
|  |  | ||||||
|  |     def _do_test_alarm_history(self, raw_query=None, parsed_query=None): | ||||||
|  |         self.args.query = raw_query | ||||||
|  |         orig = sys.stdout | ||||||
|  |         sys.stdout = cStringIO.StringIO() | ||||||
|  |         history = [alarms.AlarmChange(mock.Mock(), change) | ||||||
|  |                    for change in self.ALARM_HISTORY] | ||||||
|  |         self.cc.alarms.get_history.return_value = history | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             ceilometer_shell.do_alarm_history(self.cc, self.args) | ||||||
|  |             self.cc.alarms.get_history.assert_called_once_with( | ||||||
|  |                 q=parsed_query, | ||||||
|  |                 alarm_id=self.ALARM_ID | ||||||
|  |             ) | ||||||
|  |             out = sys.stdout.getvalue() | ||||||
|  |             required = [ | ||||||
|  |                 '.*creation%sname: scombo.*' % self.TIMESTAMP_RE, | ||||||
|  |                 '.*rule change%sdescription: combination of one.*' % | ||||||
|  |                 self.TIMESTAMP_RE, | ||||||
|  |                 '.*state transition%sstate: alarm.*' % self.TIMESTAMP_RE, | ||||||
|  |             ] | ||||||
|  |             for r in required: | ||||||
|  |                 self.assertThat(out, matchers.MatchesRegex(r, re.DOTALL)) | ||||||
|  |         finally: | ||||||
|  |             sys.stdout.close() | ||||||
|  |             sys.stdout = orig | ||||||
|  |  | ||||||
|  |     def test_alarm_all_history(self): | ||||||
|  |         self._do_test_alarm_history() | ||||||
|  |  | ||||||
|  |     def test_alarm_constrained_history(self): | ||||||
|  |         parsed_query = [dict(field='timestamp', | ||||||
|  |                              value='2013-10-03T08:59:28', | ||||||
|  |                              op='gt')] | ||||||
|  |         self._do_test_alarm_history(raw_query='timestamp>2013-10-03T08:59:28', | ||||||
|  |                                     parsed_query=parsed_query) | ||||||
| @@ -19,6 +19,7 @@ | |||||||
| import warnings | import warnings | ||||||
|  |  | ||||||
| from ceilometerclient.common import base | from ceilometerclient.common import base | ||||||
|  | from ceilometerclient.common import utils | ||||||
| from ceilometerclient.v2 import options | from ceilometerclient.v2 import options | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -34,7 +35,7 @@ UPDATABLE_ATTRIBUTES = [ | |||||||
|     'repeat_actions', |     'repeat_actions', | ||||||
|     'threshold_rule', |     'threshold_rule', | ||||||
|     'combination_rule', |     'combination_rule', | ||||||
|     ] | ] | ||||||
| CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['project_id', 'user_id'] | CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['project_id', 'user_id'] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -50,6 +51,14 @@ class Alarm(base.Resource): | |||||||
|         return super(Alarm, self).__getattr__(k) |         return super(Alarm, self).__getattr__(k) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AlarmChange(base.Resource): | ||||||
|  |     def __repr__(self): | ||||||
|  |         return "<AlarmChange %s>" % self._info | ||||||
|  |  | ||||||
|  |     def __getattr__(self, k): | ||||||
|  |         return super(AlarmChange, self).__getattr__(k) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AlarmManager(base.Manager): | class AlarmManager(base.Manager): | ||||||
|     resource_class = Alarm |     resource_class = Alarm | ||||||
|  |  | ||||||
| @@ -67,12 +76,12 @@ class AlarmManager(base.Manager): | |||||||
|             return None |             return None | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _compat_legacy_alarm_kwargs(cls, kwargs): |     def _compat_legacy_alarm_kwargs(cls, kwargs, create=False): | ||||||
|         cls._compat_counter_rename_kwargs(kwargs) |         cls._compat_counter_rename_kwargs(kwargs, create) | ||||||
|         cls._compat_alarm_before_rule_type_kwargs(kwargs) |         cls._compat_alarm_before_rule_type_kwargs(kwargs, create) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _compat_counter_rename_kwargs(kwargs): |     def _compat_counter_rename_kwargs(kwargs, create=False): | ||||||
|         # NOTE(jd) Compatibility with Havana-2 API |         # NOTE(jd) Compatibility with Havana-2 API | ||||||
|         if 'counter_name' in kwargs: |         if 'counter_name' in kwargs: | ||||||
|             warnings.warn("counter_name has been renamed to meter_name", |             warnings.warn("counter_name has been renamed to meter_name", | ||||||
| @@ -80,40 +89,40 @@ class AlarmManager(base.Manager): | |||||||
|             kwargs['meter_name'] = kwargs['counter_name'] |             kwargs['meter_name'] = kwargs['counter_name'] | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _compat_alarm_before_rule_type_kwargs(kwargs): |     def _compat_alarm_before_rule_type_kwargs(kwargs, create=False): | ||||||
|         # NOTE(sileht) Compatibility with Havana-3 API |         # NOTE(sileht) Compatibility with Havana-3 API | ||||||
|         if kwargs.get('type'): |         if create and 'type' not in kwargs: | ||||||
|             return |             warnings.warn("alarm without type set is deprecated", | ||||||
|         warnings.warn("alarm without type set is deprecated", |                           DeprecationWarning) | ||||||
|                       DeprecationWarning) |             kwargs['type'] = 'threshold' | ||||||
|  |  | ||||||
|         kwargs['type'] = 'threshold' |  | ||||||
|         kwargs['threshold_rule'] = {} |  | ||||||
|         for field in ['period', 'evaluation_periods', 'threshold', |         for field in ['period', 'evaluation_periods', 'threshold', | ||||||
|                       'statistic', 'comparison_operator', 'meter_name']: |                       'statistic', 'comparison_operator', 'meter_name']: | ||||||
|             if field in kwargs: |             if field in kwargs: | ||||||
|                 kwargs['threshold_rule'][field] = kwargs[field] |                 kwargs.setdefault('threshold_rule', {})[field] = kwargs[field] | ||||||
|                 del kwargs[field] |                 del kwargs[field] | ||||||
|  |  | ||||||
|         query = [] |  | ||||||
|         if 'matching_metadata' in kwargs: |         if 'matching_metadata' in kwargs: | ||||||
|  |             query = [] | ||||||
|             for key in kwargs['matching_metadata']: |             for key in kwargs['matching_metadata']: | ||||||
|                 query.append({'field': key, |                 query.append({'field': key, | ||||||
|                               'op': 'eq', |                               'op': 'eq', | ||||||
|                               'value': kwargs['matching_metadata'][key]}) |                               'value': kwargs['matching_metadata'][key]}) | ||||||
|             del kwargs['matching_metadata'] |             del kwargs['matching_metadata'] | ||||||
|         kwargs['threshold_rule']['query'] = query |             kwargs['threshold_rule']['query'] = query | ||||||
|  |  | ||||||
|     def create(self, **kwargs): |     def create(self, **kwargs): | ||||||
|         self._compat_legacy_alarm_kwargs(kwargs) |         self._compat_legacy_alarm_kwargs(kwargs, create=True) | ||||||
|         new = dict((key, value) for (key, value) in kwargs.items() |         new = dict((key, value) for (key, value) in kwargs.items() | ||||||
|                    if key in CREATION_ATTRIBUTES) |                    if key in CREATION_ATTRIBUTES) | ||||||
|         return self._create(self._path(), new) |         return self._create(self._path(), new) | ||||||
|  |  | ||||||
|     def update(self, alarm_id, **kwargs): |     def update(self, alarm_id, **kwargs): | ||||||
|         self._compat_legacy_alarm_kwargs(kwargs) |         self._compat_legacy_alarm_kwargs(kwargs) | ||||||
|         updated = dict((key, value) for (key, value) in kwargs.items() |         updated = self.get(alarm_id).to_dict() | ||||||
|                        if key in UPDATABLE_ATTRIBUTES) |         kwargs = dict((k, v) for k, v in kwargs.items() | ||||||
|  |                       if k in updated and k in UPDATABLE_ATTRIBUTES) | ||||||
|  |         utils.merge_nested_dict(updated, kwargs, depth=1) | ||||||
|         return self._update(self._path(alarm_id), updated) |         return self._update(self._path(alarm_id), updated) | ||||||
|  |  | ||||||
|     def delete(self, alarm_id): |     def delete(self, alarm_id): | ||||||
| @@ -129,3 +138,8 @@ class AlarmManager(base.Manager): | |||||||
|         resp, body = self.api.json_request('GET', |         resp, body = self.api.json_request('GET', | ||||||
|                                            "%s/state" % self._path(alarm_id)) |                                            "%s/state" % self._path(alarm_id)) | ||||||
|         return body |         return body | ||||||
|  |  | ||||||
|  |     def get_history(self, alarm_id, q=None): | ||||||
|  |         path = '%s/history' % self._path(alarm_id) | ||||||
|  |         url = options.build_url(path, q) | ||||||
|  |         return self._list(url, obj_class=AlarmChange) | ||||||
|   | |||||||
| @@ -28,11 +28,14 @@ ALARM_STATES = ['ok', 'alarm', 'insufficient_data'] | |||||||
| ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] | ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] | ||||||
| ALARM_COMBINATION_OPERATORS = ['and', 'or'] | ALARM_COMBINATION_OPERATORS = ['and', 'or'] | ||||||
| STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] | STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] | ||||||
|  | OPERATORS_STRING = dict(gt='>', ge='>=', | ||||||
|  |                         lt='<', le="<=", | ||||||
|  |                         eq='==', ne='!=') | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
|            help='key[op]value; list.') |            help='key[op]value; list.') | ||||||
| @utils.arg('-m', '--meter', metavar='<NAME>', | @utils.arg('-m', '--meter', metavar='<NAME>', required=True, | ||||||
|            help='Name of meter to show samples for.') |            help='Name of meter to show samples for.') | ||||||
| @utils.arg('-p', '--period', metavar='<PERIOD>', | @utils.arg('-p', '--period', metavar='<PERIOD>', | ||||||
|            help='Period in seconds over which to group samples.') |            help='Period in seconds over which to group samples.') | ||||||
| @@ -41,8 +44,6 @@ def do_statistics(cc, args): | |||||||
|     fields = {'meter_name': args.meter, |     fields = {'meter_name': args.meter, | ||||||
|               'q': options.cli_to_array(args.query), |               'q': options.cli_to_array(args.query), | ||||||
|               'period': args.period} |               'period': args.period} | ||||||
|     if args.meter is None: |  | ||||||
|         raise exc.CommandError('Meter name not provided (-m <meter name>)') |  | ||||||
|     try: |     try: | ||||||
|         statistics = cc.statistics.list(**fields) |         statistics = cc.statistics.list(**fields) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
| @@ -59,14 +60,12 @@ def do_statistics(cc, args): | |||||||
|  |  | ||||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
|            help='key[op]value; list.') |            help='key[op]value; list.') | ||||||
| @utils.arg('-m', '--meter', metavar='<NAME>', | @utils.arg('-m', '--meter', metavar='<NAME>', required=True, | ||||||
|            help='Name of meter to show samples for.') |            help='Name of meter to show samples for.') | ||||||
| def do_sample_list(cc, args): | def do_sample_list(cc, args): | ||||||
|     '''List the samples for this meters.''' |     '''List the samples for this meters.''' | ||||||
|     fields = {'meter_name': args.meter, |     fields = {'meter_name': args.meter, | ||||||
|               'q': options.cli_to_array(args.query)} |               'q': options.cli_to_array(args.query)} | ||||||
|     if args.meter is None: |  | ||||||
|         raise exc.CommandError('Meter name not provided (-m <meter name>)') |  | ||||||
|     try: |     try: | ||||||
|         samples = cc.samples.list(**fields) |         samples = cc.samples.list(**fields) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
| @@ -86,15 +85,15 @@ def do_sample_list(cc, args): | |||||||
| @utils.arg('--user-id', metavar='<USER_ID>', | @utils.arg('--user-id', metavar='<USER_ID>', | ||||||
|            help='User to associate with sample ' |            help='User to associate with sample ' | ||||||
|                 '(only settable by admin users)') |                 '(only settable by admin users)') | ||||||
| @utils.arg('-r', '--resource-id', metavar='<RESOURCE_ID>', | @utils.arg('-r', '--resource-id', metavar='<RESOURCE_ID>', required=True, | ||||||
|            help='ID of the resource.') |            help='ID of the resource.') | ||||||
| @utils.arg('-m', '--meter-name', metavar='<METER_NAME>', | @utils.arg('-m', '--meter-name', metavar='<METER_NAME>', | ||||||
|            help='the meter name') |            help='the meter name') | ||||||
| @utils.arg('--meter-type', metavar='<METER_TYPE>', | @utils.arg('--meter-type', metavar='<METER_TYPE>', required=True, | ||||||
|            help='the meter type') |            help='the meter type') | ||||||
| @utils.arg('--meter-unit', metavar='<METER_UNIT>', | @utils.arg('--meter-unit', metavar='<METER_UNIT>', required=True, | ||||||
|            help='the meter unit') |            help='the meter unit') | ||||||
| @utils.arg('--sample-volume', metavar='<SAMPLE_VOLUME>', | @utils.arg('--sample-volume', metavar='<SAMPLE_VOLUME>', required=True, | ||||||
|            help='The sample volume') |            help='The sample volume') | ||||||
| @utils.arg('--resource-metadata', metavar='<RESOURCE_METADATA>', | @utils.arg('--resource-metadata', metavar='<RESOURCE_METADATA>', | ||||||
|            help='resource metadata') |            help='resource metadata') | ||||||
| @@ -128,6 +127,65 @@ def do_meter_list(cc, args={}): | |||||||
|                      sortby=0) |                      sortby=0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _display_rule(type, rule): | ||||||
|  |     if type == 'threshold': | ||||||
|  |         return ('%(meter_name)s %(comparison_operator)s ' | ||||||
|  |                 '%(threshold)s during %(evaluation_periods)s x %(period)ss' % | ||||||
|  |                 { | ||||||
|  |                     'meter_name': rule['meter_name'], | ||||||
|  |                     'threshold': rule['threshold'], | ||||||
|  |                     'evaluation_periods': rule['evaluation_periods'], | ||||||
|  |                     'period': rule['period'], | ||||||
|  |                     'comparison_operator': OPERATORS_STRING.get( | ||||||
|  |                         rule['comparison_operator']) | ||||||
|  |                 }) | ||||||
|  |     elif type == 'combination': | ||||||
|  |         return ('combinated states (%(operator)s) of %(alarms)s' % { | ||||||
|  |             'operator': rule['operator'].upper(), | ||||||
|  |             'alarms': ", ".join(rule['alarm_ids'])}) | ||||||
|  |     else: | ||||||
|  |         # just dump all | ||||||
|  |         return "\n".join(["%s: %s" % (f, v) | ||||||
|  |                           for f, v in rule.iteritems()]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def alarm_rule_formatter(alarm): | ||||||
|  |     return _display_rule(alarm.type, alarm.rule) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _infer_type(detail): | ||||||
|  |     if 'type' in detail: | ||||||
|  |         return detail['type'] | ||||||
|  |     elif 'meter_name' in detail['rule']: | ||||||
|  |         return 'threshold' | ||||||
|  |     elif 'alarms' in detail['rule']: | ||||||
|  |         return 'combination' | ||||||
|  |     else: | ||||||
|  |         return 'unknown' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def alarm_change_detail_formatter(change): | ||||||
|  |     detail = json.loads(change.detail) | ||||||
|  |     fields = [] | ||||||
|  |     if change.type == 'state transition': | ||||||
|  |         fields.append('state: %s' % detail['state']) | ||||||
|  |     elif change.type == 'creation' or change.type == 'deletion': | ||||||
|  |         for k in ['name', 'description', 'type', 'rule']: | ||||||
|  |             if k == 'rule': | ||||||
|  |                 fields.append('rule: %s' % _display_rule(detail['type'], | ||||||
|  |                                                          detail[k])) | ||||||
|  |             else: | ||||||
|  |                 fields.append('%s: %s' % (k, detail[k])) | ||||||
|  |     elif change.type == 'rule change': | ||||||
|  |         for k, v in detail.iteritems(): | ||||||
|  |             if k == 'rule': | ||||||
|  |                 fields.append('rule: %s' % _display_rule(_infer_type(detail), | ||||||
|  |                                                          v)) | ||||||
|  |             else: | ||||||
|  |                 fields.append('%s: %s' % (k, v)) | ||||||
|  |     return '\n'.join(fields) | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
|            help='key[op]value; list.') |            help='key[op]value; list.') | ||||||
| def do_alarm_list(cc, args={}): | def do_alarm_list(cc, args={}): | ||||||
| @@ -135,29 +193,38 @@ def do_alarm_list(cc, args={}): | |||||||
|     alarms = cc.alarms.list(q=options.cli_to_array(args.query)) |     alarms = cc.alarms.list(q=options.cli_to_array(args.query)) | ||||||
|     # omit action initially to keep output width sane |     # omit action initially to keep output width sane | ||||||
|     # (can switch over to vertical formatting when available from CLIFF) |     # (can switch over to vertical formatting when available from CLIFF) | ||||||
|     field_labels = ['Name', 'Description', 'State', 'Enabled', 'Continuous', |     field_labels = ['Alarm ID', 'Name', 'State', 'Enabled', 'Continuous', | ||||||
|                     'Alarm ID', 'User ID', 'Project ID'] |                     'Alarm condition'] | ||||||
|     fields = ['name', 'description', 'state', 'enabled', 'repeat_actions', |     fields = ['alarm_id', 'name', 'state', 'enabled', 'repeat_actions', | ||||||
|               'alarm_id', 'user_id', 'project_id'] |               'rule'] | ||||||
|     utils.print_list(alarms, fields, field_labels, |     utils.print_list(alarms, fields, field_labels, | ||||||
|                      sortby=0) |                      formatters={'rule': alarm_rule_formatter}, sortby=0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def alarm_query_formater(alarm): | ||||||
|  |     qs = [] | ||||||
|  |     for q in alarm.rule['query']: | ||||||
|  |         qs.append('%s %s %s' % ( | ||||||
|  |             q['field'], OPERATORS_STRING.get(q['op']), q['value'])) | ||||||
|  |     return r' AND\n'.join(qs) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _display_alarm(alarm): | def _display_alarm(alarm): | ||||||
|     fields = ['name', 'description', 'type', 'rule', |     fields = ['name', 'description', 'type', | ||||||
|               'state', 'enabled', 'alarm_id', 'user_id', 'project_id', |               'state', 'enabled', 'alarm_id', 'user_id', 'project_id', | ||||||
|               'alarm_actions', 'ok_actions', 'insufficient_data_actions', |               'alarm_actions', 'ok_actions', 'insufficient_data_actions', | ||||||
|               'repeat_actions'] |               'repeat_actions'] | ||||||
|     data = dict([(f, getattr(alarm, f, '')) for f in fields]) |     data = dict([(f, getattr(alarm, f, '')) for f in fields]) | ||||||
|  |     data.update(alarm.rule) | ||||||
|  |     if alarm.type == 'threshold': | ||||||
|  |         data['query'] = alarm_query_formater(alarm) | ||||||
|     utils.print_dict(data, wrap=72) |     utils.print_dict(data, wrap=72) | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|            help='ID of the alarm to show.') |            help='ID of the alarm to show.') | ||||||
| def do_alarm_show(cc, args={}): | def do_alarm_show(cc, args={}): | ||||||
|     '''Show an alarm.''' |     '''Show an alarm.''' | ||||||
|     if args.alarm_id is None: |  | ||||||
|         raise exc.CommandError('Alarm ID not provided (-a <alarm id>)') |  | ||||||
|     try: |     try: | ||||||
|         alarm = cc.alarms.get(args.alarm_id) |         alarm = cc.alarms.get(args.alarm_id) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
| @@ -166,45 +233,49 @@ def do_alarm_show(cc, args={}): | |||||||
|         _display_alarm(alarm) |         _display_alarm(alarm) | ||||||
|  |  | ||||||
|  |  | ||||||
| def common_alarm_arguments(func): | def common_alarm_arguments(create=False): | ||||||
|     @utils.arg('--name', metavar='<NAME>', required=True, |     def _wrapper(func): | ||||||
|                help='Name of the alarm (must be unique per tenant)') |         @utils.arg('--name', metavar='<NAME>', required=create, | ||||||
|     @utils.arg('--project-id', metavar='<PROJECT_ID>', |                    help='Name of the alarm (must be unique per tenant)') | ||||||
|                help='Tenant to associate with alarm ' |         @utils.arg('--project-id', metavar='<PROJECT_ID>', | ||||||
|                '(only settable by admin users)') |                    help='Tenant to associate with alarm ' | ||||||
|     @utils.arg('--user-id', metavar='<USER_ID>', |                    '(only settable by admin users)') | ||||||
|                help='User to associate with alarm ' |         @utils.arg('--user-id', metavar='<USER_ID>', | ||||||
|                '(only settable by admin users)') |                    help='User to associate with alarm ' | ||||||
|     @utils.arg('--description', metavar='<DESCRIPTION>', |                    '(only settable by admin users)') | ||||||
|                help='Free text description of the alarm') |         @utils.arg('--description', metavar='<DESCRIPTION>', | ||||||
|     @utils.arg('--state', metavar='<STATE>', |                    help='Free text description of the alarm') | ||||||
|                help='State of the alarm, one of: ' + str(ALARM_STATES)) |         @utils.arg('--state', metavar='<STATE>', | ||||||
|     @utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}', |                    help='State of the alarm, one of: ' + str(ALARM_STATES)) | ||||||
|                help='True if alarm evaluation/actioning is enabled') |         @utils.arg('--enabled', type=utils.string_to_bool, | ||||||
|     @utils.arg('--alarm-action', dest='alarm_actions', |                    metavar='{True|False}', | ||||||
|                metavar='<Webhook URL>', action='append', default=None, |                    help='True if alarm evaluation/actioning is enabled') | ||||||
|                help=('URL to invoke when state transitions to alarm. ' |         @utils.arg('--alarm-action', dest='alarm_actions', | ||||||
|                      'May be used multiple times.')) |                    metavar='<Webhook URL>', action='append', default=None, | ||||||
|     @utils.arg('--ok-action', dest='ok_actions', |                    help=('URL to invoke when state transitions to alarm. ' | ||||||
|                metavar='<Webhook URL>', action='append', default=None, |                          'May be used multiple times.')) | ||||||
|                help=('URL to invoke when state transitions to OK. ' |         @utils.arg('--ok-action', dest='ok_actions', | ||||||
|                      'May be used multiple times.')) |                    metavar='<Webhook URL>', action='append', default=None, | ||||||
|     @utils.arg('--insufficient-data-action', dest='insufficient_data_actions', |                    help=('URL to invoke when state transitions to OK. ' | ||||||
|                metavar='<Webhook URL>', action='append', default=None, |                          'May be used multiple times.')) | ||||||
|                help=('URL to invoke when state transitions to unkown. ' |         @utils.arg('--insufficient-data-action', | ||||||
|                      'May be used multiple times.')) |                    dest='insufficient_data_actions', | ||||||
|     @utils.arg('--repeat-actions', dest='repeat_actions', |                    metavar='<Webhook URL>', action='append', default=None, | ||||||
|                metavar='{True|False}', type=utils.string_to_bool, |                    help=('URL to invoke when state transitions to unkown. ' | ||||||
|                default=False, |                          'May be used multiple times.')) | ||||||
|                help=('True if actions should be repeatedly notified ' |         @utils.arg('--repeat-actions', dest='repeat_actions', | ||||||
|                      'while alarm remains in target state')) |                    metavar='{True|False}', type=utils.string_to_bool, | ||||||
|     @functools.wraps(func) |                    default=False, | ||||||
|     def _wrapper(*args, **kwargs): |                    help=('True if actions should be repeatedly notified ' | ||||||
|         return func(*args, **kwargs) |                          'while alarm remains in target state')) | ||||||
|  |         @functools.wraps(func) | ||||||
|  |         def _wrapped(*args, **kwargs): | ||||||
|  |             return func(*args, **kwargs) | ||||||
|  |         return _wrapped | ||||||
|     return _wrapper |     return _wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
| @common_alarm_arguments | @common_alarm_arguments(create=True) | ||||||
| @utils.arg('--period', type=int, metavar='<PERIOD>', | @utils.arg('--period', type=int, metavar='<PERIOD>', | ||||||
|            help='Length of each period (seconds) to evaluate over') |            help='Length of each period (seconds) to evaluate over') | ||||||
| @utils.arg('--evaluation-periods', type=int, metavar='<COUNT>', | @utils.arg('--evaluation-periods', type=int, metavar='<COUNT>', | ||||||
| @@ -229,7 +300,7 @@ def do_alarm_create(cc, args={}): | |||||||
|     _display_alarm(alarm) |     _display_alarm(alarm) | ||||||
|  |  | ||||||
|  |  | ||||||
| @common_alarm_arguments | @common_alarm_arguments(create=True) | ||||||
| @utils.arg('--meter-name', metavar='<METRIC>', required=True, | @utils.arg('--meter-name', metavar='<METRIC>', required=True, | ||||||
|            dest='threshold_rule/meter_name', |            dest='threshold_rule/meter_name', | ||||||
|            help='Metric to evaluate against') |            help='Metric to evaluate against') | ||||||
| @@ -264,7 +335,7 @@ def do_alarm_threshold_create(cc, args={}): | |||||||
|     _display_alarm(alarm) |     _display_alarm(alarm) | ||||||
|  |  | ||||||
|  |  | ||||||
| @common_alarm_arguments | @common_alarm_arguments(create=True) | ||||||
| @utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>', | @utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>', | ||||||
|            required=True, dest='combination_rule/alarm_ids', |            required=True, dest='combination_rule/alarm_ids', | ||||||
|            help='List of alarm id') |            help='List of alarm id') | ||||||
| @@ -283,18 +354,18 @@ def do_alarm_combination_create(cc, args={}): | |||||||
|  |  | ||||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|            help='ID of the alarm to update.') |            help='ID of the alarm to update.') | ||||||
| @common_alarm_arguments | @common_alarm_arguments() | ||||||
| @utils.arg('--period', type=int, metavar='<PERIOD>', | @utils.arg('--period', type=int, metavar='<PERIOD>', | ||||||
|            help='Length of each period (seconds) to evaluate over') |            help='Length of each period (seconds) to evaluate over') | ||||||
| @utils.arg('--evaluation-periods', type=int, metavar='<COUNT>', | @utils.arg('--evaluation-periods', type=int, metavar='<COUNT>', | ||||||
|            help='Number of periods to evaluate over') |            help='Number of periods to evaluate over') | ||||||
| @utils.arg('--meter-name', metavar='<METRIC>', required=True, | @utils.arg('--meter-name', metavar='<METRIC>', | ||||||
|            help='Metric to evaluate against') |            help='Metric to evaluate against') | ||||||
| @utils.arg('--statistic', metavar='<STATISTIC>', | @utils.arg('--statistic', metavar='<STATISTIC>', | ||||||
|            help='Statistic to evaluate, one of: ' + str(STATISTICS)) |            help='Statistic to evaluate, one of: ' + str(STATISTICS)) | ||||||
| @utils.arg('--comparison-operator', metavar='<OPERATOR>', | @utils.arg('--comparison-operator', metavar='<OPERATOR>', | ||||||
|            help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) |            help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) | ||||||
| @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True, | @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', | ||||||
|            help='Threshold to evaluate against') |            help='Threshold to evaluate against') | ||||||
| @utils.arg('--matching-metadata', dest='matching_metadata', | @utils.arg('--matching-metadata', dest='matching_metadata', | ||||||
|            metavar='<Matching Metadata>', action='append', default=None, |            metavar='<Matching Metadata>', action='append', default=None, | ||||||
| @@ -314,9 +385,9 @@ def do_alarm_update(cc, args={}): | |||||||
|  |  | ||||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|            help='ID of the alarm to update.') |            help='ID of the alarm to update.') | ||||||
| @common_alarm_arguments | @common_alarm_arguments() | ||||||
| @utils.arg('--meter-name', metavar='<METRIC>', | @utils.arg('--meter-name', metavar='<METRIC>', | ||||||
|            dest='threshold_rule/meter_name', required=True, |            dest='threshold_rule/meter_name', | ||||||
|            help='Metric to evaluate against') |            help='Metric to evaluate against') | ||||||
| @utils.arg('--period', type=int, metavar='<PERIOD>', | @utils.arg('--period', type=int, metavar='<PERIOD>', | ||||||
|            dest='threshold_rule/period', |            dest='threshold_rule/period', | ||||||
| @@ -330,7 +401,7 @@ def do_alarm_update(cc, args={}): | |||||||
| @utils.arg('--comparison-operator', metavar='<OPERATOR>', | @utils.arg('--comparison-operator', metavar='<OPERATOR>', | ||||||
|            dest='threshold_rule/comparison_operator', |            dest='threshold_rule/comparison_operator', | ||||||
|            help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) |            help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) | ||||||
| @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True, | @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', | ||||||
|            dest='threshold_rule/threshold', |            dest='threshold_rule/threshold', | ||||||
|            help='Threshold to evaluate against') |            help='Threshold to evaluate against') | ||||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
| @@ -355,9 +426,9 @@ def do_alarm_threshold_update(cc, args={}): | |||||||
|  |  | ||||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|            help='ID of the alarm to update.') |            help='ID of the alarm to update.') | ||||||
| @common_alarm_arguments | @common_alarm_arguments() | ||||||
| @utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>', | @utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>', | ||||||
|            dest='combination_rule/alarm_ids', required=True, |            dest='combination_rule/alarm_ids', | ||||||
|            help='List of alarm id') |            help='List of alarm id') | ||||||
| @utils.arg('---operator', metavar='<OPERATOR>', | @utils.arg('---operator', metavar='<OPERATOR>', | ||||||
|            dest='combination_rule/operator', |            dest='combination_rule/operator', | ||||||
| @@ -380,8 +451,6 @@ def do_alarm_combination_update(cc, args={}): | |||||||
|            help='ID of the alarm to delete.') |            help='ID of the alarm to delete.') | ||||||
| def do_alarm_delete(cc, args={}): | def do_alarm_delete(cc, args={}): | ||||||
|     '''Delete an alarm.''' |     '''Delete an alarm.''' | ||||||
|     if args.alarm_id is None: |  | ||||||
|         raise exc.CommandError('Alarm ID not provided (-a <alarm id>)') |  | ||||||
|     try: |     try: | ||||||
|         cc.alarms.delete(args.alarm_id) |         cc.alarms.delete(args.alarm_id) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
| @@ -392,28 +461,45 @@ def do_alarm_delete(cc, args={}): | |||||||
|            help='ID of the alarm state to set.') |            help='ID of the alarm state to set.') | ||||||
| @utils.arg('--state', metavar='<STATE>', required=True, | @utils.arg('--state', metavar='<STATE>', required=True, | ||||||
|            help='State of the alarm, one of: ' + str(ALARM_STATES)) |            help='State of the alarm, one of: ' + str(ALARM_STATES)) | ||||||
| def do_alarm_set_state(cc, args={}): | def do_alarm_state_set(cc, args={}): | ||||||
|     '''Set the state of an alarm.''' |     '''Set the state of an alarm.''' | ||||||
|     try: |     try: | ||||||
|         cc.alarms.set_state(args.alarm_id, args.state) |         state = cc.alarms.set_state(args.alarm_id, args.state) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
|         raise exc.CommandError('Alarm not found: %s' % args.alarm_id) |         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.print_dict({'state': state}, wrap=72) | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|            help='ID of the alarm state to show.') |            help='ID of the alarm state to show.') | ||||||
| def do_alarm_get_state(cc, args={}): | def do_alarm_state_get(cc, args={}): | ||||||
|     '''Get the state of an alarm.''' |     '''Get the state of an alarm.''' | ||||||
|     try: |     try: | ||||||
|         cc.alarms.set_state(args.alarm_id, args.state) |         state = cc.alarms.get_state(args.alarm_id) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
|         raise exc.CommandError('Alarm not found: %s' % args.alarm_id) |         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.print_dict({'state': state}, wrap=72) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||||
|  |            help='ID of the alarm for which history is shown.') | ||||||
|  | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
|  |            help='key[op]value; list.') | ||||||
|  | def do_alarm_history(cc, args={}): | ||||||
|  |     '''Display the change history of an alarm.''' | ||||||
|  |     kwargs = dict(alarm_id=args.alarm_id, | ||||||
|  |                   q=options.cli_to_array(args.query)) | ||||||
|  |     try: | ||||||
|  |         history = cc.alarms.get_history(**kwargs) | ||||||
|  |     except exc.HTTPNotFound: | ||||||
|  |         raise exc.CommandError('Alarm not found: %s' % args.alarm_id) | ||||||
|  |     field_labels = ['Type', 'Timestamp', 'Detail'] | ||||||
|  |     fields = ['type', 'timestamp', 'detail'] | ||||||
|  |     utils.print_list(history, fields, field_labels, | ||||||
|  |                      formatters={'detail': alarm_change_detail_formatter}, | ||||||
|  |                      sortby=1) | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | @utils.arg('-q', '--query', metavar='<QUERY>', | ||||||
|            help='key[op]value; list.') |            help='key[op]value; list.') | ||||||
| def do_resource_list(cc, args={}): | def do_resource_list(cc, args={}): | ||||||
| @@ -426,12 +512,10 @@ def do_resource_list(cc, args={}): | |||||||
|                      sortby=1) |                      sortby=1) | ||||||
|  |  | ||||||
|  |  | ||||||
| @utils.arg('-r', '--resource_id', metavar='<RESOURCE_ID>', | @utils.arg('-r', '--resource_id', metavar='<RESOURCE_ID>', required=True, | ||||||
|            help='ID of the resource to show.') |            help='ID of the resource to show.') | ||||||
| def do_resource_show(cc, args={}): | def do_resource_show(cc, args={}): | ||||||
|     '''Show the resource.''' |     '''Show the resource.''' | ||||||
|     if args.resource_id is None: |  | ||||||
|         raise exc.CommandError('Resource id not provided (-r <resource id>)') |  | ||||||
|     try: |     try: | ||||||
|         resource = cc.resources.get(args.resource_id) |         resource = cc.resources.get(args.resource_id) | ||||||
|     except exc.HTTPNotFound: |     except exc.HTTPNotFound: | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -18,5 +18,5 @@ | |||||||
| import setuptools | import setuptools | ||||||
|  |  | ||||||
| setuptools.setup( | setuptools.setup( | ||||||
|     setup_requires=['pbr>=0.5.21,<1.0'], |     setup_requires=['pbr'], | ||||||
|     pbr=True) |     pbr=True) | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ hacking>=0.5.6,<0.8 | |||||||
| coverage>=3.6 | coverage>=3.6 | ||||||
| discover | discover | ||||||
| fixtures>=0.3.14 | fixtures>=0.3.14 | ||||||
|  | mock>=1.0 | ||||||
| mox>=0.5.3 | mox>=0.5.3 | ||||||
| python-subunit | python-subunit | ||||||
| sphinx>=1.1.2 | sphinx>=1.1.2 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Thomas Goirand
					Thomas Goirand