Adam Dyess has proposed merging ~addyess/charm-nagios:blacken into charm-nagios:master.
Requested reviews: Nagios Charm developers (nagios-charmers) For more details, see: https://code.launchpad.net/~addyess/charm-nagios/+git/charm-nagios/+merge/387617 -- Your team Nagios Charm developers is requested to review the proposed merge of ~addyess/charm-nagios:blacken into charm-nagios:master.
diff --git a/Makefile b/Makefile index 49a4a5c..e5b661e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ endif default: echo Nothing to do -test: lint proof unittest functional +test: lint proof unittests functional @echo "Testing charm $(CHARM_NAME)" lint: @@ -34,7 +34,7 @@ functional: build PYTEST_CLOUD_REGION=$(PYTEST_CLOUD_REGION) \ tox -e functional -unittest: +unittests: @echo "Running unit tests" @tox -e unit diff --git a/bin/charm_helpers_sync.py b/bin/charm_helpers_sync.py index 7c0c194..a689b84 100644 --- a/bin/charm_helpers_sync.py +++ b/bin/charm_helpers_sync.py @@ -29,36 +29,36 @@ from fnmatch import fnmatch import six -CHARM_HELPERS_REPO = 'https://github.com/juju/charm-helpers' +CHARM_HELPERS_REPO = "https://github.com/juju/charm-helpers" def parse_config(conf_file): if not os.path.isfile(conf_file): - logging.error('Invalid config file: %s.' % conf_file) + logging.error("Invalid config file: %s." % conf_file) return False return yaml.load(open(conf_file).read()) def clone_helpers(work_dir, repo): - dest = os.path.join(work_dir, 'charm-helpers') - logging.info('Cloning out %s to %s.' % (repo, dest)) + dest = os.path.join(work_dir, "charm-helpers") + logging.info("Cloning out %s to %s." % (repo, dest)) branch = None - if '@' in repo: - repo, branch = repo.split('@', 1) - cmd = ['git', 'clone', '--depth=1'] + if "@" in repo: + repo, branch = repo.split("@", 1) + cmd = ["git", "clone", "--depth=1"] if branch is not None: - cmd += ['--branch', branch] + cmd += ["--branch", branch] cmd += [repo, dest] subprocess.check_call(cmd) return dest def _module_path(module): - return os.path.join(*module.split('.')) + return os.path.join(*module.split(".")) def _src_path(src, module): - return os.path.join(src, 'charmhelpers', _module_path(module)) + return os.path.join(src, "charmhelpers", _module_path(module)) def _dest_path(dest, module): @@ -66,73 +66,70 @@ def _dest_path(dest, module): def _is_pyfile(path): - return os.path.isfile(path + '.py') + 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') + """ + 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() + logging.info("Adding missing __init__.py: %s" % _i) + open(_i, "wb").close() def sync_pyfile(src, dest): - src = src + '.py' + src = src + ".py" src_dir = os.path.dirname(src) - logging.info('Syncing pyfile: %s -> %s.' % (src, dest)) + 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) + 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: + 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] + 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 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)) + 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) + 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) + 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) + logging.debug("Removing existing directory: %s" % dest) shutil.rmtree(dest) - logging.info('Syncing directory: %s -> %s.' % (src, dest)) + logging.info("Syncing directory: %s -> %s." % (src, dest)) shutil.copytree(src, dest, ignore=get_filter(opts)) ensure_init(dest) @@ -141,47 +138,44 @@ def sync_directory(src, dest, opts=None): def sync(src, dest, module, opts=None): # Sync charmhelpers/__init__.py for bootstrap code. - sync_pyfile(_src_path(src, '__init__'), dest) + sync_pyfile(_src_path(src, "__init__"), dest) # Sync other __init__.py files in the path leading to module. m = [] - steps = module.split('.')[:-1] + 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))) + 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))) + 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) + 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(',') + 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: + if "|" not in inc: return (inc, global_options) - inc, opts = inc.split('|') + inc, opts = inc.split("|") return (inc, parse_sync_options(opts) + global_options) def sync_helpers(include, src, dest, options=None): if os.path.exists(dest): - logging.debug('Removing existing directory: %s' % dest) + logging.debug("Removing existing directory: %s" % dest) shutil.rmtree(dest) if not os.path.isdir(dest): os.makedirs(dest) @@ -198,19 +192,19 @@ def sync_helpers(include, src, dest, options=None): if isinstance(v, list): for m in v: inc, opts = extract_options(m, global_options) - sync(src, dest, '%s.%s' % (k, inc), opts) + sync(src, dest, "%s.%s" % (k, inc), opts) -if __name__ == '__main__': +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('-r', '--repository', action='store', dest='repo', - help='charm-helpers git repository (overrides config)') - parser.add_option('-d', '--destination', action='store', dest='dest_dir', - help='sync destination dir (overrides config)') + 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( + "-r", "--repository", action="store", dest="repo", help="charm-helpers git repository (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: @@ -219,43 +213,42 @@ if __name__ == '__main__': logging.basicConfig(level=logging.INFO) if opts.config: - logging.info('Loading charm helper config from %s.' % 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) + logging.error("Could not parse config from %s." % opts.config) sys.exit(1) else: config = {} - if 'repo' not in config: - config['repo'] = CHARM_HELPERS_REPO + if "repo" not in config: + config["repo"] = CHARM_HELPERS_REPO if opts.repo: - config['repo'] = opts.repo + config["repo"] = opts.repo if opts.dest_dir: - config['destination'] = opts.dest_dir + config["destination"] = opts.dest_dir - if 'destination' not in config: - logging.error('No destination dir. specified as option or config.') + if "destination" not in config: + logging.error("No destination dir. specified as option or config.") sys.exit(1) - if 'include' not in config: + if "include" not in config: if not args: - logging.error('No modules to sync specified as option or config.') + logging.error("No modules to sync specified as option or config.") sys.exit(1) - config['include'] = [] - [config['include'].append(a) for a in args] + config["include"] = [] + [config["include"].append(a) for a in args] sync_options = None - if 'options' in config: - sync_options = config['options'] + if "options" in config: + sync_options = config["options"] tmpd = tempfile.mkdtemp() try: - checkout = clone_helpers(tmpd, config['repo']) - sync_helpers(config['include'], checkout, config['destination'], - options=sync_options) + checkout = clone_helpers(tmpd, config["repo"]) + 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) + logging.debug("Cleaning up %s" % tmpd) shutil.rmtree(tmpd) diff --git a/hooks/common.py b/hooks/common.py index ec34670..b3f403f 100644 --- a/hooks/common.py +++ b/hooks/common.py @@ -1,37 +1,42 @@ -import subprocess -import socket +"""Provide common utilities to many of the hooks in this charm.""" import os -import os.path import re import shutil +import socket +import subprocess import tempfile from charmhelpers.core.hookenv import ( + config, log, network_get, network_get_primary_address, unit_get, - config, ) from pynag import Model -INPROGRESS_DIR = '/etc/nagios3-inprogress' -INPROGRESS_CFG = '/etc/nagios3-inprogress/nagios.cfg' -INPROGRESS_CONF_D = '/etc/nagios3-inprogress/conf.d' -CHARM_CFG = '/etc/nagios3-inprogress/conf.d/charm.cfg' -MAIN_NAGIOS_BAK = '/etc/nagios3.bak' -MAIN_NAGIOS_DIR = '/etc/nagios3' -MAIN_NAGIOS_CFG = '/etc/nagios3/nagios.cfg' -PLUGIN_PATH = '/usr/lib/nagios/plugins' +INPROGRESS_DIR = "/etc/nagios3-inprogress" +INPROGRESS_CFG = "/etc/nagios3-inprogress/nagios.cfg" +INPROGRESS_CONF_D = "/etc/nagios3-inprogress/conf.d" +CHARM_CFG = "/etc/nagios3-inprogress/conf.d/charm.cfg" +MAIN_NAGIOS_BAK = "/etc/nagios3.bak" +MAIN_NAGIOS_DIR = "/etc/nagios3" +MAIN_NAGIOS_CFG = "/etc/nagios3/nagios.cfg" +PLUGIN_PATH = "/usr/lib/nagios/plugins" Model.cfg_file = INPROGRESS_CFG Model.pynag_directory = INPROGRESS_CONF_D -reduce_RE = re.compile(r'[\W_]') +REDUCE_RE = re.compile(r"[\W_]") def check_ip(n): + """ + Validate string is an ip address. + + @param str n: string to check is an IP + """ try: socket.inet_pton(socket.AF_INET, n) return True @@ -43,14 +48,14 @@ def check_ip(n): return False -def get_local_ingress_address(binding='website'): - # using network-get to retrieve the address details if available. - log('Getting hostname for binding %s' % binding) +def get_local_ingress_address(binding="website"): + """Use network-get to retrieve the address details if available.""" + log("Getting hostname for binding %s" % binding) try: network_info = network_get(binding) - if network_info is not None and 'ingress-addresses' in network_info: - log('Using ingress-addresses') - hostname = network_info['ingress-addresses'][0] + if network_info is not None and "ingress-addresses" in network_info: + log("Using ingress-addresses") + hostname = network_info["ingress-addresses"][0] log(hostname) return hostname except NotImplementedError: @@ -60,26 +65,28 @@ def get_local_ingress_address(binding='website'): # Pre 2.3 output try: hostname = network_get_primary_address(binding) - log('Using primary-addresses') + log("Using primary-addresses") except NotImplementedError: # pre Juju 2.0 - hostname = unit_get('private-address') - log('Using unit_get private address') + hostname = unit_get("private-address") + log("Using unit_get private address") log(hostname) return hostname def get_remote_relation_attr(remote_unit, attr_name, relation_id=None): + """Get Remote Relation Attributes.""" args = ["relation-get", attr_name, remote_unit] if relation_id is not None: - args.extend(['-r', relation_id]) + args.extend(["-r", relation_id]) return subprocess.check_output(args).strip() def get_ip_and_hostname(remote_unit, relation_id=None): - hostname = get_remote_relation_attr(remote_unit, 'ingress-address', relation_id) + """Get IP and Hostname from remote unit.""" + hostname = get_remote_relation_attr(remote_unit, "ingress-address", relation_id) if hostname is None or not len(hostname): - hostname = get_remote_relation_attr(remote_unit, 'private-address', relation_id) + hostname = get_remote_relation_attr(remote_unit, "private-address", relation_id) if hostname is None or not len(hostname): log("relation-get failed") @@ -89,18 +96,22 @@ def get_ip_and_hostname(remote_unit, relation_id=None): ip_address = hostname else: ip_address = socket.getaddrinfo(hostname, None)[0][4][0] - return (ip_address, remote_unit.replace('/', '-')) + return (ip_address, remote_unit.replace("/", "-")) def refresh_hostgroups(): # noqa:C901 - """ Not the most efficient thing but since we're only - parsing what is already on disk here its not too bad """ - hosts = [x['host_name'] for x in Model.Host.objects.all if x['host_name']] + """ + Refresh Host Groups. + + Not the most efficient thing but since we're only + parsing what is already on disk here its not too bad + """ + hosts = [x["host_name"] for x in Model.Host.objects.all if x["host_name"]] hgroups = {} for host in hosts: try: - (service, unit_id) = host.rsplit('-', 1) + (service, unit_id) = host.rsplit("-", 1) except ValueError: continue if service in hgroups: @@ -109,8 +120,8 @@ def refresh_hostgroups(): # noqa:C901 hgroups[service] = [host] # Find existing autogenerated - auto_hgroups = Model.Hostgroup.objects.filter(notes__contains='#autogenerated#') - auto_hgroups = [x.get_attribute('hostgroup_name') for x in auto_hgroups] + auto_hgroups = Model.Hostgroup.objects.filter(notes__contains="#autogenerated#") + auto_hgroups = [x.get_attribute("hostgroup_name") for x in auto_hgroups] # Delete the ones not in hgroups to_delete = set(auto_hgroups).difference(set(hgroups.keys())) @@ -127,10 +138,10 @@ def refresh_hostgroups(): # noqa:C901 except (ValueError, KeyError): hgroup = Model.Hostgroup() hgroup.set_filename(CHARM_CFG) - hgroup.set_attribute('hostgroup_name', hgroup_name) - hgroup.set_attribute('notes', '#autogenerated#') + hgroup.set_attribute("hostgroup_name", hgroup_name) + hgroup.set_attribute("notes", "#autogenerated#") - hgroup.set_attribute('members', ','.join(members)) + hgroup.set_attribute("members", ",".join(members)) hgroup.save() @@ -138,15 +149,14 @@ def _make_check_command(args): args = [str(arg) for arg in args] # There is some worry of collision, but the uniqueness of the initial # command should be enough. - signature = reduce_RE.sub('_', ''.join( - [os.path.basename(arg) for arg in args])) + signature = REDUCE_RE.sub("_", "".join([os.path.basename(arg) for arg in args])) Model.Command.objects.reload_cache() try: cmd = Model.Command.objects.get_by_shortname(signature) except (ValueError, KeyError): cmd = Model.Command() - cmd.set_attribute('command_name', signature) - cmd.set_attribute('command_line', ' '.join(args)) + cmd.set_attribute("command_name", signature) + cmd.set_attribute("command_line", " ".join(args)) cmd.save() return signature @@ -157,165 +167,167 @@ def _extend_args(args, cmd_args, switch, value): def customize_http(service, name, extra): - args = [] - cmd_args = [] - plugin = os.path.join(PLUGIN_PATH, 'check_http') - port = extra.get('port', 80) - path = extra.get('path', '/') + """Customize the http check.""" + plugin = os.path.join(PLUGIN_PATH, "check_http") + port = extra.get("port", 80) + path = extra.get("path", "/") args = [port, path] - cmd_args = [plugin, '-p', '"$ARG1$"', '-u', '"$ARG2$"'] - if 'status' in extra: - _extend_args(args, cmd_args, '-e', extra['status']) - if 'host' in extra: - _extend_args(args, cmd_args, '-H', extra['host']) - cmd_args.extend(('-I', '$HOSTADDRESS$')) + cmd_args = [plugin, "-p", '"$ARG1$"', "-u", '"$ARG2$"'] + if "status" in extra: + _extend_args(args, cmd_args, "-e", extra["status"]) + if "host" in extra: + _extend_args(args, cmd_args, "-H", extra["host"]) + cmd_args.extend(("-I", "$HOSTADDRESS$")) else: - cmd_args.extend(('-H', '$HOSTADDRESS$')) - check_timeout = config('check_timeout') + cmd_args.extend(("-H", "$HOSTADDRESS$")) + check_timeout = config("check_timeout") if check_timeout is not None: - cmd_args.extend(('-t', check_timeout)) + cmd_args.extend(("-t", check_timeout)) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_mysql(service, name, extra): - plugin = os.path.join(PLUGIN_PATH, 'check_mysql') + """Customize the mysql check.""" + plugin = os.path.join(PLUGIN_PATH, "check_mysql") args = [] - cmd_args = [plugin, '-H', '$HOSTADDRESS$'] - if 'user' in extra: - _extend_args(args, cmd_args, '-u', extra['user']) - if 'password' in extra: - _extend_args(args, cmd_args, '-p', extra['password']) - check_timeout = config('check_timeout') + cmd_args = [plugin, "-H", "$HOSTADDRESS$"] + if "user" in extra: + _extend_args(args, cmd_args, "-u", extra["user"]) + if "password" in extra: + _extend_args(args, cmd_args, "-p", extra["password"]) + check_timeout = config("check_timeout") if check_timeout is not None: - cmd_args.extend(('-t', check_timeout)) + cmd_args.extend(("-t", check_timeout)) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_pgsql(service, name, extra): - plugin = os.path.join(PLUGIN_PATH, 'check_pgsql') + """Customize the pgsql check.""" + plugin = os.path.join(PLUGIN_PATH, "check_pgsql") args = [] - cmd_args = [plugin, '-H', '$HOSTADDRESS$'] - check_timeout = config('check_timeout') + cmd_args = [plugin, "-H", "$HOSTADDRESS$"] + check_timeout = config("check_timeout") if check_timeout is not None: - cmd_args.extend(('-t', check_timeout)) + cmd_args.extend(("-t", check_timeout)) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_nrpe(service, name, extra): - plugin = os.path.join(PLUGIN_PATH, 'check_nrpe') + """Customize the nrpe check.""" + plugin = os.path.join(PLUGIN_PATH, "check_nrpe") args = [] - cmd_args = [plugin, '-H', '$HOSTADDRESS$'] - if name in ('mem', 'swap'): - cmd_args.extend(('-c', 'check_%s' % name)) - elif 'command' in extra: - cmd_args.extend(('-c', extra['command'])) + cmd_args = [plugin, "-H", "$HOSTADDRESS$"] + if name in ("mem", "swap"): + cmd_args.extend(("-c", "check_%s" % name)) + elif "command" in extra: + cmd_args.extend(("-c", extra["command"])) else: - cmd_args.extend(('-c', extra)) - check_timeout = config('check_timeout') + cmd_args.extend(("-c", extra)) + check_timeout = config("check_timeout") if check_timeout is not None: - cmd_args.extend(('-t', check_timeout)) + cmd_args.extend(("-t", check_timeout)) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_rpc(service, name, extra): - """ Customize the check_rpc plugin to check things like nfs.""" - plugin = os.path.join(PLUGIN_PATH, 'check_rpc') + """Customize the check_rpc plugin to check things like nfs.""" + plugin = os.path.join(PLUGIN_PATH, "check_rpc") args = [] # /usr/lib/nagios/plugins/check_rpc -H <host> -C <rpc_command> - cmd_args = [plugin, '-H', '$HOSTADDRESS$'] - if 'rpc_command' in extra: - cmd_args.extend(('-C', extra['rpc_command'])) - if 'program_version' in extra: - cmd_args.extend(('-c', extra['program_version'])) + cmd_args = [plugin, "-H", "$HOSTADDRESS$"] + if "rpc_command" in extra: + cmd_args.extend(("-C", extra["rpc_command"])) + if "program_version" in extra: + cmd_args.extend(("-c", extra["program_version"])) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_tcp(service, name, extra): - """ Customize tcp can be used to check things like memcached. """ - plugin = os.path.join(PLUGIN_PATH, 'check_tcp') + """Customize tcp can be used to check things like memcached.""" + plugin = os.path.join(PLUGIN_PATH, "check_tcp") args = [] # /usr/lib/nagios/plugins/check_tcp -H <host> -E - cmd_args = [plugin, '-H', '$HOSTADDRESS$', '-E'] - if 'port' in extra: - cmd_args.extend(('-p', extra['port'])) - if 'string' in extra: - cmd_args.extend(('-s', "'{}'".format(extra['string']))) - if 'expect' in extra: - cmd_args.extend(('-e', extra['expect'])) - if 'warning' in extra: - cmd_args.extend(('-w', extra['warning'])) - if 'critical' in extra: - cmd_args.extend(('-c', extra['critical'])) - if 'timeout' in extra: - cmd_args.extend(('-t', extra['timeout'])) - check_timeout = config('check_timeout') + cmd_args = [plugin, "-H", "$HOSTADDRESS$", "-E"] + if "port" in extra: + cmd_args.extend(("-p", extra["port"])) + if "string" in extra: + cmd_args.extend(("-s", "'{}'".format(extra["string"]))) + if "expect" in extra: + cmd_args.extend(("-e", extra["expect"])) + if "warning" in extra: + cmd_args.extend(("-w", extra["warning"])) + if "critical" in extra: + cmd_args.extend(("-c", extra["critical"])) + if "timeout" in extra: + cmd_args.extend(("-t", extra["timeout"])) + check_timeout = config("check_timeout") if check_timeout is not None: - cmd_args.extend(('-t', check_timeout)) + cmd_args.extend(("-t", check_timeout)) check_command = _make_check_command(cmd_args) - cmd = '%s!%s' % (check_command, '!'.join([str(x) for x in args])) - service.set_attribute('check_command', cmd) + cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args])) + service.set_attribute("check_command", cmd) return True def customize_service(service, family, name, extra): - """ The monitors.yaml names are mapped to methods that customize services. """ - customs = {'http': customize_http, - 'mysql': customize_mysql, - 'nrpe': customize_nrpe, - 'tcp': customize_tcp, - 'rpc': customize_rpc, - 'pgsql': customize_pgsql, - } + """Customize Service based on names in monitors.yaml.""" + customs = { + "http": customize_http, + "mysql": customize_mysql, + "nrpe": customize_nrpe, + "tcp": customize_tcp, + "rpc": customize_rpc, + "pgsql": customize_pgsql, + } if family in customs: return customs[family](service, name, extra) return False def update_localhost(): - """ Update the localhost definition to use the ubuntu icons.""" - + """Update the localhost definition to use the ubuntu icons.""" Model.cfg_file = MAIN_NAGIOS_CFG - Model.pynag_directory = os.path.join(MAIN_NAGIOS_DIR, 'conf.d') - hosts = Model.Host.objects.filter(host_name='localhost', - object_type='host') + Model.pynag_directory = os.path.join(MAIN_NAGIOS_DIR, "conf.d") + hosts = Model.Host.objects.filter(host_name="localhost", object_type="host") for host in hosts: - host.icon_image = 'base/ubuntu.png' - host.icon_image_alt = 'Ubuntu Linux' - host.vrml_image = 'ubuntu.png' - host.statusmap_image = 'base/ubuntu.gd2' + host.icon_image = "base/ubuntu.png" + host.icon_image_alt = "Ubuntu Linux" + host.vrml_image = "ubuntu.png" + host.statusmap_image = "base/ubuntu.gd2" host.save() def get_pynag_host(target_id, owner_unit=None, owner_relation=None): + """Get Pynag Host by target_id.""" try: host = Model.Host.objects.get_by_shortname(target_id) except (ValueError, KeyError): host = Model.Host() host.set_filename(CHARM_CFG) - host.set_attribute('host_name', target_id) - host.set_attribute('use', 'generic-host') + host.set_attribute("host_name", target_id) + host.set_attribute("use", "generic-host") # Adding the ubuntu icon image definitions to the host. - host.set_attribute('icon_image', 'base/ubuntu.png') - host.set_attribute('icon_image_alt', 'Ubuntu Linux') - host.set_attribute('vrml_image', 'ubuntu.png') - host.set_attribute('statusmap_image', 'base/ubuntu.gd2') + host.set_attribute("icon_image", "base/ubuntu.png") + host.set_attribute("icon_image_alt", "Ubuntu Linux") + host.set_attribute("vrml_image", "ubuntu.png") + host.set_attribute("statusmap_image", "base/ubuntu.gd2") host.save() host = Model.Host.objects.get_by_shortname(target_id) apply_host_policy(target_id, owner_unit, owner_relation) @@ -323,22 +335,23 @@ def get_pynag_host(target_id, owner_unit=None, owner_relation=None): def get_pynag_service(target_id, service_name): - services = Model.Service.objects.filter(host_name=target_id, - service_description=service_name) + """Get Pynag service by target_id and service name.""" + services = Model.Service.objects.filter(host_name=target_id, service_description=service_name) if len(services) == 0: service = Model.Service() service.set_filename(CHARM_CFG) - service.set_attribute('service_description', service_name) - service.set_attribute('host_name', target_id) - service.set_attribute('use', 'generic-service') + service.set_attribute("service_description", service_name) + service.set_attribute("host_name", target_id) + service.set_attribute("use", "generic-service") else: service = services[0] return service -def apply_host_policy(target_id, owner_unit, owner_relation): - ssh_service = get_pynag_service(target_id, 'SSH') - ssh_service.set_attribute('check_command', 'check_ssh') +def apply_host_policy(target_id, _owner_unit, _owner_relation): + """Apply host policy.""" + ssh_service = get_pynag_service(target_id, "SSH") + ssh_service.set_attribute("check_command", "check_ssh") ssh_service.save() @@ -365,6 +378,7 @@ def _commit_in_config(find_me, replacement): def initialize_inprogress_config(): + """Initialize In-progress config.""" if os.path.exists(INPROGRESS_DIR): shutil.rmtree(INPROGRESS_DIR) shutil.copytree(MAIN_NAGIOS_DIR, INPROGRESS_DIR) @@ -374,6 +388,7 @@ def initialize_inprogress_config(): def flush_inprogress_config(): + """Flush In-progress config.""" if not os.path.exists(INPROGRESS_DIR): return if os.path.exists(MAIN_NAGIOS_BAK): diff --git a/hooks/monitors_relation_changed.py b/hooks/monitors_relation_changed.py index 5e4f664..15ed680 100755 --- a/hooks/monitors_relation_changed.py +++ b/hooks/monitors_relation_changed.py @@ -1,50 +1,54 @@ #!/usr/bin/python -# monitors-relation-changed - Process monitors.yaml into remote nagios monitors -# Copyright Canonical 2012 Canonical Ltd. All Rights Reserved -# Author: Clint Byrum <[email protected]> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +monitors-relation-changed - Process monitors.yaml into remote nagios monitors. + +Copyright Canonical 2020 Canonical Ltd. All Rights Reserved +Author: Clint Byrum <[email protected]> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" -import sys import os -import yaml import re +import sys from collections import defaultdict from charmhelpers.core.hookenv import ( - relation_get, + DEBUG, ingress_address, + log, related_units, + relation_get, relation_ids, - log, - DEBUG ) from common import ( customize_service, + flush_inprogress_config, get_pynag_host, get_pynag_service, - refresh_hostgroups, initialize_inprogress_config, - flush_inprogress_config + refresh_hostgroups, ) +import yaml + REQUIRED_REL_DATA_KEYS = [ - 'target-address', - 'monitors', - 'target-id', + "target-address", + "monitors", + "target-id", ] @@ -52,20 +56,17 @@ def _prepare_relation_data(unit, rid): relation_data = relation_get(unit=unit, rid=rid) if not relation_data: - msg = ( - 'no relation data found for unit {} in relation {} - ' - 'skipping'.format(unit, rid) - ) + msg = "no relation data found for unit {} in relation {} - " "skipping".format(unit, rid) log(msg, level=DEBUG) return {} - if rid.split(':')[0] == 'nagios': + if rid.split(":")[0] == "nagios": # Fake it for the more generic 'nagios' relation - relation_data['target-id'] = unit.replace('/', '-') - relation_data['monitors'] = {'monitors': {'remote': {}}} + relation_data["target-id"] = unit.replace("/", "-") + relation_data["monitors"] = {"monitors": {"remote": {}}} - if not relation_data.get('target-address'): - relation_data['target-address'] = ingress_address(unit=unit, rid=rid) + if not relation_data.get("target-address"): + relation_data["target-address"] = ingress_address(unit=unit, rid=rid) for key in REQUIRED_REL_DATA_KEYS: if not relation_data.get(key): @@ -73,10 +74,7 @@ def _prepare_relation_data(unit, rid): # the relation at first (e.g. gnocchi). After a few hook runs, # though, they add the key. For this reason I think using a logging # level higher than DEBUG could be misleading - msg = ( - '{} not found for unit {} in relation {} - ' - 'skipping'.format(key, unit, rid) - ) + msg = "{} not found for unit {} in relation {} - " "skipping".format(key, unit, rid) log(msg, level=DEBUG) return {} @@ -85,7 +83,7 @@ def _prepare_relation_data(unit, rid): def _collect_relation_data(): all_relations = defaultdict(dict) - for relname in ['nagios', 'monitors']: + for relname in ["nagios", "monitors"]: for relid in relation_ids(relname): for unit in related_units(relid): relation_data = _prepare_relation_data(unit=unit, rid=relid) @@ -96,29 +94,31 @@ def _collect_relation_data(): def main(argv): # noqa: C901 - # Note that one can pass in args positionally, 'monitors.yaml targetid - # and target-address' so the hook can be tested without being in a hook - # context. - # + """ + Handle monitor-relation-* hooks. + + Note that one can pass in args positionally, 'monitors.yaml targetid + and target-address' so the hook can be tested without being in a hook + context. + """ if len(argv) > 1: - relation_settings = {'monitors': open(argv[1]).read(), - 'target-id': argv[2]} + relation_settings = {"monitors": open(argv[1]).read(), "target-id": argv[2]} if len(argv) > 3: - relation_settings['target-address'] = argv[3] - all_relations = {'monitors:99': {'testing/0': relation_settings}} + relation_settings["target-address"] = argv[3] + all_relations = {"monitors:99": {"testing/0": relation_settings}} else: all_relations = _collect_relation_data() # Hack to work around http://pad.lv/1025478 targets_with_addresses = set() - for relid, units in all_relations.iteritems(): + for relid, units in all_relations.items(): for unit, relation_settings in units.items(): - if 'target-id' in relation_settings: - targets_with_addresses.add(relation_settings['target-id']) + if "target-id" in relation_settings: + targets_with_addresses.add(relation_settings["target-id"]) new_all_relations = {} - for relid, units in all_relations.iteritems(): + for relid, units in all_relations.items(): for unit, relation_settings in units.items(): - if relation_settings['target-id'] in targets_with_addresses: + if relation_settings["target-id"] in targets_with_addresses: if relid not in new_all_relations: new_all_relations[relid] = {} new_all_relations[relid][unit] = relation_settings @@ -128,22 +128,23 @@ def main(argv): # noqa: C901 # make a dict of machine ids to target-id hostnames all_hosts = {} for relid, units in all_relations.items(): - for unit, relation_settings in units.iteritems(): - machine_id = relation_settings.get('machine_id', None) + for unit, relation_settings in units.items(): + machine_id = relation_settings.get("machine_id", None) if machine_id: - all_hosts[machine_id] = relation_settings['target-id'] + all_hosts[machine_id] = relation_settings["target-id"] for relid, units in all_relations.items(): apply_relation_config(relid, units, all_hosts) refresh_hostgroups() flush_inprogress_config() - os.system('service nagios3 reload') + os.system("service nagios3 reload") -def apply_relation_config(relid, units, all_hosts): # noqa: C901 - for unit, relation_settings in units.iteritems(): - monitors = relation_settings['monitors'] - target_id = relation_settings['target-id'] - machine_id = relation_settings.get('machine_id', None) +def apply_relation_config(relid, units, all_hosts): # noqa: C901 + """Apply relation config to every related unit.""" + for unit, relation_settings in units.items(): + monitors = relation_settings["monitors"] + target_id = relation_settings["target-id"] + machine_id = relation_settings.get("machine_id", None) parent_host = None if machine_id: container_regex = re.compile(r"(\d+)/lx[cd]/\d+") @@ -155,7 +156,7 @@ def apply_relation_config(relid, units, all_hosts): # noqa: C901 # If not set, we don't mess with it, as multiple services may feed # monitors in for a particular address. Generally a primary will set # this to its own private-address - target_address = relation_settings.get('target-address', None) + target_address = relation_settings.get("target-address", None) if type(monitors) != dict: monitors = yaml.safe_load(monitors) @@ -164,23 +165,22 @@ def apply_relation_config(relid, units, all_hosts): # noqa: C901 host = get_pynag_host(target_id) if not target_address: raise Exception("No Target Address provied by NRPE service!") - host.set_attribute('address', target_address) + host.set_attribute("address", target_address) if parent_host: # We assume that we only want one parent and will overwrite any # existing parents for this host. - host.set_attribute('parents', parent_host) + host.set_attribute("parents", parent_host) host.save() - for mon_family, mons in monitors['monitors']['remote'].iteritems(): - for mon_name, mon in mons.iteritems(): - service_name = '%s-%s' % (target_id, mon_name) + for mon_family, mons in monitors["monitors"]["remote"].items(): + for mon_name, mon in mons.items(): + service_name = "%s-%s" % (target_id, mon_name) service = get_pynag_service(target_id, service_name) if customize_service(service, mon_family, mon_name, mon): service.save() else: - print('Ignoring %s due to unknown family %s' % (mon_name, - mon_family)) + print("Ignoring %s due to unknown family %s" % (mon_name, mon_family)) -if __name__ == '__main__': +if __name__ == "__main__": main(sys.argv) diff --git a/hooks/upgrade_charm.py b/hooks/upgrade_charm.py index ea7532c..705dc4c 100755 --- a/hooks/upgrade_charm.py +++ b/hooks/upgrade_charm.py @@ -1,44 +1,48 @@ #!/usr/bin/env python +""" +Handle the upgrade-charm hooks among others. -# Rewritten from bash to python 3/2/2014 for charm helper inclusion -# of SSL-Everywhere! +Rewritten from bash to python 3/2/2014 for charm helper inclusion of SSL-Everywhere! +""" import base64 -from jinja2 import Template +import errno import glob +import grp import os import pwd -import grp -import stat -import errno import shutil +import stat import subprocess + +from charmhelpers import fetch from charmhelpers.contrib import ssl from charmhelpers.core import hookenv, host -from charmhelpers import fetch from common import update_localhost +from jinja2 import Template + # Gather facts -legacy_relations = hookenv.config('legacy') -extra_config = hookenv.config('extraconfig') -enable_livestatus = hookenv.config('enable_livestatus') -livestatus_path = hookenv.config('livestatus_path') -enable_pagerduty = hookenv.config('enable_pagerduty') -pagerduty_key = hookenv.config('pagerduty_key') -pagerduty_path = hookenv.config('pagerduty_path') -notification_levels = hookenv.config('pagerduty_notification_levels') -nagios_user = hookenv.config('nagios_user') -nagios_group = hookenv.config('nagios_group') -ssl_config = str(hookenv.config('ssl')).lower() -charm_dir = os.environ['CHARM_DIR'] -cert_domain = hookenv.unit_get('public-address') +legacy_relations = hookenv.config("legacy") +extra_config = hookenv.config("extraconfig") +enable_livestatus = hookenv.config("enable_livestatus") +livestatus_path = hookenv.config("livestatus_path") +enable_pagerduty = hookenv.config("enable_pagerduty") +pagerduty_key = hookenv.config("pagerduty_key") +pagerduty_path = hookenv.config("pagerduty_path") +notification_levels = hookenv.config("pagerduty_notification_levels") +nagios_user = hookenv.config("nagios_user") +nagios_group = hookenv.config("nagios_group") +ssl_config = str(hookenv.config("ssl")).lower() +charm_dir = os.environ["CHARM_DIR"] +cert_domain = hookenv.unit_get("public-address") nagios_cfg = "/etc/nagios3/nagios.cfg" nagios_cgi_cfg = "/etc/nagios3/cgi.cfg" pagerduty_cfg = "/etc/nagios3/conf.d/pagerduty_nagios.cfg" pagerduty_cron = "/etc/cron.d/nagios-pagerduty-flush" -password = hookenv.config('password') -ro_password = hookenv.config('ro-password') -nagiosadmin = hookenv.config('nagiosadmin') or 'nagiosadmin' +password = hookenv.config("password") +ro_password = hookenv.config("ro-password") +nagiosadmin = hookenv.config("nagiosadmin") or "nagiosadmin" SSL_CONFIGURED = ssl_config in ["on", "only"] HTTP_ENABLED = ssl_config not in ["only"] @@ -46,33 +50,41 @@ HTTP_ENABLED = ssl_config not in ["only"] def warn_legacy_relations(): """ - Checks the charm relations for legacy relations + Check the charm relations for legacy relations. + Inserts warnings into the log about legacy relations, as they will be removed in the future """ if legacy_relations is not None: - hookenv.log("Relations have been radically changed." - " The monitoring interface is not supported anymore.", - "WARNING") - hookenv.log("Please use the generic juju-info or the monitors interface", - "WARNING") + hookenv.log( + "Relations have been radically changed." " The monitoring interface is not supported anymore.", "WARNING" + ) + hookenv.log("Please use the generic juju-info or the monitors interface", "WARNING") -# If the charm has extra configuration provided, write that to the -# proper nagios3 configuration file, otherwise remove the config def write_extra_config(): + """ + Write Extra Config. + + If the charm has extra configuration provided, write that to the proper + nagios3 configuration file, otherwise remove the config. + """ # Be predjudice about this - remove the file always. - if host.file_hash('/etc/nagios3/conf.d/extra.cfg') is not None: - os.remove('/etc/nagios3/conf.d/extra.cfg') + if host.file_hash("/etc/nagios3/conf.d/extra.cfg") is not None: + os.remove("/etc/nagios3/conf.d/extra.cfg") # If we have a config, then write it. the hook reconfiguration will # handle the details if extra_config is not None: - host.write_file('/etc/nagios3/conf.d/extra.cfg', extra_config) + host.write_file("/etc/nagios3/conf.d/extra.cfg", extra_config) -# Equivalent of mkdir -p, since we can't rely on -# python 3.2 os.makedirs exist_ok argument def mkdir_p(path): + """ + Create directory recursively. + + Equivalent of mkdir -p, since we can't rely on py32 os.makedirs + `exist_ok` argument + """ try: os.makedirs(path) except OSError as exc: # Python >2.5 @@ -82,8 +94,8 @@ def mkdir_p(path): raise -# Fix the path to be world executable def fixpath(path): + """Fix the path to be world executable.""" if os.path.isdir(path): st = os.stat(path) os.chmod(path, st.st_mode | stat.S_IXOTH) @@ -92,10 +104,11 @@ def fixpath(path): def enable_livestatus_config(): + """Enable livestatus config.""" if enable_livestatus: hookenv.log("Livestatus is enabled") fetch.apt_update() - fetch.apt_install('check-mk-livestatus') + fetch.apt_install("check-mk-livestatus") # Make the directory and fix perms on it hookenv.log("Fixing perms on livestatus_path") @@ -113,42 +126,43 @@ def enable_livestatus_config(): os.chown(livestatus_dir, uid, gid) st = os.stat(livestatus_path) os.chmod(livestatus_path, st.st_mode | stat.S_IRGRP) - os.chmod(livestatus_dir, st.st_mode | stat.S_IRGRP | - stat.S_ISGID | stat.S_IXUSR | stat.S_IXGRP) + os.chmod(livestatus_dir, st.st_mode | stat.S_IRGRP | stat.S_ISGID | stat.S_IXUSR | stat.S_IXGRP) def enable_pagerduty_config(): + """Enable pagerduty config.""" if enable_pagerduty: hookenv.log("Pagerduty is enabled") fetch.apt_update() - fetch.apt_install('libhttp-parser-perl') + fetch.apt_install("libhttp-parser-perl") env = os.environ - proxy = env.get('JUJU_CHARM_HTTPS_PROXY') or env.get('https_proxy') - proxy_switch = '--proxy {}'.format(proxy) if proxy else '' + proxy = env.get("JUJU_CHARM_HTTPS_PROXY") or env.get("https_proxy") + proxy_switch = "--proxy {}".format(proxy) if proxy else "" # Ship the pagerduty_nagios.cfg file - template_values = {'pagerduty_key': pagerduty_key, - 'pagerduty_path': pagerduty_path, - 'proxy_switch': proxy_switch, - 'notification_levels': notification_levels} - - with open('hooks/templates/pagerduty_nagios_cfg.tmpl', 'r') as f: - templateDef = f.read() - - t = Template(templateDef) - with open(pagerduty_cfg, 'w') as f: + template_values = { + "pagerduty_key": pagerduty_key, + "pagerduty_path": pagerduty_path, + "proxy_switch": proxy_switch, + "notification_levels": notification_levels, + } + + with open("hooks/templates/pagerduty_nagios_cfg.tmpl", "r") as f: + template_def = f.read() + + t = Template(template_def) + with open(pagerduty_cfg, "w") as f: f.write(t.render(template_values)) - with open('hooks/templates/nagios-pagerduty-flush-cron.tmpl', 'r') as f2: - templateDef = f2.read() + with open("hooks/templates/nagios-pagerduty-flush-cron.tmpl", "r") as f2: + template_def = f2.read() - t2 = Template(templateDef) - with open(pagerduty_cron, 'w') as f2: + t2 = Template(template_def) + with open(pagerduty_cron, "w") as f2: f2.write(t2.render(template_values)) # Ship the pagerduty_nagios.pl script - shutil.copy('files/pagerduty_nagios.pl', - '/usr/local/bin/pagerduty_nagios.pl') + shutil.copy("files/pagerduty_nagios.pl", "/usr/local/bin/pagerduty_nagios.pl") # Create the pagerduty queue dir if not os.path.isdir(pagerduty_path): @@ -168,33 +182,18 @@ def enable_pagerduty_config(): # Multiple Email Contacts contactgroup_members = hookenv.config("contactgroup-members") contacts = [] - admin_email = list( - filter(None, set(hookenv.config('admin_email').split(','))) - ) + admin_email = list(filter(None, set(hookenv.config("admin_email").split(",")))) if len(admin_email) == 0: hookenv.log("admin_email is unset, this isn't valid config") hookenv.status_set("blocked", "admin_email is not configured") return if len(admin_email) == 1: hookenv.log("Setting one admin email address '%s'" % admin_email[0]) - contacts = [{ - 'contact_name': 'root', - 'alias': 'Root', - 'email': admin_email[0] - }] + contacts = [{"contact_name": "root", "alias": "Root", "email": admin_email[0]}] elif len(admin_email) > 1: hookenv.log("Setting %d admin email addresses" % len(admin_email)) - contacts = [ - { - 'contact_name': email, - 'alias': email, - 'email': email - } - for email in admin_email - ] - contactgroup_members = ', '.join([ - c['contact_name'] for c in contacts - ]) + contacts = [{"contact_name": email, "alias": email, "email": email} for email in admin_email] + contactgroup_members = ", ".join([c["contact_name"] for c in contacts]) # Update contacts for admin if enable_pagerduty: @@ -202,39 +201,43 @@ def enable_pagerduty_config(): if "pagerduty" not in contactgroup_members: contactgroup_members += ", pagerduty" - template_values = {'admin_service_notification_period': hookenv.config('admin_service_notification_period'), - 'admin_host_notification_period': hookenv.config('admin_host_notification_period'), - 'admin_service_notification_options': hookenv.config('admin_service_notification_options'), - 'admin_host_notification_options': hookenv.config('admin_host_notification_options'), - 'admin_service_notification_commands': hookenv.config('admin_service_notification_commands'), - 'admin_host_notification_commands': hookenv.config('admin_host_notification_commands'), - 'contacts': contacts, - 'contactgroup_members': contactgroup_members} - - with open('hooks/templates/contacts-cfg.tmpl', 'r') as f: - templateDef = f.read() - - t = Template(templateDef) - with open('/etc/nagios3/conf.d/contacts_nagios2.cfg', 'w') as f: + template_values = { + "admin_service_notification_period": hookenv.config("admin_service_notification_period"), + "admin_host_notification_period": hookenv.config("admin_host_notification_period"), + "admin_service_notification_options": hookenv.config("admin_service_notification_options"), + "admin_host_notification_options": hookenv.config("admin_host_notification_options"), + "admin_service_notification_commands": hookenv.config("admin_service_notification_commands"), + "admin_host_notification_commands": hookenv.config("admin_host_notification_commands"), + "contacts": contacts, + "contactgroup_members": contactgroup_members, + } + + with open("hooks/templates/contacts-cfg.tmpl", "r") as f: + template_def = f.read() + + t = Template(template_def) + with open("/etc/nagios3/conf.d/contacts_nagios2.cfg", "w") as f: f.write(t.render(template_values)) - host.service_reload('nagios3') + host.service_reload("nagios3") # Gather local facts for SSL deployment -deploy_key_path = os.path.join(charm_dir, 'data', '%s.key' % (cert_domain)) -deploy_cert_path = os.path.join(charm_dir, 'data', '%s.crt' % (cert_domain)) -deploy_csr_path = os.path.join(charm_dir, 'data', '%s.csr' % (cert_domain)) +deploy_key_path = os.path.join(charm_dir, "data", "%s.key" % (cert_domain)) +deploy_cert_path = os.path.join(charm_dir, "data", "%s.crt" % (cert_domain)) +deploy_csr_path = os.path.join(charm_dir, "data", "%s.csr" % (cert_domain)) # set basename for SSL key locations -cert_file = '/etc/ssl/certs/%s.pem' % (cert_domain) -key_file = '/etc/ssl/private/%s.key' % (cert_domain) -chain_file = '/etc/ssl/certs/%s.csr' % (cert_domain) +cert_file = "/etc/ssl/certs/%s.pem" % (cert_domain) +key_file = "/etc/ssl/private/%s.key" % (cert_domain) +chain_file = "/etc/ssl/certs/%s.csr" % (cert_domain) -# Check for key and certificate, since the CSR is optional -# leave it out of the dir file check and let the config manager -# worry about it def check_ssl_files(): + """ + Check for key and certificate, since the CSR is optional. + + Leave it out of the dir file check and let the config manager worry about it. + """ key = os.path.exists(deploy_key_path) cert = os.path.exists(deploy_cert_path) if key is False or cert is False: @@ -242,25 +245,24 @@ def check_ssl_files(): return True -# Decode the SSL keys from their base64 encoded values in the configuration def decode_ssl_keys(): - if hookenv.config('ssl_key'): + """Decode the SSL keys from their base64 encoded values in the config.""" + if hookenv.config("ssl_key"): hookenv.log("Writing key from config ssl_key: %s" % key_file) - with open(key_file, 'w') as f: - f.write(str(base64.b64decode(hookenv.config('ssl_key')))) - if hookenv.config('ssl_cert'): - with open(cert_file, 'w') as f: - f.write(str(base64.b64decode(hookenv.config('ssl_cert')))) - if hookenv.config('ssl_chain'): - with open(chain_file, 'w') as f: - f.write(str(base64.b64decode(hookenv.config('ssl_cert')))) + with open(key_file, "w") as f: + f.write(str(base64.b64decode(hookenv.config("ssl_key")))) + if hookenv.config("ssl_cert"): + with open(cert_file, "w") as f: + f.write(str(base64.b64decode(hookenv.config("ssl_cert")))) + if hookenv.config("ssl_chain"): + with open(chain_file, "w") as f: + f.write(str(base64.b64decode(hookenv.config("ssl_cert")))) def enable_ssl(): - # Set the basename of all ssl files - + """Set the basename of all ssl files.""" # Validate that we have configs, and generate a self signed certificate. - if not hookenv.config('ssl_cert'): + if not hookenv.config("ssl_cert"): # bail if keys already exist if os.path.exists(cert_file): hookenv.log("Keys exist, not creating keys!", "WARNING") @@ -279,106 +281,104 @@ def nagios_bool(value): def update_config(): - host_context = hookenv.config('nagios_host_context') - local_host_name = 'nagios' + """Update Config.""" + host_context = hookenv.config("nagios_host_context") principal_unitname = hookenv.principal_unit() # Fallback to using "primary" if it exists. if principal_unitname: local_host_name = principal_unitname else: - local_host_name = hookenv.local_unit().replace('/', '-') - template_values = {'nagios_user': nagios_user, - 'nagios_group': nagios_group, - 'enable_livestatus': enable_livestatus, - 'livestatus_path': livestatus_path, - 'livestatus_args': hookenv.config('livestatus_args'), - 'check_external_commands': hookenv.config('check_external_commands'), - 'command_check_interval': hookenv.config('command_check_interval'), - 'command_file': hookenv.config('command_file'), - 'debug_file': hookenv.config('debug_file'), - 'debug_verbosity': hookenv.config('debug_verbosity'), - 'debug_level': hookenv.config('debug_level'), - 'daemon_dumps_core': hookenv.config('daemon_dumps_core'), - 'flap_detection': nagios_bool(hookenv.config('flap_detection')), - 'admin_email': hookenv.config('admin_email'), - 'admin_pager': hookenv.config('admin_pager'), - 'log_rotation_method': hookenv.config('log_rotation_method'), - 'log_archive_path': hookenv.config('log_archive_path'), - 'use_syslog': hookenv.config('use_syslog'), - 'monitor_self': hookenv.config('monitor_self'), - 'nagios_hostname': "{}-{}".format(host_context, local_host_name), - 'load_monitor': hookenv.config('load_monitor'), - 'is_container': host.is_container(), - 'service_check_timeout': hookenv.config('service_check_timeout'), - 'service_check_timeout_state': hookenv.config('service_check_timeout_state'), - } - - with open('hooks/templates/nagios-cfg.tmpl', 'r') as f: - templateDef = f.read() - - t = Template(templateDef) - with open(nagios_cfg, 'w') as f: + local_host_name = hookenv.local_unit().replace("/", "-") + template_values = { + "nagios_user": nagios_user, + "nagios_group": nagios_group, + "enable_livestatus": enable_livestatus, + "livestatus_path": livestatus_path, + "livestatus_args": hookenv.config("livestatus_args"), + "check_external_commands": hookenv.config("check_external_commands"), + "command_check_interval": hookenv.config("command_check_interval"), + "command_file": hookenv.config("command_file"), + "debug_file": hookenv.config("debug_file"), + "debug_verbosity": hookenv.config("debug_verbosity"), + "debug_level": hookenv.config("debug_level"), + "daemon_dumps_core": hookenv.config("daemon_dumps_core"), + "flap_detection": nagios_bool(hookenv.config("flap_detection")), + "admin_email": hookenv.config("admin_email"), + "admin_pager": hookenv.config("admin_pager"), + "log_rotation_method": hookenv.config("log_rotation_method"), + "log_archive_path": hookenv.config("log_archive_path"), + "use_syslog": hookenv.config("use_syslog"), + "monitor_self": hookenv.config("monitor_self"), + "nagios_hostname": "{}-{}".format(host_context, local_host_name), + "load_monitor": hookenv.config("load_monitor"), + "is_container": host.is_container(), + "service_check_timeout": hookenv.config("service_check_timeout"), + "service_check_timeout_state": hookenv.config("service_check_timeout_state"), + } + + with open("hooks/templates/nagios-cfg.tmpl", "r") as f: + template_def = f.read() + + t = Template(template_def) + with open(nagios_cfg, "w") as f: f.write(t.render(template_values)) - with open('hooks/templates/localhost_nagios2.cfg.tmpl', 'r') as f: - templateDef = f.read() - t = Template(templateDef) - with open('/etc/nagios3/conf.d/localhost_nagios2.cfg', 'w') as f: + with open("hooks/templates/localhost_nagios2.cfg.tmpl", "r") as f: + template_def = f.read() + t = Template(template_def) + with open("/etc/nagios3/conf.d/localhost_nagios2.cfg", "w") as f: f.write(t.render(template_values)) - host.service_reload('nagios3') + host.service_reload("nagios3") def update_cgi_config(): - template_values = {'nagiosadmin': nagiosadmin, - 'ro_password': ro_password} - with open('hooks/templates/nagios-cgi.tmpl', 'r') as f: - templateDef = f.read() + """Update CGI Config.""" + template_values = {"nagiosadmin": nagiosadmin, "ro_password": ro_password} + with open("hooks/templates/nagios-cgi.tmpl", "r") as f: + template_def = f.read() - t = Template(templateDef) - with open(nagios_cgi_cfg, 'w') as f: + t = Template(template_def) + with open(nagios_cgi_cfg, "w") as f: f.write(t.render(template_values)) - host.service_reload('nagios3') - host.service_reload('apache2') + host.service_reload("nagios3") + host.service_reload("apache2") def update_apache(): """ Nagios3 is deployed as a global apache application from the archive. + We'll get a little funky and add the SSL keys to the default-ssl config which sets our keys, including the self-signed ones, as the host keyfiles. """ - # Start by Setting the ports.conf + with open("hooks/templates/ports-cfg.jinja2", "r") as f: + template_def = f.read() + t = Template(template_def) + ports_conf = "/etc/apache2/ports.conf" - with open('hooks/templates/ports-cfg.jinja2', 'r') as f: - templateDef = f.read() - t = Template(templateDef) - ports_conf = '/etc/apache2/ports.conf' - - with open(ports_conf, 'w') as f: - f.write(t.render({'enable_http': HTTP_ENABLED})) + with open(ports_conf, "w") as f: + f.write(t.render({"enable_http": HTTP_ENABLED})) # Next setup the default-ssl.conf if os.path.exists(chain_file) and os.path.getsize(chain_file) > 0: ssl_chain = chain_file else: ssl_chain = None - template_values = {'ssl_key': key_file, - 'ssl_cert': cert_file, - 'ssl_chain': ssl_chain} - with open('hooks/templates/default-ssl.tmpl', 'r') as f: - templateDef = f.read() - - t = Template(templateDef) - ssl_conf = '/etc/apache2/sites-available/default-ssl.conf' - with open(ssl_conf, 'w') as f: + template_values = {"ssl_key": key_file, "ssl_cert": cert_file, "ssl_chain": ssl_chain} + with open("hooks/templates/default-ssl.tmpl", "r") as f: + template_def = f.read() + + t = Template(template_def) + ssl_conf = "/etc/apache2/sites-available/default-ssl.conf" + with open(ssl_conf, "w") as f: f.write(t.render(template_values)) # Create directory for extra *.include files installed by subordinates try: - os.makedirs('/etc/apache2/vhost.d/') + os.makedirs("/etc/apache2/vhost.d/") except OSError: pass @@ -386,23 +386,35 @@ def update_apache(): sites = glob.glob("/etc/apache2/sites-available/*.conf") non_ssl = set(sites) - {ssl_conf} for each in non_ssl: - site = os.path.basename(each).strip('.conf') + site = os.path.basename(each).strip(".conf") Apache2Site(site).action(enabled=HTTP_ENABLED) # Configure the behavior of https site Apache2Site("default-ssl").action(enabled=SSL_CONFIGURED) # Finally, restart apache2 - host.service_reload('apache2') + host.service_reload("apache2") class Apache2Site: + """Wrapper for dealing with apache2 sites.""" + def __init__(self, site): + """ + Create wrapper object based on site name. + + @params str site: site to manage + """ self.site = site - self.is_ssl = 'ssl' in site.lower() + self.is_ssl = "ssl" in site.lower() self.port = 443 if self.is_ssl else 80 def action(self, enabled): + """ + Enable or disable based on boolean. + + @params bool enabled: should we enable or disable? + """ fn = self._enable if enabled else self._disable return fn() @@ -410,36 +422,33 @@ class Apache2Site: try: subprocess.check_output(args, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: - hookenv.log("Apache2Site: `{}`, returned {}, stdout:\n{}" - .format(e.cmd, e.returncode, e.output), "ERROR") + hookenv.log("Apache2Site: `{}`, returned {}, stdout:\n{}".format(e.cmd, e.returncode, e.output), "ERROR") def _enable(self): hookenv.log("Apache2Site: Enabling %s..." % self.site, "INFO") - self._call(['a2ensite', self.site]) + self._call(["a2ensite", self.site]) if self.port == 443: - self._call(['a2enmod', 'ssl']) + self._call(["a2enmod", "ssl"]) hookenv.open_port(self.port) def _disable(self): hookenv.log("Apache2Site: Disabling %s..." % self.site, "INFO") - self._call(['a2dissite', self.site]) + self._call(["a2dissite", self.site]) hookenv.close_port(self.port) def update_password(account, password): """Update the charm and Apache's record of the password for the supplied account.""" - account_file = ''.join(['/var/lib/juju/nagios.', account, '.passwd']) + account_file = "".join(["/var/lib/juju/nagios.", account, ".passwd"]) if password: - with open(account_file, 'w') as f: + with open(account_file, "w") as f: f.write(password) os.fchmod(f.fileno(), 0o0400) - subprocess.call(['htpasswd', '-b', '/etc/nagios3/htpasswd.users', - account, password]) + subprocess.call(["htpasswd", "-b", "/etc/nagios3/htpasswd.users", account, password]) else: """ password was empty, it has been removed. We should delete the account """ os.path.isfile(account_file) and os.remove(account_file) - subprocess.call(['htpasswd', '-D', '/etc/nagios3/htpasswd.users', - account]) + subprocess.call(["htpasswd", "-D", "/etc/nagios3/htpasswd.users", account]) hookenv.status_set("active", "ready") @@ -453,12 +462,12 @@ if SSL_CONFIGURED: update_apache() update_localhost() update_cgi_config() -update_password('nagiosro', ro_password) +update_password("nagiosro", ro_password) if password: update_password(nagiosadmin, password) -if nagiosadmin != 'nagiosadmin': - update_password('nagiosadmin', False) +if nagiosadmin != "nagiosadmin": + update_password("nagiosadmin", False) -subprocess.call(['scripts/postfix_loopback_only.sh']) -subprocess.call(['hooks/mymonitors-relation-joined']) -subprocess.call(['hooks/monitors-relation-changed']) +subprocess.call(["scripts/postfix_loopback_only.sh"]) +subprocess.call(["hooks/mymonitors-relation-joined"]) +subprocess.call(["hooks/monitors-relation-changed"]) diff --git a/hooks/website_relation_joined.py b/hooks/website_relation_joined.py index 984ae80..42e5b47 100755 --- a/hooks/website_relation_joined.py +++ b/hooks/website_relation_joined.py @@ -1,21 +1,21 @@ #!/usr/bin/python -# website-relation-joined - Set the hostname into remote nagios http consumers -# Copyright Canonical 2017 Canonical Ltd. All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# +""" +website-relation-joined - Set the hostname into remote nagios http consumers. + +Copyright Canonical 2020 Canonical Ltd. All Rights Reserved + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. # This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -import common +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" from charmhelpers.core.hookenv import ( config, @@ -23,17 +23,20 @@ from charmhelpers.core.hookenv import ( relation_set, ) +import common + def main(): - relation_data = {'hostname': common.get_local_ingress_address()} - sslcfg = config()['ssl'] - if sslcfg == 'only': - relation_data['port'] = 443 + """Handle website-relation-joined hook.""" + relation_data = {"hostname": common.get_local_ingress_address()} + sslcfg = config()["ssl"] + if sslcfg == "only": + relation_data["port"] = 443 else: - relation_data['port'] = 80 - log('website-relation-joined data %s' % relation_data) + relation_data["port"] = 80 + log("website-relation-joined data %s" % relation_data) relation_set(None, **relation_data) -if __name__ == '__main__': # pragma: no cover +if __name__ == "__main__": # pragma: no cover main() diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index fafe7f0..5b31d0b 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 - +"""Configure pytest for functional testing.""" import asyncio import json import os @@ -16,9 +16,9 @@ import pytest STAT_FILE = "python3 -c \"import json; import os; s=os.stat('%s'); print(json.dumps({'uid': s.st_uid, 'gid': s.st_gid, 'mode': oct(s.st_mode), 'size': s.st_size}))\"" # noqa: E501 [email protected]_fixture(scope='session') [email protected]_fixture(scope="session") def event_loop(request): - """Override the default pytest event loop to allow for broaded scopedv fixtures.""" + """Override the pytest event loop to allow for broader scoped fixtures.""" loop = asyncio.get_event_loop_policy().new_event_loop() asyncio.set_event_loop(loop) loop.set_debug(True) @@ -27,7 +27,7 @@ def event_loop(request): asyncio.set_event_loop(None) [email protected](scope='session') [email protected](scope="session") async def controller(): """Connect to the current controller.""" controller = Controller() @@ -36,21 +36,21 @@ async def controller(): await controller.disconnect() [email protected](scope='session') [email protected](scope="session") async def model(controller): """Create a model that lives only for the duration of the test.""" model_name = "functest-{}".format(uuid.uuid4()) model = await controller.add_model(model_name) yield model await model.disconnect() - if os.getenv('PYTEST_KEEP_MODEL'): + if os.getenv("PYTEST_KEEP_MODEL"): return await controller.destroy_model(model_name) while model_name in await controller.list_models(): await asyncio.sleep(1) [email protected](scope='session') [email protected](scope="session") async def current_model(): """Return the current model, does not create or destroy it.""" model = Model() @@ -62,29 +62,37 @@ async def current_model(): @pytest.fixture async def get_app(model): """Return the application requested.""" + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _get_app(name): try: return model.applications[name] except KeyError: raise JujuError("Cannot find application {}".format(name)) + return _get_app @pytest.fixture async def get_unit(model): """Return the requested <app_name>/<unit_number> unit.""" + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _get_unit(name): try: - (app_name, unit_number) = name.split('/') + (app_name, unit_number) = name.split("/") return model.applications[app_name].units[unit_number] except (KeyError, ValueError): raise JujuError("Cannot find unit {}".format(name)) + return _get_unit @pytest.fixture async def get_entity(model, get_unit, get_app): """Return a unit or an application.""" + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _get_entity(name): try: return await get_unit(name) @@ -93,6 +101,7 @@ async def get_entity(model, get_unit, get_app): return await get_app(name) except JujuError: raise JujuError("Cannot find entity {}".format(name)) + return _get_entity @@ -101,17 +110,15 @@ async def run_command(get_unit): """ Run a command on a unit. - :param cmd: Command to be run - :param target: Unit object or unit name string + :param get_unit: fixture defining on which unit to run the command """ + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _run_command(cmd, target): - unit = ( - target - if type(target) is juju.unit.Unit - else await get_unit(target) - ) + unit = target if type(target) is juju.unit.Unit else await get_unit(target) action = await unit.run(cmd) return action.results + return _run_command @@ -120,13 +127,15 @@ async def file_stat(run_command): """ Run stat on a file. - :param path: File path - :param target: Unit object or unit name string + :param run_command: fixture defining how to run a command """ + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _file_stat(path, target): cmd = STAT_FILE % path results = await run_command(cmd, target) - return json.loads(results['Stdout']) + return json.loads(results["Stdout"]) + return _file_stat @@ -135,37 +144,45 @@ async def file_contents(run_command): """ Return the contents of a file. - :param path: File path - :param target: Unit object or unit name string + :param run_command: fixture defining how to run a command """ + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _file_contents(path, target): - cmd = 'cat {}'.format(path) + cmd = "cat {}".format(path) results = await run_command(cmd, target) - return results['Stdout'] + return results["Stdout"] + return _file_contents @pytest.fixture async def reconfigure_app(get_app, model): """Apply a different config to the requested app.""" + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _reconfigure_app(cfg, target): - application = ( - target - if type(target) is juju.application.Application - else await get_app(target) - ) + application = target if type(target) is juju.application.Application else await get_app(target) await application.set_config(cfg) await application.get_config() - await model.block_until(lambda: application.status == 'active') + await model.block_until(lambda: application.status == "active") + return _reconfigure_app @pytest.fixture async def create_group(run_command): - """Create the UNIX group specified.""" + """ + Create the UNIX group specified. + + :param run_command: fixture defining how to run a command + """ + # This comment stops black style adding a blank line here, which causes flake8 D202. + async def _create_group(group_name, target): cmd = "sudo groupadd %s" % group_name await run_command(cmd, target) + return _create_group @@ -183,93 +200,90 @@ SERIES = [ ############ # FIXTURES # ############ [email protected](scope='session', params=SERIES) [email protected](scope="session", params=SERIES) def series(request): """Return ubuntu version (i.e. xenial) in use in the test.""" return request.param [email protected](scope='session') [email protected](scope="session") async def relatives(model): + """Fixture providing the necessary dependant applications to test nagios.""" nrpe = "nrpe" - nrpe_app = await model.deploy( - 'cs:' + nrpe, application_name=nrpe, - series='trusty', config={}, - num_units=0 - ) + nrpe_app = await model.deploy("cs:" + nrpe, application_name=nrpe, series="trusty", config={}, num_units=0) mysql = "mysql" - mysql_app = await model.deploy( - 'cs:' + mysql, application_name=mysql, - series='trusty', config={} - ) + mysql_app = await model.deploy("cs:" + mysql, application_name=mysql, series="trusty", config={}) mediawiki = "mediawiki" - mediawiki_app = await model.deploy( - 'cs:' + mediawiki, application_name=mediawiki, - series='trusty', config={} - ) + mediawiki_app = await model.deploy("cs:" + mediawiki, application_name=mediawiki, series="trusty", config={}) - await model.add_relation('mysql:db', 'mediawiki:db') - await model.add_relation('mysql:juju-info', 'nrpe:general-info') - await model.add_relation('mediawiki:juju-info', 'nrpe:general-info') - await model.block_until( - lambda: all(_.status == "active" for _ in (mysql_app, mediawiki_app)) - ) + await model.add_relation("mysql:db", "mediawiki:db") + await model.add_relation("mysql:juju-info", "nrpe:general-info") + await model.add_relation("mediawiki:juju-info", "nrpe:general-info") + await model.block_until(lambda: all(_.status == "active" for _ in (mysql_app, mediawiki_app))) yield {mediawiki: mediawiki_app, mysql: mysql_app, nrpe: nrpe_app} [email protected](scope='session') [email protected](scope="session") async def deploy_app(relatives, model, series): """Return application of the charm under test.""" app_name = "nagios-{}".format(series) """Deploy the nagios app.""" nagios_app = await model.deploy( - os.path.join(CHARM_BUILD_DIR, 'nagios'), + os.path.join(CHARM_BUILD_DIR, "nagios"), application_name=app_name, series=series, - config={ - 'enable_livestatus': False, - 'ssl': False, - 'extraconfig': '', - 'enable_pagerduty': False - } + config={"enable_livestatus": False, "ssl": False, "extraconfig": "", "enable_pagerduty": False}, ) - await model.add_relation('{}:monitors'.format(app_name), 'mysql:monitors') - await model.add_relation('{}:nagios'.format(app_name), 'mediawiki:juju-info') - await model.add_relation('nrpe:monitors', '{}:monitors'.format(app_name)) + await model.add_relation("{}:monitors".format(app_name), "mysql:monitors") + await model.add_relation("{}:nagios".format(app_name), "mediawiki:juju-info") + await model.add_relation("nrpe:monitors", "{}:monitors".format(app_name)) await model.block_until(lambda: nagios_app.status == "active") - await model.block_until(lambda: all( - _.status == "active" - for _ in list(relatives.values()) + [nagios_app] - )) + await model.block_until(lambda: all(_.status == "active" for _ in list(relatives.values()) + [nagios_app])) yield nagios_app - if os.getenv('PYTEST_KEEP_MODEL'): + if os.getenv("PYTEST_KEEP_MODEL"): return await nagios_app.destroy() class Agent: + """Wraps Juju Unit and Application.""" + def __init__(self, unit, application): + """ + Create an Agent object. + + @param unit: Wraps the unit object from juju + @param application: Associated juju application + """ self.u = unit self.application = application self.model = unit.model def is_active(self, status): + """Check whether the unit is currently active with a particular agent status.""" u = self.u return u.agent_status == status and u.workload_status == "active" async def block_until_or_timeout(self, lambda_f, **kwargs): + """Wait until a condition is met or the event times out.""" await self.block_until(lambda_f, ignore_timeout=True, **kwargs) - async def block_until(self, lambda_f, timeout=120, wait_period=5, - ignore_timeout=False): + async def block_until(self, lambda_f, timeout=120, wait_period=5, ignore_timeout=False): + """ + Wait until a condition is met. + + @param lambda_f: condition definition called with no arguments. + @param float timeout: how long to wait until the next check + @param float wait_period: how many times to wait before declaring + TimeoutError + @param bool ignore_timeout: if True, don't raise TimeoutError + """ try: - await self.model.block_until( - lambda_f, timeout=timeout, wait_period=wait_period - ) + await self.model.block_until(lambda_f, timeout=timeout, wait_period=wait_period) except asyncio.TimeoutError: if not ignore_timeout: raise @@ -279,7 +293,7 @@ class Agent: async def unit(model, deploy_app): """Return the unit we've deployed.""" unit = Agent(deploy_app.units[0], deploy_app) - await unit.block_until(lambda: unit.is_active('idle')) + await unit.block_until(lambda: unit.is_active("idle")) return unit @@ -287,4 +301,4 @@ async def unit(model, deploy_app): async def auth(file_contents, unit): """Return the basic auth credentials.""" nagiospwd = await file_contents("/var/lib/juju/nagios.passwd", unit.u) - return 'nagiosadmin', nagiospwd.strip() + return "nagiosadmin", nagiospwd.strip() diff --git a/tests/functional/test_config.py b/tests/functional/test_config.py index 2e1cab6..1d3c9c7 100644 --- a/tests/functional/test_config.py +++ b/tests/functional/test_config.py @@ -1,40 +1,52 @@ +"""Test configuring the juju application.""" from async_generator import asynccontextmanager + import pytest + import requests + pytestmark = pytest.mark.asyncio @asynccontextmanager async def config(unit, item, test_value, post_test): + """ + Fixture that applies config changes before and after tests. + + @param Agent unit: Unit object whose application is configured + @param str item: Name of config to test + @param str test_value: Value of config during the test + @param str post_test: Value after the test completes + """ await unit.application.set_config({item: test_value}) await unit.block_until_or_timeout( - lambda: unit.is_active('executing'), timeout=5, + lambda: unit.is_active("executing"), timeout=5, ) - await unit.block_until(lambda: unit.is_active('idle')) + await unit.block_until(lambda: unit.is_active("idle")) yield test_value await unit.application.set_config({item: post_test}) await unit.block_until_or_timeout( - lambda: unit.is_active('executing'), timeout=5, + lambda: unit.is_active("executing"), timeout=5, ) - await unit.block_until(lambda: unit.is_active('idle')) + await unit.block_until(lambda: unit.is_active("idle")) [email protected](params=['on', 'only']) [email protected](params=["on", "only"]) async def ssl(unit, request): """ - Enable SSL before a test, then disable after test + Enable SSL before a test, then disable after test. :param Agent unit: unit from the fixture :param request: test parameters """ - async with config(unit, 'ssl', request.param, 'off') as value: + async with config(unit, "ssl", request.param, "off") as value: yield value @pytest.fixture async def extra_config(unit): """ - Enable extraconfig for a test, and revert afterwards + Enable extraconfig for a test, and revert afterwards. :param Agent unit: unit from the fixture """ @@ -52,33 +64,34 @@ async def extra_config(unit): @pytest.fixture async def livestatus_path(unit): """ - Enable livestatus before a test, then disable after test + Enable livestatus before a test, then disable after test. :param Agent unit: unit from the fixture """ async with config(unit, "enable_livestatus", "true", "false"): app_config = await unit.application.get_config() - yield app_config['livestatus_path']['value'] + yield app_config["livestatus_path"]["value"] @pytest.fixture() async def enable_pagerduty(unit): """ - Enable enable_pagerduty before first test, then disable after last test + Enable enable_pagerduty before first test, then disable after last test. :param Agent unit: unit from the fixture """ async with config(unit, "enable_pagerduty", "true", "false"): app_config = await unit.application.get_config() - yield app_config['pagerduty_path']['value'] + yield app_config["pagerduty_path"]["value"] ######### # TESTS # ######### async def test_web_interface_with_ssl(auth, unit, ssl): + """Test Web Interface using ssl.""" http_url = "http://%s/nagios3/" % unit.u.public_address - if ssl == 'only': + if ssl == "only": with pytest.raises(requests.ConnectionError): requests.get(http_url, auth=auth) else: @@ -90,25 +103,23 @@ async def test_web_interface_with_ssl(auth, unit, ssl): assert r.status_code == 200, "HTTPs Admin login failed" [email protected]('extra_config') [email protected]("extra_config") async def test_extra_config(auth, unit): - host_url = "http://%s/cgi-bin/nagios3/status.cgi?" \ - "hostgroup=all&style=hostdetail" % unit.u.public_address + """Test setting extra_config.""" + host_url = "http://%s/cgi-bin/nagios3/status.cgi?" "hostgroup=all&style=hostdetail" % unit.u.public_address r = requests.get(host_url, auth=auth) - assert r.text.find('extra_config'), "Nagios is not monitoring extra_config" + assert r.text.find("extra_config"), "Nagios is not monitoring extra_config" async def test_live_status(unit, livestatus_path, file_stat): + """Test setting livestatus feature.""" stat = await file_stat(livestatus_path, unit.u) - assert stat['size'] == 0, ( - "File %s didn't match expected size" % livestatus_path - ) + assert stat["size"] == 0, "File %s didn't match expected size" % livestatus_path async def test_pager_duty(unit, enable_pagerduty, file_stat): + """Test setting pager_duty feature.""" stat = await file_stat(enable_pagerduty, unit.u) - assert stat['size'] != 0, ( - "Directory %s wasn't a non-zero size" % enable_pagerduty - ) - stat = await file_stat('/etc/nagios3/conf.d/pagerduty_nagios.cfg', unit.u) - assert stat['size'] != 0, "pagerduty_config wasn't a non-zero sized file" + assert stat["size"] != 0, "Directory %s wasn't a non-zero size" % enable_pagerduty + stat = await file_stat("/etc/nagios3/conf.d/pagerduty_nagios.cfg", unit.u) + assert stat["size"] != 0, "pagerduty_config wasn't a non-zero sized file" diff --git a/tests/functional/test_deploy.py b/tests/functional/test_deploy.py index 9a9e067..1a9d958 100644 --- a/tests/functional/test_deploy.py +++ b/tests/functional/test_deploy.py @@ -1,11 +1,11 @@ +"""Test Deployment and after effects based on default application config.""" import pytest + import requests + pytestmark = pytest.mark.asyncio -######### -# TESTS # -######### async def test_status(deploy_app): """Check that the app is in active state.""" assert deploy_app.status == "active" @@ -22,18 +22,14 @@ async def test_web_interface_is_protected(auth, unit): async def test_hosts_being_monitored(auth, unit): - host_url = ("http://%s/cgi-bin/nagios3/status.cgi?" - "hostgroup=all&style=hostdetail") % unit.u.public_address + """Check that nagios is monitoring related apps.""" + host_url = ("http://%s/cgi-bin/nagios3/status.cgi?" "hostgroup=all&style=hostdetail") % unit.u.public_address r = requests.get(host_url, auth=auth) - assert r.text.find('mysql') and r.text.find('mediawiki'), \ - "Nagios is not monitoring the hosts it supposed to." + assert r.text.find("mysql") and r.text.find("mediawiki"), "Nagios is not monitoring the hosts it supposed to." async def test_nrpe_monitors_config(relatives, unit, file_contents): - # look for disk root check in nrpe config - mysql_unit = relatives['mysql'].units[0] - contents = await file_contents( - '/etc/nagios/nrpe.d/check_disk_root.cfg', - mysql_unit - ) + """Look for disk root check in nrpe config.""" + mysql_unit = relatives["mysql"].units[0] + contents = await file_contents("/etc/nagios/nrpe.d/check_disk_root.cfg", mysql_unit) assert contents, "disk root check config not found." diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index f6fceac..33450e1 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,5 +1,6 @@ +"""Configure pytest for unit testing.""" import os import sys -HOOKS = os.path.join(os.path.dirname(__file__), '..', '..', 'hooks') +HOOKS = os.path.join(os.path.dirname(__file__), "..", "..", "hooks") sys.path.append(HOOKS) diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py index 9833a02..ba9069e 100644 --- a/tests/unit/test_common.py +++ b/tests/unit/test_common.py @@ -1,5 +1,12 @@ +"""Testing hooks/common.py.""" import common def test_check_ip(): + """Validate check_ip command.""" + # Ensure that 1.2.3.4 is a valid IPv4 Address assert common.check_ip("1.2.3.4") + # Ensure that ::1 is a valid IPv6 Address + assert common.check_ip("::1") + # Ensure that 'flying-spaghetti-monster' is not an ip address + assert not common.check_ip("flying-spaghetti-monster") diff --git a/tests/unit/test_monitor_relation_changed.py b/tests/unit/test_monitor_relation_changed.py index 3a7f5d6..ffd7948 100644 --- a/tests/unit/test_monitor_relation_changed.py +++ b/tests/unit/test_monitor_relation_changed.py @@ -1,7 +1,11 @@ +"""Testing hooks/monitor_relation_changed.py.""" import monitors_relation_changed def test_has_main(): - # THIS IS A REALLY LAME TEST -- but it's a start for where there was nothing - # if you add tests later, please do better than me - assert hasattr(monitors_relation_changed, 'main') + """ + THIS IS A REALLY LAME TEST -- but it's a start for where there was nothing. + + if you add tests later, please do better than me + """ + assert hasattr(monitors_relation_changed, "main") diff --git a/tests/unit/test_website_relation_joined.py b/tests/unit/test_website_relation_joined.py index 3164428..1e1a8d6 100644 --- a/tests/unit/test_website_relation_joined.py +++ b/tests/unit/test_website_relation_joined.py @@ -1,19 +1,18 @@ +"""Testing hooks/website_relation_joined.py.""" import unittest.mock as mock import pytest + import website_relation_joined [email protected]('common.get_local_ingress_address') [email protected]('website_relation_joined.config') [email protected]('website_relation_joined.relation_set') [email protected]('ssl', [ - ('only', 443), - ('on', 80), - ('off', 80) -], ids=['ssl=only', 'ssl=on', 'ssl=off']) [email protected]("common.get_local_ingress_address") [email protected]("website_relation_joined.config") [email protected]("website_relation_joined.relation_set") [email protected]("ssl", [("only", 443), ("on", 80), ("off", 80)], ids=["ssl=only", "ssl=on", "ssl=off"]) def test_main(relation_set, config, get_local_ingress_address, ssl): - get_local_ingress_address.return_value = 'example.com' - config.return_value = {'ssl': ssl[0]} + """Tests the website_relation_joined hook.""" + get_local_ingress_address.return_value = "example.com" + config.return_value = {"ssl": ssl[0]} website_relation_joined.main() - relation_set.assert_called_with(None, port=ssl[1], hostname='example.com') + relation_set.assert_called_with(None, port=ssl[1], hostname="example.com") diff --git a/tox.ini b/tox.ini index 34f9248..dabaa6e 100644 --- a/tox.ini +++ b/tox.ini @@ -36,8 +36,16 @@ deps = -r{toxinidir}/tests/functional/requirements.txt -r{toxinidir}/requirements.txt [testenv:lint] -commands = flake8 -deps = flake8 +commands = + flake8 + black --check --line-length 120 --exclude /(\.eggs|\.git|\.tox|\.venv|build|dist|charmhelpers|mod)/ . +deps = + black + flake8 + flake8-docstrings + flake8-import-order + pep8-naming + flake8-colors [flake8] exclude =
-- Mailing list: https://launchpad.net/~nagios-charmers Post to : [email protected] Unsubscribe : https://launchpad.net/~nagios-charmers More help : https://help.launchpad.net/ListHelp

