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]
                       <log-level>

Implements: blueprint dynamic-log-levels
Depends-On: Ia5ef81135044733f1dd3970a116f97457b0371de
Change-Id: I50b5ea93464b15751e45afa0a05475651eeb53a8
This commit is contained in:
Gorka Eguileor 2017-03-14 13:52:14 +01:00
parent eaee84097a
commit 6a00d30e96
6 changed files with 204 additions and 0 deletions

View File

@ -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
#

View File

@ -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)

View File

@ -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: <log-level>'
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)

View File

@ -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 '<LogLevel: binary=%s host=%s prefix=%s level=%s>' % (
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

View File

@ -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='<log-level>',
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)

View File

@ -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.