"""Nrpe utils module.""" import glob import os import shutil import subprocess from charmhelpers import fetch from charmhelpers.core import hookenv from charmhelpers.core import host from charmhelpers.core.services import helpers from charmhelpers.core.services.base import ( ManagerCallback, PortManagerCallback, ) from charmhelpers.core.templating import render import nrpe_helpers import yaml def restart_rsync(service_name): """Restart rsync.""" host.service_restart("rsync") def restart_nrpe(service_name): """Restart nrpe.""" host.service_restart("nagios-nrpe-server") def determine_packages(): """Return a list of packages this charm needs installed.""" pkgs = [ "nagios-nrpe-server", "nagios-plugins-basic", "nagios-plugins-standard", "python3", "python3-netifaces", ] if hookenv.config("export_nagios_definitions"): pkgs.append("rsync") if hookenv.config("nagios_master") not in ["None", "", None]: pkgs.append("rsync") return pkgs def install_packages(service_name): """Install packages.""" fetch.apt_update() apt_options = [ # avoid installing rpcbind LP#1873171 "--no-install-recommends", # and retain the default option too "--option=Dpkg::Options::=--force-confold", ] fetch.apt_install(determine_packages(), options=apt_options, fatal=True) def remove_host_export_fragments(service_name): """Remove nagios host config fragment.""" for fname in glob.glob("/var/lib/nagios/export/host__*"): os.unlink(fname) def install_charm_files(service_name): """Install files shipped with charm.""" # The preinst script of nagios-nrpe-server deb package will add nagios user # and create this dir as home # ref: https://git.launchpad.net/ubuntu/+source/nagios-nrpe/tree/debian/nagios-nrpe-server.preinst#n28 # NOQA: E501 nagios_home = "/var/lib/nagios" # it's possible dir owner be changed to root by other process, e.g.: LP1866382 # here we ensure owner is nagios, but didn't apply it resursively intentionally. shutil.chown(nagios_home, user="nagios", group="nagios") # the `2` in mode will setgid for group, set dir permission to `drwxr-sr-x`. # the `s` (setgid) will ensure any file created in this dir inherits parent dir # group `nagios`, regardless of the effective user, such as root. os.chmod(nagios_home, 0o2755) # 2 will set the s flag for group nag_dirs = [ "/etc/nagios/nrpe.d/", "/usr/local/lib/nagios/plugins", "/var/lib/nagios/export/", ] for nag_dir in nag_dirs: if not os.path.exists(nag_dir): host.mkdir(nag_dir, perms=0o755) charm_file_dir = os.path.join(hookenv.charm_dir(), "files") charm_plugin_dir = os.path.join(charm_file_dir, "plugins") pkg_plugin_dir = "/usr/lib/nagios/plugins/" local_plugin_dir = "/usr/local/lib/nagios/plugins/" shutil.copy2( os.path.join(charm_file_dir, "nagios_plugin.py"), pkg_plugin_dir + "/nagios_plugin.py", ) shutil.copy2( os.path.join(charm_file_dir, "nagios_plugin3.py"), pkg_plugin_dir + "/nagios_plugin3.py", ) shutil.copy2(os.path.join(charm_file_dir, "default_rsync"), "/etc/default/rsync") shutil.copy2(os.path.join(charm_file_dir, "rsyncd.conf"), "/etc/rsyncd.conf") host.mkdir("/etc/rsync-juju.d", perms=0o755) host.rsync(charm_plugin_dir, "/usr/local/lib/nagios/", options=["--executability"]) for nagios_plugin in ("nagios_plugin.py", "nagios_plugin3.py"): if not os.path.exists(local_plugin_dir + nagios_plugin): os.symlink(pkg_plugin_dir + nagios_plugin, local_plugin_dir + nagios_plugin) def render_nrpe_check_config(checkctxt): """Write nrpe check definition.""" # Only render if we actually have cmd parameters if checkctxt["cmd_params"]: render( "nrpe_command.tmpl", "/etc/nagios/nrpe.d/{}.cfg".format(checkctxt["cmd_name"]), checkctxt, ) def render_nrped_files(service_name): """Render each of the predefined checks.""" for checkctxt in nrpe_helpers.SubordinateCheckDefinitions()["checks"]: # Clean up existing files for fname in checkctxt["matching_files"]: try: os.unlink(fname) except FileNotFoundError: # Don't clean up non-existent files pass render_nrpe_check_config(checkctxt) process_local_monitors() process_user_monitors() def process_user_monitors(): """Collect the user defined local monitors from config.""" if hookenv.config("monitors"): monitors = yaml.safe_load(hookenv.config("monitors")) else: return try: local_user_checks = monitors["monitors"]["local"].keys() except KeyError as e: hookenv.log("no local monitors found in monitors config: {}".format(e)) return for checktype in local_user_checks: for check in monitors["monitors"]["local"][checktype].keys(): check_def = nrpe_helpers.NRPECheckCtxt( checktype, monitors["monitors"]["local"][checktype][check], "user" ) render_nrpe_check_config(check_def) def process_local_monitors(): """Get all the monitor dicts and write out and local checks.""" monitor_dicts = nrpe_helpers.MonitorsRelation().get_monitor_dicts() for monitor_src in monitor_dicts.keys(): monitor_dict = monitor_dicts[monitor_src] if not (monitor_dict and "local" in monitor_dict["monitors"]): continue monitors = monitor_dict["monitors"]["local"] for checktype in monitors: for check in monitors[checktype]: render_nrpe_check_config( nrpe_helpers.NRPECheckCtxt( checktype, monitors[checktype][check], monitor_src, ) ) def update_nrpe_external_master_relation(service_name): """Update nrpe external master relation. Send updated nagios_hostname to charms attached to nrpe_external_master relation. """ principal_relation = nrpe_helpers.PrincipalRelation() for rid in hookenv.relation_ids("nrpe-external-master"): hookenv.relation_set( relation_id=rid, relation_settings=principal_relation.provide_data() ) def update_monitor_relation(service_name): """Send updated monitor yaml to charms attached to monitor relation.""" monitor_relation = nrpe_helpers.MonitorsRelation() for rid in hookenv.relation_ids("monitors"): hookenv.relation_set( relation_id=rid, relation_settings=monitor_relation.provide_data() ) def has_consumer(): """Check for the monitor relation or external monitor config.""" return hookenv.config("nagios_master") not in ["None", "", None] or bool( hookenv.relation_ids("monitors") ) class TolerantPortManagerCallback(PortManagerCallback): """Manage unit ports. Specialization of the PortManagerCallback. It will open or close ports as its superclass, but will not raise an error on conflicts for opening ports For context, see: https://bugs.launchpad.net/juju/+bug/1750079 and https://github.com/juju/charm-helpers/pull/152 """ def __call__(self, manager, service_name, event_name): """Open unit ports.""" service = manager.get_service(service_name) new_ports = service.get("ports", []) port_file = os.path.join(hookenv.charm_dir(), ".{}.ports".format(service_name)) if os.path.exists(port_file): with open(port_file) as fp: old_ports = fp.read().split(",") for old_port in old_ports: if bool(old_port) and not self.ports_contains(old_port, new_ports): hookenv.close_port(old_port) with open(port_file, "w") as fp: fp.write(",".join(str(port) for port in new_ports)) for port in new_ports: # A port is either a number or 'ICMP' protocol = "TCP" if str(port).upper() == "ICMP": protocol = "ICMP" if event_name == "start": try: hookenv.open_port(port, protocol) except subprocess.CalledProcessError as err: if err.returncode == 1: hookenv.log( "open_port returns: {}, ignoring".format(err), level=hookenv.INFO, ) else: raise elif event_name == "stop": hookenv.close_port(port, protocol) maybe_open_ports = TolerantPortManagerCallback() class ExportManagerCallback(ManagerCallback): """Defer lookup of nagios_hostname. This class exists in order to defer lookup of nagios_hostname() until the template is ready to be rendered. This should reduce the incidence of incorrectly-rendered hostnames in /var/lib/nagios/exports. See charmhelpers.core.services.base.ManagerCallback and charmhelpers.core.services.helpers.TemplateCallback for more background. """ def __call__(self, manager, service_name, event_name): """Render export_host.cfg.""" nag_hostname = nrpe_helpers.PrincipalRelation().nagios_hostname() target = "/var/lib/nagios/export/host__{}.cfg".format(nag_hostname) renderer = helpers.render_template( source="export_host.cfg.tmpl", target=target, perms=0o644, ) renderer(manager, service_name, event_name) create_host_export_fragment = ExportManagerCallback()