Adding apache web server monitoring for OpenStack
Change-Id: I0c50e1d2825dd6324ef8ebd4b3a1bc32e1b2a3d5
This commit is contained in:
parent
39ade0e4dd
commit
42f7c5ac16
38
README.md
38
README.md
|
@ -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:
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue