Initial working mon-setup

This commit is contained in:
Tim Kuhlman 2014-06-06 10:34:09 -06:00
parent 4a791b3716
commit 771f63128a
9 changed files with 181 additions and 55 deletions

View File

@ -1,5 +1,4 @@
mon-agent #mon-agent
=========
The Monitoring Agent is a system for gathering metrics and sending them to The Monitoring Agent is a system for gathering metrics and sending them to
the Monitoring API. In its basic configuration, the Agent collects metrics the Monitoring API. In its basic configuration, the Agent collects metrics
@ -10,4 +9,8 @@ these may be used to gather metrics against remote systems and services as well.
For information on deploying and using the Monitoring Agent, please see the For information on deploying and using the Monitoring Agent, please see the
[Wiki](https://github.com/hpcloud-mon/mon-agent/wiki) [Wiki](https://github.com/hpcloud-mon/mon-agent/wiki)
# Simple Installation
- `pip install mon-agent`
- Run configuration `mon-setup -u me -p pass -s mini-mon --keystone_url https://keystone --mon_url https://mon-api`
Copyright (c) 2014 Hewlett-Packard Development Company, L.P. Copyright (c) 2014 Hewlett-Packard Development Company, L.P.

View File

@ -1,29 +1,24 @@
[Api] [Api]
# Monitoring API URL: URL for the monitoring API # Monitoring API URL: URL for the monitoring API
# Example: https://region-a.geo-1.monitoring.hpcloudsvc.com/v1.1/metrics # Example: https://region-a.geo-1.monitoring.hpcloudsvc.com/v1.1/metrics
url: CHANGE_ME url: {args.mon_url}
# Monitoring API Project Id: Project Id for the monitoring API # Keystone Username
project_id: CHANGE_ME username: {args.username}
# Monitoring API Username: Username for the monitoring API # Keystone Password
username: CHANGE_ME password: {args.password}
# Monitoring API Password: Password for the monitoring API
password: CHANGE_ME
# Use Keystone for Authentication?: Use Keystone for authentication
# True or False
use_keystone: True
# Keystone API URL: URL for the Keystone server to use # Keystone API URL: URL for the Keystone server to use
# Example: https://region-a.geo-1.identity.hpcloudsvc.com:35357/v3/auth/tokens # Example: https://region-a.geo-1.identity.hpcloudsvc.com:35357/v3/auth/tokens
keystone_url: CHANGE_ME keystone_url: {args.keystone_url}
[Main] [Main]
# Force the hostname to whatever you want. # Force the hostname to whatever you want.
#hostname: mymachine.mydomain hostname: {hostname}
# Optional dimensions to be sent with every metric from this node # Optional dimensions to be sent with every metric from this node
# They should be in the format name:value and seperated by a comma # They should be in the format name:value and seperated by a comma
# Example (dimensions: service:nova, group:group_a, ...) # Example (dimensions: service:nova, group:group_a, ...)
dimensions: service:mini-mon dimensions: service:{args.service}
# Set the threshold for accepting points to allow anything # Set the threshold for accepting points to allow anything
# with recent_point_threshold seconds # with recent_point_threshold seconds
@ -119,7 +114,7 @@ monstatsd_port : 8125
# Logging # Logging
# ========================================================================== # # ========================================================================== #
log_level: DEBUG log_level: INFO
collector_log_file: /var/log/mon-agent/collector.log collector_log_file: /var/log/mon-agent/collector.log
forwarder_log_file: /var/log/mon-agent/forwarder.log forwarder_log_file: /var/log/mon-agent/forwarder.log

View File

@ -824,7 +824,7 @@ def get_mon_api_config(config):
'project_id': '', 'project_id': '',
'username': '', 'username': '',
'password': False, 'password': False,
'use_keystone': False, 'use_keystone': True,
'keystone_url': '', 'keystone_url': '',
'dimensions': None} 'dimensions': None}

View File

