Merge tag '1.0.5' into debian/unstable

python-ceilometerclient 1.0.5
This commit is contained in:
Thomas Goirand
2013-10-03 11:10:38 +08:00
27 changed files with 479 additions and 122 deletions

View File

@@ -29,6 +29,7 @@ def _get_ksclient(**kwargs):
tenant_id=kwargs.get('tenant_id'), tenant_id=kwargs.get('tenant_id'),
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'),
insecure=kwargs.get('insecure')) insecure=kwargs.get('insecure'))
@@ -68,6 +69,7 @@ def get_client(api_version, **kwargs):
'tenant_id': kwargs.get('os_tenant_id'), 'tenant_id': kwargs.get('os_tenant_id'),
'tenant_name': kwargs.get('os_tenant_name'), 'tenant_name': kwargs.get('os_tenant_name'),
'auth_url': kwargs.get('os_auth_url'), 'auth_url': kwargs.get('os_auth_url'),
'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'),
'insecure': kwargs.get('insecure'), 'insecure': kwargs.get('insecure'),

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -140,6 +140,17 @@ def args_array_to_dict(kwargs, key_to_convert):
return kwargs return kwargs
def key_with_slash_to_nested_dict(kwargs):
nested_kwargs = {}
for k in kwargs.keys():
keys = k.split('/', 1)
if len(keys) == 2:
nested_kwargs.setdefault(keys[0], {})[keys[1]] = kwargs[k]
del kwargs[k]
kwargs.update(nested_kwargs)
return kwargs
def exit(msg=''): def exit(msg=''):
if msg: if msg:
print >> sys.stderr, msg print >> sys.stderr, msg

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2013 OpenStack LLC. # Copyright 2013 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -57,3 +57,20 @@ class UtilsTest(test_utils.BaseTestCase):
'matching_metadata': {'metadata.key': 'metadata_value'}, 'matching_metadata': {'metadata.key': 'metadata_value'},
'other': 'value' 'other': 'value'
}) })
def test_key_with_slash_to_nested_dict(self):
my_args = {
'combination_rule/alarm_ids': ['id1', 'id2'],
'combination_rule/operator': 'and',
'threshold_rule/threshold': 400,
'threshold_rule/statictic': 'avg',
'threshold_rule/comparison_operator': 'or',
}
nested_dict = utils.key_with_slash_to_nested_dict(my_args)
self.assertEqual(nested_dict, {
'combination_rule': {'alarm_ids': ['id1', 'id2'],
'operator': 'and'},
'threshold_rule': {'threshold': 400,
'statictic': 'avg',
'comparison_operator': 'or'},
})

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -22,36 +22,91 @@ import testtools
from ceilometerclient.tests import utils from ceilometerclient.tests import utils
import ceilometerclient.v2.alarms import ceilometerclient.v2.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'],
u'description': u'An alarm', u'description': u'An alarm',
u'matching_metadata': {u'key_name': u'key_value'}, u'type': u'threshold',
u'evaluation_periods': 2, u'threshold_rule': {
u'meter_name': u'storage.objects',
u'query': [{u'field': u'key_name',
u'op': u'eq',
u'value': u'key_value'}],
u'evaluation_periods': 2,
u'period': 240.0,
u'statistic': u'avg',
u'threshold': 200.0,
u'comparison_operator': 'gt',
},
u'timestamp': u'2013-05-09T13:41:23.085000', u'timestamp': u'2013-05-09T13:41:23.085000',
u'enabled': True, u'enabled': True,
u'counter_name': u'storage.objects',
u'period': 240.0,
u'alarm_id': u'alarm-id', u'alarm_id': u'alarm-id',
u'state': u'ok', u'state': u'ok',
u'insufficient_data_actions': [u'http://site:8000/nodata'], u'insufficient_data_actions': [u'http://site:8000/nodata'],
u'statistic': u'avg',
u'threshold': 200.0,
u'user_id': u'user-id', u'user_id': u'user-id',
u'project_id': u'project-id', u'project_id': u'project-id',
u'state_timestamp': u'2013-05-09T13:41:23.085000', u'state_timestamp': u'2013-05-09T13:41:23.085000',
u'comparison_operator': 'gt',
u'repeat_actions': False, u'repeat_actions': False,
u'name': 'SwiftObjectAlarm'} u'name': 'SwiftObjectAlarm'}
CREATE_ALARM = copy.deepcopy(AN_ALARM) CREATE_ALARM = copy.deepcopy(AN_ALARM)
del CREATE_ALARM['timestamp'] del CREATE_ALARM['timestamp']
del CREATE_ALARM['state_timestamp'] del CREATE_ALARM['state_timestamp']
del CREATE_ALARM['alarm_id'] del CREATE_ALARM['alarm_id']
DELTA_ALARM = {u'alarm_actions': ['url1', 'url2'], DELTA_ALARM = {u'alarm_actions': ['url1', 'url2']}
u'comparison_operator': u'lt', DELTA_ALARM_RULE = {u'comparison_operator': u'lt',
u'threshold': 42.1} u'threshold': 42.1,
u'meter_name': u'foobar',
u'query': [{u'field': u'key_name',
u'op': u'eq',
u'value': u'key_value'}]}
UPDATED_ALARM = copy.deepcopy(AN_ALARM) UPDATED_ALARM = copy.deepcopy(AN_ALARM)
UPDATED_ALARM.update(DELTA_ALARM) UPDATED_ALARM.update(DELTA_ALARM)
UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE)
UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM)
del UPDATE_ALARM['user_id']
del UPDATE_ALARM['project_id']
del UPDATE_ALARM['name']
del UPDATE_ALARM['alarm_id']
del UPDATE_ALARM['timestamp']
del UPDATE_ALARM['state_timestamp']
AN_LEGACY_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'],
u'ok_actions': [u'http://site:8000/ok'],
u'description': u'An alarm',
u'matching_metadata': {u'key_name': u'key_value'},
u'evaluation_periods': 2,
u'timestamp': u'2013-05-09T13:41:23.085000',
u'enabled': True,
u'meter_name': u'storage.objects',
u'period': 240.0,
u'alarm_id': u'alarm-id',
u'state': u'ok',
u'insufficient_data_actions': [u'http://site:8000/nodata'],
u'statistic': u'avg',
u'threshold': 200.0,
u'user_id': u'user-id',
u'project_id': u'project-id',
u'state_timestamp': u'2013-05-09T13:41:23.085000',
u'comparison_operator': 'gt',
u'repeat_actions': False,
u'name': 'SwiftObjectAlarm'}
CREATE_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM)
del CREATE_LEGACY_ALARM['timestamp']
del CREATE_LEGACY_ALARM['state_timestamp']
del CREATE_LEGACY_ALARM['alarm_id']
DELTA_LEGACY_ALARM = {u'alarm_actions': ['url1', 'url2'],
u'comparison_operator': u'lt',
u'meter_name': u'foobar',
u'threshold': 42.1}
UPDATED_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM)
UPDATED_LEGACY_ALARM.update(DELTA_LEGACY_ALARM)
UPDATE_LEGACY_ALARM = copy.deepcopy(UPDATED_LEGACY_ALARM)
del UPDATE_LEGACY_ALARM['user_id']
del UPDATE_LEGACY_ALARM['project_id']
del UPDATE_LEGACY_ALARM['name']
del UPDATE_LEGACY_ALARM['alarm_id']
del UPDATE_LEGACY_ALARM['timestamp']
del UPDATE_LEGACY_ALARM['state_timestamp']
fixtures = { fixtures = {
'/v2/alarms': '/v2/alarms':
@@ -76,6 +131,18 @@ fixtures = {
UPDATED_ALARM, UPDATED_ALARM,
), ),
}, },
'/v2/alarms/alarm-id/state':
{
'PUT': (
{},
'alarm'
),
'GET': (
{},
'alarm'
),
},
'/v2/alarms?q.op=&q.op=&q.value=project-id&q.value=SwiftObjectAlarm' '/v2/alarms?q.op=&q.op=&q.value=project-id&q.value=SwiftObjectAlarm'
'&q.field=project_id&q.field=name': '&q.field=project_id&q.field=name':
{ {
@@ -135,6 +202,7 @@ class AlarmManagerTest(testtools.TestCase):
self.assertEqual(self.api.calls, expect) self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm) self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id') self.assertEqual(alarm.alarm_id, 'alarm-id')
self.assertEqual(alarm.rule, alarm.threshold_rule)
def test_create(self): def test_create(self):
alarm = self.mgr.create(**CREATE_ALARM) alarm = self.mgr.create(**CREATE_ALARM)
@@ -145,16 +213,32 @@ class AlarmManagerTest(testtools.TestCase):
self.assertTrue(alarm) self.assertTrue(alarm)
def test_update(self): def test_update(self):
alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM)
expect = [ expect = [
('PUT', '/v2/alarms/alarm-id', {}, DELTA_ALARM), ('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM),
] ]
self.assertEqual(self.api.calls, expect) self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm) self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id') self.assertEqual(alarm.alarm_id, 'alarm-id')
for (key, value) in DELTA_ALARM.iteritems(): for (key, value) in UPDATED_ALARM.iteritems():
self.assertEqual(getattr(alarm, key), value) self.assertEqual(getattr(alarm, key), value)
def test_set_state(self):
state = self.mgr.set_state(alarm_id='alarm-id', state='alarm')
expect = [
('PUT', '/v2/alarms/alarm-id/state', {}, 'alarm'),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(state, 'alarm')
def test_get_state(self):
state = self.mgr.get_state(alarm_id='alarm-id')
expect = [
('GET', '/v2/alarms/alarm-id/state', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(state, 'alarm')
def test_delete(self): def test_delete(self):
deleted = self.mgr.delete(alarm_id='victim-id') deleted = self.mgr.delete(alarm_id='victim-id')
expect = [ expect = [
@@ -162,3 +246,57 @@ 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)
class AlarmLegacyManagerTest(testtools.TestCase):
def setUp(self):
super(AlarmLegacyManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v2.alarms.AlarmManager(self.api)
def test_create(self):
alarm = self.mgr.create(**CREATE_LEGACY_ALARM)
expect = [
('POST', '/v2/alarms', {}, CREATE_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
def test_create_counter_name(self):
create = {}
create.update(CREATE_LEGACY_ALARM)
create['counter_name'] = CREATE_LEGACY_ALARM['meter_name']
del create['meter_name']
alarm = self.mgr.create(**create)
expect = [
('POST', '/v2/alarms', {}, CREATE_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
def test_update(self):
alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_LEGACY_ALARM)
expect = [
('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id')
for (key, value) in UPDATED_ALARM.iteritems():
self.assertEqual(getattr(alarm, key), value)
def test_update_counter_name(self):
updated = {}
updated.update(UPDATE_LEGACY_ALARM)
updated['counter_name'] = UPDATED_LEGACY_ALARM['meter_name']
del updated['meter_name']
alarm = self.mgr.update(alarm_id='alarm-id', **updated)
expect = [
('PUT', '/v2/alarms/alarm-id', {}, UPDATE_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id')
for (key, value) in UPDATED_ALARM.iteritems():
self.assertEqual(getattr(alarm, key), value)

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -16,33 +16,39 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import warnings
from ceilometerclient.common import base from ceilometerclient.common import base
from ceilometerclient.v2 import options from ceilometerclient.v2 import options
UPDATABLE_ATTRIBUTES = [ UPDATABLE_ATTRIBUTES = [
'name',
'description', 'description',
'period', 'type',
'evaluation_periods',
'state', 'state',
'enabled', 'enabled',
'counter_name',
'statistic',
'comparison_operator',
'threshold',
'alarm_actions', 'alarm_actions',
'ok_actions', 'ok_actions',
'insufficient_data_actions', 'insufficient_data_actions',
'repeat_actions', 'repeat_actions',
'matching_metadata', 'threshold_rule',
'combination_rule',
] ]
CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['name', 'project_id', 'user_id'] CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['project_id', 'user_id']
class Alarm(base.Resource): class Alarm(base.Resource):
def __repr__(self): def __repr__(self):
return "<Alarm %s>" % self._info return "<Alarm %s>" % self._info
def __getattr__(self, k):
# Alias to have the Alarm client object
# that look like the Alarm storage object
if k == 'rule':
k = '%s_rule' % self.type
return super(Alarm, self).__getattr__(k)
class AlarmManager(base.Manager): class AlarmManager(base.Manager):
resource_class = Alarm resource_class = Alarm
@@ -60,15 +66,66 @@ class AlarmManager(base.Manager):
except IndexError: except IndexError:
return None return None
@classmethod
def _compat_legacy_alarm_kwargs(cls, kwargs):
cls._compat_counter_rename_kwargs(kwargs)
cls._compat_alarm_before_rule_type_kwargs(kwargs)
@staticmethod
def _compat_counter_rename_kwargs(kwargs):
# NOTE(jd) Compatibility with Havana-2 API
if 'counter_name' in kwargs:
warnings.warn("counter_name has been renamed to meter_name",
DeprecationWarning)
kwargs['meter_name'] = kwargs['counter_name']
@staticmethod
def _compat_alarm_before_rule_type_kwargs(kwargs):
# NOTE(sileht) Compatibility with Havana-3 API
if kwargs.get('type'):
return
warnings.warn("alarm without type set is deprecated",
DeprecationWarning)
kwargs['type'] = 'threshold'
kwargs['threshold_rule'] = {}
for field in ['period', 'evaluation_periods', 'threshold',
'statistic', 'comparison_operator', 'meter_name']:
if field in kwargs:
kwargs['threshold_rule'][field] = kwargs[field]
del kwargs[field]
query = []
if 'matching_metadata' in kwargs:
for key in kwargs['matching_metadata']:
query.append({'field': key,
'op': 'eq',
'value': kwargs['matching_metadata'][key]})
del kwargs['matching_metadata']
kwargs['threshold_rule']['query'] = query
def create(self, **kwargs): def create(self, **kwargs):
self._compat_legacy_alarm_kwargs(kwargs)
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)
updated = dict((key, value) for (key, value) in kwargs.items() updated = dict((key, value) for (key, value) in kwargs.items()
if key in UPDATABLE_ATTRIBUTES) if key in UPDATABLE_ATTRIBUTES)
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):
return self._delete(self._path(alarm_id)) return self._delete(self._path(alarm_id))
def set_state(self, alarm_id, state):
resp, body = self.api.json_request('PUT',
"%s/state" % self._path(alarm_id),
body=state)
return body
def get_state(self, alarm_id):
resp, body = self.api.json_request('GET',
"%s/state" % self._path(alarm_id))
return body

View File

@@ -1,4 +1,4 @@
# Copyright 2012 OpenStack LLC. # Copyright 2012 OpenStack Foundation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools
import json import json
from ceilometerclient.common import utils from ceilometerclient.common import utils
@@ -25,6 +26,7 @@ from ceilometerclient.v2 import options
ALARM_STATES = ['ok', 'alarm', 'insufficient_data'] 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']
STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] STATISTICS = ['max', 'min', 'avg', 'sum', 'count']
@@ -79,10 +81,10 @@ def do_sample_list(cc, args):
@utils.arg('--project-id', metavar='<PROJECT_ID>', @utils.arg('--project-id', metavar='<PROJECT_ID>',
help='Tenant to associate with alarm ' help='Tenant to associate with sample '
'(only settable by admin users)') '(only settable by admin users)')
@utils.arg('--user-id', metavar='<USER_ID>', @utils.arg('--user-id', metavar='<USER_ID>',
help='User to associate with alarm ' 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>',
help='ID of the resource.') help='ID of the resource.')
@@ -133,22 +135,19 @@ 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', 'Metric', 'Period', 'Count', field_labels = ['Name', 'Description', 'State', 'Enabled', 'Continuous',
'Threshold', 'Comparison', 'State', 'Enabled', 'Alarm ID', 'Alarm ID', 'User ID', 'Project ID']
'User ID', 'Project ID'] fields = ['name', 'description', 'state', 'enabled', 'repeat_actions',
fields = ['name', 'description', 'counter_name', 'period', 'alarm_id', 'user_id', 'project_id']
'evaluation_periods', 'threshold', 'comparison_operator',
'state', 'enabled', 'alarm_id', 'user_id', 'project_id']
utils.print_list(alarms, fields, field_labels, utils.print_list(alarms, fields, field_labels,
sortby=0) sortby=0)
def _display_alarm(alarm): def _display_alarm(alarm):
fields = ['name', 'description', 'counter_name', 'period', fields = ['name', 'description', 'type', 'rule',
'evaluation_periods', 'threshold', 'comparison_operator',
'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', 'matching_metadata'] 'repeat_actions']
data = dict([(f, getattr(alarm, f, '')) for f in fields]) data = dict([(f, getattr(alarm, f, '')) for f in fields])
utils.print_dict(data, wrap=72) utils.print_dict(data, wrap=72)
@@ -167,111 +166,218 @@ def do_alarm_show(cc, args={}):
_display_alarm(alarm) _display_alarm(alarm)
@utils.arg('--name', metavar='<NAME>', def common_alarm_arguments(func):
help='Name of the alarm (must be unique per tenant)') @utils.arg('--name', metavar='<NAME>', required=True,
@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>',
help='Free text description of the alarm')
@utils.arg('--state', metavar='<STATE>',
help='State of the alarm, one of: ' + str(ALARM_STATES))
@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}',
help='True if alarm evaluation/actioning is enabled')
@utils.arg('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to alarm. '
'May be used multiple times.'))
@utils.arg('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to OK. '
'May be used multiple times.'))
@utils.arg('--insufficient-data-action', dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to unkown. '
'May be used multiple times.'))
@utils.arg('--repeat-actions', dest='repeat_actions',
metavar='{True|False}', type=utils.string_to_bool,
default=False,
help=('True if actions should be repeatedly notified '
'while alarm remains in target state'))
@functools.wraps(func)
def _wrapper(*args, **kwargs):
return func(*args, **kwargs)
return _wrapper
@common_alarm_arguments
@utils.arg('--period', type=int, metavar='<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('--state', metavar='<STATE>', @utils.arg('--meter-name', metavar='<METRIC>', required=True,
help='State of the alarm, one of: ' + str(ALARM_STATES))
@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}',
help='True if alarm evaluation/actioning is enabled')
@utils.arg('--counter-name', metavar='<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>', @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True,
help='Threshold to evaluate against') help='Threshold to evaluate against')
@utils.arg('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to alarm. '
'May be used multiple times.'))
@utils.arg('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to OK. '
'May be used multiple times.'))
@utils.arg('--insufficient-data-action', dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to unkown. '
'May be used multiple times.'))
@utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}',
type=utils.string_to_bool, default=False,
help=('True if actions should be repeatedly notified '
'while alarm remains in target state'))
@utils.arg('--matching-metadata', dest='matching_metadata', @utils.arg('--matching-metadata', dest='matching_metadata',
metavar='<Matching Metadata>', action='append', default=None, metavar='<Matching Metadata>', action='append', default=None,
help=('A meter should match this resource metadata (key=value) ' help=('A meter should match this resource metadata (key=value) '
'additionally to the counter_name')) 'additionally to the meter_name'))
def do_alarm_create(cc, args={}): def do_alarm_create(cc, args={}):
'''Create a new alarm.''' '''Create a new alarm (Deprecated).'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.args_array_to_dict(fields, "matching_metadata") fields = utils.args_array_to_dict(fields, "matching_metadata")
alarm = cc.alarms.create(**fields) alarm = cc.alarms.create(**fields)
_display_alarm(alarm) _display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', @common_alarm_arguments
@utils.arg('--meter-name', metavar='<METRIC>', required=True,
dest='threshold_rule/meter_name',
help='Metric to evaluate against')
@utils.arg('--period', type=int, metavar='<PERIOD>',
dest='threshold_rule/period',
help='Length of each period (seconds) to evaluate over')
@utils.arg('--evaluation-periods', type=int, metavar='<COUNT>',
dest='threshold_rule/evaluation_periods',
help='Number of periods to evaluate over')
@utils.arg('--statistic', metavar='<STATISTIC>',
dest='threshold_rule/statistic',
help='Statistic to evaluate, one of: ' + str(STATISTICS))
@utils.arg('--comparison-operator', metavar='<OPERATOR>',
dest='threshold_rule/comparison_operator',
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
@utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True,
dest='threshold_rule/threshold',
help='Threshold to evaluate against')
@utils.arg('-q', '--query', metavar='<QUERY>',
dest='threshold_rule/query',
help='The query to find the data for computing statistics '
'(key[op]value; list.)')
def do_alarm_threshold_create(cc, args={}):
'''Create a new alarm based on computed statistics.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.key_with_slash_to_nested_dict(fields)
fields['type'] = 'threshold'
if 'query' in fields['threshold_rule']:
fields['threshold_rule']['query'] = options.cli_to_array(
fields['threshold_rule']['query'])
alarm = cc.alarms.create(**fields)
_display_alarm(alarm)
@common_alarm_arguments
@utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>',
required=True, dest='combination_rule/alarm_ids',
help='List of alarm id')
@utils.arg('--operator', metavar='<OPERATOR>',
dest='combination_rule/operator',
help='Operator to compare with, one of: ' + str(
ALARM_COMBINATION_OPERATORS))
def do_alarm_combination_create(cc, args={}):
'''Create a new alarm based on state of other alarms.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.key_with_slash_to_nested_dict(fields)
fields['type'] = 'combination'
alarm = cc.alarms.create(**fields)
_display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True,
help='ID of the alarm to update.') help='ID of the alarm to update.')
@utils.arg('--description', metavar='<DESCRIPTION>', @common_alarm_arguments
help='Free text description of the alarm')
@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('--state', metavar='<STATE>', @utils.arg('--meter-name', metavar='<METRIC>', required=True,
help='State of the alarm, one of: ' + str(ALARM_STATES))
@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}',
help='True if alarm evaluation/actioning is enabled')
@utils.arg('--counter-name', metavar='<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>', @utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True,
help='Threshold to evaluate against') help='Threshold to evaluate against')
@utils.arg('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to alarm. '
'May be used multiple times.'))
@utils.arg('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to OK. '
'May be used multiple times.'))
@utils.arg('--insufficient-data-action', dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to unkown. '
'May be used multiple times.'))
@utils.arg('--repeat-actions', dest='repeat_actions',
metavar='{True|False}', type=utils.string_to_bool,
help=('True if actions should be repeatedly notified '
'while alarm remains in target state'))
@utils.arg('--matching-metadata', dest='matching_metadata', @utils.arg('--matching-metadata', dest='matching_metadata',
metavar='<Matching Metadata>', action='append', default=None, metavar='<Matching Metadata>', action='append', default=None,
help=('A meter should match this resource metadata (key=value) ' help=('A meter should match this resource metadata (key=value) '
'additionally to the counter_name')) 'additionally to the meter_name'))
def do_alarm_update(cc, args={}): def do_alarm_update(cc, args={}):
'''Update an existing alarm.''' '''Update an existing alarm.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.args_array_to_dict(fields, "matching_metadata") fields = utils.args_array_to_dict(fields, "matching_metadata")
fields.pop('alarm_id') fields.pop('alarm_id')
alarm = cc.alarms.update(args.alarm_id, **fields) try:
alarm = cc.alarms.update(args.alarm_id, **fields)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
_display_alarm(alarm) _display_alarm(alarm)
@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 update.')
@common_alarm_arguments
@utils.arg('--meter-name', metavar='<METRIC>',
dest='threshold_rule/meter_name', required=True,
help='Metric to evaluate against')
@utils.arg('--period', type=int, metavar='<PERIOD>',
dest='threshold_rule/period',
help='Length of each period (seconds) to evaluate over')
@utils.arg('--evaluation-periods', type=int, metavar='<COUNT>',
dest='threshold_rule/evaluation_periods',
help='Number of periods to evaluate over')
@utils.arg('--statistic', metavar='<STATISTIC>',
dest='threshold_rule/statistic',
help='Statistic to evaluate, one of: ' + str(STATISTICS))
@utils.arg('--comparison-operator', metavar='<OPERATOR>',
dest='threshold_rule/comparison_operator',
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
@utils.arg('--threshold', type=float, metavar='<THRESHOLD>', required=True,
dest='threshold_rule/threshold',
help='Threshold to evaluate against')
@utils.arg('-q', '--query', metavar='<QUERY>',
dest='threshold_rule/query',
help='The query to find the data for computing statistics '
'(key[op]value; list.)')
def do_alarm_threshold_update(cc, args={}):
'''Update an existing alarm based on computed statistics.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.key_with_slash_to_nested_dict(fields)
fields.pop('alarm_id')
fields['type'] = 'threshold'
if 'query' in fields['threshold_rule']:
fields['threshold_rule']['query'] = options.cli_to_array(
fields['threshold_rule']['query'])
try:
alarm = cc.alarms.update(args.alarm_id, **fields)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
_display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True,
help='ID of the alarm to update.')
@common_alarm_arguments
@utils.arg('--alarm_ids', action='append', metavar='<ALARM IDS>',
dest='combination_rule/alarm_ids', required=True,
help='List of alarm id')
@utils.arg('---operator', metavar='<OPERATOR>',
dest='combination_rule/operator',
help='Operator to compare with, one of: ' + str(
ALARM_COMBINATION_OPERATORS))
def do_alarm_combination_update(cc, args={}):
'''Update an existing alarm based on state of other alarms.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields = utils.key_with_slash_to_nested_dict(fields)
fields.pop('alarm_id')
fields['type'] = 'combination'
try:
alarm = cc.alarms.update(args.alarm_id, **fields)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
_display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True,
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: if args.alarm_id is None:
@@ -282,6 +388,32 @@ def do_alarm_delete(cc, args={}):
raise exc.CommandError('Alarm not found: %s' % args.alarm_id) raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True,
help='ID of the alarm state to set.')
@utils.arg('--state', metavar='<STATE>', required=True,
help='State of the alarm, one of: ' + str(ALARM_STATES))
def do_alarm_set_state(cc, args={}):
'''Set the state of an alarm.'''
try:
cc.alarms.set_state(args.alarm_id, args.state)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
state = cc.alarms.get_state(args.alarm_id)
utils.print_dict({'state': state}, wrap=72)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True,
help='ID of the alarm state to show.')
def do_alarm_get_state(cc, args={}):
'''Get the state of an alarm.'''
try:
cc.alarms.set_state(args.alarm_id, args.state)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
state = cc.alarms.get_state(args.alarm_id)
utils.print_dict({'state': state}, wrap=72)
@utils.arg('-q', '--query', metavar='<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={}):

View File

@@ -26,7 +26,7 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
copyright = u'OpenStack LLC' copyright = u'OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True add_function_parentheses = True
@@ -56,7 +56,7 @@ latex_documents = [
'index', 'index',
'%s.tex' % project, '%s.tex' % project,
u'%s Documentation' % project, u'%s Documentation' % project,
u'OpenStack LLC', u'OpenStack Foundation',
'manual' 'manual'
), ),
] ]

View File

@@ -3,4 +3,4 @@ argparse
httplib2 httplib2
iso8601>=0.1.4 iso8601>=0.1.4
PrettyTable>=0.6,<0.8 PrettyTable>=0.6,<0.8
python-keystoneclient>=0.3.0 python-keystoneclient>=0.3.2

View File

@@ -1,11 +1,11 @@
# Install bounded pep8/pyflakes first, then let flake8 install # Install bounded pep8/pyflakes first, then let flake8 install
pep8==1.4.5 pep8==1.4.5
pyflakes==0.7.2 pyflakes>=0.7.2,<0.7.4
flake8==2.0 flake8==2.0
hacking>=0.5.6,<0.7 hacking>=0.5.6,<0.8
coverage>=3.6 coverage>=3.6
discover discover
fixtures>=0.3.12 fixtures>=0.3.14
mox>=0.5.3 mox>=0.5.3
python-subunit python-subunit
sphinx>=1.1.2 sphinx>=1.1.2

View File

@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py26,py27,pep8 envlist = py26,py27,pypy,pep8
[testenv] [testenv]
setenv = VIRTUAL_ENV={envdir} setenv = VIRTUAL_ENV={envdir}