Merge lp:~brad-marshall/charms/trusty/swift-storage/add-nrpe-checks-fix-rsyncd-conf into lp:~openstack-charmers-archive/charms/trusty/swift-storage/trunk
- Trusty Tahr (14.04)
- add-nrpe-checks-fix-rsyncd-conf
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Disapprove | ||
Review via email: mp+241483@code.launchpad.net |
Commit message
Description of the change
This adds support for nrpe-external-
This relies on my nrpe-external-
Ryan Beisner (1chb1n) wrote : | # |
Ryan Beisner (1chb1n) wrote : | # |
UOSCI bot says:
charm_unit_test #821 trusty-
UNIT FAIL: unit-test failed
UNIT Results (max last 5 lines):
swift_
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://
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
UOSCI bot says:
charm_amulet_test #366 trusty-
AMULET FAIL: amulet-test failed
AMULET Results (max last 5 lines):
juju-
juju-
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://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
UOSCI bot says:
charm_lint_check #1241 trusty-
LINT FAIL: lint-test failed
LINT Results (max last 5 lines):
hooks/
hooks/
hooks/
hooks/
make: *** [lint] Error 1
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
UOSCI bot says:
charm_unit_test #1075 trusty-
UNIT FAIL: unit-test failed
UNIT Results (max last 5 lines):
swift_
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://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
UOSCI bot says:
charm_amulet_test #544 trusty-
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://
Build: http://
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.
Preview Diff
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) |
UOSCI bot says: swift-storage for brad-marshall mp241483
charm_lint_check #986 trusty-
LINT FAIL: lint-test failed
LINT Results (max last 5 lines): swift_storage_ hooks.py: 165:80: E501 line too long (90 > 79 characters) swift_storage_ hooks.py: 174:22: E251 unexpected spaces around keyword / parameter equals swift_storage_ hooks.py: 174:24: E251 unexpected spaces around keyword / parameter equals swift_storage_ utils.py: 267:1: W391 blank line at end of file
hooks/
hooks/
hooks/
hooks/
make: *** [lint] Error 1
Full lint test output: http:// paste.ubuntu. com/8955703/ 10.98.191. 181:8080/ job/charm_ lint_check/ 986/
Build: http://