Merge lp:~jacekn/charms/precise/swift-storage/n-e-m-with-concat into lp:~openstack-charmers-archive/charms/trusty/swift-storage/next

Proposed by James Page
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
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.

Description of the change

This change adds nrpe-external-master support to the charm including basic nagios plugin

To post a comment you must log in.
Revision history for this message
James Page (james-page) 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.

review: Needs Information
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers.yaml'
--- charm-helpers.yaml 2014-03-25 17:05:07 +0000
+++ charm-helpers.yaml 2014-06-02 09:35:40 +0000
@@ -9,3 +9,4 @@
9 - apache9 - apache
10 - cluster10 - cluster
11 - payload.execd11 - payload.execd
12 - contrib.charmsupport
1213
=== modified file 'config.yaml'
--- config.yaml 2012-12-19 23:09:13 +0000
+++ config.yaml 2014-06-02 09:35:40 +0000
@@ -49,4 +49,17 @@
49 default: 600249 default: 6002
50 type: int50 type: int
51 description: Listening port of the swift-account-server.51 description: Listening port of the swift-account-server.
5252 nagios-check-params:
53 default: "-m -r 60 180 10 20"
54 type: string
55 description: String appended to nagios check
56 nagios_context:
57 default: "juju"
58 type: string
59 description: |
60 Used by the nrpe-external-master subordinate charm.
61 A string that will be prepended to instance name to set the host name
62 in nagios. So for instance the hostname would be something like:
63 juju-myservice-0
64 If you're running multiple environments with the same services in them
65 this allows you to differentiate between them.
5366
=== added directory 'hooks/charmhelpers/contrib/charmsupport'
=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2014-06-02 09:35:40 +0000
@@ -0,0 +1,219 @@
1"""Compatibility with the nrpe-external-master charm"""
2# Copyright 2012 Canonical Ltd.
3#
4# Authors:
5# Matthew Wedgwood <matthew.wedgwood@canonical.com>
6
7import subprocess
8import pwd
9import grp
10import os
11import re
12import shlex
13import yaml
14
15from charmhelpers.core.hookenv import (
16 config,
17 local_unit,
18 log,
19 relation_ids,
20 relation_set,
21)
22
23from charmhelpers.core.host import service
24
25# This module adds compatibility with the nrpe-external-master and plain nrpe
26# subordinate charms. To use it in your charm:
27#
28# 1. Update metadata.yaml
29#
30# provides:
31# (...)
32# nrpe-external-master:
33# interface: nrpe-external-master
34# scope: container
35#
36# and/or
37#
38# provides:
39# (...)
40# local-monitors:
41# interface: local-monitors
42# scope: container
43
44#
45# 2. Add the following to config.yaml
46#
47# nagios_context:
48# default: "juju"
49# type: string
50# description: |
51# Used by the nrpe subordinate charms.
52# A string that will be prepended to instance name to set the host name
53# in nagios. So for instance the hostname would be something like:
54# juju-myservice-0
55# If you're running multiple environments with the same services in them
56# this allows you to differentiate between them.
57#
58# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
59#
60# 4. Update your hooks.py with something like this:
61#
62# from charmsupport.nrpe import NRPE
63# (...)
64# def update_nrpe_config():
65# nrpe_compat = NRPE()
66# nrpe_compat.add_check(
67# shortname = "myservice",
68# description = "Check MyService",
69# check_cmd = "check_http -w 2 -c 10 http://localhost"
70# )
71# nrpe_compat.add_check(
72# "myservice_other",
73# "Check for widget failures",
74# check_cmd = "/srv/myapp/scripts/widget_check"
75# )
76# nrpe_compat.write()
77#
78# def config_changed():
79# (...)
80# update_nrpe_config()
81#
82# def nrpe_external_master_relation_changed():
83# update_nrpe_config()
84#
85# def local_monitors_relation_changed():
86# update_nrpe_config()
87#
88# 5. ln -s hooks.py nrpe-external-master-relation-changed
89# ln -s hooks.py local-monitors-relation-changed
90
91
92class CheckException(Exception):
93 pass
94
95
96class Check(object):
97 shortname_re = '[A-Za-z0-9-_]+$'
98 service_template = ("""
99#---------------------------------------------------
100# This file is Juju managed
101#---------------------------------------------------
102define service {{
103 use active-service
104 host_name {nagios_hostname}
105 service_description {nagios_hostname}[{shortname}] """
106 """{description}
107 check_command check_nrpe!{command}
108 servicegroups {nagios_servicegroup}
109}}
110""")
111
112 def __init__(self, shortname, description, check_cmd):
113 super(Check, self).__init__()
114 # XXX: could be better to calculate this from the service name
115 if not re.match(self.shortname_re, shortname):
116 raise CheckException("shortname must match {}".format(
117 Check.shortname_re))
118 self.shortname = shortname
119 self.command = "check_{}".format(shortname)
120 # Note: a set of invalid characters is defined by the
121 # Nagios server config
122 # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
123 self.description = description
124 self.check_cmd = self._locate_cmd(check_cmd)
125
126 def _locate_cmd(self, check_cmd):
127 search_path = (
128 '/usr/lib/nagios/plugins',
129 '/usr/local/lib/nagios/plugins',
130 )
131 parts = shlex.split(check_cmd)
132 for path in search_path:
133 if os.path.exists(os.path.join(path, parts[0])):
134 command = os.path.join(path, parts[0])
135 if len(parts) > 1:
136 command += " " + " ".join(parts[1:])
137 return command
138 log('Check command not found: {}'.format(parts[0]))
139 return ''
140
141 def write(self, nagios_context, hostname):
142 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
143 self.command)
144 with open(nrpe_check_file, 'w') as nrpe_check_config:
145 nrpe_check_config.write("# check {}\n".format(self.shortname))
146 nrpe_check_config.write("command[{}]={}\n".format(
147 self.command, self.check_cmd))
148
149 if not os.path.exists(NRPE.nagios_exportdir):
150 log('Not writing service config as {} is not accessible'.format(
151 NRPE.nagios_exportdir))
152 else:
153 self.write_service_config(nagios_context, hostname)
154
155 def write_service_config(self, nagios_context, hostname):
156 for f in os.listdir(NRPE.nagios_exportdir):
157 if re.search('.*{}.cfg'.format(self.command), f):
158 os.remove(os.path.join(NRPE.nagios_exportdir, f))
159
160 templ_vars = {
161 'nagios_hostname': hostname,
162 'nagios_servicegroup': nagios_context,
163 'description': self.description,
164 'shortname': self.shortname,
165 'command': self.command,
166 }
167 nrpe_service_text = Check.service_template.format(**templ_vars)
168 nrpe_service_file = '{}/service__{}_{}.cfg'.format(
169 NRPE.nagios_exportdir, hostname, self.command)
170 with open(nrpe_service_file, 'w') as nrpe_service_config:
171 nrpe_service_config.write(str(nrpe_service_text))
172
173 def run(self):
174 subprocess.call(self.check_cmd)
175
176
177class NRPE(object):
178 nagios_logdir = '/var/log/nagios'
179 nagios_exportdir = '/var/lib/nagios/export'
180 nrpe_confdir = '/etc/nagios/nrpe.d'
181
182 def __init__(self, hostname=None):
183 super(NRPE, self).__init__()
184 self.config = config()
185 self.nagios_context = self.config['nagios_context']
186 self.unit_name = local_unit().replace('/', '-')
187 if hostname:
188 self.hostname = hostname
189 else:
190 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
191 self.checks = []
192
193 def add_check(self, *args, **kwargs):
194 self.checks.append(Check(*args, **kwargs))
195
196 def write(self):
197 try:
198 nagios_uid = pwd.getpwnam('nagios').pw_uid
199 nagios_gid = grp.getgrnam('nagios').gr_gid
200 except:
201 log("Nagios user not set up, nrpe checks not updated")
202 return
203
204 if not os.path.exists(NRPE.nagios_logdir):
205 os.mkdir(NRPE.nagios_logdir)
206 os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
207
208 nrpe_monitors = {}
209 monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
210 for nrpecheck in self.checks:
211 nrpecheck.write(self.nagios_context, self.hostname)
212 nrpe_monitors[nrpecheck.shortname] = {
213 "command": nrpecheck.command,
214 }
215
216 service('restart', 'nagios-nrpe-server')
217
218 for rid in relation_ids("local-monitors"):
219 relation_set(relation_id=rid, monitors=yaml.dump(monitors))
0220
=== added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py'
--- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2014-06-02 09:35:40 +0000
@@ -0,0 +1,156 @@
1'''
2Functions for managing volumes in juju units. One volume is supported per unit.
3Subordinates may have their own storage, provided it is on its own partition.
4
5Configuration stanzas:
6 volume-ephemeral:
7 type: boolean
8 default: true
9 description: >
10 If false, a volume is mounted as sepecified in "volume-map"
11 If true, ephemeral storage will be used, meaning that log data
12 will only exist as long as the machine. YOU HAVE BEEN WARNED.
13 volume-map:
14 type: string
15 default: {}
16 description: >
17 YAML map of units to device names, e.g:
18 "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
19 Service units will raise a configure-error if volume-ephemeral
20 is 'true' and no volume-map value is set. Use 'juju set' to set a
21 value and 'juju resolved' to complete configuration.
22
23Usage:
24 from charmsupport.volumes import configure_volume, VolumeConfigurationError
25 from charmsupport.hookenv import log, ERROR
26 def post_mount_hook():
27 stop_service('myservice')
28 def post_mount_hook():
29 start_service('myservice')
30
31 if __name__ == '__main__':
32 try:
33 configure_volume(before_change=pre_mount_hook,
34 after_change=post_mount_hook)
35 except VolumeConfigurationError:
36 log('Storage could not be configured', ERROR)
37'''
38
39# XXX: Known limitations
40# - fstab is neither consulted nor updated
41
42import os
43from charmhelpers.core import hookenv
44from charmhelpers.core import host
45import yaml
46
47
48MOUNT_BASE = '/srv/juju/volumes'
49
50
51class VolumeConfigurationError(Exception):
52 '''Volume configuration data is missing or invalid'''
53 pass
54
55
56def get_config():
57 '''Gather and sanity-check volume configuration data'''
58 volume_config = {}
59 config = hookenv.config()
60
61 errors = False
62
63 if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
64 volume_config['ephemeral'] = True
65 else:
66 volume_config['ephemeral'] = False
67
68 try:
69 volume_map = yaml.safe_load(config.get('volume-map', '{}'))
70 except yaml.YAMLError as e:
71 hookenv.log("Error parsing YAML volume-map: {}".format(e),
72 hookenv.ERROR)
73 errors = True
74 if volume_map is None:
75 # probably an empty string
76 volume_map = {}
77 elif not isinstance(volume_map, dict):
78 hookenv.log("Volume-map should be a dictionary, not {}".format(
79 type(volume_map)))
80 errors = True
81
82 volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
83 if volume_config['device'] and volume_config['ephemeral']:
84 # asked for ephemeral storage but also defined a volume ID
85 hookenv.log('A volume is defined for this unit, but ephemeral '
86 'storage was requested', hookenv.ERROR)
87 errors = True
88 elif not volume_config['device'] and not volume_config['ephemeral']:
89 # asked for permanent storage but did not define volume ID
90 hookenv.log('Ephemeral storage was requested, but there is no volume '
91 'defined for this unit.', hookenv.ERROR)
92 errors = True
93
94 unit_mount_name = hookenv.local_unit().replace('/', '-')
95 volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
96
97 if errors:
98 return None
99 return volume_config
100
101
102def mount_volume(config):
103 if os.path.exists(config['mountpoint']):
104 if not os.path.isdir(config['mountpoint']):
105 hookenv.log('Not a directory: {}'.format(config['mountpoint']))
106 raise VolumeConfigurationError()
107 else:
108 host.mkdir(config['mountpoint'])
109 if os.path.ismount(config['mountpoint']):
110 unmount_volume(config)
111 if not host.mount(config['device'], config['mountpoint'], persist=True):
112 raise VolumeConfigurationError()
113
114
115def unmount_volume(config):
116 if os.path.ismount(config['mountpoint']):
117 if not host.umount(config['mountpoint'], persist=True):
118 raise VolumeConfigurationError()
119
120
121def managed_mounts():
122 '''List of all mounted managed volumes'''
123 return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
124
125
126def configure_volume(before_change=lambda: None, after_change=lambda: None):
127 '''Set up storage (or don't) according to the charm's volume configuration.
128 Returns the mount point or "ephemeral". before_change and after_change
129 are optional functions to be called if the volume configuration changes.
130 '''
131
132 config = get_config()
133 if not config:
134 hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
135 raise VolumeConfigurationError()
136
137 if config['ephemeral']:
138 if os.path.ismount(config['mountpoint']):
139 before_change()
140 unmount_volume(config)
141 after_change()
142 return 'ephemeral'
143 else:
144 # persistent storage
145 if os.path.ismount(config['mountpoint']):
146 mounts = dict(managed_mounts())
147 if mounts.get(config['mountpoint']) != config['device']:
148 before_change()
149 unmount_volume(config)
150 mount_volume(config)
151 after_change()
152 else:
153 before_change()
154 mount_volume(config)
155 after_change()
156 return config['mountpoint']
0157
=== added symlink 'hooks/nrpe-external-master-relation-changed'
=== target is u'swift_storage_hooks.py'
=== added symlink 'hooks/nrpe-external-master-relation-joined'
=== target is u'swift_storage_hooks.py'
=== modified file 'hooks/swift_storage_hooks.py'
--- hooks/swift_storage_hooks.py 2013-09-27 16:33:06 +0000
+++ hooks/swift_storage_hooks.py 2014-06-02 09:35:40 +0000
@@ -13,6 +13,7 @@
13 register_configs,13 register_configs,
14 save_script_rc,14 save_script_rc,
15 setup_storage,15 setup_storage,
16 concat_rsync_fragments,
16)17)
1718
18from charmhelpers.core.hookenv import (19from charmhelpers.core.hookenv import (
@@ -21,10 +22,11 @@
21 log,22 log,
22 relation_get,23 relation_get,
23 relation_set,24 relation_set,
25 relations_of_type,
24)26)
2527
26from charmhelpers.fetch import apt_install, apt_update28from charmhelpers.fetch import apt_install, apt_update
27from charmhelpers.core.host import restart_on_change29from charmhelpers.core.host import restart_on_change, rsync
28from charmhelpers.payload.execd import execd_preinstall30from charmhelpers.payload.execd import execd_preinstall
2931
30from charmhelpers.contrib.openstack.utils import (32from charmhelpers.contrib.openstack.utils import (
@@ -32,8 +34,11 @@
32 openstack_upgrade_available,34 openstack_upgrade_available,
33)35)
3436
37from charmhelpers.contrib.charmsupport.nrpe import NRPE
38
35hooks = Hooks()39hooks = Hooks()
36CONFIGS = register_configs()40CONFIGS = register_configs()
41NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
3742
3843
39@hooks.hook()44@hooks.hook()
@@ -52,7 +57,22 @@
52 if openstack_upgrade_available('swift'):57 if openstack_upgrade_available('swift'):
53 do_openstack_upgrade(configs=CONFIGS)58 do_openstack_upgrade(configs=CONFIGS)
54 CONFIGS.write_all()59 CONFIGS.write_all()
60
61 # If basenode is not installed and managing rsyncd.conf, replicate
62 # its core functionality. Otherwise concat files
63 if not os.path.exists('/etc/rsyncd.d/001-basenode'):
64 with open('templates/rsyncd.conf') as _in:
65 rsync_header = _in.read()
66 with open('/etc/rsyncd.d/050-swift-storage') as _in:
67 rsync_fragment = _in.read()
68 with open('/etc/rsyncd.conf', 'w') as out:
69 out.write(rsync_header + rsync_fragment)
70 else:
71 concat_rsync_fragments()
72
55 save_script_rc()73 save_script_rc()
74 if relations_of_type('nrpe-external-master'):
75 nrpe_relation()
5676
5777
58@hooks.hook()78@hooks.hook()
@@ -80,6 +100,29 @@
80 fetch_swift_rings(rings_url)100 fetch_swift_rings(rings_url)
81101
82102
103@hooks.hook('nrpe-external-master-relation-joined')
104@hooks.hook('nrpe-external-master-relation-changed')
105def nrpe_relation():
106 log('Refreshing nrpe checks')
107 rsync(os.path.join(os.getenv('CHARM_DIR'), 'scripts',
108 'check_swift_storage.py'),
109 os.path.join(NAGIOS_PLUGINS, 'check_swift_storage.py'))
110 # Find out if nrpe set nagios_hostname
111 hostname = None
112 for rel in relations_of_type('nrpe-external-master'):
113 if 'nagios_hostname' in rel:
114 hostname = rel['nagios_hostname']
115 break
116 nrpe = NRPE(hostname=hostname)
117 nrpe.add_check(
118 shortname='swift_storage',
119 description='Check swift storage',
120 check_cmd='check_swift_storage.py {}'.format(
121 config('nagios-check-params'))
122 )
123 nrpe.write()
124
125
83def main():126def main():
84 try:127 try:
85 hooks.execute(sys.argv)128 hooks.execute(sys.argv)
86129
=== modified file 'hooks/swift_storage_utils.py'
--- hooks/swift_storage_utils.py 2014-04-07 14:50:34 +0000
+++ hooks/swift_storage_utils.py 2014-06-02 09:35:40 +0000
@@ -70,7 +70,7 @@
70]70]
7171
72RESTART_MAP = {72RESTART_MAP = {
73 '/etc/rsyncd.conf': ['rsync'],73 '/etc/rsyncd.d/050-swift-storage': ['rsync'],
74 '/etc/swift/account-server.conf': ACCOUNT_SVCS,74 '/etc/swift/account-server.conf': ACCOUNT_SVCS,
75 '/etc/swift/container-server.conf': CONTAINER_SVCS,75 '/etc/swift/container-server.conf': CONTAINER_SVCS,
76 '/etc/swift/object-server.conf': OBJECT_SVCS,76 '/etc/swift/object-server.conf': OBJECT_SVCS,
@@ -90,6 +90,11 @@
90 ]90 ]
91 [mkdir(d, owner='swift', group='swift') for d in dirs91 [mkdir(d, owner='swift', group='swift') for d in dirs
92 if not os.path.isdir(d)]92 if not os.path.isdir(d)]
93 root_dirs = [
94 '/etc/rsyncd.d',
95 ]
96 [mkdir(d, owner='root', group='root') for d in root_dirs
97 if not os.path.isdir(d)]
9398
9499
95def register_configs():100def register_configs():
@@ -98,7 +103,7 @@
98 openstack_release=release)103 openstack_release=release)
99 configs.register('/etc/swift/swift.conf',104 configs.register('/etc/swift/swift.conf',
100 [SwiftStorageContext()])105 [SwiftStorageContext()])
101 configs.register('/etc/rsyncd.conf',106 configs.register('/etc/rsyncd.d/050-swift-storage',
102 [RsyncContext()])107 [RsyncContext()])
103 for server in ['account', 'object', 'container']:108 for server in ['account', 'object', 'container']:
104 configs.register('/etc/swift/%s-server.conf' % server,109 configs.register('/etc/swift/%s-server.conf' % server,
@@ -209,3 +214,14 @@
209 'OPENSTACK_URL_%s' % svc: url,214 'OPENSTACK_URL_%s' % svc: url,
210 })215 })
211 _save_script_rc(**env_vars)216 _save_script_rc(**env_vars)
217
218
219def concat_rsync_fragments():
220 log('Concatenating rsyncd.d fragments')
221 rsyncd_dir = '/etc/rsyncd.d'
222 rsyncd_conf = ""
223 for filename in sorted(os.listdir(rsyncd_dir)):
224 with open(os.path.join(rsyncd_dir, filename), 'r') as fragment:
225 rsyncd_conf += fragment.read()
226 with open('/etc/rsyncd.conf', 'w') as f:
227 f.write(rsyncd_conf)
212228
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-07-11 20:45:19 +0000
+++ metadata.yaml 2014-06-02 09:35:40 +0000
@@ -8,3 +8,6 @@
8provides:8provides:
9 swift-storage:9 swift-storage:
10 interface: swift10 interface: swift
11 nrpe-external-master:
12 interface: nrpe-external-master
13 scope: container
1114
=== modified file 'revision'
--- revision 2013-07-19 21:13:59 +0000
+++ revision 2014-06-02 09:35:40 +0000
@@ -1,1 +1,1 @@
1901100
2\ No newline at end of file2\ No newline at end of file
33
=== added file 'scripts/check_swift_storage.py'
--- scripts/check_swift_storage.py 1970-01-01 00:00:00 +0000
+++ scripts/check_swift_storage.py 2014-06-02 09:35:40 +0000
@@ -0,0 +1,136 @@
1#!/usr/bin/env python
2
3# Copyright (C) 2014 Canonical
4# All Rights Reserved
5# Author: Jacek Nykis
6
7import sys
8import json
9import urllib2
10import argparse
11import hashlib
12import datetime
13
14STATUS_OK = 0
15STATUS_WARN = 1
16STATUS_CRIT = 2
17STATUS_UNKNOWN = 3
18
19
20def generate_md5(filename):
21 with open(filename, 'rb') as f:
22 md5 = hashlib.md5()
23 buffer = f.read(2 ** 20)
24 while buffer:
25 md5.update(buffer)
26 buffer = f.read(2 ** 20)
27 return md5.hexdigest()
28
29
30def check_md5(base_url):
31 url = base_url + "ringmd5"
32 ringfiles = ["/etc/swift/object.ring.gz",
33 "/etc/swift/account.ring.gz",
34 "/etc/swift/container.ring.gz"]
35 results = []
36 try:
37 data = urllib2.urlopen(url).read()
38 j = json.loads(data)
39 except urllib2.URLError:
40 return [(STATUS_UNKNOWN, "Can't open url: {}".format(url))]
41 except ValueError:
42 return [(STATUS_UNKNOWN, "Can't parse status data")]
43
44 for ringfile in ringfiles:
45 try:
46 if generate_md5(ringfile) != j[ringfile]:
47 results.append((STATUS_CRIT,
48 "Ringfile {} MD5 sum mismatch".format(ringfile)))
49 except IOError:
50 results.append(
51 (STATUS_UNKNOWN, "Can't open ringfile {}".format(ringfile)))
52 if results:
53 return results
54 else:
55 return [(STATUS_OK, "OK")]
56
57
58def check_replication(base_url, limits):
59 types = ["account", "object", "container"]
60 results = []
61 for repl in types:
62 url = base_url + "replication/" + repl
63 try:
64 data = urllib2.urlopen(url).read()
65 j = json.loads(data)
66 except urllib2.URLError:
67 results.append((STATUS_UNKNOWN, "Can't open url: {}".format(url)))
68 continue
69 except ValueError:
70 results.append((STATUS_UNKNOWN, "Can't parse status data"))
71 continue
72
73 if "object_replication_last" in j:
74 repl_last = datetime.datetime.fromtimestamp(j["object_replication_last"])
75 else:
76 repl_last = datetime.datetime.fromtimestamp(j["replication_last"])
77 delta = datetime.datetime.now() - repl_last
78 if delta.seconds >= limits[1]:
79 results.append((STATUS_CRIT,
80 "'{}' replication lag is {} seconds".format(repl, delta.seconds)))
81 elif delta.seconds >= limits[0]:
82 results.append((STATUS_WARN,
83 "'{}' replication lag is {} seconds".format(repl, delta.seconds)))
84 if "replication_stats" in j:
85 errors = j["replication_stats"]["failure"]
86 if errors >= limits[3]:
87 results.append(
88 (STATUS_CRIT, "{} replication failures".format(errors)))
89 elif errors >= limits[2]:
90 results.append(
91 (STATUS_WARN, "{} replication failures".format(errors)))
92 if results:
93 return results
94 else:
95 return [(STATUS_OK, "OK")]
96
97
98if __name__ == '__main__':
99 parser = argparse.ArgumentParser(description='Check swift-storage health')
100 parser.add_argument('-H', '--host', dest='host', default='localhost',
101 help='Hostname to query')
102 parser.add_argument('-p', '--port', dest='port', default='6000',
103 type=int, help='Port number')
104 parser.add_argument('-r', '--replication', dest='check_replication',
105 type=int, nargs=4, help='Check replication status',
106 metavar=('lag_warn', 'lag_crit', 'failures_warn', 'failures_crit'))
107 parser.add_argument('-m', '--md5', dest='check_md5', action='store_true',
108 help='Compare server rings md5sum with local copy')
109 args = parser.parse_args()
110
111 if not args.check_replication and not args.check_md5:
112 print "You must use -r or -m switch"
113 sys.exit(STATUS_UNKNOWN)
114
115 base_url = "http://{}:{}/recon/".format(args.host, args.port)
116 results = []
117 if args.check_replication:
118 results.extend(check_replication(base_url, args.check_replication))
119 if args.check_md5:
120 results.extend(check_md5(base_url))
121
122 crits = ';'.join([i[1] for i in results if i[0] == STATUS_CRIT])
123 warns = ';'.join([i[1] for i in results if i[0] == STATUS_WARN])
124 unknowns = ';'.join([i[1] for i in results if i[0] == STATUS_UNKNOWN])
125 if crits:
126 print "CRITICAL: " + crits
127 sys.exit(STATUS_CRIT)
128 elif warns:
129 print "WARNING: " + warns
130 sys.exit(STATUS_WARN)
131 elif unknowns:
132 print "UNKNOWN: " + unknowns
133 sys.exit(STATUS_UNKNOWN)
134 else:
135 print "OK"
136 sys.exit(0)
0137
=== added file 'templates/050-swift-storage'
--- templates/050-swift-storage 1970-01-01 00:00:00 +0000
+++ templates/050-swift-storage 2014-06-02 09:35:40 +0000
@@ -0,0 +1,24 @@
1[account]
2uid = swift
3gid = swift
4max connections = 2
5path = /srv/node/
6read only = false
7lock file = /var/lock/account.lock
8
9[container]
10uid = swift
11gid = swift
12max connections = 2
13path = /srv/node/
14read only = false
15lock file = /var/lock/container.lock
16
17[object]
18uid = swift
19gid = swift
20max connections = 2
21path = /srv/node/
22read only = false
23lock file = /var/lock/object.lock
24
025
=== modified file 'templates/rsyncd.conf'
--- templates/rsyncd.conf 2013-07-19 19:52:45 +0000
+++ templates/rsyncd.conf 2014-06-02 09:35:40 +0000
@@ -1,23 +1,5 @@
1uid = swift1uid = nobody
2gid = swift2gid = nogroup
3log file = /var/log/rsyncd.log3syslog facility = daemon
4pid file = /var/run/rsyncd.pid4socket options = SO_KEEPALIVE
5address = {{ local_ip }}5
6
7[account]
8max connections = 2
9path = /srv/node/
10read only = false
11lock file = /var/lock/account.lock
12
13[container]
14max connections = 2
15path = /srv/node/
16read only = false
17lock file = /var/lock/container.lock
18
19[object]
20max connections = 2
21path = /srv/node/
22read only = false
23lock file = /var/lock/object.lock
246
=== modified file 'unit_tests/test_swift_storage_relations.py'
--- unit_tests/test_swift_storage_relations.py 2013-09-27 16:33:06 +0000
+++ unit_tests/test_swift_storage_relations.py 2014-06-02 09:35:40 +0000
@@ -1,6 +1,6 @@
1from mock import patch, MagicMock1from mock import patch, MagicMock
22
3from test_utils import CharmTestCase3from test_utils import CharmTestCase, patch_open
44
5import swift_storage_utils as utils5import swift_storage_utils as utils
66
@@ -21,6 +21,7 @@
21 'log',21 'log',
22 'relation_set',22 'relation_set',
23 'relation_get',23 'relation_get',
24 'relations_of_type',
24 # charmhelpers.core.host25 # charmhelpers.core.host
25 'apt_update',26 'apt_update',
26 'apt_install',27 'apt_install',
@@ -60,13 +61,19 @@
6061
61 def test_config_changed_no_upgrade_available(self):62 def test_config_changed_no_upgrade_available(self):
62 self.openstack_upgrade_available.return_value = False63 self.openstack_upgrade_available.return_value = False
63 hooks.config_changed()64 self.relations_of_type.return_value = False
65 with patch_open() as (_open, _file):
66 _file.read.return_value = "foo"
67 hooks.config_changed()
64 self.assertFalse(self.do_openstack_upgrade.called)68 self.assertFalse(self.do_openstack_upgrade.called)
65 self.assertTrue(self.CONFIGS.write_all.called)69 self.assertTrue(self.CONFIGS.write_all.called)
6670
67 def test_config_changed_upgrade_available(self):71 def test_config_changed_upgrade_available(self):
68 self.openstack_upgrade_available.return_value = True72 self.openstack_upgrade_available.return_value = True
69 hooks.config_changed()73 self.relations_of_type.return_value = False
74 with patch_open() as (_open, _file):
75 _file.read.return_value = "foo"
76 hooks.config_changed()
70 self.assertTrue(self.do_openstack_upgrade.called)77 self.assertTrue(self.do_openstack_upgrade.called)
71 self.assertTrue(self.CONFIGS.write_all.called)78 self.assertTrue(self.CONFIGS.write_all.called)
7279
7380
=== modified file 'unit_tests/test_swift_storage_utils.py'
--- unit_tests/test_swift_storage_utils.py 2014-03-20 13:50:49 +0000
+++ unit_tests/test_swift_storage_utils.py 2014-06-02 09:35:40 +0000
@@ -74,7 +74,8 @@
74 ex_dirs = [74 ex_dirs = [
75 call('/etc/swift', owner='swift', group='swift'),75 call('/etc/swift', owner='swift', group='swift'),
76 call('/var/cache/swift', owner='swift', group='swift'),76 call('/var/cache/swift', owner='swift', group='swift'),
77 call('/srv/node', owner='swift', group='swift')77 call('/srv/node', owner='swift', group='swift'),
78 call('/etc/rsyncd.d', owner='root', group='root')
78 ]79 ]
79 self.assertEquals(ex_dirs, self.mkdir.call_args_list)80 self.assertEquals(ex_dirs, self.mkdir.call_args_list)
8081
@@ -196,7 +197,7 @@
196 openstack_release='grizzly')197 openstack_release='grizzly')
197 ex = [198 ex = [
198 call('/etc/swift/swift.conf', ['swift_server_context']),199 call('/etc/swift/swift.conf', ['swift_server_context']),
199 call('/etc/rsyncd.conf', ['rsync_context']),200 call('/etc/rsyncd.d/050-swift-storage', ['rsync_context']),
200 call('/etc/swift/account-server.conf', ['swift_context']),201 call('/etc/swift/account-server.conf', ['swift_context']),
201 call('/etc/swift/object-server.conf', ['swift_context']),202 call('/etc/swift/object-server.conf', ['swift_context']),
202 call('/etc/swift/container-server.conf', ['swift_context'])203 call('/etc/swift/container-server.conf', ['swift_context'])

Subscribers

People subscribed via source and target branches