Merge "Add log retrieval to Cassandra"

This commit is contained in:
Jenkins 2016-08-18 17:25:39 +00:00 committed by Gerrit Code Review
commit 754a2dee7a
10 changed files with 127 additions and 2 deletions

View File

@ -0,0 +1,3 @@
---
features:
- Enable database log retrieval on Cassandra instances.

View File

@ -44,3 +44,4 @@ osprofiler>=1.3.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0
oslo.db>=4.10.0 # Apache-2.0 oslo.db>=4.10.0 # Apache-2.0
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
xmltodict>=0.10.1 # MIT

View File

@ -862,8 +862,12 @@ cassandra_opts = [
cfg.ListOpt('ignore_dbs', default=['system', 'system_auth', cfg.ListOpt('ignore_dbs', default=['system', 'system_auth',
'system_traces'], 'system_traces'],
help='Databases to exclude when listing databases.'), help='Databases to exclude when listing databases.'),
cfg.StrOpt('guest_log_exposed_logs', default='', cfg.StrOpt('guest_log_exposed_logs', default='system',
help='List of Guest Logs to expose for publishing.'), help='List of Guest Logs to expose for publishing.'),
cfg.StrOpt('system_log_level',
choices=['ALL', 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'],
default='INFO',
help='Cassandra log verbosity.'),
cfg.BoolOpt('cluster_support', default=True, cfg.BoolOpt('cluster_support', default=True,
help='Enable clusters to be created and managed.'), help='Enable clusters to be created and managed.'),
cfg.StrOpt('api_strategy', cfg.StrOpt('api_strategy',

View File

@ -21,6 +21,7 @@ import json
import re import re
import six import six
from six.moves.configparser import SafeConfigParser from six.moves.configparser import SafeConfigParser
import xmltodict
import yaml import yaml
@ -427,3 +428,16 @@ class Base64Codec(StreamCodec):
# py27 & py34 seem to understand bytearray the same # py27 & py34 seem to understand bytearray the same
return bytearray([item for item in base64.b64decode(stream)]) return bytearray([item for item in base64.b64decode(stream)])
class XmlCodec(StreamCodec):
def __init__(self, encoding='utf-8'):
self._encoding = encoding
def serialize(self, dict_data):
return xmltodict.unparse(
dict_data, output=None, encoding=self._encoding, pretty=True)
def deserialize(self, stream):
return xmltodict.parse(stream, encoding=self._encoding)

View File

@ -25,6 +25,7 @@ from trove.common.notification import EndNotification
from trove.guestagent import backup from trove.guestagent import backup
from trove.guestagent.datastore.experimental.cassandra import service from trove.guestagent.datastore.experimental.cassandra import service
from trove.guestagent.datastore import manager from trove.guestagent.datastore import manager
from trove.guestagent import guest_log
from trove.guestagent import volume from trove.guestagent import volume
@ -34,6 +35,8 @@ CONF = cfg.CONF
class Manager(manager.Manager): class Manager(manager.Manager):
GUEST_LOG_DEFS_SYSTEM_LABEL = 'system'
def __init__(self, manager_name='cassandra'): def __init__(self, manager_name='cassandra'):
super(Manager, self).__init__(manager_name) super(Manager, self).__init__(manager_name)
self._app = None self._app = None
@ -62,6 +65,29 @@ class Manager(manager.Manager):
def configuration_manager(self): def configuration_manager(self):
return self.app.configuration_manager return self.app.configuration_manager
@property
def datastore_log_defs(self):
system_log_file = self.validate_log_file(
self.app.cassandra_system_log_file, self.app.cassandra_owner)
return {
self.GUEST_LOG_DEFS_SYSTEM_LABEL: {
self.GUEST_LOG_TYPE_LABEL: guest_log.LogType.USER,
self.GUEST_LOG_USER_LABEL: self.app.cassandra_owner,
self.GUEST_LOG_FILE_LABEL: system_log_file
}
}
def guest_log_enable(self, context, log_name, disable):
if disable:
LOG.debug("Disabling system log.")
self.app.set_logging_level('OFF')
else:
log_level = CONF.get(self.manager_name).get('system_log_level')
LOG.debug("Enabling system log with logging level: %s" % log_level)
self.app.set_logging_level(log_level)
return False
def restart(self, context): def restart(self, context):
self.app.restart() self.app.restart()

View File

@ -32,6 +32,7 @@ from trove.common import instance as rd_instance
from trove.common.stream_codecs import IniCodec from trove.common.stream_codecs import IniCodec
from trove.common.stream_codecs import PropertiesCodec from trove.common.stream_codecs import PropertiesCodec
from trove.common.stream_codecs import SafeYamlCodec from trove.common.stream_codecs import SafeYamlCodec
from trove.common.stream_codecs import XmlCodec
from trove.common import utils from trove.common import utils
from trove.guestagent.common.configuration import ConfigurationManager from trove.guestagent.common.configuration import ConfigurationManager
from trove.guestagent.common.configuration import OneFileOverrideStrategy from trove.guestagent.common.configuration import OneFileOverrideStrategy
@ -62,6 +63,7 @@ class CassandraApp(object):
CASSANDRA_CONF_FILE = "cassandra.yaml" CASSANDRA_CONF_FILE = "cassandra.yaml"
CASSANDRA_TOPOLOGY_FILE = 'cassandra-rackdc.properties' CASSANDRA_TOPOLOGY_FILE = 'cassandra-rackdc.properties'
CASSANDRA_LOGBACK_FILE = "logback.xml"
_TOPOLOGY_CODEC = PropertiesCodec( _TOPOLOGY_CODEC = PropertiesCodec(
delimiter='=', unpack_singletons=True, string_mappings={ delimiter='=', unpack_singletons=True, string_mappings={
@ -82,6 +84,14 @@ class CassandraApp(object):
SafeYamlCodec(default_flow_style=False), requires_root=True, SafeYamlCodec(default_flow_style=False), requires_root=True,
override_strategy=OneFileOverrideStrategy(revision_dir)) override_strategy=OneFileOverrideStrategy(revision_dir))
lb_revision_dir = guestagent_utils.build_file_path(
os.path.dirname(self.cassandra_logback), 'logback-overrides')
self.logback_conf_manager = ConfigurationManager(
self.cassandra_logback,
self.cassandra_owner, self.cassandra_owner,
XmlCodec(), requires_root=True,
override_strategy=OneFileOverrideStrategy(lb_revision_dir))
@property @property
def service_candidates(self): def service_candidates(self):
return ['cassandra'] return ['cassandra']
@ -117,6 +127,20 @@ class CassandraApp(object):
def cassandra_working_dir(self): def cassandra_working_dir(self):
return "/var/lib/cassandra" return "/var/lib/cassandra"
@property
def cassandra_system_log_file(self):
return guestagent_utils.build_file_path(
self.cassandra_log_dir, 'system', 'log')
@property
def cassandra_log_dir(self):
return "/var/log/cassandra"
@property
def cassandra_logback(self):
return guestagent_utils.build_file_path(self.cassandra_conf_dir,
self.CASSANDRA_LOGBACK_FILE)
@property @property
def default_superuser_name(self): def default_superuser_name(self):
return "cassandra" return "cassandra"
@ -686,6 +710,16 @@ class CassandraApp(object):
# <keyspace> ( <table> ... ) # <keyspace> ( <table> ... )
self._run_nodetool_command('flush', keyspace, *tables) self._run_nodetool_command('flush', keyspace, *tables)
def set_logging_level(self, log_level):
"""Set the log Cassandra's system log verbosity level.
"""
# Apply the change at runtime.
self._run_nodetool_command('setlogginglevel', 'root', log_level)
# Persist the change.
self.logback_conf_manager.apply_system_override(
{'configuration': {'root': {'@level': log_level}}})
def _run_nodetool_command(self, cmd, *args, **kwargs): def _run_nodetool_command(self, cmd, *args, **kwargs):
"""Execute a nodetool command on this node. """Execute a nodetool command on this node.
""" """

View File

@ -157,3 +157,6 @@ class CassandraHelper(TestHelper):
def get_invalid_groups(self): def get_invalid_groups(self):
return [{'sstable_preemptive_open_interval_in_mb': -1}, return [{'sstable_preemptive_open_interval_in_mb': -1},
{'sstable_preemptive_open_interval_in_mb': 'string_value'}] {'sstable_preemptive_open_interval_in_mb': 'string_value'}]
def get_exposed_user_log_names(self):
return ['system']

View File

@ -674,3 +674,12 @@ class GuestLogRunner(TestRunner):
expected_type=guest_log.LogType.SYS.name, expected_type=guest_log.LogType.SYS.name,
expected_status=guest_log.LogStatus.Ready.name, expected_status=guest_log.LogStatus.Ready.name,
expected_published=0, expected_pending=1) expected_published=0, expected_pending=1)
class CassandraGuestLogRunner(GuestLogRunner):
def run_test_log_show(self):
self.assert_log_show(self.auth_client,
self._get_exposed_user_log_name(),
expected_published=0,
expected_pending=None)

View File

@ -775,3 +775,22 @@ class GuestAgentCassandraDBManagerTest(DatastoreManagerTest):
'list_superusers', 'list_superusers',
return_value=[trove_admin, other_admin]): return_value=[trove_admin, other_admin]):
self.assertTrue(self.manager.is_root_enabled(self.context)) self.assertTrue(self.manager.is_root_enabled(self.context))
def test_guest_log_enable(self):
self._assert_guest_log_enable(False, 'INFO')
self._assert_guest_log_enable(True, 'OFF')
def _assert_guest_log_enable(self, disable, expected_level):
with patch.multiple(
self.manager._app,
logback_conf_manager=DEFAULT,
_run_nodetool_command=DEFAULT
) as app_mocks:
self.assertFalse(self.manager.guest_log_enable(
Mock(), Mock(), disable))
(app_mocks['logback_conf_manager'].apply_system_override.
assert_called_once_with(
{'configuration': {'root': {'@level': expected_level}}}))
app_mocks['_run_nodetool_command'].assert_called_once_with(
'setlogginglevel', 'root', expected_level)

View File

@ -26,7 +26,7 @@ from testtools import ExpectedException
from trove.common import exception from trove.common import exception
from trove.common.stream_codecs import ( from trove.common.stream_codecs import (
Base64Codec, IdentityCodec, IniCodec, JsonCodec, Base64Codec, IdentityCodec, IniCodec, JsonCodec,
KeyValueCodec, PropertiesCodec, YamlCodec) KeyValueCodec, PropertiesCodec, XmlCodec, YamlCodec)
from trove.common import utils from trove.common import utils
from trove.guestagent.common import guestagent_utils from trove.guestagent.common import guestagent_utils
from trove.guestagent.common import operating_system from trove.guestagent.common import operating_system
@ -132,6 +132,18 @@ class TestOperatingSystem(trove_testtools.TestCase):
self._test_file_codec(data, JsonCodec()) self._test_file_codec(data, JsonCodec())
def test_xml_file_codec(self):
data = {'document': {'@name': 'mydocument', '@ttl': '10',
'author': {'@name': 'Jycll ;-)'},
'page': [{'@number': '1', 'paragraph':
['lorem ipsum', 'more lorem ipsum']},
{'@number': '1', 'paragraph':
['lorem ipsum', 'more lorem ipsum']}]
}
}
self._test_file_codec(data, XmlCodec())
def _test_file_codec(self, data, read_codec, write_codec=None, def _test_file_codec(self, data, read_codec, write_codec=None,
expected_data=None, expected_data=None,
expected_exception=None, expected_exception=None,