@ -3,44 +3,77 @@
""" """
import argparse import argparse
import logging
import os
import socket
import sys import sys
import detection from detection import nova
import service from service import sysv
# List of all detection plugins to run
DETECTION_PLUGINS = [nova.Nova]
# Map OS to service type
OS_SERVICE_MAP = {'linux': sysv.SysV}
DETECTION_CLASSES = [detection.Core] log = logging.getLogger(__name__)
OS_SERVICE_MAP = {'linux': service.sysv.SysV}
def main(argv=None): def main(argv=None):
parser = argparse.ArgumentParser(description='Detect running daemons then configure and start the agent.') parser = argparse.ArgumentParser(description='Detect running daemons then configure and start the agent.')
# parser.add_argument('--foo') parser.add_argument('-u', '--username', help="Keystone username used to post metrics", required=True)
# - It will need to be invoked with keystone credentials. Other options you can invoke with include: parser.add_argument('-p', '--password', help="Keystone password used to post metrics", required=True)
# - non-interactive parser.add_argument('-s', '--service', help="Service this node is associated with.", required=True)
# - force overwrite of existing config parser.add_argument('--keystone_url', help="Keystone url", required=True)
# - alternative config output directory parser.add_argument('--mon_url', help="Mon API url", required=True)
# - Optional active check config to include - parser.add_argument('--config_dir', help="Alternative configuration directory", default='/etc/mon-agent')
parser.add_argument('--template_dir', help="Alternative template directory", default='/usr/local/share/mon/agent')
parser.add_argument('--headless', help="Run in a non-interactive mode", action="store_true")
parser.add_argument('--overwrite', help="Overwrite existing configuration", action="store_true")
parser.add_argument('-v', '--verbose', help="Verbose Output", action="store_true")
args = parser.parse_args() args = parser.parse_args()
# todo these are currently hardcoded if args.verbose:
config_dir = '/etc/mon-agent' log.setLevel(logging.DEBUG)
overwrite = True else:
# todo add the ability to build the config in a temp dir then diff log.setLevel(logging.INFO)
# todo implement a way to include some predefined configuration, useful for active checks
# todo if overwrite is set I should either warn or just delete any config files not in the new config
# todo add the ability to build the config in a temp dir then diff
# Detect os # Detect os
detected_os = 'linux' # todo add detection detected_os = 'linux' # todo add detection
# Run through detection and config building # Service enable, includes setup of config directories so must be done before configuration
for detect_class in DETECTION_CLASSES: # todo is there a better place for basic directories to be made then the service enabling?
detect = detect_class(config_dir, overwrite) agent_service = OS_SERVICE_MAP[detected_os](os.path.join(args.template_dir, 'mon-agent.init'), args.config_dir)
detect.build_config()
# Service enable/start
agent_service = OS_SERVICE_MAP[detected_os]
# Todo add logic for situations where either enable or start is not needed or if not running as root isn't possible # Todo add logic for situations where either enable or start is not needed or if not running as root isn't possible
agent_service.enable() agent_service.enable()
# Write the main agent.conf
agent_template = open(os.path.join(args.template_dir, 'agent.conf.template'), 'r')
agent_conf = open(os.path.join(args.config_dir, 'agent.conf'), 'w')
agent_conf.write(agent_template.read().format(args=args, hostname=socket.gethostname()))
agent_template.close()
agent_conf.close()
# Link the supervisor.conf
supervisor_path = os.path.join(args.config_dir, 'supervisor.conf')
if os.path.exists(supervisor_path):
os.remove(supervisor_path)
os.symlink(os.path.join(args.template_dir, 'supervisor.conf'), supervisor_path)
# Run through detection and config building for the plugins
for detect_class in DETECTION_PLUGINS:
detect = detect_class(args.config_dir, args.overwrite)
if detect.has_dependencies():
detect.build_config()
else:
log.warn('{0} found but not configured as it is missing dependencies: {1}'.format(detect.name,
detect.dependencies))
#todo add option to install dependencies
# Now that the config is build start the service
agent_service.start(restart=True) agent_service.start(restart=True)

View File

@ -3,21 +3,30 @@
""" """
class Base(object): class Plugin(object):
"""Base detection class implementing the interface.""" """Abstract class implemented by the mon-agent plugin detection classes"""
# todo these should include dependency detection
def __init__(self, config_dir, overwrite=True): def __init__(self, config_dir, overwrite=True):
self.config_dir = config_dir self.config_dir = config_dir
self.dependencies = ()
self.overwrite = overwrite self.overwrite = overwrite
self._detect()
def _detect(self):
"""Run detection"""
raise NotImplementedError
def build_config(self): def build_config(self):
raise NotImplementedError raise NotImplementedError
def has_dependencies(self):
raise NotImplementedError
class Core(Base): @property
"""Detect details related to the core mon-agent configuration.""" def name(self):
"""Return _name if set otherwise the class name"""
if '_name' in self.__dict__:
class Plugin(Base): return self._name
"""Abstract class implemented by the mon-agent plugin detection classes""" else:
# todo these should include dependency detection return self.__class__.__name__

View File

