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