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 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
[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.

View File

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

View File

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

View File

@ -3,44 +3,77 @@
"""
import argparse
import logging
import os
import socket
import sys
import detection
import service
from detection import nova
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]
OS_SERVICE_MAP = {'linux': service.sysv.SysV}
log = logging.getLogger(__name__)
def main(argv=None):
parser = argparse.ArgumentParser(description='Detect running daemons then configure and start the agent.')
# parser.add_argument('--foo')
# - It will need to be invoked with keystone credentials. Other options you can invoke with include:
# - non-interactive
# - force overwrite of existing config
# - alternative config output directory
# - Optional active check config to include -
parser.add_argument('-u', '--username', help="Keystone username used to post metrics", required=True)
parser.add_argument('-p', '--password', help="Keystone password used to post metrics", required=True)
parser.add_argument('-s', '--service', help="Service this node is associated with.", required=True)
parser.add_argument('--keystone_url', help="Keystone url", required=True)
parser.add_argument('--mon_url', help="Mon API url", required=True)
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()
# todo these are currently hardcoded
config_dir = '/etc/mon-agent'
overwrite = True
# todo add the ability to build the config in a temp dir then diff
if args.verbose:
log.setLevel(logging.DEBUG)
else:
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
detected_os = 'linux' # todo add detection
# Run through detection and config building
for detect_class in DETECTION_CLASSES:
detect = detect_class(config_dir, overwrite)
detect.build_config()
# Service enable/start
agent_service = OS_SERVICE_MAP[detected_os]
# Service enable, includes setup of config directories so must be done before configuration
# todo is there a better place for basic directories to be made then the service enabling?
agent_service = OS_SERVICE_MAP[detected_os](os.path.join(args.template_dir, 'mon-agent.init'), args.config_dir)
# 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()
# 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)

View File

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

View File

@ -3,3 +3,15 @@ from . import Plugin
class Nova(Plugin):
"""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
"""
import psutil
class Service(object):
"""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
def enable(self):
@ -32,4 +34,9 @@ class Service(object):
def is_running(self):
"""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.
"""
from glob import glob
import logging
import os
import pwd
import subprocess
from . import Service
log = logging.getLogger(__name__)
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"""
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.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',
'redis',
'simplejson',
'supervisor',
'tornado',
'python-monclient',
]
@ -137,7 +138,7 @@ setup(
],
},
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/*'))],
test_suite='nose.collector'
)