Coverage for hooks/common.py : 16%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import subprocess
2import socket
3import os
4import os.path
5import re
6import shutil
7import tempfile
9from charmhelpers.core.hookenv import (
10 log,
11 network_get,
12 network_get_primary_address,
13 unit_get,
14 config,
15)
17from pynag import Model
19INPROGRESS_DIR = "/etc/nagios3-inprogress"
20INPROGRESS_CFG = "/etc/nagios3-inprogress/nagios.cfg"
21INPROGRESS_CONF_D = "/etc/nagios3-inprogress/conf.d"
22CHARM_CFG = "/etc/nagios3-inprogress/conf.d/charm.cfg"
23MAIN_NAGIOS_BAK = "/etc/nagios3.bak"
24MAIN_NAGIOS_DIR = "/etc/nagios3"
25MAIN_NAGIOS_CFG = "/etc/nagios3/nagios.cfg"
26PLUGIN_PATH = "/usr/lib/nagios/plugins"
28Model.cfg_file = INPROGRESS_CFG
29Model.pynag_directory = INPROGRESS_CONF_D
31reduce_RE = re.compile(r"[\W_]")
34def check_ip(n):
35 try:
36 socket.inet_pton(socket.AF_INET, n)
37 return True
38 except socket.error:
39 try:
40 socket.inet_pton(socket.AF_INET6, n)
41 return True
42 except socket.error:
43 return False
46def get_local_ingress_address(binding="website"):
47 # using network-get to retrieve the address details if available.
48 log("Getting hostname for binding %s" % binding)
49 try:
50 network_info = network_get(binding)
51 if network_info is not None and "ingress-addresses" in network_info:
52 log("Using ingress-addresses")
53 hostname = network_info["ingress-addresses"][0]
54 log(hostname)
55 return hostname
56 except NotImplementedError:
57 # We'll fallthrough to the Pre 2.3 code below.
58 pass
60 # Pre 2.3 output
61 try:
62 hostname = network_get_primary_address(binding)
63 log("Using primary-addresses")
64 except NotImplementedError:
65 # pre Juju 2.0
66 hostname = unit_get("private-address")
67 log("Using unit_get private address")
68 log(hostname)
69 return hostname
72def get_remote_relation_attr(remote_unit, attr_name, relation_id=None):
73 args = ["relation-get", attr_name, remote_unit]
74 if relation_id is not None:
75 args.extend(["-r", relation_id])
76 return subprocess.check_output(args).strip()
79def get_ip_and_hostname(remote_unit, relation_id=None):
80 hostname = get_remote_relation_attr(remote_unit, "ingress-address", relation_id)
81 if hostname is None or not len(hostname):
82 hostname = get_remote_relation_attr(remote_unit, "private-address", relation_id)
84 if hostname is None or not len(hostname):
85 log("relation-get failed")
86 return 2
87 if check_ip(hostname):
88 # Some providers don't provide hostnames, so use the remote unit name.
89 ip_address = hostname
90 else:
91 ip_address = socket.getaddrinfo(hostname, None)[0][4][0]
92 return (ip_address, remote_unit.replace("/", "-"))
95def refresh_hostgroups(): # noqa:C901
96 """ Not the most efficient thing but since we're only
97 parsing what is already on disk here its not too bad """
98 hosts = [x["host_name"] for x in Model.Host.objects.all if x["host_name"]]
100 hgroups = {}
101 for host in hosts:
102 try:
103 (service, unit_id) = host.rsplit("-", 1)
104 except ValueError:
105 continue
106 if service in hgroups:
107 hgroups[service].append(host)
108 else:
109 hgroups[service] = [host]
111 # Find existing autogenerated
112 auto_hgroups = Model.Hostgroup.objects.filter(notes__contains="#autogenerated#")
113 auto_hgroups = [x.get_attribute("hostgroup_name") for x in auto_hgroups]
115 # Delete the ones not in hgroups
116 to_delete = set(auto_hgroups).difference(set(hgroups.keys()))
117 for hgroup_name in to_delete:
118 try:
119 hgroup = Model.Hostgroup.objects.get_by_shortname(hgroup_name)
120 hgroup.delete()
121 except (ValueError, KeyError):
122 pass
124 for hgroup_name, members in hgroups.iteritems():
125 try:
126 hgroup = Model.Hostgroup.objects.get_by_shortname(hgroup_name)
127 except (ValueError, KeyError):
128 hgroup = Model.Hostgroup()
129 hgroup.set_filename(CHARM_CFG)
130 hgroup.set_attribute("hostgroup_name", hgroup_name)
131 hgroup.set_attribute("notes", "#autogenerated#")
133 hgroup.set_attribute("members", ",".join(members))
134 hgroup.save()
137def _make_check_command(args):
138 args = [str(arg) for arg in args]
139 # There is some worry of collision, but the uniqueness of the initial
140 # command should be enough.
141 signature = reduce_RE.sub("_", "".join([os.path.basename(arg) for arg in args]))
142 Model.Command.objects.reload_cache()
143 try:
144 cmd = Model.Command.objects.get_by_shortname(signature)
145 except (ValueError, KeyError):
146 cmd = Model.Command()
147 cmd.set_attribute("command_name", signature)
148 cmd.set_attribute("command_line", " ".join(args))
149 cmd.save()
150 return signature
153def _extend_args(args, cmd_args, switch, value):
154 args.append(value)
155 cmd_args.extend((switch, '"$ARG%d$"' % len(args)))
158def customize_http(service, name, extra):
159 args = []
160 cmd_args = []
161 plugin = os.path.join(PLUGIN_PATH, "check_http")
162 port = extra.get("port", 80)
163 path = extra.get("path", "/")
164 args = [port, path]
165 cmd_args = [plugin, "-p", '"$ARG1$"', "-u", '"$ARG2$"']
166 if "status" in extra:
167 _extend_args(args, cmd_args, "-e", extra["status"])
168 if "host" in extra:
169 _extend_args(args, cmd_args, "-H", extra["host"])
170 cmd_args.extend(("-I", "$HOSTADDRESS$"))
171 else:
172 cmd_args.extend(("-H", "$HOSTADDRESS$"))
173 check_timeout = config("check_timeout")
174 if check_timeout is not None:
175 cmd_args.extend(("-t", check_timeout))
176 check_command = _make_check_command(cmd_args)
177 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
178 service.set_attribute("check_command", cmd)
179 return True
182def customize_mysql(service, name, extra):
183 plugin = os.path.join(PLUGIN_PATH, "check_mysql")
184 args = []
185 cmd_args = [plugin, "-H", "$HOSTADDRESS$"]
186 if "user" in extra:
187 _extend_args(args, cmd_args, "-u", extra["user"])
188 if "password" in extra:
189 _extend_args(args, cmd_args, "-p", extra["password"])
190 check_timeout = config("check_timeout")
191 if check_timeout is not None:
192 cmd_args.extend(("-t", check_timeout))
193 check_command = _make_check_command(cmd_args)
194 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
195 service.set_attribute("check_command", cmd)
196 return True
199def customize_pgsql(service, name, extra):
200 plugin = os.path.join(PLUGIN_PATH, "check_pgsql")
201 args = []
202 cmd_args = [plugin, "-H", "$HOSTADDRESS$"]
203 check_timeout = config("check_timeout")
204 if check_timeout is not None:
205 cmd_args.extend(("-t", check_timeout))
206 check_command = _make_check_command(cmd_args)
207 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
208 service.set_attribute("check_command", cmd)
209 return True
212def customize_nrpe(service, name, extra):
213 plugin = os.path.join(PLUGIN_PATH, "check_nrpe")
214 args = []
215 cmd_args = [plugin, "-H", "$HOSTADDRESS$"]
216 if name in ("mem", "swap"):
217 cmd_args.extend(("-c", "check_%s" % name))
218 elif "command" in extra:
219 cmd_args.extend(("-c", extra["command"]))
220 else:
221 cmd_args.extend(("-c", extra))
222 check_timeout = config("check_timeout")
223 if check_timeout is not None:
224 cmd_args.extend(("-t", check_timeout))
225 check_command = _make_check_command(cmd_args)
226 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
227 service.set_attribute("check_command", cmd)
228 return True
231def customize_rpc(service, name, extra):
232 """ Customize the check_rpc plugin to check things like nfs."""
233 plugin = os.path.join(PLUGIN_PATH, "check_rpc")
234 args = []
235 # /usr/lib/nagios/plugins/check_rpc -H <host> -C <rpc_command>
236 cmd_args = [plugin, "-H", "$HOSTADDRESS$"]
237 if "rpc_command" in extra:
238 cmd_args.extend(("-C", extra["rpc_command"]))
239 if "program_version" in extra:
240 cmd_args.extend(("-c", extra["program_version"]))
242 check_command = _make_check_command(cmd_args)
243 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
244 service.set_attribute("check_command", cmd)
245 return True
248def customize_tcp(service, name, extra):
249 """ Customize tcp can be used to check things like memcached. """
250 plugin = os.path.join(PLUGIN_PATH, "check_tcp")
251 args = []
252 # /usr/lib/nagios/plugins/check_tcp -H <host> -E
253 cmd_args = [plugin, "-H", "$HOSTADDRESS$", "-E"]
254 if "port" in extra:
255 cmd_args.extend(("-p", extra["port"]))
256 if "string" in extra:
257 cmd_args.extend(("-s", "'{}'".format(extra["string"])))
258 if "expect" in extra:
259 cmd_args.extend(("-e", extra["expect"]))
260 if "warning" in extra:
261 cmd_args.extend(("-w", extra["warning"]))
262 if "critical" in extra:
263 cmd_args.extend(("-c", extra["critical"]))
264 if "timeout" in extra:
265 cmd_args.extend(("-t", extra["timeout"]))
266 check_timeout = config("check_timeout")
267 if check_timeout is not None:
268 cmd_args.extend(("-t", check_timeout))
270 check_command = _make_check_command(cmd_args)
271 cmd = "%s!%s" % (check_command, "!".join([str(x) for x in args]))
272 service.set_attribute("check_command", cmd)
273 return True
276def customize_service(service, family, name, extra):
277 """ The monitors.yaml names are mapped to methods that customize services. """
278 customs = {
279 "http": customize_http,
280 "mysql": customize_mysql,
281 "nrpe": customize_nrpe,
282 "tcp": customize_tcp,
283 "rpc": customize_rpc,
284 "pgsql": customize_pgsql,
285 }
286 if family in customs:
287 return customs[family](service, name, extra)
288 return False
291def update_localhost():
292 """ Update the localhost definition to use the ubuntu icons."""
294 Model.cfg_file = MAIN_NAGIOS_CFG
295 Model.pynag_directory = os.path.join(MAIN_NAGIOS_DIR, "conf.d")
296 hosts = Model.Host.objects.filter(host_name="localhost", object_type="host")
297 for host in hosts:
298 host.icon_image = "base/ubuntu.png"
299 host.icon_image_alt = "Ubuntu Linux"
300 host.vrml_image = "ubuntu.png"
301 host.statusmap_image = "base/ubuntu.gd2"
302 host.save()
305def get_pynag_host(target_id, owner_unit=None, owner_relation=None):
306 try:
307 host = Model.Host.objects.get_by_shortname(target_id)
308 except (ValueError, KeyError):
309 host = Model.Host()
310 host.set_filename(CHARM_CFG)
311 host.set_attribute("host_name", target_id)
312 host.set_attribute("use", "generic-host")
313 # Adding the ubuntu icon image definitions to the host.
314 host.set_attribute("icon_image", "base/ubuntu.png")
315 host.set_attribute("icon_image_alt", "Ubuntu Linux")
316 host.set_attribute("vrml_image", "ubuntu.png")
317 host.set_attribute("statusmap_image", "base/ubuntu.gd2")
318 host.save()
319 host = Model.Host.objects.get_by_shortname(target_id)
320 apply_host_policy(target_id, owner_unit, owner_relation)
321 return host
324def get_pynag_service(target_id, service_name):
325 services = Model.Service.objects.filter(
326 host_name=target_id, service_description=service_name
327 )
328 if len(services) == 0:
329 service = Model.Service()
330 service.set_filename(CHARM_CFG)
331 service.set_attribute("service_description", service_name)
332 service.set_attribute("host_name", target_id)
333 service.set_attribute("use", "generic-service")
334 else:
335 service = services[0]
336 return service
339def apply_host_policy(target_id, owner_unit, owner_relation):
340 ssh_service = get_pynag_service(target_id, "SSH")
341 ssh_service.set_attribute("check_command", "check_ssh")
342 ssh_service.save()
345def _replace_in_config(find_me, replacement):
346 with open(INPROGRESS_CFG) as cf:
347 with tempfile.NamedTemporaryFile(dir=INPROGRESS_DIR, delete=False) as new_cf:
348 for line in cf:
349 new_cf.write(line.replace(find_me, replacement))
350 new_cf.flush()
351 os.chmod(new_cf.name, 0o644)
352 os.unlink(INPROGRESS_CFG)
353 os.rename(new_cf.name, INPROGRESS_CFG)
356def _commit_in_config(find_me, replacement):
357 with open(MAIN_NAGIOS_CFG) as cf:
358 with tempfile.NamedTemporaryFile(dir=MAIN_NAGIOS_DIR, delete=False) as new_cf:
359 for line in cf:
360 new_cf.write(line.replace(find_me, replacement))
361 new_cf.flush()
362 os.chmod(new_cf.name, 0o644)
363 os.unlink(MAIN_NAGIOS_CFG)
364 os.rename(new_cf.name, MAIN_NAGIOS_CFG)
367def initialize_inprogress_config():
368 if os.path.exists(INPROGRESS_DIR):
369 shutil.rmtree(INPROGRESS_DIR)
370 shutil.copytree(MAIN_NAGIOS_DIR, INPROGRESS_DIR)
371 _replace_in_config(MAIN_NAGIOS_DIR, INPROGRESS_DIR)
372 if os.path.exists(CHARM_CFG):
373 os.unlink(CHARM_CFG)
376def flush_inprogress_config():
377 if not os.path.exists(INPROGRESS_DIR):
378 return
379 if os.path.exists(MAIN_NAGIOS_BAK):
380 shutil.rmtree(MAIN_NAGIOS_BAK)
381 if os.path.exists(MAIN_NAGIOS_DIR):
382 shutil.move(MAIN_NAGIOS_DIR, MAIN_NAGIOS_BAK)
383 shutil.move(INPROGRESS_DIR, MAIN_NAGIOS_DIR)
384 # now that directory has been changed need to update the config file
385 # to reflect the real stuff..
386 _commit_in_config(INPROGRESS_DIR, MAIN_NAGIOS_DIR)