Extend InfluxDB check

Added autodetection of influxdb.
Plugin configures process and http_check
monitoring.

Added note informing that this auto-plugin
can be extended further with retrieving
internal metrics of InfluxDB.

Also, marked MonInfluxDB as depracated in
favour of InfluxDB plugin.

Change-Id: I9a435482bbe7da4aedd06b1678331cf83ccc4587
This commit is contained in:
Tomasz Trębski 2017-04-07 13:41:07 +02:00
parent 0d6b92d7e8
commit 0b0b11044e
5 changed files with 408 additions and 20 deletions

View File

@ -1137,6 +1137,35 @@ instances:
type: gauge
```
## InfluxDB
Auto-detection for InfluxDB plugin comes with two checks enabled:
* process monitoring with following configuration
```python
{
'detailed': True,
'search_string': ['influxd'],
'exact_match': False,
'name': 'influxd',
'dimensions': {
'component': 'influxdb',
'service': 'influxdb'
}
}
```
* http_check monitoring
```python
{
'name': 'influxdb',
'url': '127.0.0.1:8086/ping'
}
```
InfluxDB does expose internal metrics on its own, however
they are subject to extend influxdb auto-detection capabilities
in future
## IIS
See [the example configuration](https://github.com/openstack/monasca-agent/blob/master/conf.d/iis.yaml.example) for how to configure the IIS plugin.

View File

@ -0,0 +1,152 @@
# Copyright 2017 FUJITSU LIMITED
#
# 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.
import logging
from oslo_utils import importutils
from monasca_setup import agent_config
from monasca_setup import detection
from monasca_setup.detection import utils
LOG = logging.getLogger(__name__)
class InfluxDB(detection.Plugin):
"""Detect InfluxDB and sets up its monitoring"""
PROC_NAME = 'influxd'
"""Name of the InfluxDB process expected to be found in the system"""
DEFAULTS = {
'bind_address': '127.0.0.1',
'bind_port': 8086
}
def _detect(self):
"""Run detection, set self.available True if the service is detected.
"""
proc = utils.find_process_name(self.PROC_NAME)
process_found = proc is not None
config_file = self._get_config_file(proc) if process_found else None
config_file_found = config_file is not None
dependencies_installed = self.dependencies_installed()
self.available = (process_found and config_file_found
and dependencies_installed)
if not self.available:
err_chunks = []
if not process_found:
err_chunks.append('\tinfluxdb plugin cannot locate '
'"%s" process.' % self.PROC_NAME)
elif not config_file_found:
err_chunks.append('\tinfluxdb plugin cannot locate '
'configuration file.')
elif not dependencies_installed:
err_chunks.append('\tinfluxdb plugin requires "toml" '
'to be installed')
LOG.error('Plugin for InfluxDB will not be configured.\n'
'Following issue have to be resolved: %s' %
'\n'.join(err_chunks))
else:
self._config = self._load_config(config_file)
def build_config(self):
"""Build the config as a Plugins object and return."""
LOG.info("\tEnabling the Monasca InfluxDB check")
config = agent_config.Plugins()
config.merge(self._monitor_process())
config.merge(self._monitor_ping_endpoint())
# NOTE(trebskit) features for next iteration
# TODO(trebskit) monitor influx metrics (show stats, show diagnostic)
# TODO(trebskit) monitor certificate expiration
return config
def _monitor_process(self):
return detection.watch_process([self.PROC_NAME],
service='influxdb',
component='influxdb',
exact_match=False)
def _monitor_ping_endpoint(self):
config = agent_config.Plugins()
http_conf = self._config.get('http', None)
if http_conf['enabled']:
host, port = self._explode_bind_address(http_conf)
listening = utils.find_addrs_listening_on_port(port)
if listening:
config['http_check'] = {
'init_config': None,
'instances': [
{
'name': 'influxdb',
'url': 'http://%s:%d/ping' % (host, port)
}
]
}
else:
LOG.warning('\tInfluxDB[http] is enabled but nothing '
'could be found listening at %d port', port)
else:
LOG.info('\tInfluxDB[http] is not enabled, skipping')
return config
def dependencies_installed(self):
return importutils.try_import('toml', False)
@staticmethod
def _explode_bind_address(http_conf):
bind_address = http_conf['bind-address']
path, port = bind_address.split(':')
if not path:
path = InfluxDB.DEFAULTS['bind_address']
if not port:
port = InfluxDB.DEFAULTS['bind_port']
return path, int(port)
@staticmethod
def _load_config(config_file):
"""Loads toml configuration from specified path.
Method loads configuration from specified `path`
and parses it with :py:class:`configparser.RawConfigParser`
"""
try:
return importutils.import_module('toml').load(config_file)
except Exception as ex:
LOG.error('Failed to parse %s', config_file)
LOG.exception(ex)
@staticmethod
def _get_config_file(proc):
cmdline = proc.as_dict(attrs=['cmdline'])['cmdline']
config_flag = '-config'
if config_flag in cmdline:
pos = cmdline.index(config_flag)
return cmdline[pos + 1]
LOG.warning('%s switch was not found in influxdb cmdline',
config_flag)
return None

View File

@ -321,26 +321,6 @@ def dropwizard_metrics(service, component, url, whitelist):
return config
class MonInfluxDB(monasca_setup.detection.Plugin):
"""Detect InfluxDB and setup some simple checks."""
def _detect(self):
"""Run detection, set self.available True if the service is detected.
"""
if find_process_name('influxd') is not None:
self.available = True
def build_config(self):
"""Build the config as a Plugins object and return."""
log.info("\tEnabling the Monasca InfluxDB check")
return watch_process(['influxd'], 'monitoring', 'influxd',
exact_match=False)
def dependencies_installed(self):
return True
class _DropwizardJavaHelper(object):
"""Mixing to locate configuration file for DropWizard app

View File

@ -40,6 +40,11 @@ monasca_agent.collector.virt =
vsphere = monasca_agent.collector.virt.vmware.inspector:VsphereInspector
xenapi = monasca_agent.collector.virt.xenapi.inspector:XenapiInspector
# list of extra dependencies that are required by some plugin
# for details see #PEP0426
[extras]
d_influxdb =
toml
[global]
setup-hooks =

View File

@ -0,0 +1,222 @@
# Copyright 2017 FUJITSU LIMITED
#
# 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.
import mock
from oslotest import base
import psutil
from monasca_setup.detection.plugins import influxdb as idb
_DEFAULT_CFG_FILE = '/etc/influxdb/influxdb.conf'
def _get_cmd(config_file=_DEFAULT_CFG_FILE):
"""Builds mocked cmdline for process"""
return ('/usr/bin/influxd -config %s' % config_file).split(' ')
_INFLUXDB_CMD = _get_cmd()
class FakeProcess(object):
cmdLine = None
def as_dict(self, attrs=None):
all_attrs = {'name': 'influxd',
'exe': FakeProcess.exe(),
'cmdline': FakeProcess.cmdline()}
if attrs:
for key in attrs:
if key not in all_attrs:
all_attrs.pop(key, None)
return all_attrs
@staticmethod
def exe():
line = FakeProcess.cmdLine
if not line:
return None
return line[0]
@staticmethod
def cmdline():
return FakeProcess.cmdLine
class TestInfluxDBDetection(base.BaseTestCase):
ADDRESSES = {
':8086': ('127.0.0.1', 8086),
'192.168.10.6:8888': ('192.168.10.6', 8888)
}
LOCATIONS = (
'/tmp/influx.conf',
_DEFAULT_CFG_FILE,
'/etc/monasca/influx.conf'
)
def setUp(self):
super(TestInfluxDBDetection, self).setUp()
with mock.patch.object(idb.InfluxDB, '_detect') as mock_detect:
self._ir = idb.InfluxDB('influxdb')
self.assertTrue(mock_detect.called)
def test_should_not_configure_if_no_process(self):
FakeProcess.cmdLine = [] # no_process
self._detect(no_proc=True)
self.assertFalse(self._ir.available)
def test_should_not_configure_has_process_no_config_located(self):
FakeProcess.cmdLine = [_INFLUXDB_CMD]
self._ir._get_config_file = mock.Mock(return_value=None)
self._detect()
self.assertFalse(self._ir.available)
@mock.patch('monasca_setup.detection.plugins.influxdb.importutils')
def test_should_not_configure_no_dependencies(self, iu):
FakeProcess.cmdLine = [_INFLUXDB_CMD]
self._ir._get_config_file = mock.Mock(return_value=True)
iu.return_value = False
self.assertFalse(self._ir.available)
@mock.patch('monasca_setup.detection.plugins.influxdb.importutils')
def test_should_be_available_if_everything_matches(self, iu):
FakeProcess.cmdLine = [_INFLUXDB_CMD]
self._ir._get_config_file = mock.Mock(return_value=_DEFAULT_CFG_FILE)
self._ir._load_config = lc = mock.Mock()
iu.try_import.return_value = True
self._detect()
self.assertTrue(self._ir.available)
lc.assert_called_with(_DEFAULT_CFG_FILE)
@mock.patch('monasca_setup.detection.plugins.influxdb.importutils')
def test_dependencies_installed_true_has_toml(self, iu):
iu.try_import = tr = mock.Mock(return_value=True)
self.assertTrue(self._ir.dependencies_installed())
tr.assert_called_with('toml', False)
@mock.patch('monasca_setup.detection.plugins.influxdb.importutils')
def test_dependencies_installed_false_no_toml(self, iu):
iu.try_import = tr = mock.Mock(return_value=False)
self.assertFalse(self._ir.dependencies_installed())
tr.assert_called_with('toml', False)
def test_should_explode_addresses(self):
for raw_address, e_host_port in self.ADDRESSES.items():
http_conf = {
'bind-address': raw_address
}
a_host_port = idb.InfluxDB._explode_bind_address(http_conf)
self.assertEqual(e_host_port, a_host_port)
def test_should_return_none_cfg_file_if_cmd_switch_missing(self):
FakeProcess.cmdLine = []
self.assertIsNone(idb.InfluxDB._get_config_file(FakeProcess()))
def test_should_return_cfg_file_path_if_cmd_switch_found(self):
for loc in self.LOCATIONS:
FakeProcess.cmdLine = _get_cmd(config_file=loc)
self.assertEqual(loc,
idb.InfluxDB._get_config_file(FakeProcess()))
@mock.patch('monasca_setup.detection.plugins.influxdb.'
'utils.find_addrs_listening_on_port')
def test_should_build_config_db_listens(self, falop):
falop.return_value = True
self._build_config(process_up=True)
@mock.patch('monasca_setup.detection.plugins.influxdb.'
'utils.find_addrs_listening_on_port')
def test_should_build_config_db_died_during_conf(self, falop):
falop.return_value = False
self._build_config(process_up=False)
@mock.patch('monasca_setup.detection.plugins.influxdb.'
'utils.find_addrs_listening_on_port')
def test_should_build_config_http_disabled(self, falop):
falop.return_value = False
self._build_config(http_enabled=False)
def _build_config(self, http_enabled=True, process_up=True):
"""Verify built configuration
:param process_up: True/False, intermediate process availability
:type process_up: bool
:param http_enabled: Is http enabled for given influx
:type http_enabled: bool
"""
monitored_items = ['process']
if process_up and http_enabled:
monitored_items.append('http_check')
for raw_address, host_port in self.ADDRESSES.items():
self._ir._config = {
'http': {
'enabled': http_enabled,
'bind-address': raw_address
}
}
built_config = self._ir.build_config()
self.assertItemsEqual(monitored_items, built_config.keys())
for key in built_config.keys():
if key == 'process':
self._verify_process_conf(built_config[key])
elif key == 'http_check':
self._verify_http_conf(built_config[key], host_port)
else:
raise 'Untested monitored item %s' % key
def _verify_http_conf(self, built_config, e_host_port):
expected_http = {
'init_config': None,
'instances': [
{
'name': 'influxdb',
'url': 'http://%s:%d/ping' % e_host_port
}
]
}
self.assertDictEqual(expected_http, built_config)
def _verify_process_conf(self, actual_config):
expected_process = {
'init_config': None,
'instances': [
{
'detailed': True,
'search_string': ['influxd'],
'exact_match': False,
'name': 'influxd',
'dimensions': {
'component': 'influxdb',
'service': 'influxdb'
}
}
]
}
self.assertDictEqual(expected_process, actual_config)
def _detect(self, no_proc=False):
self._ir.available = False
processes = [FakeProcess()] if not no_proc else []
process_iter = mock.patch.object(psutil, 'process_iter',
return_value=processes)
with process_iter as mock_process_iter:
self._ir._detect()
self.assertTrue(mock_process_iter.called)