
The repo is Python 3 now, so update hacking to version 3.0 which supports Python 3. Fix problems found. Update local hacking checks for new flake8. Change-Id: I6396403d0a62f5403fc5b7fb04b6ce790c332c84
799 lines
28 KiB
Python
799 lines
28 KiB
Python
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
|
|
# Copyright 2016 FUJITSU LIMITED
|
|
# Copyright 2017 SUSE Linux GmbH
|
|
# 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.
|
|
|
|
"""Classes for monitoring the monitoring server stack.
|
|
|
|
Covering mon-persister, mon-api and mon-thresh.
|
|
Kafka, mysql, vertica and influxdb are covered by other detection plugins. Mon-notification
|
|
uses statsd.
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
import yaml
|
|
|
|
from six.moves import configparser
|
|
|
|
import monasca_setup.agent_config
|
|
import monasca_setup.detection
|
|
from monasca_setup.detection import find_process_cmdline
|
|
from monasca_setup.detection.utils import get_agent_username
|
|
from monasca_setup.detection.utils import watch_process
|
|
from monasca_setup.detection.utils import watch_process_by_username
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_APACHE_MARKERS = 'httpd', 'apache',
|
|
"""List of all strings in process command line that indicate application
|
|
runs in Apache/mod_wsgi"""
|
|
_PYTHON_LANG_MARKERS = ('python', 'gunicorn') + _APACHE_MARKERS
|
|
"""List of all strings that if found in process exe
|
|
mean that application runs under Python"""
|
|
_JAVA_LANG_MARKERS = 'java',
|
|
"""List of all strings that if found in process exe
|
|
mean that application runs under Java"""
|
|
|
|
_DEFAULT_API_PORT = 8070
|
|
"""Default TCP port which monasca-api process should be available by"""
|
|
|
|
|
|
def _get_impl_lang(process):
|
|
"""Return implementation language of the application behind process
|
|
|
|
:param process: current process
|
|
:type process: psutil.Process
|
|
:return: implementation lang, either java or python
|
|
:rtype: str
|
|
|
|
"""
|
|
p_exe = process.as_dict(['exe'])['exe']
|
|
for lm in _PYTHON_LANG_MARKERS:
|
|
if lm in p_exe:
|
|
return 'python'
|
|
for lm in _JAVA_LANG_MARKERS:
|
|
if lm in p_exe:
|
|
return 'java'
|
|
raise Exception(('Cannot determine language '
|
|
'implementation from process exe %s') % p_exe)
|
|
|
|
|
|
class MonAgent(monasca_setup.detection.Plugin):
|
|
"""Detect the Monsaca agent engine and setup some simple checks."""
|
|
|
|
def _detect(self):
|
|
"""Run detection, set self.available True if the service is detected."""
|
|
self.available = True
|
|
agent_process_list = ['monasca-collector', 'monasca-forwarder',
|
|
'monasca-statsd']
|
|
for process in agent_process_list:
|
|
if find_process_cmdline(process) is None:
|
|
self.available = False
|
|
return
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return."""
|
|
log.info("\tEnabling the Monasca Agent process check")
|
|
return watch_process_by_username(get_agent_username(), 'monasca-agent',
|
|
'monitoring', 'monasca-agent')
|
|
|
|
def dependencies_installed(self):
|
|
return True
|
|
|
|
|
|
class MonAPI(monasca_setup.detection.Plugin):
|
|
"""Detect mon_api and setup monitoring."""
|
|
|
|
PARTIAL_ERR_MSG = 'Plugin for monasca-api will not be configured.'
|
|
|
|
def _detect(self):
|
|
"""Detects if monasca-api runs in the system
|
|
|
|
Method distinguishes between Java and Python implementation
|
|
hence provides different agent's configuration.
|
|
|
|
"""
|
|
|
|
def check_port():
|
|
for conn in api_process.connections('inet'):
|
|
if conn.laddr[1] == api_port:
|
|
return True
|
|
return False
|
|
|
|
def correct_apache_listener(process):
|
|
"""Sets api_process to the parent httpd process.
|
|
|
|
Method evaluates if process executable is correlated
|
|
with apache-mod_wsgi. If so, retrieves parent process.
|
|
Otherwise returns None
|
|
|
|
:param process: current process
|
|
:type process: psutil.Process
|
|
:returns: parent process or None
|
|
:rtype: (psutil.Process, None)
|
|
|
|
"""
|
|
|
|
p_exe = process.as_dict(['exe'])['exe']
|
|
for m in _APACHE_MARKERS:
|
|
if m in p_exe:
|
|
return process.parent()
|
|
return None
|
|
|
|
api_process = find_process_cmdline('monasca-api')
|
|
process_found = api_process is not None
|
|
|
|
if process_found:
|
|
impl_lang = _get_impl_lang(api_process)
|
|
if impl_lang == 'python':
|
|
apache_process = correct_apache_listener(api_process)
|
|
if apache_process:
|
|
log.info('\tmonasca-api runs under Apache WSGI')
|
|
api_process = apache_process
|
|
|
|
impl_helper = self._init_impl_helper(api_process.as_dict(['cmdline'])['cmdline'],
|
|
impl_lang)
|
|
impl_helper.load_configuration()
|
|
|
|
api_port = impl_helper.get_bound_port()
|
|
port_taken = check_port()
|
|
|
|
if not port_taken:
|
|
log.error('monasca-api is not listening on port %d. %s'
|
|
% (api_port, self.PARTIAL_ERR_MSG))
|
|
return
|
|
|
|
log.info('\tmonasca-api implementation is %s', impl_lang)
|
|
|
|
self.available = port_taken
|
|
self._impl_helper = impl_helper
|
|
else:
|
|
log.warning('monasca-api process has not been found. %s'
|
|
% self.PARTIAL_ERR_MSG)
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return."""
|
|
config = monasca_setup.agent_config.Plugins()
|
|
|
|
log.info("\tEnabling the monasca-api process check")
|
|
config.merge(watch_process(
|
|
search_strings=['monasca-api'],
|
|
service='monitoring',
|
|
component='monasca-api',
|
|
exact_match=False
|
|
))
|
|
impl_config = self._impl_helper.build_config()
|
|
if impl_config:
|
|
config.merge(impl_config)
|
|
|
|
return config
|
|
|
|
def dependencies_installed(self):
|
|
return True
|
|
|
|
def _init_impl_helper(self, cmdline, impl_lang):
|
|
"""Returns appropriate helper implementation.
|
|
|
|
:param impl_lang: implementation language, either `java` or `python`
|
|
:type impl_lang: str
|
|
:return: implementation helper
|
|
:rtype: Union(_MonAPIJavaHelper,_MonAPIPythonHelper)
|
|
|
|
"""
|
|
if impl_lang == 'java':
|
|
return _MonAPIJavaHelper(cmdline=cmdline)
|
|
else:
|
|
return _MonAPIPythonHelper(cmdline=cmdline, args=self.args)
|
|
|
|
|
|
class MonNotification(monasca_setup.detection.Plugin):
|
|
"""Detect the Monsaca notification engine and setup some simple checks."""
|
|
|
|
def _detect(self):
|
|
"""Run detection, set self.available True if the service is detected."""
|
|
if find_process_cmdline('monasca-notification') is not None:
|
|
self.available = True
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return."""
|
|
log.info("\tEnabling the Monasca Notification healthcheck")
|
|
notification_process = find_process_cmdline('monasca-notification')
|
|
notification_user = notification_process.as_dict(['username'])['username']
|
|
return watch_process_by_username(notification_user, 'monasca-notification',
|
|
'monitoring', 'monasca-notification')
|
|
|
|
def dependencies_installed(self):
|
|
return True
|
|
|
|
|
|
class MonPersister(monasca_setup.detection.Plugin):
|
|
"""Detect mon_persister and setup monitoring."""
|
|
|
|
PARTIAL_ERR_MSG = 'Plugin for monasca-persister will not be configured.'
|
|
|
|
def _detect(self):
|
|
"""Detects if monasca-persister runs in the system
|
|
|
|
Method distinguishes between Java and Python implementation
|
|
hence provides different agent's configuration.
|
|
|
|
"""
|
|
p_process = find_process_cmdline('monasca-persister')
|
|
process_found = p_process is not None
|
|
|
|
if process_found:
|
|
impl_lang = _get_impl_lang(p_process)
|
|
impl_helper = self._init_impl_helper(
|
|
p_process.as_dict(['cmdline'])['cmdline'],
|
|
impl_lang
|
|
)
|
|
|
|
if impl_helper is not None:
|
|
impl_helper.load_configuration()
|
|
|
|
self._impl_helper = impl_helper
|
|
self.available = True
|
|
|
|
log.info('\tmonasca-persister implementation is %s', impl_lang)
|
|
|
|
else:
|
|
log.info('monasca-persister process has not been found. %s'
|
|
% self.PARTIAL_ERR_MSG)
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return."""
|
|
config = monasca_setup.agent_config.Plugins()
|
|
|
|
log.info("\tEnabling the Monasca persister process check")
|
|
config.merge(watch_process(
|
|
search_strings=['monasca-persister'],
|
|
service='monitoring',
|
|
component='monasca-persister',
|
|
exact_match=False
|
|
))
|
|
if self._impl_helper is not None:
|
|
impl_config = self._impl_helper.build_config()
|
|
if impl_config:
|
|
config.merge(impl_config)
|
|
|
|
return config
|
|
|
|
def dependencies_installed(self):
|
|
return True
|
|
|
|
@staticmethod
|
|
def _init_impl_helper(cmdline, impl_lang):
|
|
"""Returns appropriate helper implementation.
|
|
|
|
Note:
|
|
|
|
This method returns the helper only for Java.
|
|
Python implementation comes with no extra setup.
|
|
|
|
:param impl_lang: implementation language, either `java` or `python`
|
|
:type impl_lang: str
|
|
:return: implementation helper
|
|
:rtype:_MonPersisterJavaHelper
|
|
|
|
"""
|
|
if impl_lang == 'java':
|
|
return _MonPersisterJavaHelper(cmdline=cmdline)
|
|
return None
|
|
|
|
|
|
class MonThresh(monasca_setup.detection.Plugin):
|
|
"""Detect the running mon-thresh and monitor."""
|
|
|
|
def _detect(self):
|
|
"""Run detection, set self.available True if the service is detected."""
|
|
# The node will be running either nimbus or supervisor or both
|
|
self.available = (find_process_cmdline('storm.daemon.nimbus') is not None or
|
|
find_process_cmdline('storm.daemon.supervisor') is not None)
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return."""
|
|
log.info("\tWatching the mon-thresh process.")
|
|
config = monasca_setup.agent_config.Plugins()
|
|
for process in ['storm.daemon.nimbus', 'storm.daemon.supervisor', 'storm.daemon.worker']:
|
|
if find_process_cmdline(process) is not None:
|
|
config.merge(
|
|
watch_process(
|
|
[process],
|
|
'monitoring',
|
|
'apache-storm',
|
|
exact_match=False,
|
|
detailed=False))
|
|
config.merge(
|
|
watch_process_by_username(
|
|
'storm',
|
|
'monasca-thresh',
|
|
'monitoring',
|
|
'apache-storm'))
|
|
return config
|
|
|
|
def dependencies_installed(self):
|
|
return True
|
|
|
|
|
|
def dropwizard_health_check(service, component, url):
|
|
"""Setup a dropwizard heathcheck to be watched by the http_check plugin."""
|
|
config = monasca_setup.agent_config.Plugins()
|
|
config['http_check'] = {'init_config': None,
|
|
'instances': [
|
|
{'name': "{0}-{1} healthcheck".format(service, component),
|
|
'url': url,
|
|
'timeout': 5,
|
|
'include_content': False,
|
|
'dimensions': {'service': service, 'component': component}}]}
|
|
return config
|
|
|
|
|
|
def dropwizard_metrics(service, component, url, whitelist):
|
|
"""Setup a dropwizard metrics check"""
|
|
config = monasca_setup.agent_config.Plugins()
|
|
config['http_metrics'] = {'init_config': None,
|
|
'instances': [{'name': "{0}-{1} metrics".format(service, component),
|
|
'url': url,
|
|
'timeout': 5,
|
|
'dimensions': {'service': service,
|
|
'component': component},
|
|
'whitelist': whitelist}]}
|
|
return config
|
|
|
|
|
|
class _DropwizardJavaHelper(object):
|
|
"""Mixing to locate configuration file for DropWizard app
|
|
|
|
Class utilizes process of search the configuartion file
|
|
for:
|
|
|
|
* monasca-api [**Java**]
|
|
* monasca-persister [**Java**]
|
|
"""
|
|
|
|
YAML_PATTERN = re.compile(r'.*\.ya?ml', re.IGNORECASE)
|
|
|
|
def __init__(self, cmdline=None):
|
|
self._cmdline = cmdline
|
|
|
|
def load_configuration(self):
|
|
"""Loads java specific configuration.
|
|
|
|
Load java specific configuration from:
|
|
|
|
* :py:attr:`DEFAULT_CONFIG_FILE`
|
|
|
|
:return: True if both configuration files were successfully loaded
|
|
:rtype: bool
|
|
|
|
"""
|
|
try:
|
|
config_file = self._get_config_file()
|
|
self._read_config_file(config_file)
|
|
except Exception as ex:
|
|
log.error('Failed to parse %s', config_file)
|
|
log.exception(ex)
|
|
raise ex
|
|
|
|
def _find_config_file_in_cmdline(self, cmdline):
|
|
# note(trebskit) file should be mentioned
|
|
# somewhere in the end of cmdline
|
|
for item in cmdline[::-1]:
|
|
if self.YAML_PATTERN.match(item):
|
|
return item
|
|
return None
|
|
|
|
def _read_config_file(self, config_file):
|
|
with open(config_file, 'r') as config:
|
|
self._cfg = yaml.safe_load(config.read())
|
|
|
|
def _get_config_file(self):
|
|
if self._cmdline:
|
|
config_file = self._find_config_file_in_cmdline(
|
|
cmdline=self._cmdline
|
|
)
|
|
if config_file:
|
|
log.debug('\tFound %s for java configuration from CLI',
|
|
config_file)
|
|
return config_file
|
|
|
|
config_file = self.DEFAULT_CONFIG_FILE
|
|
log.debug('\tAssuming default configuration file=%s', config_file)
|
|
return config_file
|
|
|
|
|
|
class _MonPersisterJavaHelper(_DropwizardJavaHelper):
|
|
"""Encapsulates Java specific configuration for monasca-persister"""
|
|
|
|
DEFAULT_CONFIG_FILE = '/etc/monasca/persister-config.yml'
|
|
"""Default location where plugin expects configuration file"""
|
|
|
|
def build_config(self):
|
|
config = monasca_setup.agent_config.Plugins()
|
|
metrics = self._collect_metrics()
|
|
self._monitor_endpoints(config, metrics)
|
|
return config
|
|
|
|
def _collect_metrics(self):
|
|
"""Collects all the metrics .
|
|
|
|
Methods will return list of all metrics that will
|
|
later be used in
|
|
:py:mod:`monasca_agent.collector.checks_d.http_metrics` to query
|
|
admin endpoint of monasca-persister.
|
|
|
|
Following group of metrics are examined:
|
|
|
|
* JVM metrics
|
|
* DB metrics ( see also :py:meth:`._collect_db_metrics` )
|
|
* Internal metrics ( see also :py:meth:`._collect_internal_metrics` )
|
|
|
|
:return: list of metrics
|
|
:rtype: list
|
|
|
|
"""
|
|
log.info("\tEnabling the monasca-persister metrics")
|
|
|
|
whitelist = [
|
|
{
|
|
"name": "jvm.memory.total.max",
|
|
"path": "gauges/jvm.memory.total.max/value",
|
|
"type": "gauge"
|
|
},
|
|
{
|
|
"name": "jvm.memory.total.used",
|
|
"path": "gauges/jvm.memory.total.used/value",
|
|
"type": "gauge"
|
|
}
|
|
]
|
|
|
|
self._collect_db_metrics(whitelist)
|
|
self._collect_internal_metrics(whitelist)
|
|
|
|
return whitelist
|
|
|
|
def _collect_db_metrics(self, whitelist):
|
|
"""Collects DB specific metrics.
|
|
|
|
Method retrieves which time-series database is used
|
|
in monasca-persister and sets up new metrics to be monitored.
|
|
|
|
Note:
|
|
Only if vertica is TSDB in monasca-persister
|
|
|
|
"""
|
|
database_type = self._cfg['databaseConfiguration']['databaseType']
|
|
if database_type == 'influxdb':
|
|
pass
|
|
elif database_type == 'vertica':
|
|
self._add_vertica_metrics(whitelist)
|
|
else:
|
|
log.warn('Failed finding database type in %s', self.DEFAULT_CONFIG_FILE)
|
|
|
|
def _collect_internal_metrics(self, whitelist):
|
|
alarm_num_threads = self._cfg['alarmHistoryConfiguration']['numThreads']
|
|
metric_num_threads = self._cfg['metricConfiguration']['numThreads']
|
|
|
|
# Dynamic Whitelist
|
|
for idx in range(alarm_num_threads):
|
|
new_thread = {
|
|
"name": "alarm-state-transitions-added-to-batch-counter[{0}]".format(idx),
|
|
"path": "counters/monasca.persister.pipeline.event."
|
|
"AlarmStateTransitionHandler[alarm-state-transition-{0}]."
|
|
"alarm-state-transitions-added-to-batch-counter/count".format(idx),
|
|
"type": "rate"}
|
|
whitelist.append(new_thread)
|
|
for idx in range(metric_num_threads):
|
|
new_thread = {
|
|
"name": "metrics-added-to-batch-counter[{0}]".format(idx),
|
|
"path": "counters/monasca.persister.pipeline.event.MetricHandler[metric-{0}]."
|
|
"metrics-added-to-batch-counter/count".format(idx),
|
|
"type": "rate"}
|
|
whitelist.append(new_thread)
|
|
|
|
def _add_vertica_metrics(self, whitelist):
|
|
whitelist.extend(
|
|
[{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-cache-hit-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-cache-hit-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-cache-miss-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-cache-miss-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-dimension-cache-hit-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-dimension-cache-hit-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-dimension-cache-miss-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"definition-dimension-cache-miss-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"dimension-cache-hit-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"dimension-cache-hit-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"dimension-cache-miss-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"dimension-cache-miss-meter/count",
|
|
"type": "rate"},
|
|
{"name": "monasca.persister.repository.vertica.VerticaMetricRepo.measurement-meter",
|
|
"path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo."
|
|
"measurement-meter/count",
|
|
"type": "rate"}])
|
|
|
|
def _monitor_endpoints(self, config, metrics):
|
|
admin_connector = self._cfg['server']['adminConnectors'][0]
|
|
|
|
try:
|
|
admin_endpoint_type = admin_connector['type']
|
|
except Exception:
|
|
admin_endpoint_type = "http"
|
|
try:
|
|
admin_endpoint_port = admin_connector['port']
|
|
except Exception:
|
|
admin_endpoint_port = 8091
|
|
|
|
log.info("\tEnabling the Monasca persister healthcheck")
|
|
config.merge(
|
|
dropwizard_health_check(
|
|
'monitoring',
|
|
'monasca-persister',
|
|
'{0}://localhost:{1}/healthcheck'.format(admin_endpoint_type,
|
|
admin_endpoint_port)))
|
|
|
|
log.info("\tEnabling the Monasca persister metrics")
|
|
config.merge(
|
|
dropwizard_metrics(
|
|
'monitoring',
|
|
'monasca-persister',
|
|
'{0}://localhost:{1}/metrics'.format(admin_endpoint_type,
|
|
admin_endpoint_port),
|
|
metrics))
|
|
|
|
|
|
class _MonAPIJavaHelper(_DropwizardJavaHelper):
|
|
"""Encapsulates Java specific configuration for monasca-api"""
|
|
|
|
DEFAULT_CONFIG_FILE = '/etc/monasca/api-config.yml'
|
|
|
|
def build_config(self):
|
|
"""Builds monitoring configuration for monasca-api Java flavour.
|
|
|
|
Method configures additional checks that are specific for
|
|
Java implementation of monasca-api.
|
|
|
|
List of checks:
|
|
|
|
* HttpCheck, :py:mod:`monasca_agent.collector.checks_d.http_check`
|
|
* HttpMetrics, :py:mod:`monasca_agent.collector.checks_d.http_metrics`
|
|
|
|
"""
|
|
config = monasca_setup.agent_config.Plugins()
|
|
|
|
log.info("\tEnabling the Monasca api metrics")
|
|
whitelist = [
|
|
{
|
|
"name": "jvm.memory.total.max",
|
|
"path": "gauges/jvm.memory.total.max/value",
|
|
"type": "gauge"
|
|
},
|
|
{
|
|
"name": "jvm.memory.total.used",
|
|
"path": "gauges/jvm.memory.total.used/value",
|
|
"type": "gauge"
|
|
},
|
|
{
|
|
"name": "metrics.published",
|
|
"path": "meters/monasca.api.app.MetricService.metrics.published/count",
|
|
"type": "rate"
|
|
}
|
|
]
|
|
|
|
if not self._is_hibernate_on():
|
|
# if hibernate is not used, it is mysql with DBI
|
|
# for that case having below entries makes sense
|
|
log.debug(
|
|
'monasca-api has not enabled Hibernate, adding DBI metrics')
|
|
whitelist.extend([
|
|
{
|
|
"name": "raw-sql.time.avg",
|
|
"path": "timers/org.skife.jdbi.v2.DBI.raw-sql/mean",
|
|
"type": "gauge"
|
|
},
|
|
{
|
|
"name": "raw-sql.time.max",
|
|
"path": "timers/org.skife.jdbi.v2.DBI.raw-sql/max",
|
|
"type": "gauge"
|
|
}
|
|
])
|
|
|
|
self._monitor_endpoints(config, whitelist)
|
|
|
|
return config
|
|
|
|
def _monitor_endpoints(self, config, metrics):
|
|
admin_connector = self._cfg['server']['adminConnectors'][0]
|
|
|
|
try:
|
|
admin_endpoint_type = admin_connector['type']
|
|
except Exception:
|
|
admin_endpoint_type = "http"
|
|
try:
|
|
admin_endpoint_port = admin_connector['port']
|
|
except Exception:
|
|
admin_endpoint_port = 8081
|
|
|
|
healthcheck_url = '{0}://localhost:{1}/healthcheck'.format(
|
|
admin_endpoint_type, admin_endpoint_port)
|
|
metric_url = '{0}://localhost:{1}/metrics'.format(
|
|
admin_endpoint_type, admin_endpoint_port)
|
|
|
|
log.info("\tEnabling the monasca-api healthcheck")
|
|
config.merge(dropwizard_health_check('monitoring', 'monasca-api',
|
|
healthcheck_url))
|
|
log.info("\tEnabling the monasca-api metrics")
|
|
config.merge(dropwizard_metrics('monitoring', 'monasca-api',
|
|
metric_url, metrics))
|
|
|
|
def _is_hibernate_on(self):
|
|
# check if api_config has been declared in __init__
|
|
# if not it means that configuration file was not found
|
|
# or monasca-api Python implementation is running
|
|
|
|
cfg = getattr(self, '_cfg', None)
|
|
if cfg is None:
|
|
return False
|
|
|
|
hibernate_cfg = cfg.get('hibernate', None)
|
|
if hibernate_cfg is None:
|
|
return False
|
|
|
|
return hibernate_cfg.get('supportEnabled', False)
|
|
|
|
def get_bound_port(self):
|
|
"""Returns port API is listening on.
|
|
|
|
Method tries to read port from the '/etc/monasca/api-config.yml'
|
|
file. In case if:
|
|
|
|
* file was not found in specified location
|
|
* file could be read from the file system
|
|
* file was changed and assumed location of port changed
|
|
|
|
code rollbacks to :py:const:`_DEFAULT_API_PORT`
|
|
|
|
:return: TCP port api is listening on
|
|
:rtype: int
|
|
|
|
"""
|
|
if self._cfg is None:
|
|
return _DEFAULT_API_PORT
|
|
try:
|
|
return self._cfg['server']['applicationConnectors'][0]['port']
|
|
except Exception as ex:
|
|
log.error('Failed to read api port from configuration file')
|
|
log.exception(ex)
|
|
return _DEFAULT_API_PORT
|
|
|
|
|
|
class _MonAPIPythonHelper(object):
|
|
"""Encapsulates Python specific configuration for monasca-api"""
|
|
|
|
DEFAULT_CONFIG_FILE = '/etc/monasca/api-config.ini'
|
|
PASTE_CLI_OPTS = '--paste', '--paster',
|
|
"""Possible flags passed to gunicorn processed,
|
|
pointing at paste file"""
|
|
|
|
def __init__(self, cmdline=None, args=None):
|
|
super(_MonAPIPythonHelper, self).__init__()
|
|
self._cmdline = cmdline
|
|
self._args = args
|
|
self._paste_config = None
|
|
|
|
def build_config(self):
|
|
# note(trebskit) intentionally left empty because gunicorn check
|
|
# seems to have some serious issues with monitoring gunicorn process
|
|
# see https://bugs.launchpad.net/monasca/+bug/1646481
|
|
return None
|
|
|
|
def load_configuration(self):
|
|
"""Loads INI file from specified path.
|
|
|
|
Method loads configuration from specified `path`
|
|
and parses it with :py:class:`configparser.RawConfigParser`
|
|
|
|
"""
|
|
try:
|
|
config_file = self._get_config_file()
|
|
self._paste_config = self._read_config_file(config_file)
|
|
except Exception as ex:
|
|
log.error('Failed to parse %s', config_file)
|
|
log.exception(ex)
|
|
raise ex
|
|
|
|
def get_bound_port(self):
|
|
"""Returns port API is listening on.
|
|
|
|
Method tries to read port from the '/etc/monasca/api-config.ini'
|
|
file. In case if:
|
|
|
|
* file was not found in specified location
|
|
* file could be read from the file system
|
|
* file was changed and assumed location of port changed
|
|
|
|
code rollbacks to :py:const:`_DEFAULT_API_PORT`
|
|
|
|
:return: TCP port api is listening on
|
|
:rtype: int
|
|
|
|
"""
|
|
if not self._paste_config:
|
|
return _DEFAULT_API_PORT
|
|
return self._paste_config.getint('server:main', 'port')
|
|
|
|
def _read_config_file(self, config_file):
|
|
cp = configparser.RawConfigParser()
|
|
return cp.readfp(open(config_file, 'r'))
|
|
|
|
def _get_config_file(self):
|
|
"""Method gets configuration file of Python monasca-api.
|
|
|
|
Method tries to examine following locations:
|
|
|
|
* cmdline of process (looking for either
|
|
of :py:attr:`_MonAPIPythonHelper.PASTE_CLI_OPTS`)
|
|
* this plugin args
|
|
|
|
loooking for location of configuration file
|
|
|
|
:param args: plugin arguments
|
|
:type args: dict
|
|
|
|
"""
|
|
|
|
if self._cmdline:
|
|
# we're interested in PASTE_CLI_OPTS
|
|
for paste in self.PASTE_CLI_OPTS:
|
|
if paste in self._cmdline:
|
|
pos = self._cmdline.index(paste)
|
|
flag = self._cmdline[pos]
|
|
config_file = self._cmdline[pos + 1]
|
|
if config_file:
|
|
log.debug(('\tFound %s=%s for python configuration '
|
|
'from CLI'),
|
|
flag, config_file)
|
|
return config_file
|
|
else:
|
|
log.warn(('\tCannot determine neither %s from process'
|
|
'cmdline'), self.PASTE_CLI_OPTS)
|
|
|
|
if self._args and 'paste-file' in self._args:
|
|
# check if args mentions config file param
|
|
config_file = self._args.get('paste-file', None)
|
|
log.debug(('\tFound paste-file=%s for python configuration '
|
|
'passed as plugin argument'), config_file)
|
|
return config_file
|
|
|
|
config_file = self.DEFAULT_CONFIG_FILE
|
|
log.debug('\tAssuming default paste_file=%s', config_file)
|
|
|
|
return config_file
|