monasca-agent/monasca_setup/detection/plugins/apache.py

197 lines
8.9 KiB
Python

# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
# (C) Copyright 2018 SUSE LLC
# 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
import os
from six.moves import urllib
import monasca_setup.agent_config
import monasca_setup.detection
log = logging.getLogger(__name__)
# Process name is apache2 on Debian derivatives, on RHEL derivatives it's httpd,
# on openSUSE/SLES it might be httpd-prefork or httpd-worker. With some versions
# of apache2-mod_perl, e.g., 2.0.8, the process name is "/usr/sbin/httpd" after
# being truncated to the UNIX limit of 15 characters. "/usr/sbin/httpd" should
# be removed from the list when we conclude the offending verson(s) of the
# mod_perl is no longer in use.
APACHE_PROCESS_NAMES = ('apache2', 'httpd', 'httpd-prefork', 'httpd-worker', '/usr/sbin/httpd')
DEFAULT_APACHE_CONFIG = '/root/.apache.cnf'
CONFIG_ARG_KEYS = set(["url", "user", "password", "use_server_status_metrics"])
class Apache(monasca_setup.detection.Plugin):
"""Detect Apache web server daemons and setup configuration to monitor apache.
This plugin will by default setup process check metrics for the apache process,
and setup server-status metrics using the input url. If only process check
metrics are desired, the use_server_status_metrics argument can be passed in
with a value of false.
This plugin needs user/password for apache if using the server-status metrics
when security is setup on the web server.
This plugin accepts arguments and if none are provided it will attempt to read
the default configuration file (/root/.apache.cnf) in a format such as:
[client]
url=http://localhost/server-status?auto
user=guest
password=guest
"""
def __init__(self, *args, **kwargs):
self._apache_process_name = 'apache2'
super(Apache, self).__init__(*args, **kwargs)
def _detect(self):
"""Run detection, set self.available True if the service is detected.
"""
process_exists = False
for proc_name in APACHE_PROCESS_NAMES:
if monasca_setup.detection.find_process_name(proc_name) is not None:
self._apache_process_name = proc_name
process_exists = True
has_args_or_config_file = (self.args is not None or
os.path.isfile(DEFAULT_APACHE_CONFIG))
self.available = process_exists and has_args_or_config_file
if not self.available:
if not process_exists:
log.info('Apache process does not exist.')
elif not has_args_or_config_file:
log.warning(('Apache process exists but '
'configuration file was not found and '
'no arguments were given.'))
def _read_apache_config(self, config_location):
# Read the apache config file to extract the needed variables.
client_section = False
apache_url = None
apache_user = None
apache_pass = None
use_server_status_metrics = True
try:
with open(config_location, "r") as config_file:
for row in config_file:
if "[client]" in row:
client_section = True
continue
if client_section:
if "url=" in row:
apache_url = row.split("=")[1].strip()
if "user=" in row:
apache_user = row.split("=")[1].strip()
if "password=" in row:
apache_pass = row.split("=")[1].strip()
if "use_server_status_metrics" in row:
use_server_status_metrics = row.split("=")[1].strip()
except IOError:
log.warn("\tUnable to read {:s}".format(config_location))
return apache_url, apache_user, apache_pass, use_server_status_metrics
def build_config(self):
"""Build the config as a Plugins object and return.
"""
config = monasca_setup.agent_config.Plugins()
# First watch the process
config.merge(monasca_setup.detection.watch_process(
[self._apache_process_name], 'apache'))
log.info("\tWatching the apache webserver process.")
error_msg = '\n\t*** The Apache plugin is not configured ***\n\tPlease correct and re-run'
'monasca-setup.'
# Attempt login, requires either an empty root password from localhost
# or relying on a configured /root/.apache.cnf
if self.dependencies_installed():
# If any of the exact keys are present, use them.
if self.args and self.args.viewkeys() & CONFIG_ARG_KEYS:
log.info("Attempting to use command args")
apache_url = self.args.get("url", None)
apache_user = self.args.get("user", None)
apache_pass = self.args.get("password", None)
use_server_status_metrics = self.args.get('use_server_status_metrics', True)
elif self.args and self.args.get("apache_config_file"):
config_file = self.args.get("apache_config_file")
apache_url, apache_user, apache_pass, use_server_status_metrics = \
self._read_apache_config(config_file)
else:
apache_url, apache_user, apache_pass, use_server_status_metrics = \
self._read_apache_config(DEFAULT_APACHE_CONFIG)
if type(use_server_status_metrics) is str:
use_server_status_metrics = (use_server_status_metrics.lower() == 'true')
if use_server_status_metrics:
if not apache_url:
missing_url_msg = ('\tNo server-status url specified.' + error_msg)
log.error(missing_url_msg)
raise Exception(missing_url_msg)
else:
log.info("\tWatching the apache process only.")
return config
if apache_user and apache_pass:
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None,
apache_url,
apache_user,
apache_pass)
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
else:
if 'https' in apache_url:
handler = urllib.request.HTTPSHandler()
else:
handler = urllib.request.HTTPHandler()
opener = urllib.request.build_opener(handler)
try:
request = opener.open(apache_url)
response = request.read()
request.close()
if 'Total Accesses:' in response:
instance_vars = {'name': apache_url, 'apache_status_url': apache_url}
if apache_user and apache_pass:
instance_vars.update({'apache_user': apache_user,
'apache_password': apache_pass})
config['apache'] = {'init_config': None, 'instances': [instance_vars]}
log.info("\tSuccessfully setup Apache plugin.")
else:
log.warn('Unable to access the Apache server-status URL;' + error_msg)
except urllib.error.URLError as e:
exception_msg = (
'\tError {0} received when accessing url {1}.'.format(e.reason, apache_url) +
'\n\tPlease ensure the Apache web server is running and your configuration ' +
'information is correct.' + error_msg)
log.error(exception_msg)
raise Exception(exception_msg)
except Exception as e:
exception_msg = (
'Error received when accessing url {0} exception {1}'.format(apache_url, e) +
error_msg)
log.error(exception_msg)
raise Exception(exception_msg)
else:
log.error(
'\tThe dependencies for Apache Web Server are not installed or unavailable.' +
error_msg)
return config
def dependencies_installed(self):
# No real dependencies to check
return True