Adding apache web server monitoring for OpenStack

Change-Id: I0c50e1d2825dd6324ef8ebd4b3a1bc32e1b2a3d5
This commit is contained in:
gary-hessler 2015-01-27 13:30:00 -07:00
parent 39ade0e4dd
commit 42f7c5ac16
5 changed files with 235 additions and 55 deletions

View File

@ -42,6 +42,7 @@
- [ZooKeeper Checks](#zookeeper-checks)
- [Kafka Checks](#kafka-checks)
- [RabbitMQ Checks](#rabbitmq-checks)
- [Apache Web Server Checks](#apache-web-server-checks)
- [OpenStack Monitoring](#openstack-monitoring)
- [Nova Checks](#nova-checks)
- [Nova Processes Monitored](#nova-processes-monitored)
@ -803,6 +804,43 @@ The RabbitMQ checks return the following metrics:
| rabbitmq.queue.messages.ack_rate | hostname, queue, vhost, service=rabbitmq | Queue |
## Apache Web Server Checks
This section describes the Apache Web Server check that can be performed by the Agent. The Apache check gathers metrics on the Apache Web Server. The Apache check requires a configuration file called apache.yaml to be available in the agent conf.d configuration directory. The config file must contain the server url, username and password (If you are using authentication) that you are interested in monitoring.
Sample config:
```
init_config:
instances:
- apache_status_url: http://localhost/server-status?auto
apache_user: root
apache_password: password
```
If you want the monasca-setup program to detect and auto-configure the plugin for you, you must create the file /root/.apache.cnf with the information needed in the configuration yaml file before running the setup program. It should look something like this:
```
[client]
url=http://localhost/server-status?auto
user=root
password=password
```
The Apache checks return the following metrics:
| Metric Name | Dimensions | Semantics |
| ----------- | ---------- | --------- |
| apache.performance.idle_worker_count | hostname, service=apache component=apache ||
| apache.performance.busy_worker_count | hostname, service=apache component=apache ||
| apache.performance.cpu_load_perc | hostname, service=apache component=apache ||
| apache.performance.uptime_sec | hostname, service=apache component=apache ||
| apache.net.total_kbytes | hostname, service=apache component=apache ||
| apache.net.hits | hostname, service=apache component=apache ||
| apache.net.kbytes_sec | hostname, service=apache component=apache ||
| apache.net.requests_sec | hostname, service=apache component=apache ||
## OpenStack Monitoring
The `monasca-setup` script when run on a system that is running OpenStack services, configures the Agent to send the following list of metrics:

View File

@ -1,90 +1,112 @@
# stdlib
import logging
import socket
import urllib2
import urlparse
from monasca_agent.collector.checks import AgentCheck
from monasca_agent.collector.checks.utils import add_basic_auth
from monasca_agent.common.util import headers
# project
import monasca_agent.common.util as util
import monasca_agent.collector.checks as checks
import monasca_agent.collector.checks.utils as utils
log = logging.getLogger(__name__)
class Apache(AgentCheck):
class Apache(checks.AgentCheck):
"""Tracks basic connection/requests/workers metrics
See http://httpd.apache.org/docs/2.2/mod/mod_status.html for more details
"""
GAUGES = {
'IdleWorkers': 'apache.performance.idle_workers',
'BusyWorkers': 'apache.performance.busy_workers',
'CPULoad': 'apache.performance.cpu_load',
'Uptime': 'apache.performance.uptime',
'Total kBytes': 'apache.net.bytes',
'Total Accesses': 'apache.net.hits',
}
RATES = {
'Total kBytes': 'apache.net.bytes_per_s',
'Total Accesses': 'apache.net.request_per_s'
}
GAUGES = {'IdleWorkers': 'apache.performance.idle_worker_count',
'BusyWorkers': 'apache.performance.busy_worker_count',
'CPULoad': 'apache.performance.cpu_load_perc',
'Total kBytes': 'apache.net.total_kbytes',
'Total Accesses': 'apache.net.hits',
}
RATES = {'Total kBytes': 'apache.net.kbytes_sec',
'Total Accesses': 'apache.net.requests_sec'
}
def __init__(self, name, init_config, agent_config, instances=None):
AgentCheck.__init__(self, name, init_config, agent_config, instances)
self.assumed_url = {}
super(Apache, self).__init__(name, init_config, agent_config, instances)
self.url = None
def check(self, instance):
if 'apache_status_url' not in instance:
self.url = instance.get('apache_status_url', None)
if not self.url:
raise Exception("Missing 'apache_status_url' in Apache config")
url = self.assumed_url.get(instance['apache_status_url'], instance['apache_status_url'])
# Load the dimensions
dimensions = instance.get('dimensions', {})
req = urllib2.Request(url, None, headers(self.agent_config))
if 'apache_user' in instance and 'apache_password' in instance:
add_basic_auth(req, instance['apache_user'], instance['apache_password'])
request = urllib2.urlopen(req)
response = request.read()
req = urllib2.Request(self.url, None, util.headers(self.agent_config))
apache_user = instance.get('apache_user', None)
apache_password = instance.get('apache_password', None)
if apache_user and apache_password:
utils.add_basic_auth(req, apache_user, apache_password)
else:
log.debug("Not using authentication for Apache Web Server")
# Submit a service check for status page availability.
parsed_url = urlparse.urlparse(self.url)
apache_host = parsed_url.hostname
apache_port = parsed_url.port or 80
service_check_name = 'apache.status'
# Add additional dimensions
if apache_host == 'localhost':
# Localhost is not very useful, so get the actual hostname
apache_host = socket.gethostname()
new_dimensions = self._set_dimensions({'apache_host': apache_host,
'apache_port': apache_port,
'service': 'apache',
'component': 'apache'})
if dimensions is not None:
new_dimensions.update(dimensions.copy())
try:
request = urllib2.urlopen(req)
except Exception as e:
self.log.info(
"%s is DOWN, error: %s. Connection failed." % (service_check_name, str(e)))
self.gauge(service_check_name, 1, dimensions=new_dimensions)
return services_checks.Status.DOWN, "%s is DOWN, error: %s. Connection failed." % (
service_check_name, str(e))
else:
self.log.debug("%s is UP" % service_check_name)
self.gauge(service_check_name, 0, dimensions=new_dimensions)
response = request.read()
metric_count = 0
# Loop through and extract the numerical values
for line in response.split('\n'):
values = line.split(': ')
if len(values) == 2: # match
if len(values) == 2: # match
metric, value = values
try:
value = float(value)
except ValueError:
continue
# Special case: kBytes => bytes
if metric == 'Total kBytes':
value *= 1024
# Send metric as a gauge, if applicable
if metric in self.GAUGES:
metric_count += 1
metric_name = self.GAUGES[metric]
self.gauge(metric_name, value, dimensions=dimensions)
log.debug('Collecting gauge data for: {0}'.format(metric_name))
self.gauge(metric_name, value, dimensions=new_dimensions)
# Send metric as a rate, if applicable
if metric in self.RATES:
metric_count += 1
metric_name = self.RATES[metric]
self.rate(metric_name, value, dimensions=dimensions)
log.debug('Collecting rate data for: {0}'.format(metric_name))
self.rate(metric_name, value, dimensions=new_dimensions)
if metric_count == 0:
if self.assumed_url.get(
instance['apache_status_url'], None) is None and url[-5:] != '?auto':
self.assumed_url[instance['apache_status_url']] = '%s?auto' % url
if self.url[-5:] != '?auto':
self.url = '%s?auto' % self.url
self.warning("Assuming url was not correct. Trying to add ?auto suffix to the url")
self.check(instance)
else:
raise Exception(
"No metrics were fetched for this instance. Make sure that %s is the proper url." %
instance['apache_status_url'])
@staticmethod
def parse_agent_config(agentConfig):
if not agentConfig.get('apache_status_url'):
return False
return {
'instances': [{'apache_status_url': agentConfig.get('apache_status_url')}]
}
return services_checks.Status.DOWN, "%s is DOWN, error: No metrics available.".format(service_check_name)
else:
log.debug("Collected {0} metrics for {1} Apache Web Server".format(apache_host, metric_count))

View File

@ -260,6 +260,7 @@ class Rate(Metric):
try:
val = self._rate(self.samples[-2], self.samples[-1])
except Exception:
log.exception("Error flushing sample.")
return []
return [self.formatter(

View File

@ -0,0 +1,118 @@
import logging
import urllib2
import monasca_setup.agent_config
import monasca_setup.detection
log = logging.getLogger(__name__)
apache_conf = '/root/.apache.cnf'
DEFAULT_APACHE_URL = 'http://localhost/server-status?auto'
class Apache(monasca_setup.detection.Plugin):
"""Detect Apache web server daemons and setup configuration to monitor them.
This plugin needs user/pass info for apache if security is setup on the web server,
this is best placed in /root/.apache.cnf in a format such as
[client]
url=http://localhost/server-status?auto
user=guest
password=guest
If this file is not provided, the plugin will attempt to connect without security
using a default URL.
"""
def _detect(self):
"""Run detection, set self.available True if the service is detected.
"""
if monasca_setup.detection.find_process_cmdline('apache2') is not None:
self.available = True
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(['apache2']))
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():
log.info(
"\tAttempting to use client credentials from {:s}".format(apache_conf))
# Read the apache config file to extract the needed variables.
client_section = False
apache_url = None
apache_user = None
apache_pass = None
try:
with open(apache_conf, "r") as confFile:
for row in confFile:
if "[client]" in row:
client_section = True
pass
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()
except IOError:
log.info("\tUnable to read {:s}".format(apache_conf))
log.info("\tWill try to setup Apache plugin using defaults.")
if not apache_url:
apache_url = DEFAULT_APACHE_URL
opener = None
if apache_user and apache_pass:
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None,
apache_url,
apache_user,
apache_pass)
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
else:
if 'https' in apache_url:
handler = urllib2.HTTPSHandler()
else:
handler = urllib2.HTTPHandler()
opener = urllib2.build_opener(handler)
response = None
try:
request = opener.open(apache_url)
response = request.read()
request.close()
if 'Total Accesses:' in response:
instance_vars = {'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 urllib2.URLError, e:
log.error('\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 in /root/.apache.cnf is correct.' + error_msg)
except urllib2.HTTPError, e:
log.error('\tError code {0} received when accessing {1}'.format(e.code, apache_url) + error_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

View File

@ -13,6 +13,7 @@ import sys
import yaml
import agent_config
import detection.plugins.apache as apache
import detection.plugins.ceilometer as ceilometer
import detection.plugins.cinder as cinder
import detection.plugins.glance as glance
@ -30,10 +31,10 @@ import detection.plugins.zookeeper as zookeeper
import service.sysv as sysv
# List of all detection plugins to run
DETECTION_PLUGINS = [ceilometer.Ceilometer, cinder.Cinder, glance.Glance,
kafka_consumer.Kafka, keystone.Keystone, libvirt.Libvirt,
mon.MonAPI, mon.MonPersister, mon.MonThresh, mysql.MySQL,
network.Network, neutron.Neutron, nova.Nova,
DETECTION_PLUGINS = [apache.Apache, ceilometer.Ceilometer, cinder.Cinder,
glance.Glance, kafka_consumer.Kafka, keystone.Keystone,
libvirt.Libvirt, mon.MonAPI, mon.MonPersister, mon.MonThresh,
mysql.MySQL, network.Network, neutron.Neutron, nova.Nova,
rabbitmq.RabbitMQ, swift.Swift, zookeeper.Zookeeper]
# Map OS to service type
OS_SERVICE_MAP = {'Linux': sysv.SysV}