Merge lp:~brad-marshall/charms/trusty/swift-storage/add-nrpe-checks-fix-rsyncd-conf into lp:~openstack-charmers-archive/charms/trusty/swift-storage/trunk

Proposed by Brad Marshall
Status: Merged
Merged at revision: 54
Proposed branch: lp:~brad-marshall/charms/trusty/swift-storage/add-nrpe-checks-fix-rsyncd-conf
Merge into: lp:~openstack-charmers-archive/charms/trusty/swift-storage/trunk
Diff against target: 959 lines (+736/-30)
16 files modified
charm-helpers-hooks.yaml (+1/-0)
config.yaml (+14/-0)
files/nrpe-external-master/check_swift_service (+25/-0)
files/nrpe-external-master/check_swift_storage.py (+136/-0)
files/sudo/swift-storage (+1/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+219/-0)
hooks/charmhelpers/contrib/charmsupport/rsync.py (+32/-0)
hooks/charmhelpers/contrib/charmsupport/volumes.py (+156/-0)
hooks/swift_storage_hooks.py (+77/-1)
hooks/swift_storage_utils.py (+31/-3)
metadata.yaml (+3/-0)
templates/001-baseconfig (+6/-0)
templates/050-swift-storage.conf (+24/-0)
templates/rsyncd.conf (+0/-23)
unit_tests/test_swift_storage_relations.py (+10/-3)
unit_tests/test_swift_storage_utils.py (+1/-0)
To merge this branch: bzr merge lp:~brad-marshall/charms/trusty/swift-storage/add-nrpe-checks-fix-rsyncd-conf
Reviewer Review Type Date Requested Status
Liam Young (community) Disapprove
Review via email: mp+241483@code.launchpad.net

Description of the change

This adds support for nrpe-external-master, and adds some basic nrpe checks.

This relies on my nrpe-external-master branch lp:~brad-marshall/charms/trusty/nrpe-external-master/fix-rsyncd-conf.

To post a comment you must log in.
Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_lint_check #986 trusty-swift-storage for brad-marshall mp241483
    LINT FAIL: lint-test failed

LINT Results (max last 5 lines):
  hooks/swift_storage_hooks.py:165:80: E501 line too long (90 > 79 characters)
  hooks/swift_storage_hooks.py:174:22: E251 unexpected spaces around keyword / parameter equals
  hooks/swift_storage_hooks.py:174:24: E251 unexpected spaces around keyword / parameter equals
  hooks/swift_storage_utils.py:267:1: W391 blank line at end of file
  make: *** [lint] Error 1

Full lint test output: http://paste.ubuntu.com/8955703/
Build: http://10.98.191.181:8080/job/charm_lint_check/986/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_unit_test #821 trusty-swift-storage for brad-marshall mp241483
    UNIT FAIL: unit-test failed

