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