Merge lp:~jjo/charms/trusty/neutron-openvswitch/add-nrpe-checks-lp1530227 into lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next

Proposed by JuanJo Ciarlante
Status: Work in progress
Proposed branch: lp:~jjo/charms/trusty/neutron-openvswitch/add-nrpe-checks-lp1530227
Merge into: lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next
Diff against target: 998 lines (+914/-0)
7 files modified
charm-helpers-hooks.yaml (+1/-0)
config.yaml (+6/-0)
files/nrpe-external-master/neutron-check-tun_ids.py (+219/-0)
hooks/charmhelpers/contrib/charmsupport/__init__.py (+15/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+458/-0)
hooks/charmhelpers/contrib/charmsupport/volumes.py (+175/-0)
hooks/neutron_ovs_hooks.py (+40/-0)
To merge this branch: bzr merge lp:~jjo/charms/trusty/neutron-openvswitch/add-nrpe-checks-lp1530227
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+281931@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

99. By JuanJo Ciarlante

[jjo] WIP: add NRPE support via NRPESet passed at relation time to nova-compute principal lp#1530227

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-hooks.yaml'
--- charm-helpers-hooks.yaml 2015-09-28 09:47:21 +0000
+++ charm-helpers-hooks.yaml 2016-01-07 21:40:19 +0000
@@ -11,3 +11,4 @@
11 - payload.execd11 - payload.execd
12 - contrib.network.ip12 - contrib.network.ip
13 - contrib.python.packages13 - contrib.python.packages
14 - contrib.charmsupport
1415
=== modified file 'config.yaml'
--- config.yaml 2015-09-15 07:47:30 +0000
+++ config.yaml 2016-01-07 21:40:19 +0000
@@ -106,3 +106,9 @@
106 which do not include a neutron-gateway (do not require l3, lbaas or vpnaas106 which do not include a neutron-gateway (do not require l3, lbaas or vpnaas
107 services) and should only be used in-conjunction with flat or VLAN provider107 services) and should only be used in-conjunction with flat or VLAN provider
108 networks configurations.108 networks configurations.
109 enable-nrpe-checks:
110 type: boolean
111 default: true
112 description: |
113 Provide nrpe data to main charm (ie nova-compute) using 'neutron-plugin'
114 relation via the 'nrpe-checks' key
109115
=== added directory 'files'
=== added directory 'files/nrpe-external-master'
=== added file 'files/nrpe-external-master/neutron-check-tun_ids.py'
--- files/nrpe-external-master/neutron-check-tun_ids.py 1970-01-01 00:00:00 +0000
+++ files/nrpe-external-master/neutron-check-tun_ids.py 2016-01-07 21:40:19 +0000
@@ -0,0 +1,219 @@
1#!/usr/bin/python
2# vim: si et sw=4 ts=4
3#
4# Author: JuanJo Ciarlante <jjo@canonical.com>
5# Copyright (C) 2015 Canonical
6# License: GPLv3
7"""
8Verify that all local OVS tun flows match the ones expected from locally
9running nova instances
10
11Example usage:
12{0}
13{0} -i br-tun # peek at other OVS interface
14{0} --conf-file=/etc/nova/nova.conf # config file to peek creds
15
16"""
17import sys
18import os
19import re
20import logging
21import argparse
22import ConfigParser
23import socket
24import subprocess
25
26from neutronclient.v2_0 import client as neutron_client
27from novaclient import client as nova_client
28
29
30(STATUS_OK, STATUS_WARN, STATUS_CRIT, STATUS_UNKNOWN) = range(0, 4)
31
32
33def get_creds(args):
34 """ return creds dictionary from conf-file (/etc/nova/nova.conf),
35 overridden by OS_ environment vars """
36 config = ConfigParser.RawConfigParser()
37 config.read(args.conf_file)
38 config_creds_section_prefix = {
39 'DEFAULT': 'neutron_admin_',
40 'neutron': 'admin_',
41 }
42
43 creds = {}
44 for key in ('auth_url', 'username', 'password', 'tenant_name'):
45 env_key = "OS_{}".format(key.upper())
46 value = os.environ.get(env_key)
47 # If no creds from environment, try known possible config_creds_keys
48 if value:
49 logging.info("get_creds: found {} as env['{}']={}".format(
50 key, env_key, value if key != 'password' else '...'))
51 if not value:
52 for section, prefix in config_creds_section_prefix.iteritems():
53 try:
54 value = config.get(section, prefix + key)
55 logging.info("get_creds: found {}.{}={}".format(
56 section, prefix + key,
57 value if key != 'password' else '...'))
58 break
59 except ConfigParser.NoOptionError:
60 pass
61 except ConfigParser.NoSectionError:
62 pass
63 if value:
64 creds[key] = value
65 else:
66 raise KeyError("Couldn't find config value for '{}'".format(key))
67
68 logging.debug("creds: username={username} tenant_name={tenant_name} "
69 "auth_url={auth_url} password=...".format(**creds))
70
71 return creds
72
73
74def nova_list_instances(nova_cli, host):
75 "return instances ids running at host"
76 logging.info('getting all instances running at host="{}" ...'.format(
77 socket.gethostname()))
78 search_opts = {'all_tenants': 1, 'host': host}
79 instances = [server.id for server in
80 nova_cli.servers.list(search_opts=search_opts)
81 if server.status == 'ACTIVE']
82 logging.info('instances count={}'.format(len(instances)))
83 logging.debug('instances: {}'.format(instances))
84 return instances
85
86
87def instances_port_nets(neutron_cli, instances):
88 "return instances ports attached network ids"
89 logging.info('getting all instances networks ...')
90 instances_nets = set()
91 for instance in instances:
92 for ports in neutron_cli.list_ports(
93 device_id=instance, fields=['id', 'network_id']).values():
94 for port in ports:
95 instances_nets.add(port['network_id'])
96 logging.info('instances networks count={}'.format(len(instances_nets)))
97 logging.debug('instances networks: {}'.format(instances_nets))
98 return instances_nets
99
100
101def neutron_networks_by_id(neutron_cli):
102 "return all neutron networks, keyed by id"
103 logging.info('getting all neutron networks ...')
104 all_nets = neutron_cli.list_networks().get('networks')
105 logging.info('neutron networks count={}'.format(len(all_nets)))
106 logging.debug('neutron networks: {}'.format(all_nets))
107 networks_by_id = {net['id']: net for net in all_nets}
108 return networks_by_id
109
110
111def get_instances_tun_ids(instances_nets, all_nets_by_id):
112 """return tun_ids from for passed instances networks
113 by looking up all_nets_by_id info"""
114 logging.info('getting network segmentation_id info for all instances...')
115 SEG_ID = 'provider:segmentation_id'
116 NET_TYPE = 'provider:network_type'
117 instances_tun_ids = {all_nets_by_id[net_id].get(SEG_ID)
118 for net_id in instances_nets
119 if all_nets_by_id[net_id].get(NET_TYPE) in
120 ('gre', 'vxlan')}
121 logging.info('instances_tun_ids: {}'.format(instances_tun_ids))
122 return instances_tun_ids
123
124
125def get_ovs_tun_ids(interface):
126 """get local tun_ids from ovs-ofctl output, ala:
127 ovs-ofctl dump-flows br-tun |egrep -o 'tun_id=\w+' """
128 logging.info('local tun_ids: running: ovs-ofctl dump-flows {}'.format(
129 interface))
130 ovs_dump = subprocess.Popen(["ovs-ofctl", "dump-flows", interface],
131 stdin=None,
132 stdout=subprocess.PIPE,
133 stderr=subprocess.PIPE)
134 ovs_tun_ids = set()
135 # match lines with: ... tun_id=0x<TUN_ID> ...
136 for line in ovs_dump.stdout:
137 match = re.search("tun_id=(?P<tun_id>0x\w+)", line)
138 if match:
139 ovs_tun_ids.add(int(match.group(1), 16))
140 logging.info('ovs_tun_ids: {}'.format(ovs_tun_ids))
141 return ovs_tun_ids
142
143
144def nrpe_check_tun_ids(expected_tun_ids, local_tun_ids, all_nets_by_id):
145 # order is important: substract local_tun_ids from expected_tun_ids,
146 # result should be empty
147 tun_ids_diff = expected_tun_ids.difference(local_tun_ids)
148 rc = STATUS_OK
149 msg = []
150 if tun_ids_diff:
151 tun_ids_str = ' '.join(['tun_id=0x{0:x}'.format(x)
152 for x in tun_ids_diff])
153 msg.append('CRITICAL: host={} missing local tun_ids: {}'.format(
154 socket.gethostname(), tun_ids_str))
155 # helper dict by tun_id
156 net_by_tun_id = {net_val.get('provider:segmentation_id'): net_val
157 for net_id, net_val in all_nets_by_id.iteritems()}
158 for tun_id in tun_ids_diff:
159 net = net_by_tun_id.get(tun_id, {})
160 msg.append('CRITICAL: tun_id=0x{0:x} network.id={id} '
161 'network.name="{name}"'.format(tun_id, **net))
162 logging.info('exp_tun_ids: {}'.format(sorted(expected_tun_ids)))
163 logging.info('loc_tun_ids: {}'.format(sorted(local_tun_ids)))
164 rc = STATUS_CRIT
165 else:
166 msg.append('OK: host={} all needed tun_ids present: {}'
167 ''.format(socket.gethostname(), list(local_tun_ids)))
168 return (rc, msg)
169
170
171def parse_args():
172 parser = argparse.ArgumentParser(
173 description=__doc__.format(*sys.argv),
174 formatter_class=argparse.RawDescriptionHelpFormatter)
175 parser.add_argument('--conf-file', default='/etc/nova/nova.conf',
176 help='config file to peek creds from')
177 parser.add_argument('-i', '--interface', default='br-tun',
178 help='OVS iface where to find tun_ids, as: '
179 'ovs-ofctl dump-flows <interface>')
180 parser.add_argument('--test', default=False, action='store_true',
181 help='simulate missing local tun_ids, force CRITICAL')
182 parser.add_argument('--verbose', default=False, action='store_true')
183 parser.add_argument('--debug', default=False, action='store_true')
184 return parser.parse_args()
185
186
187if __name__ == '__main__':
188 args = parse_args()
189 if args.verbose:
190 logging.basicConfig(level=logging.INFO)
191 if args.debug:
192 logging.basicConfig(level=logging.DEBUG)
193
194 # initialize needed clients
195 creds = get_creds(args)
196 logging.info("initializing nova_client")
197 nova_cli = nova_client.Client(1.1, creds['username'], creds['password'],
198 creds['tenant_name'], creds['auth_url'])
199 logging.info("initializing neutron_client")
200 neutron_cli = neutron_client.Client(**creds)
201
202 # instances: local instances id-s (ie running at this host)
203 # instances_nets: local instances' networks id-s
204 # all_nets_by_id: all neutron networks, keyed by id
205 # exp_tun_ids: local instances' networks' segmentation_id-s
206 # loc_tun_ids: locally present tun_ids from ovs-ofctl dump-flows br-tun
207 instances = nova_list_instances(nova_cli, socket.gethostname())
208 instances_nets = instances_port_nets(neutron_cli, instances)
209 all_nets_by_id = neutron_networks_by_id(neutron_cli)
210 exp_tun_ids = get_instances_tun_ids(instances_nets, all_nets_by_id)
211 loc_tun_ids = get_ovs_tun_ids(args.interface)
212
213 if args.test:
214 logging.info('TEST: remove a local tun_id'.format(args.interface))
215 loc_tun_ids.pop()
216
217 rc, msg = nrpe_check_tun_ids(exp_tun_ids, loc_tun_ids, all_nets_by_id)
218 print "\n".join(msg)
219 sys.exit(rc)
0220
=== added directory 'hooks/charmhelpers/contrib/charmsupport'
=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
--- hooks/charmhelpers/contrib/charmsupport/__init__.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/charmsupport/__init__.py 2016-01-07 21:40:19 +0000
@@ -0,0 +1,15 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
016
=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2016-01-07 21:40:19 +0000
@@ -0,0 +1,458 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17"""Compatibility with the nrpe-external-master charm"""
18# Copyright 2012 Canonical Ltd.
19#
20# Authors:
21# Matthew Wedgwood <matthew.wedgwood@canonical.com>
22
23import subprocess
24import pwd
25import grp
26import os
27import glob
28import shutil
29import re
30import shlex
31import yaml
32
33from charmhelpers.core.hookenv import (
34 config,
35 local_unit,
36 log,
37 relation_ids,
38 relation_set,
39 relations_of_type,
40)
41
42from charmhelpers.core.host import (
43 service,
44 rsync,
45)
46NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
47
48# This module adds compatibility with the nrpe-external-master and plain nrpe
49# subordinate charms. To use it in your charm:
50#
51# 1. Update metadata.yaml
52#
53# provides:
54# (...)
55# nrpe-external-master:
56# interface: nrpe-external-master
57# scope: container
58#
59# and/or
60#
61# provides:
62# (...)
63# local-monitors:
64# interface: local-monitors
65# scope: container
66
67#
68# 2. Add the following to config.yaml
69#
70# nagios_context:
71# default: "juju"
72# type: string
73# description: |
74# Used by the nrpe subordinate charms.
75# A string that will be prepended to instance name to set the host name
76# in nagios. So for instance the hostname would be something like:
77# juju-myservice-0
78# If you're running multiple environments with the same services in them
79# this allows you to differentiate between them.
80# nagios_servicegroups:
81# default: ""
82# type: string
83# description: |
84# A comma-separated list of nagios servicegroups.
85# If left empty, the nagios_context will be used as the servicegroup
86#
87# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
88#
89# 4. Update your hooks.py with something like this:
90#
91# from charmsupport.nrpe import NRPE
92# (...)
93# def update_nrpe_config():
94# nrpe_compat = NRPE()
95# nrpe_compat.add_check(
96# shortname = "myservice",
97# description = "Check MyService",
98# check_cmd = "check_http -w 2 -c 10 http://localhost"
99# )
100# nrpe_compat.add_check(
101# "myservice_other",
102# "Check for widget failures",
103# check_cmd = "/srv/myapp/scripts/widget_check"
104# )
105# nrpe_compat.write()
106#
107# def config_changed():
108# (...)
109# update_nrpe_config()
110#
111# def nrpe_external_master_relation_changed():
112# update_nrpe_config()
113#
114# def local_monitors_relation_changed():
115# update_nrpe_config()
116#
117# 5. ln -s hooks.py nrpe-external-master-relation-changed
118# ln -s hooks.py local-monitors-relation-changed
119
120
121class CheckException(Exception):
122 pass
123
124
125class Check(object):
126 shortname_re = '[A-Za-z0-9-_]+$'
127 service_template = ("""
128#---------------------------------------------------
129# This file is Juju managed
130#---------------------------------------------------
131define service {{
132 use active-service
133 host_name {nagios_hostname}
134 service_description {nagios_hostname}[{shortname}] """
135 """{description}
136 check_command check_nrpe!{command}
137 servicegroups {nagios_servicegroup}
138}}
139""")
140
141 def __init__(self, shortname, description, check_cmd):
142 super(Check, self).__init__()
143 # XXX: could be better to calculate this from the service name
144 if not re.match(self.shortname_re, shortname):
145 raise CheckException("shortname must match {}".format(
146 Check.shortname_re))
147 self.shortname = shortname
148 self.command = "check_{}".format(shortname)
149 # Note: a set of invalid characters is defined by the
150 # Nagios server config
151 # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
152 self.description = description
153 self.check_cmd = self._locate_cmd(check_cmd)
154
155 def _locate_cmd(self, check_cmd):
156 search_path = (
157 '/usr/lib/nagios/plugins',
158 '/usr/local/lib/nagios/plugins',
159 )
160 parts = shlex.split(check_cmd)
161 for path in search_path:
162 if os.path.exists(os.path.join(path, parts[0])):
163 command = os.path.join(path, parts[0])
164 if len(parts) > 1:
165 command += " " + " ".join(parts[1:])
166 return command
167 log('Check command not found: {}'.format(parts[0]))
168 return ''
169
170 def write(self, nagios_context, hostname, nagios_servicegroups):
171 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
172 self.command)
173 with open(nrpe_check_file, 'w') as nrpe_check_config:
174 nrpe_check_config.write("# check {}\n".format(self.shortname))
175 nrpe_check_config.write("command[{}]={}\n".format(
176 self.command, self.check_cmd))
177
178 if not os.path.exists(NRPE.nagios_exportdir):
179 log('Not writing service config as {} is not accessible'.format(
180 NRPE.nagios_exportdir))
181 else:
182 self.write_service_config(nagios_context, hostname,
183 nagios_servicegroups)
184
185 def write_service_config(self, nagios_context, hostname,
186 nagios_servicegroups):
187 for f in os.listdir(NRPE.nagios_exportdir):
188 if re.search('.*{}.cfg'.format(self.command), f):
189 os.remove(os.path.join(NRPE.nagios_exportdir, f))
190
191 templ_vars = {
192 'nagios_hostname': hostname,
193 'nagios_servicegroup': nagios_servicegroups,
194 'description': self.description,
195 'shortname': self.shortname,
196 'command': self.command,
197 }
198 nrpe_service_text = Check.service_template.format(**templ_vars)
199 nrpe_service_file = '{}/service__{}_{}.cfg'.format(
200 NRPE.nagios_exportdir, hostname, self.command)
201 with open(nrpe_service_file, 'w') as nrpe_service_config:
202 nrpe_service_config.write(str(nrpe_service_text))
203
204 def run(self):
205 subprocess.call(self.check_cmd)
206
207
208class Cron(object):
209 def __init__(self, filename, cron_freq, cron_user, cron_cmd):
210 super(Cron, self).__init__()
211 self.filename = filename
212 self.cron_freq = cron_freq
213 self.cron_user = cron_user
214 self.cron_cmd = cron_cmd
215
216 def write(self):
217 cron_filename = '/etc/cron.d/{}'.format(self.filename)
218 log("cron.write: {}".format(cron_filename))
219 with open(cron_filename, 'w') as cron_file:
220 cron_file.write("# cron {}\n".format(self.filename))
221 cron_file.write("{} {} {}\n".format(self.cron_freq,
222 self.cron_user,
223 self.cron_cmd))
224
225class NagiosPlugin(object):
226 def __init__(self, filename):
227 super(NagiosPlugin, self).__init__()
228 self.filename = filename
229
230 def write(self):
231 if not os.path.exists(NAGIOS_PLUGINS):
232 os.makedirs(NAGIOS_PLUGINS)
233 if os.path.exists(self.filename):
234 log("NagiosPlugin.write: {} {}".format(self.filename, NAGIOS_PLUGINS))
235 rsync(self.filename, NAGIOS_PLUGINS)
236 else:
237 log("SKIPPED: NagiosPlugin.write: {} {}".format(self.filename, NAGIOS_PLUGINS))
238
239
240class NRPE(object):
241 nagios_logdir = '/var/log/nagios'
242 nagios_exportdir = '/var/lib/nagios/export'
243 nrpe_confdir = '/etc/nagios/nrpe.d'
244
245 def __init__(self, hostname=None):
246 super(NRPE, self).__init__()
247 self.config = config()
248 self.nagios_context = self.config['nagios_context']
249 if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
250 self.nagios_servicegroups = self.config['nagios_servicegroups']
251 else:
252 self.nagios_servicegroups = self.nagios_context
253 self.unit_name = local_unit().replace('/', '-')
254 if hostname:
255 self.hostname = hostname
256 else:
257 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
258 self.checks = []
259 self.crons = []
260 self.nagios_plugins = []
261
262 def add_check(self, *args, **kwargs):
263 self.checks.append(Check(*args, **kwargs))
264
265 def add_cron(self, *args, **kwargs):
266 self.crons.append(Cron(*args, **kwargs))
267
268 def add_nagios_plugin(self, *args, **kwargs):
269 self.nagios_plugins.append(NagiosPlugin(*args, **kwargs))
270
271 def add_from_config(self, config_key):
272 saved_config = config()
273 nrpe_set = NRPESet(saved_config.get(config_key))
274 log('NRPE.add_from_config: nrpe_set={}'.format(str(nrpe_set)))
275 if nrpe_set:
276 for check in nrpe_set.checks:
277 self.add_check(*check)
278 for cron in nrpe_set.crons:
279 self.add_cron(*cron)
280 for nagios_plugins in nrpe_set.nagios_plugins:
281 self.add_nagios_plugin(*nagios_plugins)
282
283 def write(self):
284 try:
285 nagios_uid = pwd.getpwnam('nagios').pw_uid
286 nagios_gid = grp.getgrnam('nagios').gr_gid
287 except:
288 log("Nagios user not set up, nrpe checks not updated")
289 return
290
291 if not os.path.exists(NRPE.nagios_logdir):
292 os.mkdir(NRPE.nagios_logdir)
293 os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
294
295 nrpe_monitors = {}
296 monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
297 for nrpecheck in self.checks:
298 nrpecheck.write(self.nagios_context, self.hostname,
299 self.nagios_servicegroups)
300 nrpe_monitors[nrpecheck.shortname] = {
301 "command": nrpecheck.command,
302 }
303
304 for cron in self.crons:
305 cron.write()
306
307 for nagios_plugins in self.nagios_plugins:
308 nagios_plugins.write()
309
310 service('restart', 'nagios-nrpe-server')
311
312 monitor_ids = relation_ids("local-monitors") + \
313 relation_ids("nrpe-external-master")
314 for rid in monitor_ids:
315 relation_set(relation_id=rid, monitors=yaml.dump(monitors))
316
317
318def get_nagios_hostcontext(relation_name='nrpe-external-master'):
319 """
320 Query relation with nrpe subordinate, return the nagios_host_context
321
322 :param str relation_name: Name of relation nrpe sub joined to
323 """
324 for rel in relations_of_type(relation_name):
325 if 'nagios_hostname' in rel:
326 return rel['nagios_host_context']
327
328
329def get_nagios_hostname(relation_name='nrpe-external-master'):
330 """
331 Query relation with nrpe subordinate, return the nagios_hostname
332
333 :param str relation_name: Name of relation nrpe sub joined to
334 """
335 for rel in relations_of_type(relation_name):
336 if 'nagios_hostname' in rel:
337 return rel['nagios_hostname']
338
339
340def get_nagios_unit_name(relation_name='nrpe-external-master'):
341 """
342 Return the nagios unit name prepended with host_context if needed
343
344 :param str relation_name: Name of relation nrpe sub joined to
345 """
346 host_context = get_nagios_hostcontext(relation_name)
347 if host_context:
348 unit = "%s:%s" % (host_context, local_unit())
349 else:
350 unit = local_unit()
351 return unit
352
353
354def add_init_service_checks(nrpe, services, unit_name):
355 """
356 Add checks for each service in list
357
358 :param NRPE nrpe: NRPE object to add check to
359 :param list services: List of services to check
360 :param str unit_name: Unit name to use in check description
361 """
362 for svc in services:
363 upstart_init = '/etc/init/%s.conf' % svc
364 sysv_init = '/etc/init.d/%s' % svc
365 if os.path.exists(upstart_init):
366 nrpe.add_check(
367 shortname=svc,
368 description='process check {%s}' % unit_name,
369 check_cmd='check_upstart_job %s' % svc
370 )
371 elif os.path.exists(sysv_init):
372 cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
373 cron_file = ('*/5 * * * * root '
374 '/usr/local/lib/nagios/plugins/check_exit_status.pl '
375 '-s /etc/init.d/%s status > '
376 '/var/lib/nagios/service-check-%s.txt\n' % (svc,
377 svc)
378 )
379 f = open(cronpath, 'w')
380 f.write(cron_file)
381 f.close()
382 nrpe.add_check(
383 shortname=svc,
384 description='process check {%s}' % unit_name,
385 check_cmd='check_status_file.py -f '
386 '/var/lib/nagios/service-check-%s.txt' % svc,
387 )
388
389
390def copy_nrpe_checks():
391 """
392 Copy the nrpe checks into place
393
394 """
395 nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
396 'charmhelpers', 'contrib', 'openstack',
397 'files')
398
399 if not os.path.exists(NAGIOS_PLUGINS):
400 os.makedirs(NAGIOS_PLUGINS)
401 for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
402 if os.path.isfile(fname):
403 shutil.copy2(fname,
404 os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
405
406
407def add_haproxy_checks(nrpe, unit_name):
408 """
409 Add checks for each service in list
410
411 :param NRPE nrpe: NRPE object to add check to
412 :param str unit_name: Unit name to use in check description
413 """
414 nrpe.add_check(
415 shortname='haproxy_servers',
416 description='Check HAProxy {%s}' % unit_name,
417 check_cmd='check_haproxy.sh')
418 nrpe.add_check(
419 shortname='haproxy_queue',
420 description='Check HAProxy queue depth {%s}' % unit_name,
421 check_cmd='check_haproxy_queue_depth.sh')
422
423class NRPESet:
424 checks = []
425 crons = []
426 nagios_plugins = []
427 def __init__(self, yaml_str='null'):
428 init_values = None
429 if yaml_str:
430 init_values = yaml.safe_load(yaml_str)
431 log('NRPESet: init_values={}'.format(init_values))
432 if type(init_values) == type({}):
433 self.checks = init_values.get('checks', [])
434 self.crons = init_values.get('crons', [])
435 self.nagios_plugins = init_values.get('nagios_plugins', [])
436
437 def add_check(self, shortname, description, check_cmd):
438 log('NRPESet: add_check({})'.format((shortname, description, check_cmd)))
439 self.checks.append((shortname, description, check_cmd))
440
441 def add_init_service_checks(self, services, unit_name=None):
442 if not unit_name:
443 unit_name = local_unit().replace('/', '-')
444 log('NRPESet: add_init_service_checks({}, {})'.format(services, unit_name))
445 add_init_service_checks(self, services, unit_name)
446
447 def add_cron(self, filename, cron_freq, cron_user, cron_cmd):
448 log('NRPESet: add_cron({}, ...)'.format(filename))
449 self.crons.append((filename, cron_freq, cron_user, cron_cmd))
450
451 def add_nagios_plugin(self, filename):
452 log('NRPESet: add_nagios_plugin({})'.format(filename))
453 self.nagios_plugins.append((filename,))
454
455 def __str__(self):
456 return yaml.safe_dump({'checks': self.checks,
457 'crons': self.crons,
458 'nagios_plugins': self.nagios_plugins})
0459
=== added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py'
--- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2016-01-07 21:40:19 +0000
@@ -0,0 +1,175 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17'''
18Functions for managing volumes in juju units. One volume is supported per unit.
19Subordinates may have their own storage, provided it is on its own partition.
20
21Configuration stanzas::
22
23 volume-ephemeral:
24 type: boolean
25 default: true
26 description: >
27 If false, a volume is mounted as sepecified in "volume-map"
28 If true, ephemeral storage will be used, meaning that log data
29 will only exist as long as the machine. YOU HAVE BEEN WARNED.
30 volume-map:
31 type: string
32 default: {}
33 description: >
34 YAML map of units to device names, e.g:
35 "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
36 Service units will raise a configure-error if volume-ephemeral
37 is 'true' and no volume-map value is set. Use 'juju set' to set a
38 value and 'juju resolved' to complete configuration.
39
40Usage::
41
42 from charmsupport.volumes import configure_volume, VolumeConfigurationError
43 from charmsupport.hookenv import log, ERROR
44 def post_mount_hook():
45 stop_service('myservice')
46 def post_mount_hook():
47 start_service('myservice')
48
49 if __name__ == '__main__':
50 try:
51 configure_volume(before_change=pre_mount_hook,
52 after_change=post_mount_hook)
53 except VolumeConfigurationError:
54 log('Storage could not be configured', ERROR)
55
56'''
57
58# XXX: Known limitations
59# - fstab is neither consulted nor updated
60
61import os
62from charmhelpers.core import hookenv
63from charmhelpers.core import host
64import yaml
65
66
67MOUNT_BASE = '/srv/juju/volumes'
68
69
70class VolumeConfigurationError(Exception):
71 '''Volume configuration data is missing or invalid'''
72 pass
73
74
75def get_config():
76 '''Gather and sanity-check volume configuration data'''
77 volume_config = {}
78 config = hookenv.config()
79
80 errors = False
81
82 if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
83 volume_config['ephemeral'] = True
84 else:
85 volume_config['ephemeral'] = False
86
87 try:
88 volume_map = yaml.safe_load(config.get('volume-map', '{}'))
89 except yaml.YAMLError as e:
90 hookenv.log("Error parsing YAML volume-map: {}".format(e),
91 hookenv.ERROR)
92 errors = True
93 if volume_map is None:
94 # probably an empty string
95 volume_map = {}
96 elif not isinstance(volume_map, dict):
97 hookenv.log("Volume-map should be a dictionary, not {}".format(
98 type(volume_map)))
99 errors = True
100
101 volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
102 if volume_config['device'] and volume_config['ephemeral']:
103 # asked for ephemeral storage but also defined a volume ID
104 hookenv.log('A volume is defined for this unit, but ephemeral '
105 'storage was requested', hookenv.ERROR)
106 errors = True
107 elif not volume_config['device'] and not volume_config['ephemeral']:
108 # asked for permanent storage but did not define volume ID
109 hookenv.log('Ephemeral storage was requested, but there is no volume '
110 'defined for this unit.', hookenv.ERROR)
111 errors = True
112
113 unit_mount_name = hookenv.local_unit().replace('/', '-')
114 volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
115
116 if errors:
117 return None
118 return volume_config
119
120
121def mount_volume(config):
122 if os.path.exists(config['mountpoint']):
123 if not os.path.isdir(config['mountpoint']):
124 hookenv.log('Not a directory: {}'.format(config['mountpoint']))
125 raise VolumeConfigurationError()
126 else:
127 host.mkdir(config['mountpoint'])
128 if os.path.ismount(config['mountpoint']):
129 unmount_volume(config)
130 if not host.mount(config['device'], config['mountpoint'], persist=True):
131 raise VolumeConfigurationError()
132
133
134def unmount_volume(config):
135 if os.path.ismount(config['mountpoint']):
136 if not host.umount(config['mountpoint'], persist=True):
137 raise VolumeConfigurationError()
138
139
140def managed_mounts():
141 '''List of all mounted managed volumes'''
142 return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
143
144
145def configure_volume(before_change=lambda: None, after_change=lambda: None):
146 '''Set up storage (or don't) according to the charm's volume configuration.
147 Returns the mount point or "ephemeral". before_change and after_change
148 are optional functions to be called if the volume configuration changes.
149 '''
150
151 config = get_config()
152 if not config:
153 hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
154 raise VolumeConfigurationError()
155
156 if config['ephemeral']:
157 if os.path.ismount(config['mountpoint']):
158 before_change()
159 unmount_volume(config)
160 after_change()
161 return 'ephemeral'
162 else:
163 # persistent storage
164 if os.path.ismount(config['mountpoint']):
165 mounts = dict(managed_mounts())
166 if mounts.get(config['mountpoint']) != config['device']:
167 before_change()
168 unmount_volume(config)
169 mount_volume(config)
170 after_change()
171 else:
172 before_change()
173 mount_volume(config)
174 after_change()
175 return config['mountpoint']
0176
=== modified file 'hooks/neutron_ovs_hooks.py'
--- hooks/neutron_ovs_hooks.py 2015-11-12 09:33:27 +0000
+++ hooks/neutron_ovs_hooks.py 2016-01-07 21:40:19 +0000
@@ -1,5 +1,6 @@
1#!/usr/bin/python1#!/usr/bin/python
22
3import os
3import sys4import sys
45
5from copy import deepcopy6from copy import deepcopy
@@ -16,6 +17,7 @@
16 log,17 log,
17 relation_set,18 relation_set,
18 relation_ids,19 relation_ids,
20 local_unit,
19)21)
2022
21from charmhelpers.core.host import (23from charmhelpers.core.host import (
@@ -45,6 +47,7 @@
45 REQUIRED_INTERFACES,47 REQUIRED_INTERFACES,
46 check_optional_relations,48 check_optional_relations,
47)49)
50from charmhelpers.contrib.charmsupport import nrpe
4851
49hooks = Hooks()52hooks = Hooks()
50CONFIGS = register_configs()53CONFIGS = register_configs()
@@ -72,6 +75,10 @@
72 for rid in relation_ids('neutron-plugin'):75 for rid in relation_ids('neutron-plugin'):
73 neutron_plugin_joined(relation_id=rid)76 neutron_plugin_joined(relation_id=rid)
7477
78 if config_value_changed('enable-nrpe-checks'):
79 for rid in relation_ids('neutron-plugin'):
80 neutron_plugin_joined(rid)
81
7582
76@hooks.hook('neutron-plugin-api-relation-changed')83@hooks.hook('neutron-plugin-api-relation-changed')
77@restart_on_change(restart_map())84@restart_on_change(restart_map())
@@ -87,6 +94,37 @@
87 neutron_plugin_joined(relation_id=rid)94 neutron_plugin_joined(relation_id=rid)
8895
8996
97def neutron_plugin_nrpe_checks():
98 log('neutron_plugin_nrpe_checks: enable-nrpe-checks={}'.format(
99 config('enable-nrpe-checks')))
100 if not config('enable-nrpe-checks'):
101 return ''
102 current_unit = local_unit().replace('/', '-')
103 nrpe_set = nrpe.NRPESet()
104 nrpe_set.add_nagios_plugin(
105 os.path.join(os.getenv('CHARM_DIR'),
106 'files', 'nrpe-external-master',
107 'neutron-check-tun_ids.py'))
108 nrpe_set.add_cron(
109 filename='nagios-check-tun_ids',
110 cron_freq='*/5 * * * *',
111 cron_user='root',
112 cron_cmd='{}/neutron-check-tun_ids.py > '
113 '/var/lib/nagios/neutron-check-tun_ids.txt'
114 ''.format(nrpe.NAGIOS_PLUGINS),
115 )
116 nrpe_set.add_check(
117 shortname='neutron_tun_ids',
118 description='Check neutron ovs tun_ids {%s}' % current_unit,
119 check_cmd='check_status_file.py -f '
120 '/var/lib/nagios/neutron-check-tun_ids.txt'
121 )
122 nrpe_set.add_init_service_checks(
123 ['openvswitch-switch', 'neutron-plugin-openvswitch-agent'])
124 log('neutron_plugin_nrpe_checks: nrpe_set={}'.format(str(nrpe_set)))
125 return str(nrpe_set)
126
127
90@hooks.hook('neutron-plugin-relation-joined')128@hooks.hook('neutron-plugin-relation-joined')
91def neutron_plugin_joined(relation_id=None):129def neutron_plugin_joined(relation_id=None):
92 if enable_local_dhcp():130 if enable_local_dhcp():
@@ -100,8 +138,10 @@
100 pkgs.extend(METADATA_PACKAGES)138 pkgs.extend(METADATA_PACKAGES)
101 purge_packages(pkgs)139 purge_packages(pkgs)
102 secret = get_shared_secret() if enable_nova_metadata() else None140 secret = get_shared_secret() if enable_nova_metadata() else None
141 nrpe_checks = neutron_plugin_nrpe_checks()
103 rel_data = {142 rel_data = {
104 'metadata-shared-secret': secret,143 'metadata-shared-secret': secret,
144 'nrpe-checks': nrpe_checks,
105 }145 }
106 relation_set(relation_id=relation_id, **rel_data)146 relation_set(relation_id=relation_id, **rel_data)
107147

Subscribers

People subscribed via source and target branches