@ -3,3 +3,15 @@ from . import Plugin
class Nova(Plugin): class Nova(Plugin):
"""Detect Nova daemons and setup configuration to monitor them.""" """Detect Nova daemons and setup configuration to monitor them."""
pass
def _detect(self):
"""Run detection"""
pass
def build_config(self):
pass
def has_dependencies(self):
pass

View File

@ -1,10 +1,12 @@
"""Classes implementing different methods for running mon-agent on startup as well as starting the process immediately """Classes implementing different methods for running mon-agent on startup as well as starting the process immediately
""" """
import psutil
class Service(object): class Service(object):
"""Abstract base class implementing the interface for various service types.""" """Abstract base class implementing the interface for various service types."""
def __init__(self, name='mon-agent'): def __init__(self, config_dir, name='mon-agent'):
self.config_dir = config_dir
self.name = name self.name = name
def enable(self): def enable(self):
@ -32,4 +34,9 @@ class Service(object):
def is_running(self): def is_running(self):
"""Returns True if mon-agent is running, false otherwise """Returns True if mon-agent is running, false otherwise
""" """
raise NotImplementedError # Looking for the supervisor process not the individual components
for process in psutil.pids():
if '/etc/mon-agent/supervisor.conf' in process.cmdline():
return True
return False

View File

@ -1,15 +1,81 @@
"""System V style service. """System V style service.
""" """
from glob import glob
import logging
import os
import pwd
import subprocess
from . import Service from . import Service
log = logging.getLogger(__name__)
class SysV(Service): class SysV(Service):
def __init__(self, init_template): def __init__(self, init_template, config_dir, name='mon-agent', username='mon-agent'):
"""Setup this service with the given init template""" """Setup this service with the given init template"""
super(SysV, self).__init__() super(SysV, self).__init__(config_dir, name)
self.init_script = '/etc/init.d/%s' % self.name
self.init_template = init_template self.init_template = init_template
self.username = username
def enable(self):
"""Sets mon-agent to start on boot.
Generally this requires running as super user
"""
# Create mon-agent user/group if needed
try:
user = pwd.getpwnam(self.username)
except KeyError:
subprocess.check_call(['useradd', '-r', self.username])
user = pwd.getpwnam(self.username)
# Create dirs
# todo log dir is hardcoded
for path in ('/var/log/mon-agent', self.config_dir, '%s/conf.d' % self.config_dir):
if not os.path.exists(path):
os.mkdir(path, 0775)
os.chown(path, user.pw_uid, user.pw_gid)
# link the init script, then enable
if not os.path.exists(self.init_script):
os.symlink(self.init_template, self.init_script)
os.chmod(self.init_script, 0755)
for runlevel in ['2', '3', '4', '5']:
link_path = '/etc/rc%s.d/S10mon-agent' % runlevel
if not os.path.exists(link_path):
os.symlink(self.init_script, link_path)
def start(self, restart=True):
"""Starts mon-agent
If the agent is running and restart is True, restart
"""
if not self.is_enabled():
log.error('The service is not enabled')
return False
subprocess.check_call([self.init_script, 'start']) # Throws CalledProcessError on error
return True
def stop(self):
"""Stops mon-agent
"""
if not self.is_enabled():
log.error('The service is not enabled')
return False
subprocess.check_call([self.init_script, 'stop']) # Throws CalledProcessError on error
return True
def is_enabled(self):
"""Returns True if mon-agent is setup to start on boot, false otherwise
"""
if not os.path.exists(self.init_script):
return False
if len(glob('/etc/rc?.d/S??mon-agent')) > 0:
return True
else:
return False
# todo largely unimplemented, all needed files end up in /usr/local/share/mon/agent
# todo don't forget to setup the proper users for the agent to run as
# todo will need to setup supervisor.con

View File

@ -24,6 +24,7 @@ install_requires=[
'PyYAML', 'PyYAML',
'redis', 'redis',
'simplejson', 'simplejson',
'supervisor',
'tornado', 'tornado',
'python-monclient', 'python-monclient',
] ]
@ -137,7 +138,7 @@ setup(
], ],
}, },
include_package_data=True, include_package_data=True,
data_files=[('share/mon/agent', ['agent.conf.example','packaging/supervisor.conf', 'packaging/mon-agent.init']), data_files=[('share/mon/agent', ['agent.conf.template', 'packaging/supervisor.conf', 'packaging/mon-agent.init']),
('share/mon/agent/conf.d', glob('conf.d/*'))], ('share/mon/agent/conf.d', glob('conf.d/*'))],
test_suite='nose.collector' test_suite='nose.collector'
) )