UNIT Results (max last 5 lines):
  swift_storage_utils 113 10 91% 253-254, 259-266
  TOTAL 238 37 84%
  Ran 33 tests in 1.198s
  FAILED (errors=3, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/8955704/
Build: http://10.98.191.181:8080/job/charm_unit_test/821/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_amulet_test #366 trusty-swift-storage for brad-marshall mp241483
    AMULET FAIL: amulet-test failed

AMULET Results (max last 5 lines):
  juju-test.conductor DEBUG : Tearing down osci-sv07 juju environment
  juju-test.conductor DEBUG : Calling "juju destroy-environment -y osci-sv07"
  juju-test INFO : Results: 1 passed, 2 failed, 0 errored
  ERROR subprocess encountered error code 2
  make: *** [test] Error 2

Full amulet test output: http://paste.ubuntu.com/8955844/
Build: http://10.98.191.181:8080/job/charm_amulet_test/366/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_lint_check #1241 trusty-swift-storage for brad-marshall mp241483
    LINT FAIL: lint-test failed

LINT Results (max last 5 lines):
  hooks/swift_storage_hooks.py:165:80: E501 line too long (90 > 79 characters)
  hooks/swift_storage_hooks.py:174:22: E251 unexpected spaces around keyword / parameter equals
  hooks/swift_storage_hooks.py:174:24: E251 unexpected spaces around keyword / parameter equals
  hooks/swift_storage_utils.py:267:1: W391 blank line at end of file
  make: *** [lint] Error 1

Full lint test output: http://paste.ubuntu.com/9281254/
Build: http://10.98.191.181:8080/job/charm_lint_check/1241/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_unit_test #1075 trusty-swift-storage for brad-marshall mp241483
    UNIT FAIL: unit-test failed

UNIT Results (max last 5 lines):
  swift_storage_utils 113 10 91% 253-254, 259-266
  TOTAL 238 37 84%
  Ran 33 tests in 1.187s
  FAILED (errors=3, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9281255/
Build: http://10.98.191.181:8080/job/charm_unit_test/1075/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_amulet_test #544 trusty-swift-storage for brad-marshall mp241483
    AMULET FAIL: amulet-test failed

AMULET Results (max last 5 lines):
  WARNING cannot delete security group "juju-osci-sv05". Used by another environment?
  WARNING cannot delete security group "juju-osci-sv05-0". Used by another environment?
  juju-test INFO : Results: 1 passed, 2 failed, 0 errored
  ERROR subprocess encountered error code 2
  make: *** [test] Error 2

Full amulet test output: http://paste.ubuntu.com/9281356/
Build: http://10.98.191.181:8080/job/charm_amulet_test/544/

Revision history for this message
Liam Young (gnuoy) wrote :

Thanks for this branch. I've taken it and fixed up a few minor bits and proposed it against the 'next' branch. It has now landed into 'next' and will promulgate to staging at the end of the month as part of the 15.01 openstack charm release.

review: Disapprove

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers-hooks.yaml'
2--- charm-helpers-hooks.yaml 2014-09-23 10:22:22 +0000
3+++ charm-helpers-hooks.yaml 2014-11-12 04:10:38 +0000
4@@ -10,3 +10,4 @@
5 - cluster
6 - payload.execd
7 - contrib.network.ip
8+ - contrib.charmsupport
9
10=== modified file 'config.yaml'
11--- config.yaml 2014-10-06 21:45:39 +0000
12+++ config.yaml 2014-11-12 04:10:38 +0000
13@@ -95,3 +95,17 @@
14 type: int
15 description: |
16 Number of replication workers to spawn.
17+ nagios-check-params:
18+ default: "-m -r 60 180 10 20"
19+ type: string
20+ description: String appended to nagios check
21+ nagios_context:
22+ default: "juju"
23+ type: string
24+ description: |
25+ Used by the nrpe-external-master subordinate charm.
26+ A string that will be prepended to instance name to set the host name
27+ in nagios. So for instance the hostname would be something like:
28+ juju-myservice-0
29+ If you're running multiple environments with the same services in them
30+ this allows you to differentiate between them.
31
32=== added directory 'files'
33=== added directory 'files/nrpe-external-master'
34=== added file 'files/nrpe-external-master/check_swift_service'
35--- files/nrpe-external-master/check_swift_service 1970-01-01 00:00:00 +0000
36+++ files/nrpe-external-master/check_swift_service 2014-11-12 04:10:38 +0000
37@@ -0,0 +1,25 @@
38+#!/bin/sh
39+
40+#
41+# check_swift_service --- exactly what it says on the tin
42+#
43+# Copyright 2013 Canonical Ltd.
44+#
45+# Authors:
46+# Paul Collins <paul.collins@canonical.com>
47+#
48+
49+STATUS=$(sudo -u swift swift-init status "$1" 2>&1)
50+
51+case $? in
52+ 0)
53+ echo "OK: ${STATUS}"
54+ exit 0
55+ ;;
56+ *)
57+ echo "CRITICAL: ${STATUS}"
58+ exit 2
59+ ;;
60+esac
61+
62+exit 1
63
64=== added file 'files/nrpe-external-master/check_swift_storage.py'
65--- files/nrpe-external-master/check_swift_storage.py 1970-01-01 00:00:00 +0000
66+++ files/nrpe-external-master/check_swift_storage.py 2014-11-12 04:10:38 +0000
67@@ -0,0 +1,136 @@
68+#!/usr/bin/env python
69+
70+# Copyright (C) 2014 Canonical
71+# All Rights Reserved
72+# Author: Jacek Nykis
73+
74+import sys
75+import json
76+import urllib2
77+import argparse
78+import hashlib
79+import datetime
80+
81+STATUS_OK = 0
82+STATUS_WARN = 1
83+STATUS_CRIT = 2
84+STATUS_UNKNOWN = 3
85+
86+
87+def generate_md5(filename):
88+ with open(filename, 'rb') as f:
89+ md5 = hashlib.md5()
90+ buffer = f.read(2 ** 20)
91+ while buffer:
92+ md5.update(buffer)
93+ buffer = f.read(2 ** 20)
94+ return md5.hexdigest()
95+
96+
97+def check_md5(base_url):
98+ url = base_url + "ringmd5"
99+ ringfiles = ["/etc/swift/object.ring.gz",
100+ "/etc/swift/account.ring.gz",
101+ "/etc/swift/container.ring.gz"]
102+ results = []
103+ try:
104+ data = urllib2.urlopen(url).read()
105+ j = json.loads(data)
106+ except urllib2.URLError:
107+ return [(STATUS_UNKNOWN, "Can't open url: {}".format(url))]
108+ except ValueError:
109+ return [(STATUS_UNKNOWN, "Can't parse status data")]
110+
111+ for ringfile in ringfiles:
112+ try:
113+ if generate_md5(ringfile) != j[ringfile]:
114+ results.append((STATUS_CRIT,
115+ "Ringfile {} MD5 sum mismatch".format(ringfile)))
116+ except IOError:
117+ results.append(
118+ (STATUS_UNKNOWN, "Can't open ringfile {}".format(ringfile)))
119+ if results:
120+ return results
121+ else:
122+ return [(STATUS_OK, "OK")]
123+
124+
125+def check_replication(base_url, limits):
126+ types = ["account", "object", "container"]
127+ results = []
128+ for repl in types:
129+ url = base_url + "replication/" + repl
130+ try:
131+ data = urllib2.urlopen(url).read()
132+ j = json.loads(data)
133+ except urllib2.URLError:
134+ results.append((STATUS_UNKNOWN, "Can't open url: {}".format(url)))
135+ continue
136+ except ValueError:
137+ results.append((STATUS_UNKNOWN, "Can't parse status data"))
138+ continue
139+
140+ if "object_replication_last" in j:
141+ repl_last = datetime.datetime.fromtimestamp(j["object_replication_last"])
142+ else:
143+ repl_last = datetime.datetime.fromtimestamp(j["replication_last"])
144+ delta = datetime.datetime.now() - repl_last
145+ if delta.seconds >= limits[1]:
146+ results.append((STATUS_CRIT,
147+ "'{}' replication lag is {} seconds".format(repl, delta.seconds)))
148+ elif delta.seconds >= limits[0]:
149+ results.append((STATUS_WARN,
150+ "'{}' replication lag is {} seconds".format(repl, delta.seconds)))
151+ if "replication_stats" in j:
152+ errors = j["replication_stats"]["failure"]
153+ if errors >= limits[3]:
154+ results.append(
155+ (STATUS_CRIT, "{} replication failures".format(errors)))
156+ elif errors >= limits[2]:
157+ results.append(
158+ (STATUS_WARN, "{} replication failures".format(errors)))
159+ if results:
160+ return results
161+ else:
162+ return [(STATUS_OK, "OK")]
163+
164+
165+if __name__ == '__main__':
166+ parser = argparse.ArgumentParser(description='Check swift-storage health')
167+ parser.add_argument('-H', '--host', dest='host', default='localhost',
168+ help='Hostname to query')
169+ parser.add_argument('-p', '--port', dest='port', default='6000',
170+ type=int, help='Port number')
171+ parser.add_argument('-r', '--replication', dest='check_replication',
172+ type=int, nargs=4, help='Check replication status',
173+ metavar=('lag_warn', 'lag_crit', 'failures_warn', 'failures_crit'))
174+ parser.add_argument('-m', '--md5', dest='check_md5', action='store_true',
175+ help='Compare server rings md5sum with local copy')
176+ args = parser.parse_args()
177+
178+ if not args.check_replication and not args.check_md5:
179+ print "You must use -r or -m switch"
180+ sys.exit(STATUS_UNKNOWN)
181+
182+ base_url = "http://{}:{}/recon/".format(args.host, args.port)
183+ results = []
184+ if args.check_replication:
185+ results.extend(check_replication(base_url, args.check_replication))
186+ if args.check_md5:
187+ results.extend(check_md5(base_url))
188+
189+ crits = ';'.join([i[1] for i in results if i[0] == STATUS_CRIT])
190+ warns = ';'.join([i[1] for i in results if i[0] == STATUS_WARN])
191+ unknowns = ';'.join([i[1] for i in results if i[0] == STATUS_UNKNOWN])
192+ if crits:
193+ print "CRITICAL: " + crits
194+ sys.exit(STATUS_CRIT)
195+ elif warns:
196+ print "WARNING: " + warns
197+ sys.exit(STATUS_WARN)
198+ elif unknowns:
199+ print "UNKNOWN: " + unknowns
200+ sys.exit(STATUS_UNKNOWN)
201+ else:
202+ print "OK"
203+ sys.exit(0)
204
205=== added directory 'files/sudo'
206=== added file 'files/sudo/swift-storage'
207--- files/sudo/swift-storage 1970-01-01 00:00:00 +0000
208+++ files/sudo/swift-storage 2014-11-12 04:10:38 +0000
209@@ -0,0 +1,1 @@
210+nagios ALL=(swift) NOPASSWD:/usr/bin/swift-init status *
211
212=== added directory 'hooks/charmhelpers/contrib/charmsupport'
213=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
214=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
215--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
216+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2014-11-12 04:10:38 +0000
217@@ -0,0 +1,219 @@
218+"""Compatibility with the nrpe-external-master charm"""
219+# Copyright 2012 Canonical Ltd.
220+#
221+# Authors:
222+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
223+
224+import subprocess
225+import pwd
226+import grp
227+import os
228+import re
229+import shlex
230+import yaml
231+
232+from charmhelpers.core.hookenv import (
233+ config,
234+ local_unit,
235+ log,
236+ relation_ids,
237+ relation_set,
238+)
239+
240+from charmhelpers.core.host import service
241+
242+# This module adds compatibility with the nrpe-external-master and plain nrpe
243+# subordinate charms. To use it in your charm:
244+#
245+# 1. Update metadata.yaml
246+#
247+# provides:
248+# (...)
249+# nrpe-external-master:
250+# interface: nrpe-external-master
251+# scope: container
252+#
253+# and/or
254+#
255+# provides:
256+# (...)
257+# local-monitors:
258+# interface: local-monitors
259+# scope: container
260+
261+#
262+# 2. Add the following to config.yaml
263+#
264+# nagios_context:
265+# default: "juju"
266+# type: string
267+# description: |
268+# Used by the nrpe subordinate charms.
269+# A string that will be prepended to instance name to set the host name
270+# in nagios. So for instance the hostname would be something like:
271+# juju-myservice-0
272+# If you're running multiple environments with the same services in them
273+# this allows you to differentiate between them.
274+#
275+# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
276+#
277+# 4. Update your hooks.py with something like this:
278+#
279+# from charmsupport.nrpe import NRPE
280+# (...)
281+# def update_nrpe_config():
282+# nrpe_compat = NRPE()
283+# nrpe_compat.add_check(
284+# shortname = "myservice",
285+# description = "Check MyService",
286+# check_cmd = "check_http -w 2 -c 10 http://localhost"
287+# )
288+# nrpe_compat.add_check(
289+# "myservice_other",
290+# "Check for widget failures",
291+# check_cmd = "/srv/myapp/scripts/widget_check"
292+# )
293+# nrpe_compat.write()
294+#
295+# def config_changed():
296+# (...)
297+# update_nrpe_config()
298+#
299+# def nrpe_external_master_relation_changed():
300+# update_nrpe_config()
301+#
302+# def local_monitors_relation_changed():
303+# update_nrpe_config()
304+#
305+# 5. ln -s hooks.py nrpe-external-master-relation-changed
306+# ln -s hooks.py local-monitors-relation-changed
307+
308+
309+class CheckException(Exception):
310+ pass
311+
312+
313+class Check(object):
314+ shortname_re = '[A-Za-z0-9-_]+$'
315+ service_template = ("""
316+#---------------------------------------------------
317+# This file is Juju managed
318+#---------------------------------------------------
319+define service {{
320+ use active-service
321+ host_name {nagios_hostname}
322+ service_description {nagios_hostname}[{shortname}] """
323+ """{description}
324+ check_command check_nrpe!{command}
325+ servicegroups {nagios_servicegroup}
326+}}
327+""")
328+
329+ def __init__(self, shortname, description, check_cmd):
330+ super(Check, self).__init__()
331+ # XXX: could be better to calculate this from the service name
332+ if not re.match(self.shortname_re, shortname):
333+ raise CheckException("shortname must match {}".format(
334+ Check.shortname_re))
335+ self.shortname = shortname
336+ self.command = "check_{}".format(shortname)
337+ # Note: a set of invalid characters is defined by the
338+ # Nagios server config
339+ # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
340+ self.description = description
341+ self.check_cmd = self._locate_cmd(check_cmd)
342+
343+ def _locate_cmd(self, check_cmd):
344+ search_path = (
345+ '/usr/lib/nagios/plugins',
346+ '/usr/local/lib/nagios/plugins',
347+ )
348+ parts = shlex.split(check_cmd)
349+ for path in search_path:
350+ if os.path.exists(os.path.join(path, parts[0])):
351+ command = os.path.join(path, parts[0])
352+ if len(parts) > 1:
353+ command += " " + " ".join(parts[1:])
354+ return command
355+ log('Check command not found: {}'.format(parts[0]))
356+ return ''
357+
358+ def write(self, nagios_context, hostname):
359+ nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
360+ self.command)
361+ with open(nrpe_check_file, 'w') as nrpe_check_config:
362+ nrpe_check_config.write("# check {}\n".format(self.shortname))
363+ nrpe_check_config.write("command[{}]={}\n".format(
364+ self.command, self.check_cmd))
365+
366+ if not os.path.exists(NRPE.nagios_exportdir):
367+ log('Not writing service config as {} is not accessible'.format(
368+ NRPE.nagios_exportdir))
369+ else:
370+ self.write_service_config(nagios_context, hostname)
371+
372+ def write_service_config(self, nagios_context, hostname):
373+ for f in os.listdir(NRPE.nagios_exportdir):
374+ if re.search('.*{}.cfg'.format(self.command), f):
375+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
376+
377+ templ_vars = {
378+ 'nagios_hostname': hostname,
379+ 'nagios_servicegroup': nagios_context,
380+ 'description': self.description,
381+ 'shortname': self.shortname,
382+ 'command': self.command,
383+ }
384+ nrpe_service_text = Check.service_template.format(**templ_vars)
385+ nrpe_service_file = '{}/service__{}_{}.cfg'.format(
386+ NRPE.nagios_exportdir, hostname, self.command)
387+ with open(nrpe_service_file, 'w') as nrpe_service_config:
388+ nrpe_service_config.write(str(nrpe_service_text))
389+
390+ def run(self):
391+ subprocess.call(self.check_cmd)
392+
393+
394+class NRPE(object):
395+ nagios_logdir = '/var/log/nagios'
396+ nagios_exportdir = '/var/lib/nagios/export'
397+ nrpe_confdir = '/etc/nagios/nrpe.d'
398+
399+ def __init__(self, hostname=None):
400+ super(NRPE, self).__init__()
401+ self.config = config()
402+ self.nagios_context = self.config['nagios_context']
403+ self.unit_name = local_unit().replace('/', '-')
404+ if hostname:
405+ self.hostname = hostname
406+ else:
407+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
408+ self.checks = []
409+
410+ def add_check(self, *args, **kwargs):
411+ self.checks.append(Check(*args, **kwargs))
412+
413+ def write(self):
414+ try:
415+ nagios_uid = pwd.getpwnam('nagios').pw_uid
416+ nagios_gid = grp.getgrnam('nagios').gr_gid
417+ except:
418+ log("Nagios user not set up, nrpe checks not updated")
419+ return
420+
421+ if not os.path.exists(NRPE.nagios_logdir):
422+ os.mkdir(NRPE.nagios_logdir)
423+ os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
424+
425+ nrpe_monitors = {}
426+ monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
427+ for nrpecheck in self.checks:
428+ nrpecheck.write(self.nagios_context, self.hostname)
429+ nrpe_monitors[nrpecheck.shortname] = {
430+ "command": nrpecheck.command,
431+ }
432+
433+ service('restart', 'nagios-nrpe-server')
434+
435+ for rid in relation_ids("local-monitors"):
436+ relation_set(relation_id=rid, monitors=yaml.dump(monitors))
437
438=== added file 'hooks/charmhelpers/contrib/charmsupport/rsync.py'
439--- hooks/charmhelpers/contrib/charmsupport/rsync.py 1970-01-01 00:00:00 +0000
440+++ hooks/charmhelpers/contrib/charmsupport/rsync.py 2014-11-12 04:10:38 +0000
441@@ -0,0 +1,32 @@
442+"""
443+Support for rsyncd.conf and using fragments dropped inside /etc/rsync-juju.d
444+"""
445+import os
446+
447+from charmhelpers.core.host import (
448+ mkdir,
449+)
450+
451+def setup_rsync():
452+ '''
453+ Ensure all directories required for rsync exist with correct permissions.
454+ '''
455+ root_dirs = [
456+ '/etc/rsync-juju.d',
457+ ]
458+ [mkdir(d, owner='root', group='root') for d in root_dirs
459+ if not os.path.isdir(d)]
460+
461+ rsyncd_base = """uid = nobody
462+gid = nogroup
463+pid file = /var/run/rsyncd.pid
464+syslog facility = daemon
465+socket options = SO_KEEPALIVE
466+
467+&include /etc/rsync-juju.d
468+"""
469+
470+ f = open('/etc/rsyncd.conf','w')
471+ f.write(rsyncd_base)
472+ f.close()
473+
474
475=== added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py'
476--- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
477+++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2014-11-12 04:10:38 +0000
478@@ -0,0 +1,156 @@
479+'''
480+Functions for managing volumes in juju units. One volume is supported per unit.
481+Subordinates may have their own storage, provided it is on its own partition.
482+
483+Configuration stanzas:
484+ volume-ephemeral:
485+ type: boolean
486+ default: true
487+ description: >
488+ If false, a volume is mounted as sepecified in "volume-map"
489+ If true, ephemeral storage will be used, meaning that log data
490+ will only exist as long as the machine. YOU HAVE BEEN WARNED.
491+ volume-map:
492+ type: string
493+ default: {}
494+ description: >
495+ YAML map of units to device names, e.g:
496+ "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
497+ Service units will raise a configure-error if volume-ephemeral
498+ is 'true' and no volume-map value is set. Use 'juju set' to set a
499+ value and 'juju resolved' to complete configuration.
500+
501+Usage:
502+ from charmsupport.volumes import configure_volume, VolumeConfigurationError
503+ from charmsupport.hookenv import log, ERROR
504+ def post_mount_hook():
505+ stop_service('myservice')
506+ def post_mount_hook():
507+ start_service('myservice')
508+
509+ if __name__ == '__main__':
510+ try:
511+ configure_volume(before_change=pre_mount_hook,
512+ after_change=post_mount_hook)
513+ except VolumeConfigurationError:
514+ log('Storage could not be configured', ERROR)
515+'''
516+
517+# XXX: Known limitations
518+# - fstab is neither consulted nor updated
519+
520+import os
521+from charmhelpers.core import hookenv
522+from charmhelpers.core import host
523+import yaml
524+
525+
526+MOUNT_BASE = '/srv/juju/volumes'
527+
528+
529+class VolumeConfigurationError(Exception):
530+ '''Volume configuration data is missing or invalid'''
531+ pass
532+
533+
534+def get_config():
535+ '''Gather and sanity-check volume configuration data'''
536+ volume_config = {}
537+ config = hookenv.config()
538+
539+ errors = False
540+
541+ if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
542+ volume_config['ephemeral'] = True
543+ else:
544+ volume_config['ephemeral'] = False
545+
546+ try:
547+ volume_map = yaml.safe_load(config.get('volume-map', '{}'))
548+ except yaml.YAMLError as e:
549+ hookenv.log("Error parsing YAML volume-map: {}".format(e),
550+ hookenv.ERROR)
551+ errors = True
552+ if volume_map is None:
553+ # probably an empty string
554+ volume_map = {}
555+ elif not isinstance(volume_map, dict):
556+ hookenv.log("Volume-map should be a dictionary, not {}".format(
557+ type(volume_map)))
558+ errors = True
559+
560+ volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
561+ if volume_config['device'] and volume_config['ephemeral']:
562+ # asked for ephemeral storage but also defined a volume ID
563+ hookenv.log('A volume is defined for this unit, but ephemeral '
564+ 'storage was requested', hookenv.ERROR)
565+ errors = True
566+ elif not volume_config['device'] and not volume_config['ephemeral']:
567+ # asked for permanent storage but did not define volume ID
568+ hookenv.log('Ephemeral storage was requested, but there is no volume '
569+ 'defined for this unit.', hookenv.ERROR)
570+ errors = True
571+
572+ unit_mount_name = hookenv.local_unit().replace('/', '-')
573+ volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
574+
575+ if errors:
576+ return None
577+ return volume_config
578+
579+
580+def mount_volume(config):
581+ if os.path.exists(config['mountpoint']):
582+ if not os.path.isdir(config['mountpoint']):
583+ hookenv.log('Not a directory: {}'.format(config['mountpoint']))
584+ raise VolumeConfigurationError()
585+ else:
586+ host.mkdir(config['mountpoint'])
587+ if os.path.ismount(config['mountpoint']):
588+ unmount_volume(config)
589+ if not host.mount(config['device'], config['mountpoint'], persist=True):
590+ raise VolumeConfigurationError()
591+
592+
593+def unmount_volume(config):
594+ if os.path.ismount(config['mountpoint']):
595+ if not host.umount(config['mountpoint'], persist=True):
596+ raise VolumeConfigurationError()
597+
598+
599+def managed_mounts():
600+ '''List of all mounted managed volumes'''
601+ return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
602+
603+
604+def configure_volume(before_change=lambda: None, after_change=lambda: None):
605+ '''Set up storage (or don't) according to the charm's volume configuration.
606+ Returns the mount point or "ephemeral". before_change and after_change
607+ are optional functions to be called if the volume configuration changes.
608+ '''
609+
610+ config = get_config()
611+ if not config:
612+ hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
613+ raise VolumeConfigurationError()
614+
615+ if config['ephemeral']:
616+ if os.path.ismount(config['mountpoint']):
617+ before_change()
618+ unmount_volume(config)
619+ after_change()
620+ return 'ephemeral'
621+ else:
622+ # persistent storage
623+ if os.path.ismount(config['mountpoint']):
624+ mounts = dict(managed_mounts())
625+ if mounts.get(config['mountpoint']) != config['device']:
626+ before_change()
627+ unmount_volume(config)
628+ mount_volume(config)
629+ after_change()
630+ else:
631+ before_change()
632+ mount_volume(config)
633+ after_change()
634+ return config['mountpoint']
635
636=== added symlink 'hooks/nrpe-external-master-relation-changed'
637=== target is u'swift_storage_hooks.py'
638=== added symlink 'hooks/nrpe-external-master-relation-joined'
639=== target is u'swift_storage_hooks.py'
640=== modified file 'hooks/swift_storage_hooks.py'
641--- hooks/swift_storage_hooks.py 2014-10-07 08:37:20 +0000
642+++ hooks/swift_storage_hooks.py 2014-11-12 04:10:38 +0000
643@@ -22,6 +22,8 @@
644 log,
645 relation_get,
646 relation_set,
647+ relations_of_type,
648+ local_unit,
649 )
650
651 from charmhelpers.fetch import (
652@@ -29,7 +31,7 @@
653 apt_update,
654 filter_installed_packages
655 )
656-from charmhelpers.core.host import restart_on_change
657+from charmhelpers.core.host import restart_on_change, rsync
658 from charmhelpers.payload.execd import execd_preinstall
659
660 from charmhelpers.contrib.openstack.utils import (
661@@ -39,9 +41,29 @@
662 from charmhelpers.contrib.network.ip import (
663 get_ipv6_addr
664 )
665+from swift_storage_utils import (
666+ PACKAGES,
667+ RESTART_MAP,
668+ SWIFT_SVCS,
669+ determine_block_devices,
670+ do_openstack_upgrade,
671+ ensure_swift_directories,
672+ fetch_swift_rings,
673+ register_configs,
674+ save_script_rc,
675+ setup_storage,
676+ concat_rsync_fragments,
677+)
678+from charmhelpers.contrib.charmsupport.nrpe import NRPE
679+
680+from charmhelpers.contrib.charmsupport.rsync import setup_rsync
681+
682+from distutils.dir_util import mkpath
683
684 hooks = Hooks()
685 CONFIGS = register_configs()
686+NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
687+SUDOERS_D = '/etc/sudoers.d'
688
689
690 @hooks.hook()
691@@ -60,15 +82,22 @@
692 if config('prefer-ipv6'):
693 assert_charm_supports_ipv6()
694
695+ ensure_swift_directories()
696+ setup_rsync()
697+
698 if openstack_upgrade_available('swift'):
699 do_openstack_upgrade(configs=CONFIGS)
700 CONFIGS.write_all()
701+
702 save_script_rc()
703+ if relations_of_type('nrpe-external-master'):
704+ update_nrpe_config()
705
706
707 @hooks.hook('upgrade-charm')
708 def upgrade_charm():
709 apt_install(filter_installed_packages(PACKAGES), fatal=True)
710+ update_nrpe_config()
711
712
713 @hooks.hook()
714@@ -100,6 +129,53 @@
715 fetch_swift_rings(rings_url)
716
717
718+@hooks.hook('nrpe-external-master-relation-joined')
719+@hooks.hook('nrpe-external-master-relation-changed')
720+def update_nrpe_config():
721+ log('Refreshing nrpe checks')
722+ if not os.path.exists(NAGIOS_PLUGINS):
723+ mkpath(NAGIOS_PLUGINS)
724+ rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nrpe-external-master',
725+ 'check_swift_storage.py'),
726+ os.path.join(NAGIOS_PLUGINS, 'check_swift_storage.py'))
727+ rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nrpe-external-master',
728+ 'check_swift_service'),
729+ os.path.join(NAGIOS_PLUGINS, 'check_swift_service'))
730+ rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'sudo',
731+ 'swift-storage'),
732+ os.path.join(SUDOERS_D, 'swift-storage'))
733+ # Find out if nrpe set nagios_hostname
734+ hostname = None
735+ host_context = None
736+ for rel in relations_of_type('nrpe-external-master'):
737+ if 'nagios_hostname' in rel:
738+ hostname = rel['nagios_hostname']
739+ host_context = rel['nagios_host_context']
740+ break
741+ nrpe = NRPE(hostname=hostname)
742+
743+ if host_context:
744+ current_unit = "%s:%s" % (host_context, local_unit())
745+ else:
746+ current_unit = local_unit()
747+
748+ # check the rings and replication
749+ nrpe.add_check(
750+ shortname='swift_storage',
751+ description='Check swift storage ring hashes and replication {%s}' % current_unit,
752+ check_cmd='check_swift_storage.py {}'.format(
753+ config('nagios-check-params'))
754+ )
755+ # check services are running
756+ for service in SWIFT_SVCS:
757+ nrpe.add_check(
758+ shortname=service,
759+ description='service {%s}' % current_unit,
760+ check_cmd = 'check_swift_service %s' % service,
761+ )
762+ nrpe.write()
763+
764+
765 def main():
766 try:
767 hooks.execute(sys.argv)
768
769=== modified file 'hooks/swift_storage_utils.py'
770--- hooks/swift_storage_utils.py 2014-10-24 14:29:07 +0000
771+++ hooks/swift_storage_utils.py 2014-11-12 04:10:38 +0000
772@@ -75,8 +75,24 @@
773 'swift-object-updater', 'swift-object-replicator'
774 ]
775
776+SWIFT_SVCS = [
777+ 'account-auditor',
778+ 'account-reaper',
779+ 'account-replicator',
780+ 'account-server',
781+ 'container-auditor',
782+ 'container-replicator',
783+ 'container-server',
784+ 'container-sync',
785+ 'container-updater',
786+ 'object-auditor',
787+ 'object-replicator',
788+ 'object-server',
789+ 'object-updater',
790+ ]
791+
792 RESTART_MAP = {
793- '/etc/rsyncd.conf': ['rsync'],
794+ '/etc/rsync-juju.d/050-swift-storage.conf': ['rsync'],
795 '/etc/swift/account-server.conf': ACCOUNT_SVCS,
796 '/etc/swift/container-server.conf': CONTAINER_SVCS,
797 '/etc/swift/object-server.conf': OBJECT_SVCS,
798@@ -104,8 +120,8 @@
799 openstack_release=release)
800 configs.register('/etc/swift/swift.conf',
801 [SwiftStorageContext()])
802- configs.register('/etc/rsyncd.conf',
803- [RsyncContext()])
804+ configs.register('/etc/rsync-juju.d/050-swift-storage.conf',
805+ [RsyncContext(), SwiftStorageServerContext()])
806 for server in ['account', 'object', 'container']:
807 configs.register('/etc/swift/%s-server.conf' % server,
808 [SwiftStorageServerContext(),
809@@ -237,3 +253,15 @@
810 if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty":
811 raise Exception("IPv6 is not supported in the charms for Ubuntu "
812 "versions less than Trusty 14.04")
813+
814+
815+def concat_rsync_fragments():
816+ log('Concatenating rsyncd.d fragments')
817+ rsyncd_dir = '/etc/rsyncd.d'
818+ rsyncd_conf = ""
819+ for filename in sorted(os.listdir(rsyncd_dir)):
820+ with open(os.path.join(rsyncd_dir, filename), 'r') as fragment:
821+ rsyncd_conf += fragment.read()
822+ with open('/etc/rsyncd.conf', 'w') as f:
823+ f.write(rsyncd_conf)
824+
825
826=== modified file 'metadata.yaml'
827--- metadata.yaml 2013-07-11 20:45:19 +0000
828+++ metadata.yaml 2014-11-12 04:10:38 +0000
829@@ -6,5 +6,8 @@
830 categories:
831 - file-servers
832 provides:
833+ nrpe-external-master:
834+ interface: nrpe-external-master
835+ scope: container
836 swift-storage:
837 interface: swift
838
839=== added file 'templates/001-baseconfig'
840--- templates/001-baseconfig 1970-01-01 00:00:00 +0000
841+++ templates/001-baseconfig 2014-11-12 04:10:38 +0000
842@@ -0,0 +1,6 @@
843+uid = nobody
844+gid = nogroup
845+pid file = /var/run/rsyncd.pid
846+syslog facility = daemon
847+socket options = SO_KEEPALIVE
848+
849
850=== added file 'templates/050-swift-storage.conf'
851--- templates/050-swift-storage.conf 1970-01-01 00:00:00 +0000
852+++ templates/050-swift-storage.conf 2014-11-12 04:10:38 +0000
853@@ -0,0 +1,24 @@
854+[account]
855+uid = swift
856+guid = swift
857+max connections = {{ account_max_connections }}
858+path = /srv/node/
859+read only = false
860+lock file = /var/lock/account.lock
861+
862+[container]
863+uid = swift
864+guid = swift
865+max connections = {{ container_max_connections }}
866+path = /srv/node/
867+read only = false
868+lock file = /var/lock/container.lock
869+
870+[object]
871+uid = swift
872+guid = swift
873+max connections = {{ object_max_connections }}
874+path = /srv/node/
875+read only = false
876+lock file = /var/lock/object.lock
877+
878
879=== removed file 'templates/rsyncd.conf'
880--- templates/rsyncd.conf 2014-09-12 09:17:27 +0000
881+++ templates/rsyncd.conf 1970-01-01 00:00:00 +0000
882@@ -1,23 +0,0 @@
883-uid = swift
884-gid = swift
885-log file = /var/log/rsyncd.log
886-pid file = /var/run/rsyncd.pid
887-address = {{ local_ip }}
888-
889-[account]
890-max connections = {{ account_max_connections }}
891-path = /srv/node/
892-read only = false
893-lock file = /var/lock/account.lock
894-
895-[container]
896-max connections = {{ container_max_connections }}
897-path = /srv/node/
898-read only = false
899-lock file = /var/lock/container.lock
900-
901-[object]
902-max connections = {{ object_max_connections }}
903-path = /srv/node/
904-read only = false
905-lock file = /var/lock/object.lock
906
907=== modified file 'unit_tests/test_swift_storage_relations.py'
908--- unit_tests/test_swift_storage_relations.py 2014-10-07 08:37:20 +0000
909+++ unit_tests/test_swift_storage_relations.py 2014-11-12 04:10:38 +0000
910@@ -1,6 +1,6 @@
911 from mock import patch, MagicMock
912
913-from test_utils import CharmTestCase
914+from test_utils import CharmTestCase, patch_open
915
916 import swift_storage_utils as utils
917
918@@ -21,6 +21,7 @@
919 'log',
920 'relation_set',
921 'relation_get',
922+ 'relations_of_type',
923 # charmhelpers.core.host
924 'apt_update',
925 'apt_install',
926@@ -62,13 +63,19 @@
927
928 def test_config_changed_no_upgrade_available(self):
929 self.openstack_upgrade_available.return_value = False
930- hooks.config_changed()
931+ self.relations_of_type.return_value = False
932+ with patch_open() as (_open, _file):
933+ _file.read.return_value = "foo"
934+ hooks.config_changed()
935 self.assertFalse(self.do_openstack_upgrade.called)
936 self.assertTrue(self.CONFIGS.write_all.called)
937
938 def test_config_changed_upgrade_available(self):
939 self.openstack_upgrade_available.return_value = True
940- hooks.config_changed()
941+ self.relations_of_type.return_value = False
942+ with patch_open() as (_open, _file):
943+ _file.read.return_value = "foo"
944+ hooks.config_changed()
945 self.assertTrue(self.do_openstack_upgrade.called)
946 self.assertTrue(self.CONFIGS.write_all.called)
947
948
949=== modified file 'unit_tests/test_swift_storage_utils.py'
950--- unit_tests/test_swift_storage_utils.py 2014-10-24 14:33:12 +0000
951+++ unit_tests/test_swift_storage_utils.py 2014-11-12 04:10:38 +0000
952@@ -85,6 +85,7 @@
953 ex_dirs = [
954 call('/etc/swift', owner='swift', group='swift'),
955 call('/var/cache/swift', owner='swift', group='swift'),
956+ call('/etc/rsyncd.d', owner='root', group='root'),
957 call('/srv/node', owner='swift', group='swift')
958 ]
959 self.assertEquals(ex_dirs, self.mkdir.call_args_list)

Subscribers

People subscribed via source and target branches