From 6a00d30e966d5fbd24cc8817afae32d9969adfb5 Mon Sep 17 00:00:00 2001 From: Gorka Eguileor Date: Tue, 14 Mar 2017 13:52:14 +0100 Subject: [PATCH] Dynamic log level support This patch adds support for microversion 3.32 allowing setting and querying Cinder services' log levels. Two new commands are available: cinder service-get-log [--binary {,*,cinder-api,cinder-volume,cinder-scheduler, cinder-backup}] [--server SERVER] [--prefix PREFIX] cinder service-set-log [--binary {,*,cinder-api,cinder-volume,cinder-scheduler, cinder-backup}] [--server SERVER] [--prefix PREFIX] Implements: blueprint dynamic-log-levels Depends-On: Ia5ef81135044733f1dd3970a116f97457b0371de Change-Id: I50b5ea93464b15751e45afa0a05475651eeb53a8 --- cinderclient/tests/unit/v3/fakes.py | 10 +++ cinderclient/tests/unit/v3/test_services.py | 42 ++++++++++ cinderclient/tests/unit/v3/test_shell.py | 76 +++++++++++++++++++ cinderclient/v3/services.py | 29 +++++++ cinderclient/v3/shell.py | 41 ++++++++++ .../service_dynamic_log-bd81d93c73fc1570.yaml | 6 ++ 6 files changed, 204 insertions(+) create mode 100644 releasenotes/notes/service_dynamic_log-bd81d93c73fc1570.yaml diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 7b6ea71..c06ca3e 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -547,6 +547,16 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient): } return 200, {}, {'message': message} + def put_os_services_set_log(self, body): + return (202, {}, {}) + + def put_os_services_get_log(self, body): + levels = [{'binary': 'cinder-api', 'host': 'host1', + 'levels': {'prefix1': 'DEBUG', 'prefix2': 'INFO'}}, + {'binary': 'cinder-volume', 'host': 'host@backend#pool', + 'levels': {'prefix3': 'WARNING', 'prefix4': 'ERROR'}}] + return (200, {}, {'log_levels': levels}) + # # resource filters # diff --git a/cinderclient/tests/unit/v3/test_services.py b/cinderclient/tests/unit/v3/test_services.py index ac45298..92f7903 100644 --- a/cinderclient/tests/unit/v3/test_services.py +++ b/cinderclient/tests/unit/v3/test_services.py @@ -41,3 +41,45 @@ class ServicesTest(utils.TestCase): client = fakes.FakeClient(version_header='3.0') svs = client.services.server_api_version() [self.assertIsInstance(s, services.Service) for s in svs] + + def test_set_log_levels(self): + expected = {'level': 'debug', 'binary': 'cinder-api', + 'server': 'host1', 'prefix': 'sqlalchemy.'} + + cs = fakes.FakeClient(version_header='3.32') + cs.services.set_log_levels(expected['level'], expected['binary'], + expected['server'], expected['prefix']) + + cs.assert_called('PUT', '/os-services/set-log', body=expected) + + def test_get_log_levels(self): + expected = {'binary': 'cinder-api', 'server': 'host1', + 'prefix': 'sqlalchemy.'} + + cs = fakes.FakeClient(version_header='3.32') + result = cs.services.get_log_levels(expected['binary'], + expected['server'], + expected['prefix']) + + cs.assert_called('PUT', '/os-services/get-log', body=expected) + expected = [services.LogLevel(cs.services, + {'binary': 'cinder-api', 'host': 'host1', + 'prefix': 'prefix1', 'level': 'DEBUG'}, + loaded=True), + services.LogLevel(cs.services, + {'binary': 'cinder-api', 'host': 'host1', + 'prefix': 'prefix2', 'level': 'INFO'}, + loaded=True), + services.LogLevel(cs.services, + {'binary': 'cinder-volume', + 'host': 'host@backend#pool', + 'prefix': 'prefix3', + 'level': 'WARNING'}, + loaded=True), + services.LogLevel(cs.services, + {'binary': 'cinder-volume', + 'host': 'host@backend#pool', + 'prefix': 'prefix4', 'level': 'ERROR'}, + loaded=True)] + # Since it will be sorted by the prefix we can compare them directly + self.assertListEqual(expected, result) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index bfd2d93..96dd7cf 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -18,6 +18,7 @@ import ddt import fixtures import mock from requests_mock.contrib import fixture as requests_mock_fixture +import six from cinderclient import client from cinderclient import exceptions @@ -788,3 +789,78 @@ class ShellTest(utils.TestCase): self.run_command(cmd) expected = {'list_replication_targets': {}} self.assert_called('POST', '/groups/1234/action', body=expected) + + @mock.patch('cinderclient.v3.services.ServiceManager.get_log_levels') + def test_service_get_log_before_3_32(self, get_levels_mock): + self.assertRaises(SystemExit, + self.run_command, '--os-volume-api-version 3.28 ' + 'service-get-log') + get_levels_mock.assert_not_called() + + @mock.patch('cinderclient.v3.services.ServiceManager.get_log_levels') + @mock.patch('cinderclient.utils.print_list') + def test_service_get_log_no_params(self, print_mock, get_levels_mock): + self.run_command('--os-volume-api-version 3.32 service-get-log') + get_levels_mock.assert_called_once_with('', '', '') + print_mock.assert_called_once_with(get_levels_mock.return_value, + ('Binary', 'Host', 'Prefix', + 'Level')) + + @ddt.data('*', 'cinder-api', 'cinder-volume', 'cinder-scheduler', + 'cinder-backup') + @mock.patch('cinderclient.v3.services.ServiceManager.get_log_levels') + @mock.patch('cinderclient.utils.print_list') + def test_service_get_log(self, binary, print_mock, get_levels_mock): + server = 'host1' + prefix = 'sqlalchemy' + + self.run_command('--os-volume-api-version 3.32 service-get-log ' + '--binary %s --server %s --prefix %s' % ( + binary, server, prefix)) + get_levels_mock.assert_called_once_with(binary, server, prefix) + print_mock.assert_called_once_with(get_levels_mock.return_value, + ('Binary', 'Host', 'Prefix', + 'Level')) + + @mock.patch('cinderclient.v3.services.ServiceManager.set_log_levels') + def test_service_set_log_before_3_32(self, set_levels_mock): + self.assertRaises(SystemExit, + self.run_command, '--os-volume-api-version 3.28 ' + 'service-set-log debug') + set_levels_mock.assert_not_called() + + @mock.patch('cinderclient.v3.services.ServiceManager.set_log_levels') + @mock.patch('cinderclient.shell.CinderClientArgumentParser.error') + def test_service_set_log_missing_required(self, error_mock, + set_levels_mock): + error_mock.side_effect = SystemExit + self.assertRaises(SystemExit, + self.run_command, '--os-volume-api-version 3.32 ' + 'service-set-log') + set_levels_mock.assert_not_called() + # Different error message from argparse library in Python 2 and 3 + if six.PY3: + msg = 'the following arguments are required: ' + else: + msg = 'too few arguments' + error_mock.assert_called_once_with(msg) + + @ddt.data('debug', 'DEBUG', 'info', 'INFO', 'warning', 'WARNING', 'error', + 'ERROR') + @mock.patch('cinderclient.v3.services.ServiceManager.set_log_levels') + def test_service_set_log_min_params(self, level, set_levels_mock): + self.run_command('--os-volume-api-version 3.32 ' + 'service-set-log %s' % level) + set_levels_mock.assert_called_once_with(level, '', '', '') + + @ddt.data('*', 'cinder-api', 'cinder-volume', 'cinder-scheduler', + 'cinder-backup') + @mock.patch('cinderclient.v3.services.ServiceManager.set_log_levels') + def test_service_set_log_levels(self, binary, set_levels_mock): + level = 'debug' + server = 'host1' + prefix = 'sqlalchemy.' + self.run_command('--os-volume-api-version 3.32 ' + 'service-set-log %s --binary %s --server %s ' + '--prefix %s' % (level, binary, server, prefix)) + set_levels_mock.assert_called_once_with(level, binary, server, prefix) diff --git a/cinderclient/v3/services.py b/cinderclient/v3/services.py index bd8a6c4..55d3ee4 100644 --- a/cinderclient/v3/services.py +++ b/cinderclient/v3/services.py @@ -18,11 +18,18 @@ service interface """ from cinderclient import api_versions +from cinderclient import base from cinderclient.v2 import services Service = services.Service +class LogLevel(base.Resource): + def __repr__(self): + return '' % ( + self.binary, self.host, self.prefix, self.level) + + class ServiceManager(services.ServiceManager): @api_versions.wraps("3.0") def server_api_version(self): @@ -36,3 +43,25 @@ class ServiceManager(services.ServiceManager): return self._get_with_base_url("", response_key='versions') except LookupError: return [] + + @api_versions.wraps("3.32") + def set_log_levels(self, level, binary, server, prefix): + """Set log level for services.""" + body = {'level': level, 'binary': binary, 'server': server, + 'prefix': prefix} + return self._update("/os-services/set-log", body) + + @api_versions.wraps("3.32") + def get_log_levels(self, binary, server, prefix): + """Get log levels for services.""" + body = {'binary': binary, 'server': server, 'prefix': prefix} + response = self._update("/os-services/get-log", body) + + log_levels = [] + for entry in response['log_levels']: + entry_levels = sorted(entry['levels'].items(), key=lambda x: x[0]) + for prefix, level in entry_levels: + log_dict = {'binary': entry['binary'], 'host': entry['host'], + 'prefix': prefix, 'level': level} + log_levels.append(LogLevel(self, log_dict, loaded=True)) + return log_levels diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 3b73b15..a389f09 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -1922,3 +1922,44 @@ def do_version_list(cs, args): print("\nServer supported API versions:") utils.print_list(result, columns) + + +@api_versions.wraps('3.32') +@utils.arg('level', + metavar='', + choices=('INFO', 'WARNING', 'ERROR', 'DEBUG', + 'info', 'warning', 'error', 'debug'), + help='Desired log level.') +@utils.arg('--binary', + choices=('', '*', 'cinder-api', 'cinder-volume', 'cinder-scheduler', + 'cinder-backup'), + default='', + help='Binary to change.') +@utils.arg('--server', + default='', + help='Host or cluster value for service.') +@utils.arg('--prefix', + default='', + help='Prefix for the log. ie: "cinder.volume.drivers.".') +def do_service_set_log(cs, args): + cs.services.set_log_levels(args.level, args.binary, args.server, + args.prefix) + + +@api_versions.wraps('3.32') +@utils.arg('--binary', + choices=('', '*', 'cinder-api', 'cinder-volume', 'cinder-scheduler', + 'cinder-backup'), + default='', + help='Binary to query.') +@utils.arg('--server', + default='', + help='Host or cluster value for service.') +@utils.arg('--prefix', + default='', + help='Prefix for the log. ie: "sqlalchemy.".') +def do_service_get_log(cs, args): + log_levels = cs.services.get_log_levels(args.binary, args.server, + args.prefix) + columns = ('Binary', 'Host', 'Prefix', 'Level') + utils.print_list(log_levels, columns) diff --git a/releasenotes/notes/service_dynamic_log-bd81d93c73fc1570.yaml b/releasenotes/notes/service_dynamic_log-bd81d93c73fc1570.yaml new file mode 100644 index 0000000..e71c7db --- /dev/null +++ b/releasenotes/notes/service_dynamic_log-bd81d93c73fc1570.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Support microversion 3.32 that allows dynamically changing and querying + Cinder services' log levels with ``service-set-log`` and + ``service-get-log`` commands.