InfluxDB-Relay autodection
Added autodetection of influxdb-relay. Plugin configures process and http_check monitoring. Change-Id: I386e87349283d35d2f4fda58f0e376ed59a6c5bf
This commit is contained in:
parent
0b0b11044e
commit
75dc1ae406
|
@ -48,6 +48,8 @@
|
|||
- [HTTP (endpoint status)](#http-endpoint-status)
|
||||
- [HTTP Metrics](#http-metrics)
|
||||
- [IIS](#iis)
|
||||
- [InfluxDB](#influxdb)
|
||||
- [InfluxDB Relay](#influxdb-relay)
|
||||
- [Jenkins](#jenkins)
|
||||
- [JsonPlugin](#jsonplugin)
|
||||
- [Simple Reporting](#simple-reporting)
|
||||
|
@ -1158,7 +1160,7 @@ Auto-detection for InfluxDB plugin comes with two checks enabled:
|
|||
```python
|
||||
{
|
||||
'name': 'influxdb',
|
||||
'url': '127.0.0.1:8086/ping'
|
||||
'url': 'http://127.0.0.1:8086/ping'
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1166,6 +1168,32 @@ Auto-detection for InfluxDB plugin comes with two checks enabled:
|
|||
they are subject to extend influxdb auto-detection capabilities
|
||||
in future
|
||||
|
||||
## InfluxDB-Relay
|
||||
**InfluxDB-Relay** does not expose any internal metrics on its own, however
|
||||
auto-detection plugin configures two checks on behalf of it:
|
||||
|
||||
|
||||
* process monitoring with following configuration
|
||||
```python
|
||||
{
|
||||
'detailed': True,
|
||||
'search_string': ['influxdb-relay'],
|
||||
'exact_match': False,
|
||||
'name': 'influxdb-relay',
|
||||
'dimensions': {
|
||||
'component': 'influxdb-relay',
|
||||
'service': 'influxdb'
|
||||
}
|
||||
}
|
||||
```
|
||||
* http_check monitoring
|
||||
```python
|
||||
{
|
||||
'name': 'influxdb-relay',
|
||||
'url': 'http://127.0.0.1:9096/ping'
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
# 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 InfluxDBRelay(detection.Plugin):
|
||||
"""Detects influxdb-relay and sets up its monitoring
|
||||
|
||||
Monitored items:
|
||||
|
||||
* process
|
||||
* http_check
|
||||
|
||||
"""
|
||||
|
||||
PROC_NAME = 'influxdb-relay'
|
||||
"""Name of the InfluxDB Relay process expected to be found in the system"""
|
||||
DEFAULTS = {
|
||||
'bind_address': '127.0.0.1',
|
||||
'bind_port': 9096
|
||||
}
|
||||
|
||||
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-relay plugin cannot locate '
|
||||
'"%s" process.' % self.PROC_NAME)
|
||||
elif not config_file_found:
|
||||
err_chunks.append('\tinfluxdb-relay plugin cannot locate '
|
||||
'configuration file.')
|
||||
elif not dependencies_installed:
|
||||
err_chunks.append('\tinfluxdb-relay plugin requires "toml" '
|
||||
'to be installed')
|
||||
LOG.error('Plugin for influxdb-relay 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 influxdb-relay check")
|
||||
config = agent_config.Plugins()
|
||||
|
||||
config.merge(self._monitor_process())
|
||||
config.merge(self._monitor_endpoint())
|
||||
|
||||
return config
|
||||
|
||||
def _monitor_process(self):
|
||||
LOG.info("\tMonitoring the influxdb-relay process")
|
||||
return detection.watch_process([self.PROC_NAME],
|
||||
service='influxdb',
|
||||
component='influxdb-relay',
|
||||
exact_match=False)
|
||||
|
||||
def _monitor_endpoint(self):
|
||||
config = agent_config.Plugins()
|
||||
http_conf = self._config.get('http', None)
|
||||
if isinstance(http_conf, list):
|
||||
http_conf = http_conf[0]
|
||||
|
||||
if http_conf:
|
||||
host, port = self._explode_bind_address(http_conf)
|
||||
listening = utils.find_addrs_listening_on_port(port)
|
||||
if listening:
|
||||
LOG.info("\tMonitoring the influxdb-relay ping endpoint")
|
||||
instance = {
|
||||
'name': 'influxdb-relay',
|
||||
'url': 'http://%s:%d/ping' % (host, port)
|
||||
}
|
||||
|
||||
config['http_check'] = {
|
||||
'init_config': None,
|
||||
'instances': [instance]
|
||||
}
|
||||
else:
|
||||
LOG.warning('\tinfluxdb-relay[http] is enabled but nothing '
|
||||
'could be found listening at %d port. '
|
||||
'It might have happened that process '
|
||||
'was just killed and hence port %d '
|
||||
'was released.', port, port)
|
||||
|
||||
return config
|
||||
|
||||
def dependencies_installed(self):
|
||||
return importutils.try_import('toml', False)
|
||||
|
||||
@staticmethod
|
||||
def _explode_bind_address(http_conf):
|
||||
bind_address = http_conf['bind-addr']
|
||||
path, port = bind_address.split(':')
|
||||
|
||||
if not path:
|
||||
path = InfluxDBRelay.DEFAULTS['bind_address']
|
||||
if not port:
|
||||
port = InfluxDBRelay.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):
|
||||
"""Tries to retrieve config file location.
|
||||
|
||||
influxdb-relay is launched with ```-config```
|
||||
flag in cmdline. That fact is used by this method.
|
||||
If, by any mean, that switch is not part of
|
||||
:py:method:`psutil.Process.cmdline`, method simply
|
||||
fallbacks to None
|
||||
|
||||
:param proc: current process, :py:class:`psutil.Process`
|
||||
:type proc: :py:class:`psutil.Process`
|
||||
:return: config file path or None
|
||||
:rtype: str or None
|
||||
|
||||
"""
|
||||
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-relay cmdline',
|
||||
config_flag)
|
||||
return None
|
|
@ -45,6 +45,8 @@ monasca_agent.collector.virt =
|
|||
[extras]
|
||||
d_influxdb =
|
||||
toml
|
||||
d_influxdb_relay =
|
||||
toml
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
# 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_relay as ir
|
||||
|
||||
_DEFAULT_CFG_FILE = '/etc/influxdb-relay/influxdb-relay.conf'
|
||||
|
||||
|
||||
def _get_cmd(config_file=_DEFAULT_CFG_FILE):
|
||||
"""Builds mocked cmdline for process"""
|
||||
return ('/usr/bin/influxdb-relay -config '
|
||||
'%s' % config_file).split(' ')
|
||||
|
||||
|
||||
_INFLUXDB_RELAY_CMD = _get_cmd()
|
||||
|
||||
|
||||
class FakeProcess(object):
|
||||
cmdLine = None
|
||||
|
||||
def as_dict(self, attrs=None):
|
||||
all_attrs = {'name': 'influxdb-relay',
|
||||
'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 TestInfluxDBRelayDetection(base.BaseTestCase):
|
||||
ADDRESSES = {
|
||||
':9096': ('127.0.0.1', 9096),
|
||||
'192.168.10.6:9999': ('192.168.10.6', 9999)
|
||||
}
|
||||
LOCATIONS = (
|
||||
'/tmp/relay.conf',
|
||||
_DEFAULT_CFG_FILE,
|
||||
'/etc/monasca/influxdb-relay'
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(TestInfluxDBRelayDetection, self).setUp()
|
||||
with mock.patch.object(ir.InfluxDBRelay, '_detect') as mock_detect:
|
||||
self._ir = ir.InfluxDBRelay('inflxudb-relay')
|
||||
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_RELAY_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_relay.importutils')
|
||||
def test_should_not_configure_no_dependencies(self, iu):
|
||||
FakeProcess.cmdLine = [_INFLUXDB_RELAY_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_relay.importutils')
|
||||
def test_should_be_available_if_everything_matches(self, iu):
|
||||
FakeProcess.cmdLine = [_INFLUXDB_RELAY_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_relay.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_relay.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-addr': raw_address
|
||||
}
|
||||
a_host_port = ir.InfluxDBRelay._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(ir.InfluxDBRelay._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,
|
||||
ir.InfluxDBRelay._get_config_file(FakeProcess()))
|
||||
|
||||
@mock.patch('monasca_setup.detection.plugins.influxdb_relay.'
|
||||
'utils.find_addrs_listening_on_port')
|
||||
def test_should_build_config_relay_listens(self, falop):
|
||||
falop.return_value = True
|
||||
self._build_config(process_up=True)
|
||||
|
||||
@mock.patch('monasca_setup.detection.plugins.influxdb_relay.'
|
||||
'utils.find_addrs_listening_on_port')
|
||||
def test_should_build_config_relay_died_during_conf(self, falop):
|
||||
falop.return_value = False
|
||||
self._build_config(process_up=False)
|
||||
|
||||
def _build_config(self, process_up=True):
|
||||
"""Verify built configuration
|
||||
|
||||
:param process_up: True/False, intermediate process availability
|
||||
:type process_up: bool
|
||||
|
||||
"""
|
||||
monitored_items = ['process']
|
||||
if process_up:
|
||||
monitored_items.append('http_check')
|
||||
|
||||
for raw_address, host_port in self.ADDRESSES.items():
|
||||
self._ir._config = {
|
||||
'http': [{
|
||||
'bind-addr': 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-relay',
|
||||
'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': ['influxdb-relay'],
|
||||
'exact_match': False,
|
||||
'name': 'influxdb-relay',
|
||||
'dimensions': {
|
||||
'component': 'influxdb-relay',
|
||||
'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)
|
1
tox.ini
1
tox.ini
|
@ -23,6 +23,7 @@ whitelist_externals = bash
|
|||
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
rm -Rf .testrepository/times.dbm
|
||||
|
||||
[testenv:py27]
|
||||
basepython = python2.7
|
||||
|
|
Loading…
Reference in New Issue