# Copyright (c) 2016 - 2017, Intel Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""``collectd.py``
`Class to abstract collectd operations`
Note:
collectd.conf path is retrieved from testcases/config/setup/setup.json in format::
{
"env": [
{
"id": "213207",
"collectd_conf_path": "/opt/collectd/etc/collectd.conf"
}
],
"cross": {}
}
If "collectd_conf_path" is not specified in setup.json then default value is set: /etc/collectd.conf
Examples of collectd usage in tests::
# If required, stop running collectd service:
env.lhost[1].ui.collectd.stop()
# Start collectd service:
env.lhost[1].ui.collectd.start()
Consistent and valid collectd.conf content is built from OrderedDict object, e.g.::
python_plugin_config = collections.OrderedDict(
(('ModulePath', '"/tmp/"'),
('Interactive', 'false'),
('Import', '"python_module_name"'),
('Module "python_module_name"', {'Test': 'arg1'})))
env.lhost[1].ui.collectd.plugins_config = collections.OrderedDict(
(('Interval', 3),
('AutoLoadPlugins', 'false'),
('LoadPlugin cpu', {}),
('LoadPlugin "csv"', {}),
('LoadPlugin "python"', {'Interval': 5, 'Globals': 'true'}),
('Plugin "csv"', {'DataDir': '"/tmp/csv_data/"'}),
('Plugin "python"', python_plugin_config)))
As shown above, config parts that depend on parameters order should be presented as OrderedDict objects.
Otherwise, dict() object can be used.
Transform data structure into multiline text block::
env.lhost[1].ui.collectd.update_config_file()
If required, in other test the default plugins configuration may be changed, e.g.::
env.lhost[1].ui.collectd.plugins_config['LoadPlugin "csv"'].update({'Interval': 9})
env.lhost[1].ui.collectd.update_config_file()
Some plugins support multiple entries of parameter with same name.
Such case should be presented as::
{param_name: [param1_value, ...]}
Example of resulting collectd.conf file::
Interval 3
AutoLoadPlugins false
<LoadPlugin cpu>
</LoadPlugin>
<LoadPlugin "csv">
Interval 9
</LoadPlugin>
<LoadPlugin "python">
Interval 5
Globals true
</LoadPlugin>
<Plugin "csv">
DataDir "/tmp/csv_data/"
</Plugin>
<Plugin "python">
ModulePath "/tmp/"
Interactive false
Import "python_module_name"
<Module "python_module_name">
Test arg1
</Module>
</Plugin>
Restart collectd service::
env.lhost[1].ui.collectd.restart()
"""
import re
import collections
from io import StringIO
from testlib.linux import service_lib
from testlib.custom_exceptions import CustomException
PARAM_BOILERPLATE = '{indent}{name} {value}\n'
[docs]def build_tagged_section(config_data, indent=None, buffer=None):
"""Fill in data into text block.
Args:
config_data(collections.Mapping): plugins configuration data structure
indent(int): indentation level
buffer(StringIO object): resulting text block
Returns:
str: resulting text block
"""
if not buffer:
buffer = StringIO()
if not indent:
indent = IndentationContext(buffer=buffer)
for k, v in config_data.items():
if isinstance(v, collections.Mapping):
with TagContext(buffer, indent, *k.split(' ', 1)), indent:
build_tagged_section(v, indent, buffer)
elif not isinstance(v, str) and isinstance(v, collections.Iterable):
buffer.write(''.join(PARAM_BOILERPLATE.format(indent=indent.pad, name=k, value=x) for x in v))
else:
buffer.write(PARAM_BOILERPLATE.format(indent=indent.pad, name=k, value=v))
return buffer.getvalue()
[docs]class IndentationContext(object):
def __init__(self, char=' ', count=4, buffer=None):
super().__init__()
self._char = char
self._count = count
self._pad = ''
self._buffer = buffer
@property
def pad(self):
return self._pad
def __enter__(self):
self._pad += self._char * self._count
def __exit__(self, *args, **kwargs):
self._pad = self._pad[:-self._count]
[docs]class TagContext(object):
def __init__(self, buffer, indent, *tag_args):
super().__init__()
self._buffer = buffer
self._indent = indent
assert tag_args
self._tag_args = tag_args
def __enter__(self):
self._buffer.write(self._indent.pad)
self._buffer.write('<')
self._buffer.write(' '.join(self._tag_args))
self._buffer.write('>\n')
def __exit__(self, *args, **kwargs):
self._buffer.write(self._indent.pad)
self._buffer.write('</')
self._buffer.write(self._tag_args[0])
self._buffer.write('>\n')
[docs]class Collectd(object):
SERVICE = 'collectd'
[docs] def __init__(self, cli_send_command):
"""Initialize Collectd class.
"""
super(Collectd, self).__init__()
self.send_command = cli_send_command
self.collectd_conf = None
self.service_manager = service_lib.SpecificServiceManager(self.SERVICE, self.send_command)
# Data structure presenting content of collectd.conf
self.plugins_config = None
[docs] def __getattr__(self, name):
"""Method for getting attribute from service_manager.
Args:
name(str): attribute name
"""
return getattr(self.service_manager, name)
[docs] def __call__(self, cmd, expected_rc):
"""Overloaded call method.
Args:
cmd(str): command to execute
expected_rc(int | set | list | frozenset): expected return code
Returns:
tuple: named tuple
"""
return self.cli_send_command(cmd, expected_rcs=expected_rc)
[docs] def update_config_file(self):
"""Create collectd configuration text and write it to collectd.conf file.
"""
# Determine collectd.conf location in collectd.service file on DUT
out = self.service_manager.cat().stdout
try:
self.collectd_conf = re.search(r'^\s*ExecStart.*-C\s(.+?)\s', out, re.M).group(1)
except AttributeError:
raise CustomException("Incorrect collectd service file.")
# Make provided collectd plugins configuration object accessible
if not self.plugins_config:
raise CustomException("No plugins config defined.")
# Build up text block and make it accessible
config_text = build_tagged_section(self.plugins_config)
self.send_command('cat > {} <<EOF\n{}\nEOF'.format(self.collectd_conf, config_text))