sync charmhelpers
This commit is contained in:
parent
58ca032658
commit
fa8866dbba
|
@ -0,0 +1,253 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Authors:
|
||||||
|
# Adam Gandelman <adamg@ubuntu.com>
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import yaml
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_config(conf_file):
|
||||||
|
if not os.path.isfile(conf_file):
|
||||||
|
logging.error('Invalid config file: %s.' % conf_file)
|
||||||
|
return False
|
||||||
|
return yaml.load(open(conf_file).read())
|
||||||
|
|
||||||
|
|
||||||
|
def clone_helpers(work_dir, branch):
|
||||||
|
dest = os.path.join(work_dir, 'charm-helpers')
|
||||||
|
logging.info('Checking out %s to %s.' % (branch, dest))
|
||||||
|
cmd = ['bzr', 'checkout', '--lightweight', branch, dest]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
|
def _module_path(module):
|
||||||
|
return os.path.join(*module.split('.'))
|
||||||
|
|
||||||
|
|
||||||
|
def _src_path(src, module):
|
||||||
|
return os.path.join(src, 'charmhelpers', _module_path(module))
|
||||||
|
|
||||||
|
|
||||||
|
def _dest_path(dest, module):
|
||||||
|
return os.path.join(dest, _module_path(module))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_pyfile(path):
|
||||||
|
return os.path.isfile(path + '.py')
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_init(path):
|
||||||
|
'''
|
||||||
|
ensure directories leading up to path are importable, omitting
|
||||||
|
parent directory, eg path='/hooks/helpers/foo'/:
|
||||||
|
hooks/
|
||||||
|
hooks/helpers/__init__.py
|
||||||
|
hooks/helpers/foo/__init__.py
|
||||||
|
'''
|
||||||
|
for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
|
||||||
|
_i = os.path.join(d, '__init__.py')
|
||||||
|
if not os.path.exists(_i):
|
||||||
|
logging.info('Adding missing __init__.py: %s' % _i)
|
||||||
|
open(_i, 'wb').close()
|
||||||
|
|
||||||
|
|
||||||
|
def sync_pyfile(src, dest):
|
||||||
|
src = src + '.py'
|
||||||
|
src_dir = os.path.dirname(src)
|
||||||
|
logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
|
||||||
|
if not os.path.exists(dest):
|
||||||
|
os.makedirs(dest)
|
||||||
|
shutil.copy(src, dest)
|
||||||
|
if os.path.isfile(os.path.join(src_dir, '__init__.py')):
|
||||||
|
shutil.copy(os.path.join(src_dir, '__init__.py'),
|
||||||
|
dest)
|
||||||
|
ensure_init(dest)
|
||||||
|
|
||||||
|
|
||||||
|
def get_filter(opts=None):
|
||||||
|
opts = opts or []
|
||||||
|
if 'inc=*' in opts:
|
||||||
|
# do not filter any files, include everything
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _filter(dir, ls):
|
||||||
|
incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
|
||||||
|
_filter = []
|
||||||
|
for f in ls:
|
||||||
|
_f = os.path.join(dir, f)
|
||||||
|
|
||||||
|
if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
|
||||||
|
if True not in [fnmatch(_f, inc) for inc in incs]:
|
||||||
|
logging.debug('Not syncing %s, does not match include '
|
||||||
|
'filters (%s)' % (_f, incs))
|
||||||
|
_filter.append(f)
|
||||||
|
else:
|
||||||
|
logging.debug('Including file, which matches include '
|
||||||
|
'filters (%s): %s' % (incs, _f))
|
||||||
|
elif (os.path.isfile(_f) and not _f.endswith('.py')):
|
||||||
|
logging.debug('Not syncing file: %s' % f)
|
||||||
|
_filter.append(f)
|
||||||
|
elif (os.path.isdir(_f) and not
|
||||||
|
os.path.isfile(os.path.join(_f, '__init__.py'))):
|
||||||
|
logging.debug('Not syncing directory: %s' % f)
|
||||||
|
_filter.append(f)
|
||||||
|
return _filter
|
||||||
|
return _filter
|
||||||
|
|
||||||
|
|
||||||
|
def sync_directory(src, dest, opts=None):
|
||||||
|
if os.path.exists(dest):
|
||||||
|
logging.debug('Removing existing directory: %s' % dest)
|
||||||
|
shutil.rmtree(dest)
|
||||||
|
logging.info('Syncing directory: %s -> %s.' % (src, dest))
|
||||||
|
|
||||||
|
shutil.copytree(src, dest, ignore=get_filter(opts))
|
||||||
|
ensure_init(dest)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(src, dest, module, opts=None):
|
||||||
|
|
||||||
|
# Sync charmhelpers/__init__.py for bootstrap code.
|
||||||
|
sync_pyfile(_src_path(src, '__init__'), dest)
|
||||||
|
|
||||||
|
# Sync other __init__.py files in the path leading to module.
|
||||||
|
m = []
|
||||||
|
steps = module.split('.')[:-1]
|
||||||
|
while steps:
|
||||||
|
m.append(steps.pop(0))
|
||||||
|
init = '.'.join(m + ['__init__'])
|
||||||
|
sync_pyfile(_src_path(src, init),
|
||||||
|
os.path.dirname(_dest_path(dest, init)))
|
||||||
|
|
||||||
|
# Sync the module, or maybe a .py file.
|
||||||
|
if os.path.isdir(_src_path(src, module)):
|
||||||
|
sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
|
||||||
|
elif _is_pyfile(_src_path(src, module)):
|
||||||
|
sync_pyfile(_src_path(src, module),
|
||||||
|
os.path.dirname(_dest_path(dest, module)))
|
||||||
|
else:
|
||||||
|
logging.warn('Could not sync: %s. Neither a pyfile or directory, '
|
||||||
|
'does it even exist?' % module)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_sync_options(options):
|
||||||
|
if not options:
|
||||||
|
return []
|
||||||
|
return options.split(',')
|
||||||
|
|
||||||
|
|
||||||
|
def extract_options(inc, global_options=None):
|
||||||
|
global_options = global_options or []
|
||||||
|
if global_options and isinstance(global_options, six.string_types):
|
||||||
|
global_options = [global_options]
|
||||||
|
if '|' not in inc:
|
||||||
|
return (inc, global_options)
|
||||||
|
inc, opts = inc.split('|')
|
||||||
|
return (inc, parse_sync_options(opts) + global_options)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_helpers(include, src, dest, options=None):
|
||||||
|
if not os.path.isdir(dest):
|
||||||
|
os.makedirs(dest)
|
||||||
|
|
||||||
|
global_options = parse_sync_options(options)
|
||||||
|
|
||||||
|
for inc in include:
|
||||||
|
if isinstance(inc, str):
|
||||||
|
inc, opts = extract_options(inc, global_options)
|
||||||
|
sync(src, dest, inc, opts)
|
||||||
|
elif isinstance(inc, dict):
|
||||||
|
# could also do nested dicts here.
|
||||||
|
for k, v in six.iteritems(inc):
|
||||||
|
if isinstance(v, list):
|
||||||
|
for m in v:
|
||||||
|
inc, opts = extract_options(m, global_options)
|
||||||
|
sync(src, dest, '%s.%s' % (k, inc), opts)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option('-c', '--config', action='store', dest='config',
|
||||||
|
default=None, help='helper config file')
|
||||||
|
parser.add_option('-D', '--debug', action='store_true', dest='debug',
|
||||||
|
default=False, help='debug')
|
||||||
|
parser.add_option('-b', '--branch', action='store', dest='branch',
|
||||||
|
help='charm-helpers bzr branch (overrides config)')
|
||||||
|
parser.add_option('-d', '--destination', action='store', dest='dest_dir',
|
||||||
|
help='sync destination dir (overrides config)')
|
||||||
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if opts.debug:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
if opts.config:
|
||||||
|
logging.info('Loading charm helper config from %s.' % opts.config)
|
||||||
|
config = parse_config(opts.config)
|
||||||
|
if not config:
|
||||||
|
logging.error('Could not parse config from %s.' % opts.config)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
if 'branch' not in config:
|
||||||
|
config['branch'] = CHARM_HELPERS_BRANCH
|
||||||
|
if opts.branch:
|
||||||
|
config['branch'] = opts.branch
|
||||||
|
if opts.dest_dir:
|
||||||
|
config['destination'] = opts.dest_dir
|
||||||
|
|
||||||
|
if 'destination' not in config:
|
||||||
|
logging.error('No destination dir. specified as option or config.')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if 'include' not in config:
|
||||||
|
if not args:
|
||||||
|
logging.error('No modules to sync specified as option or config.')
|
||||||
|
sys.exit(1)
|
||||||
|
config['include'] = []
|
||||||
|
[config['include'].append(a) for a in args]
|
||||||
|
|
||||||
|
sync_options = None
|
||||||
|
if 'options' in config:
|
||||||
|
sync_options = config['options']
|
||||||
|
tmpd = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
checkout = clone_helpers(tmpd, config['branch'])
|
||||||
|
sync_helpers(config['include'], checkout, config['destination'],
|
||||||
|
options=sync_options)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("Could not sync: %s" % e)
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
logging.debug('Cleaning up %s' % tmpd)
|
||||||
|
shutil.rmtree(tmpd)
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,360 @@
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""Compatibility with the nrpe-external-master charm"""
|
||||||
|
# Copyright 2012 Canonical Ltd.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Matthew Wedgwood <matthew.wedgwood@canonical.com>
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import shutil
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from charmhelpers.core.hookenv import (
|
||||||
|
config,
|
||||||
|
local_unit,
|
||||||
|
log,
|
||||||
|
relation_ids,
|
||||||
|
relation_set,
|
||||||
|
relations_of_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
from charmhelpers.core.host import service
|
||||||
|
|
||||||
|
# This module adds compatibility with the nrpe-external-master and plain nrpe
|
||||||
|
# subordinate charms. To use it in your charm:
|
||||||
|
#
|
||||||
|
# 1. Update metadata.yaml
|
||||||
|
#
|
||||||
|
# provides:
|
||||||
|
# (...)
|
||||||
|
# nrpe-external-master:
|
||||||
|
# interface: nrpe-external-master
|
||||||
|
# scope: container
|
||||||
|
#
|
||||||
|
# and/or
|
||||||
|
#
|
||||||
|
# provides:
|
||||||
|
# (...)
|
||||||
|
# local-monitors:
|
||||||
|
# interface: local-monitors
|
||||||
|
# scope: container
|
||||||
|
|
||||||
|
#
|
||||||
|
# 2. Add the following to config.yaml
|
||||||
|
#
|
||||||
|
# nagios_context:
|
||||||
|
# default: "juju"
|
||||||
|
# type: string
|
||||||
|
# description: |
|
||||||
|
# Used by the nrpe subordinate charms.
|
||||||
|
# A string that will be prepended to instance name to set the host name
|
||||||
|
# in nagios. So for instance the hostname would be something like:
|
||||||
|
# juju-myservice-0
|
||||||
|
# If you're running multiple environments with the same services in them
|
||||||
|
# this allows you to differentiate between them.
|
||||||
|
# nagios_servicegroups:
|
||||||
|
# default: ""
|
||||||
|
# type: string
|
||||||
|
# description: |
|
||||||
|
# A comma-separated list of nagios servicegroups.
|
||||||
|
# If left empty, the nagios_context will be used as the servicegroup
|
||||||
|
#
|
||||||
|
# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
|
||||||
|
#
|
||||||
|
# 4. Update your hooks.py with something like this:
|
||||||
|
#
|
||||||
|
# from charmsupport.nrpe import NRPE
|
||||||
|
# (...)
|
||||||
|
# def update_nrpe_config():
|
||||||
|
# nrpe_compat = NRPE()
|
||||||
|
# nrpe_compat.add_check(
|
||||||
|
# shortname = "myservice",
|
||||||
|
# description = "Check MyService",
|
||||||
|
# check_cmd = "check_http -w 2 -c 10 http://localhost"
|
||||||
|
# )
|
||||||
|
# nrpe_compat.add_check(
|
||||||
|
# "myservice_other",
|
||||||
|
# "Check for widget failures",
|
||||||
|
# check_cmd = "/srv/myapp/scripts/widget_check"
|
||||||
|
# )
|
||||||
|
# nrpe_compat.write()
|
||||||
|
#
|
||||||
|
# def config_changed():
|
||||||
|
# (...)
|
||||||
|
# update_nrpe_config()
|
||||||
|
#
|
||||||
|
# def nrpe_external_master_relation_changed():
|
||||||
|
# update_nrpe_config()
|
||||||
|
#
|
||||||
|
# def local_monitors_relation_changed():
|
||||||
|
# update_nrpe_config()
|
||||||
|
#
|
||||||
|
# 5. ln -s hooks.py nrpe-external-master-relation-changed
|
||||||
|
# ln -s hooks.py local-monitors-relation-changed
|
||||||
|
|
||||||
|
|
||||||
|
class CheckException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Check(object):
|
||||||
|
shortname_re = '[A-Za-z0-9-_]+$'
|
||||||
|
service_template = ("""
|
||||||
|
#---------------------------------------------------
|
||||||
|
# This file is Juju managed
|
||||||
|
#---------------------------------------------------
|
||||||
|
define service {{
|
||||||
|
use active-service
|
||||||
|
host_name {nagios_hostname}
|
||||||
|
service_description {nagios_hostname}[{shortname}] """
|
||||||
|
"""{description}
|
||||||
|
check_command check_nrpe!{command}
|
||||||
|
servicegroups {nagios_servicegroup}
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
|
||||||
|
def __init__(self, shortname, description, check_cmd):
|
||||||
|
super(Check, self).__init__()
|
||||||
|
# XXX: could be better to calculate this from the service name
|
||||||
|
if not re.match(self.shortname_re, shortname):
|
||||||
|
raise CheckException("shortname must match {}".format(
|
||||||
|
Check.shortname_re))
|
||||||
|
self.shortname = shortname
|
||||||
|
self.command = "check_{}".format(shortname)
|
||||||
|
# Note: a set of invalid characters is defined by the
|
||||||
|
# Nagios server config
|
||||||
|
# The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
|
||||||
|
self.description = description
|
||||||
|
self.check_cmd = self._locate_cmd(check_cmd)
|
||||||
|
|
||||||
|
def _locate_cmd(self, check_cmd):
|
||||||
|
search_path = (
|
||||||
|
'/usr/lib/nagios/plugins',
|
||||||
|
'/usr/local/lib/nagios/plugins',
|
||||||
|
)
|
||||||
|
parts = shlex.split(check_cmd)
|
||||||
|
for path in search_path:
|
||||||
|
if os.path.exists(os.path.join(path, parts[0])):
|
||||||
|
command = os.path.join(path, parts[0])
|
||||||
|
if len(parts) > 1:
|
||||||
|
command += " " + " ".join(parts[1:])
|
||||||
|
return command
|
||||||
|
log('Check command not found: {}'.format(parts[0]))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def write(self, nagios_context, hostname, nagios_servicegroups):
|
||||||
|
nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
|
||||||
|
self.command)
|
||||||
|
with open(nrpe_check_file, 'w') as nrpe_check_config:
|
||||||
|
nrpe_check_config.write("# check {}\n".format(self.shortname))
|
||||||
|
nrpe_check_config.write("command[{}]={}\n".format(
|
||||||
|
self.command, self.check_cmd))
|
||||||
|
|
||||||
|
if not os.path.exists(NRPE.nagios_exportdir):
|
||||||
|
log('Not writing service config as {} is not accessible'.format(
|
||||||
|
NRPE.nagios_exportdir))
|
||||||
|
else:
|
||||||
|
self.write_service_config(nagios_context, hostname,
|
||||||
|
nagios_servicegroups)
|
||||||
|
|
||||||
|
def write_service_config(self, nagios_context, hostname,
|
||||||
|
nagios_servicegroups):
|
||||||
|
for f in os.listdir(NRPE.nagios_exportdir):
|
||||||
|
if re.search('.*{}.cfg'.format(self.command), f):
|
||||||
|
os.remove(os.path.join(NRPE.nagios_exportdir, f))
|
||||||
|
|
||||||
|
templ_vars = {
|
||||||
|
'nagios_hostname': hostname,
|
||||||
|
'nagios_servicegroup': nagios_servicegroups,
|
||||||
|
'description': self.description,
|
||||||
|
'shortname': self.shortname,
|
||||||
|
'command': self.command,
|
||||||
|
}
|
||||||
|
nrpe_service_text = Check.service_template.format(**templ_vars)
|
||||||
|
nrpe_service_file = '{}/service__{}_{}.cfg'.format(
|
||||||
|
NRPE.nagios_exportdir, hostname, self.command)
|
||||||
|
with open(nrpe_service_file, 'w') as nrpe_service_config:
|
||||||
|
nrpe_service_config.write(str(nrpe_service_text))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
subprocess.call(self.check_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
class NRPE(object):
|
||||||
|
nagios_logdir = '/var/log/nagios'
|
||||||
|
nagios_exportdir = '/var/lib/nagios/export'
|
||||||
|
nrpe_confdir = '/etc/nagios/nrpe.d'
|
||||||
|
|
||||||
|
def __init__(self, hostname=None):
|
||||||
|
super(NRPE, self).__init__()
|
||||||
|
self.config = config()
|
||||||
|
self.nagios_context = self.config['nagios_context']
|
||||||
|
if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
|
||||||
|
self.nagios_servicegroups = self.config['nagios_servicegroups']
|
||||||
|
else:
|
||||||
|
self.nagios_servicegroups = self.nagios_context
|
||||||
|
self.unit_name = local_unit().replace('/', '-')
|
||||||
|
if hostname:
|
||||||
|
self.hostname = hostname
|
||||||
|
else:
|
||||||
|
self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
|
||||||
|
self.checks = []
|
||||||
|
|
||||||
|
def add_check(self, *args, **kwargs):
|
||||||
|
self.checks.append(Check(*args, **kwargs))
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
try:
|
||||||
|
nagios_uid = pwd.getpwnam('nagios').pw_uid
|
||||||
|
nagios_gid = grp.getgrnam('nagios').gr_gid
|
||||||
|
except:
|
||||||
|
log("Nagios user not set up, nrpe checks not updated")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(NRPE.nagios_logdir):
|
||||||
|
os.mkdir(NRPE.nagios_logdir)
|
||||||
|
os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
|
||||||
|
|
||||||
|
nrpe_monitors = {}
|
||||||
|
monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
|
||||||
|
for nrpecheck in self.checks:
|
||||||
|
nrpecheck.write(self.nagios_context, self.hostname,
|
||||||
|
self.nagios_servicegroups)
|
||||||
|
nrpe_monitors[nrpecheck.shortname] = {
|
||||||
|
"command": nrpecheck.command,
|
||||||
|
}
|
||||||
|
|
||||||
|
service('restart', 'nagios-nrpe-server')
|
||||||
|
|
||||||
|
monitor_ids = relation_ids("local-monitors") + \
|
||||||
|
relation_ids("nrpe-external-master")
|
||||||
|
for rid in monitor_ids:
|
||||||
|
relation_set(relation_id=rid, monitors=yaml.dump(monitors))
|
||||||
|
|
||||||
|
|
||||||
|
def get_nagios_hostcontext(relation_name='nrpe-external-master'):
|
||||||
|
"""
|
||||||
|
Query relation with nrpe subordinate, return the nagios_host_context
|
||||||
|
|
||||||
|
:param str relation_name: Name of relation nrpe sub joined to
|
||||||
|
"""
|
||||||
|
for rel in relations_of_type(relation_name):
|
||||||
|
if 'nagios_hostname' in rel:
|
||||||
|
return rel['nagios_host_context']
|
||||||
|
|
||||||
|
|
||||||
|
def get_nagios_hostname(relation_name='nrpe-external-master'):
|
||||||
|
"""
|
||||||
|
Query relation with nrpe subordinate, return the nagios_hostname
|
||||||
|
|
||||||
|
:param str relation_name: Name of relation nrpe sub joined to
|
||||||
|
"""
|
||||||
|
for rel in relations_of_type(relation_name):
|
||||||
|
if 'nagios_hostname' in rel:
|
||||||
|
return rel['nagios_hostname']
|
||||||
|
|
||||||
|
|
||||||
|
def get_nagios_unit_name(relation_name='nrpe-external-master'):
|
||||||
|
"""
|
||||||
|
Return the nagios unit name prepended with host_context if needed
|
||||||
|
|
||||||
|
:param str relation_name: Name of relation nrpe sub joined to
|
||||||
|
"""
|
||||||
|
host_context = get_nagios_hostcontext(relation_name)
|
||||||
|
if host_context:
|
||||||
|
unit = "%s:%s" % (host_context, local_unit())
|
||||||
|
else:
|
||||||
|
unit = local_unit()
|
||||||
|
return unit
|
||||||
|
|
||||||
|
|
||||||
|
def add_init_service_checks(nrpe, services, unit_name):
|
||||||
|
"""
|
||||||
|
Add checks for each service in list
|
||||||
|
|
||||||
|
:param NRPE nrpe: NRPE object to add check to
|
||||||
|
:param list services: List of services to check
|
||||||
|
:param str unit_name: Unit name to use in check description
|
||||||
|
"""
|
||||||
|
for svc in services:
|
||||||
|
upstart_init = '/etc/init/%s.conf' % svc
|
||||||
|
sysv_init = '/etc/init.d/%s' % svc
|
||||||
|
if os.path.exists(upstart_init):
|
||||||
|
nrpe.add_check(
|
||||||
|
shortname=svc,
|
||||||
|
description='process check {%s}' % unit_name,
|
||||||
|
check_cmd='check_upstart_job %s' % svc
|
||||||
|
)
|
||||||
|
elif os.path.exists(sysv_init):
|
||||||
|
cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
|
||||||
|
cron_file = ('*/5 * * * * root '
|
||||||
|
'/usr/local/lib/nagios/plugins/check_exit_status.pl '
|
||||||
|
'-s /etc/init.d/%s status > '
|
||||||
|
'/var/lib/nagios/service-check-%s.txt\n' % (svc,
|
||||||
|
svc)
|
||||||
|
)
|
||||||
|
f = open(cronpath, 'w')
|
||||||
|
f.write(cron_file)
|
||||||
|
f.close()
|
||||||
|
nrpe.add_check(
|
||||||
|
shortname=svc,
|
||||||
|
description='process check {%s}' % unit_name,
|
||||||
|
check_cmd='check_status_file.py -f '
|
||||||
|
'/var/lib/nagios/service-check-%s.txt' % svc,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_nrpe_checks():
|
||||||
|
"""
|
||||||
|
Copy the nrpe checks into place
|
||||||
|
|
||||||
|
"""
|
||||||
|
NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
|
||||||
|
nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
|
||||||
|
'charmhelpers', 'contrib', 'openstack',
|
||||||
|
'files')
|
||||||
|
|
||||||
|
if not os.path.exists(NAGIOS_PLUGINS):
|
||||||
|
os.makedirs(NAGIOS_PLUGINS)
|
||||||
|
for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
|
||||||
|
if os.path.isfile(fname):
|
||||||
|
shutil.copy2(fname,
|
||||||
|
os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
|
||||||
|
|
||||||
|
|
||||||
|
def add_haproxy_checks(nrpe, unit_name):
|
||||||
|
"""
|
||||||
|
Add checks for each service in list
|
||||||
|
|
||||||
|
:param NRPE nrpe: NRPE object to add check to
|
||||||
|
:param str unit_name: Unit name to use in check description
|
||||||
|
"""
|
||||||
|
nrpe.add_check(
|
||||||
|
shortname='haproxy_servers',
|
||||||
|
description='Check HAProxy {%s}' % unit_name,
|
||||||
|
check_cmd='check_haproxy.sh')
|
||||||
|
nrpe.add_check(
|
||||||
|
shortname='haproxy_queue',
|
||||||
|
description='Check HAProxy queue depth {%s}' % unit_name,
|
||||||
|
check_cmd='check_haproxy_queue_depth.sh')
|
|
@ -0,0 +1,175 @@
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Functions for managing volumes in juju units. One volume is supported per unit.
|
||||||
|
Subordinates may have their own storage, provided it is on its own partition.
|
||||||
|
|
||||||
|
Configuration stanzas::
|
||||||
|
|
||||||
|
volume-ephemeral:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description: >
|
||||||
|
If false, a volume is mounted as sepecified in "volume-map"
|
||||||
|
If true, ephemeral storage will be used, meaning that log data
|
||||||
|
will only exist as long as the machine. YOU HAVE BEEN WARNED.
|
||||||
|
volume-map:
|
||||||
|
type: string
|
||||||
|
default: {}
|
||||||
|
description: >
|
||||||
|
YAML map of units to device names, e.g:
|
||||||
|
"{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
|
||||||
|
Service units will raise a configure-error if volume-ephemeral
|
||||||
|
is 'true' and no volume-map value is set. Use 'juju set' to set a
|
||||||
|
value and 'juju resolved' to complete configuration.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
from charmsupport.volumes import configure_volume, VolumeConfigurationError
|
||||||
|
from charmsupport.hookenv import log, ERROR
|
||||||
|
def post_mount_hook():
|
||||||
|
stop_service('myservice')
|
||||||
|
def post_mount_hook():
|
||||||
|
start_service('myservice')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
configure_volume(before_change=pre_mount_hook,
|
||||||
|
after_change=post_mount_hook)
|
||||||
|
except VolumeConfigurationError:
|
||||||
|
log('Storage could not be configured', ERROR)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# XXX: Known limitations
|
||||||
|
# - fstab is neither consulted nor updated
|
||||||
|
|
||||||
|
import os
|
||||||
|
from charmhelpers.core import hookenv
|
||||||
|
from charmhelpers.core import host
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
MOUNT_BASE = '/srv/juju/volumes'
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeConfigurationError(Exception):
|
||||||
|
'''Volume configuration data is missing or invalid'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
'''Gather and sanity-check volume configuration data'''
|
||||||
|
volume_config = {}
|
||||||
|
config = hookenv.config()
|
||||||
|
|
||||||
|
errors = False
|
||||||
|
|
||||||
|
if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
|
||||||
|
volume_config['ephemeral'] = True
|
||||||
|
else:
|
||||||
|
volume_config['ephemeral'] = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
volume_map = yaml.safe_load(config.get('volume-map', '{}'))
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
hookenv.log("Error parsing YAML volume-map: {}".format(e),
|
||||||
|
hookenv.ERROR)
|
||||||
|
errors = True
|
||||||
|
if volume_map is None:
|
||||||
|
# probably an empty string
|
||||||
|
volume_map = {}
|
||||||
|
elif not isinstance(volume_map, dict):
|
||||||
|
hookenv.log("Volume-map should be a dictionary, not {}".format(
|
||||||
|
type(volume_map)))
|
||||||
|
errors = True
|
||||||
|
|
||||||
|
volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
|
||||||
|
if volume_config['device'] and volume_config['ephemeral']:
|
||||||
|
# asked for ephemeral storage but also defined a volume ID
|
||||||
|
hookenv.log('A volume is defined for this unit, but ephemeral '
|
||||||
|
'storage was requested', hookenv.ERROR)
|
||||||
|
errors = True
|
||||||
|
elif not volume_config['device'] and not volume_config['ephemeral']:
|
||||||
|
# asked for permanent storage but did not define volume ID
|
||||||
|
hookenv.log('Ephemeral storage was requested, but there is no volume '
|
||||||
|
'defined for this unit.', hookenv.ERROR)
|
||||||
|
errors = True
|
||||||
|
|
||||||
|
unit_mount_name = hookenv.local_unit().replace('/', '-')
|
||||||
|
volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return None
|
||||||
|
return volume_config
|
||||||
|
|
||||||
|
|
||||||
|
def mount_volume(config):
|
||||||
|
if os.path.exists(config['mountpoint']):
|
||||||
|
if not os.path.isdir(config['mountpoint']):
|
||||||
|
hookenv.log('Not a directory: {}'.format(config['mountpoint']))
|
||||||
|
raise VolumeConfigurationError()
|
||||||
|
else:
|
||||||
|
host.mkdir(config['mountpoint'])
|
||||||
|
if os.path.ismount(config['mountpoint']):
|
||||||
|
unmount_volume(config)
|
||||||
|
if not host.mount(config['device'], config['mountpoint'], persist=True):
|
||||||
|
raise VolumeConfigurationError()
|
||||||
|
|
||||||
|
|
||||||
|
def unmount_volume(config):
|
||||||
|
if os.path.ismount(config['mountpoint']):
|
||||||
|
if not host.umount(config['mountpoint'], persist=True):
|
||||||
|
raise VolumeConfigurationError()
|
||||||
|
|
||||||
|
|
||||||
|
def managed_mounts():
|
||||||
|
'''List of all mounted managed volumes'''
|
||||||
|
return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
|
||||||
|
|
||||||
|
|
||||||
|
def configure_volume(before_change=lambda: None, after_change=lambda: None):
|
||||||
|
'''Set up storage (or don't) according to the charm's volume configuration.
|
||||||
|
Returns the mount point or "ephemeral". before_change and after_change
|
||||||
|
are optional functions to be called if the volume configuration changes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
config = get_config()
|
||||||
|
if not config:
|
||||||
|
hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
|
||||||
|
raise VolumeConfigurationError()
|
||||||
|
|
||||||
|
if config['ephemeral']:
|
||||||
|
if os.path.ismount(config['mountpoint']):
|
||||||
|
before_change()
|
||||||
|
unmount_volume(config)
|
||||||
|
after_change()
|
||||||
|
return 'ephemeral'
|
||||||
|
else:
|
||||||
|
# persistent storage
|
||||||
|
if os.path.ismount(config['mountpoint']):
|
||||||
|
mounts = dict(managed_mounts())
|
||||||
|
if mounts.get(config['mountpoint']) != config['device']:
|
||||||
|
before_change()
|
||||||
|
unmount_volume(config)
|
||||||
|
mount_volume(config)
|
||||||
|
after_change()
|
||||||
|
else:
|
||||||
|
before_change()
|
||||||
|
mount_volume(config)
|
||||||
|
after_change()
|
||||||
|
return config['mountpoint']
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from charmhelpers.fetch import apt_install, apt_update
|
||||||
|
from charmhelpers.core.hookenv import charm_dir, log
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pip import main as pip_execute
|
||||||
|
except ImportError:
|
||||||
|
apt_update()
|
||||||
|
apt_install('python-pip')
|
||||||
|
from pip import main as pip_execute
|
||||||
|
|
||||||
|
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_options(given, available):
|
||||||
|
"""Given a set of options, check if available"""
|
||||||
|
for key, value in sorted(given.items()):
|
||||||
|
if key in available:
|
||||||
|
yield "--{0}={1}".format(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install_requirements(requirements, **options):
|
||||||
|
"""Install a requirements file """
|
||||||
|
command = ["install"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'src', 'log', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
command.append("-r {0}".format(requirements))
|
||||||
|
log("Installing from file: {} with options: {}".format(requirements,
|
||||||
|
command))
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
|
||||||
|
"""Install a python package"""
|
||||||
|
if venv:
|
||||||
|
venv_python = os.path.join(venv, 'bin/pip')
|
||||||
|
command = [venv_python, "install"]
|
||||||
|
else:
|
||||||
|
command = ["install"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'src', 'log', 'index-url', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
if upgrade:
|
||||||
|
command.append('--upgrade')
|
||||||
|
|
||||||
|
if isinstance(package, list):
|
||||||
|
command.extend(package)
|
||||||
|
else:
|
||||||
|
command.append(package)
|
||||||
|
|
||||||
|
log("Installing {} package with options: {}".format(package,
|
||||||
|
command))
|
||||||
|
if venv:
|
||||||
|
subprocess.check_call(command)
|
||||||
|
else:
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_uninstall(package, **options):
|
||||||
|
"""Uninstall a python package"""
|
||||||
|
command = ["uninstall", "-q", "-y"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'log', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
if isinstance(package, list):
|
||||||
|
command.extend(package)
|
||||||
|
else:
|
||||||
|
command.append(package)
|
||||||
|
|
||||||
|
log("Uninstalling {} package with options: {}".format(package,
|
||||||
|
command))
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_list():
|
||||||
|
"""Returns the list of current python installed packages
|
||||||
|
"""
|
||||||
|
return pip_execute(["list"])
|
||||||
|
|
||||||
|
|
||||||
|
def pip_create_virtualenv(path=None):
|
||||||
|
"""Create an isolated Python environment."""
|
||||||
|
apt_install('python-virtualenv')
|
||||||
|
|
||||||
|
if path:
|
||||||
|
venv_path = path
|
||||||
|
else:
|
||||||
|
venv_path = os.path.join(charm_dir(), 'venv')
|
||||||
|
|
||||||
|
if not os.path.exists(venv_path):
|
||||||
|
subprocess.check_call(['virtualenv', venv_path])
|
Loading…
Reference in New Issue