diff --git a/ceilometerclient/__init__.py b/ceilometerclient/__init__.py index d6efb69..b73e453 100644 --- a/ceilometerclient/__init__.py +++ b/ceilometerclient/__init__.py @@ -12,10 +12,4 @@ __all__ = ['__version__'] -import pbr.version - -version_info = pbr.version.VersionInfo('python-ceilometerclient') -try: - __version__ = version_info.version_string() -except AttributeError: - __version__ = None +__version__ = "REDHATCEILOMETERCLIENTVERSION" diff --git a/ceilometerclient/shell.py b/ceilometerclient/shell.py index e8f5752..7356789 100644 --- a/ceilometerclient/shell.py +++ b/ceilometerclient/shell.py @@ -30,6 +30,20 @@ from ceilometerclient.openstack.common import cliutils +def _positive_non_zero_int(argument_value): + if argument_value is None: + return None + try: + value = int(argument_value) + except ValueError: + msg = "%s must be an integer" % argument_value + raise argparse.ArgumentTypeError(msg) + if value <= 0: + msg = "%s must be greater than 0" % argument_value + raise argparse.ArgumentTypeError(msg) + return value + + class CeilometerShell(object): def get_base_parser(self): @@ -64,6 +78,7 @@ def get_base_parser(self): parser.add_argument('--timeout', default=600, + type=_positive_non_zero_int, help='Number of seconds to wait for a response.') parser.add_argument('--ceilometer-url', diff --git a/ceilometerclient/tests/test_client.py b/ceilometerclient/tests/test_client.py index faed8ee..02735c5 100644 --- a/ceilometerclient/tests/test_client.py +++ b/ceilometerclient/tests/test_client.py @@ -72,3 +72,38 @@ def test_client_without_auth_plugin(self): def test_client_with_auth_plugin(self): c = self.create_client(FAKE_ENV, api_version=2) self.assertIsInstance(c.auth_plugin, str) + + def test_v2_client_timeout_invalid_value(self): + env = FAKE_ENV.copy() + env['timeout'] = 'abc' + self.assertRaises(ValueError, self.create_client, env) + env['timeout'] = '1.5' + self.assertRaises(ValueError, self.create_client, env) + + def _test_v2_client_timeout_integer(self, timeout, expected_value): + env = FAKE_ENV.copy() + env['timeout'] = timeout + expected = { + 'auth_plugin': 'fake_auth', + 'timeout': expected_value, + 'original_ip': None, + 'http': None, + 'region_name': None, + 'verify': None, + 'timings': None, + 'keyring_saver': None, + 'cert': None, + 'endpoint_type': None, + 'user_agent': None, + 'debug': None, + } + cls = 'ceilometerclient.openstack.common.apiclient.client.HTTPClient' + with mock.patch(cls) as mocked: + self.create_client(env) + mocked.assert_called_with(**expected) + + def test_v2_client_timeout_zero(self): + self._test_v2_client_timeout_integer(0, None) + + def test_v2_client_timeout_valid_value(self): + self._test_v2_client_timeout_integer(30, 30) diff --git a/ceilometerclient/tests/test_shell.py b/ceilometerclient/tests/test_shell.py index cadfcc8..53a1952 100644 --- a/ceilometerclient/tests/test_shell.py +++ b/ceilometerclient/tests/test_shell.py @@ -148,3 +148,32 @@ def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): self.make_env(FAKE_V3_ENV) args = ['event-list'] self.assertRaises(SystemExit, ceilometer_shell.main, args) + + +class ShellTimeoutTest(ShellTestBase): + + @mock.patch('sys.stderr', new=six.StringIO()) + def _test_timeout(self, timeout, expected_msg): + args = ['--timeout', timeout, 'alarm-list'] + self.assertRaises(SystemExit, ceilometer_shell.main, args) + self.assertEqual(expected_msg, sys.stderr.getvalue().splitlines()[-1]) + + def test_timeout_invalid_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + 'abc must be an integer') + self._test_timeout('abc', expected_msg) + + def test_timeout_negative_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '-1 must be greater than 0') + self._test_timeout('-1', expected_msg) + + def test_timeout_float_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '1.5 must be an integer') + self._test_timeout('1.5', expected_msg) + + def test_timeout_zero(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '0 must be greater than 0') + self._test_timeout('0', expected_msg) diff --git a/ceilometerclient/tests/v2/test_shell.py b/ceilometerclient/tests/v2/test_shell.py index 632684f..0e5c189 100644 --- a/ceilometerclient/tests/v2/test_shell.py +++ b/ceilometerclient/tests/v2/test_shell.py @@ -24,6 +24,7 @@ from ceilometerclient import exc from ceilometerclient import shell as base_shell +from ceilometerclient.tests import test_shell from ceilometerclient.tests import utils from ceilometerclient.v2 import alarms from ceilometerclient.v2 import samples @@ -901,3 +902,150 @@ def test_obsolete_alarm_id(self): 'alarm-combination-update', 'alarm-delete', 'alarm-state-get', 'alarm-history']: self._test_entity_obsoleted('alarm_id', 'abcde', False, method) + + +class ShellEventListCommandTest(utils.BaseTestCase): + + EVENTS = [ + { + "traits": [], + "generated": "2015-01-12T04:03:25.741471", + "message_id": "fb2bef58-88af-4380-8698-e0f18fcf452d", + "event_type": "compute.instance.create.start", + "traits": [{ + "name": "state", + "type": "string", + "value": "building", + }], + }, + { + "traits": [], + "generated": "2015-01-12T04:03:28.452495", + "message_id": "9b20509a-576b-4995-acfa-1a24ee5cf49f", + "event_type": "compute.instance.create.end", + "traits": [{ + "name": "state", + "type": "string", + "value": "active", + }], + }, + ] + + def setUp(self): + super(ShellEventListCommandTest, self).setUp() + self.cc = mock.Mock() + self.args = mock.Mock() + self.args.query = None + self.args.no_traits = None + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_event_list(self): + ret_events = [events.Event(mock.Mock(), event) + for event in self.EVENTS] + self.cc.events.list.return_value = ret_events + ceilometer_shell.do_event_list(self.cc, self.args) + self.assertEqual('''\ ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +| Message ID | Event Type |\ + Generated | Traits | ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start |\ + 2015-01-12T04:03:25.741471 | +-------+--------+----------+ | +| | |\ + | | name | type | value | | +| | |\ + | +-------+--------+----------+ | +| | |\ + | | state | string | building | | +| | |\ + | +-------+--------+----------+ | +| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end |\ + 2015-01-12T04:03:28.452495 | +-------+--------+--------+ | +| | |\ + | | name | type | value | | +| | |\ + | +-------+--------+--------+ | +| | |\ + | | state | string | active | | +| | |\ + | +-------+--------+--------+ | ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +''', sys.stdout.getvalue()) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_event_list_no_traits(self): + self.args.no_traits = True + ret_events = [events.Event(mock.Mock(), event) + for event in self.EVENTS] + self.cc.events.list.return_value = ret_events + ceilometer_shell.do_event_list(self.cc, self.args) + self.assertEqual('''\ ++--------------------------------------+-------------------------------\ ++----------------------------+ +| Message ID | Event Type \ +| Generated | ++--------------------------------------+-------------------------------\ ++----------------------------+ +| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start \ +| 2015-01-12T04:03:25.741471 | +| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end \ +| 2015-01-12T04:03:28.452495 | ++--------------------------------------+-------------------------------\ ++----------------------------+ +''', sys.stdout.getvalue()) + + +class ShellShadowedArgsTest(test_shell.ShellTestBase): + + def _test_project_id_alarm(self, command, args, method): + self.make_env(test_shell.FAKE_V2_ENV) + cli_args = [ + '--os-project-id', '0ba30185ddf44834914a0b859d244c56', + '--debug', command, + '--project-id', 'the-project-id-i-want-to-set', + '--name', 'project-id-test'] + args + with mock.patch.object(alarms.AlarmManager, method) as mocked: + base_shell.main(cli_args) + args, kwargs = mocked.call_args + self.assertEqual('the-project-id-i-want-to-set', + kwargs.get('project_id')) + + def test_project_id_threshold_alarm(self): + cli_args = ['--meter-name', 'cpu', '--threshold', '90'] + self._test_project_id_alarm('alarm-create', cli_args, 'create') + self._test_project_id_alarm('alarm-threshold-create', + cli_args, 'create') + cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] + self._test_project_id_alarm('alarm-update', cli_args, 'update') + self._test_project_id_alarm('alarm-threshold-update', + cli_args, 'update') + + def test_project_id_combination_alarm(self): + cli_args = ['--alarm_ids', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', + '--alarm_ids', 'b189bcca-0a7b-49a9-a244-a927ac291881'] + self._test_project_id_alarm('alarm-combination-create', + cli_args, 'create') + cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] + self._test_project_id_alarm('alarm-combination-update', + cli_args, 'update') + + @mock.patch.object(samples.OldSampleManager, 'create') + def test_project_id_sample_create(self, mocked): + self.make_env(test_shell.FAKE_V2_ENV) + cli_args = [ + '--os-project-id', '0ba30185ddf44834914a0b859d244c56', + '--debug', 'sample-create', + '--project-id', 'the-project-id-i-want-to-set', + '--resource-id', 'b666633d-9bb6-4e05-89c0-ee5a8752fb0b', + '--meter-name', 'cpu', + '--meter-type', 'cumulative', + '--meter-unit', 'ns', + '--sample-volume', '10086', + ] + base_shell.main(cli_args) + args, kwargs = mocked.call_args + self.assertEqual('the-project-id-i-want-to-set', + kwargs.get('project_id')) diff --git a/ceilometerclient/v2/client.py b/ceilometerclient/v2/client.py index 95be8f5..85caf36 100644 --- a/ceilometerclient/v2/client.py +++ b/ceilometerclient/v2/client.py @@ -32,11 +32,14 @@ class Client(object): """Client for the Ceilometer v2 API. - :param string endpoint: A user-supplied endpoint URL for the ceilometer + :param endpoint: A user-supplied endpoint URL for the ceilometer service. - :param function token: Provides token for authentication. - :param integer timeout: Allows customization of the timeout for client - http requests. (optional) + :type endpoint: string + :param token: Provides token for authentication. + :type token: function + :param timeout: Allows customization of the timeout for client + http requests. (optional) + :type timeout: integer """ def __init__(self, *args, **kwargs): @@ -44,6 +47,13 @@ def __init__(self, *args, **kwargs): """Initialize a new client for the Ceilometer v2 API.""" self.auth_plugin = kwargs.get('auth_plugin') \ or ceiloclient.get_auth_plugin(*args, **kwargs) + + timeout = kwargs.get('timeout') + if timeout is not None: + timeout = int(timeout) + if timeout <= 0: + timeout = None + self.client = client.HTTPClient( auth_plugin=self.auth_plugin, region_name=kwargs.get('region_name'), @@ -51,7 +61,7 @@ def __init__(self, *args, **kwargs): original_ip=kwargs.get('original_ip'), verify=kwargs.get('verify'), cert=kwargs.get('cacert'), - timeout=kwargs.get('timeout'), + timeout=timeout, timings=kwargs.get('timings'), keyring_saver=kwargs.get('keyring_saver'), debug=kwargs.get('debug'), diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py index aa6b4b1..c3f96a8 100644 --- a/ceilometerclient/v2/shell.py +++ b/ceilometerclient/v2/shell.py @@ -145,10 +145,22 @@ def do_sample_list(cc, args): sortby=None) -@utils.arg('--project-id', metavar='', +def _restore_shadowed_arg(shadowed, observed): + def wrapper(func): + @functools.wraps(func) + def wrapped(cc, args): + v = getattr(args, observed, None) + setattr(args, shadowed, v) + return func(cc, args) + return wrapped + return wrapper + + +@utils.arg('--project-id', metavar='', + dest='sample_project_id', help='Tenant to associate with sample ' '(only settable by admin users).') -@utils.arg('--user-id', metavar='', +@utils.arg('--user-id', metavar='', help='User to associate with sample ' '(only settable by admin users).') @utils.arg('-r', '--resource-id', metavar='', required=True, @@ -166,12 +178,15 @@ def do_sample_list(cc, args): 'key-value pairs e.g. {"key":"value"}.') @utils.arg('--timestamp', metavar='', help='The sample timestamp.') +@_restore_shadowed_arg('project_id', 'sample_project_id') def do_sample_create(cc, args={}): - '''Create a sample.''' - arg_to_field_mapping = {'meter_name': 'counter_name', - 'meter_unit': 'counter_unit', - 'meter_type': 'counter_type', - 'sample_volume': 'counter_volume'} + """Create a sample.""" + arg_to_field_mapping = { + 'meter_name': 'counter_name', + 'meter_unit': 'counter_unit', + 'meter_type': 'counter_type', + 'sample_volume': 'counter_volume', + } fields = {} for var in vars(args).items(): k, v = var[0], var[1] @@ -361,10 +376,11 @@ def common_alarm_arguments(create=False): def _wrapper(func): @utils.arg('--name', metavar='', required=create, help='Name of the alarm (must be unique per tenant).') - @utils.arg('--project-id', metavar='', + @utils.arg('--project-id', metavar='', + dest='alarm_project_id', help='Tenant to associate with alarm ' '(only settable by admin users).') - @utils.arg('--user-id', metavar='', + @utils.arg('--user-id', metavar='', help='User to associate with alarm ' '(only settable by admin users).') @utils.arg('--description', metavar='', @@ -429,6 +445,7 @@ def _wrapped(*args, **kwargs): default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') def do_alarm_create(cc, args={}): '''Create a new alarm (Deprecated). Use alarm-threshold-create instead.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) @@ -467,6 +484,7 @@ def do_alarm_create(cc, args={}): default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') 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())) @@ -493,6 +511,7 @@ def do_alarm_threshold_create(cc, args={}): default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') 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())) @@ -534,6 +553,7 @@ def do_alarm_combination_create(cc, args={}): metavar='{True|False}', type=strutils.bool_from_string, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') def do_alarm_update(cc, args={}): '''Update an existing alarm (Deprecated).''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) @@ -585,6 +605,7 @@ def do_alarm_update(cc, args={}): metavar='{True|False}', type=strutils.bool_from_string, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') 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())) @@ -623,6 +644,7 @@ def do_alarm_threshold_update(cc, args={}): metavar='{True|False}', type=strutils.bool_from_string, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state.')) +@_restore_shadowed_arg('project_id', 'alarm_project_id') 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())) diff --git a/setup.py b/setup.py index 7363757..09230a0 100644 --- a/setup.py +++ b/setup.py @@ -26,5 +26,4 @@ pass setuptools.setup( - setup_requires=['pbr'], pbr=True)