Merge lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased into lp:~openstack-charmers-archive/charms/trusty/swift-storage/next

Proposed by Alexander List
Status: Needs review
Proposed branch: lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased
Merge into: lp:~openstack-charmers-archive/charms/trusty/swift-storage/next
Diff against target: 931 lines (+691/-32)
15 files modified
charm-helpers-hooks.yaml (+1/-0)
config.yaml (+14/-0)
files/nagios/check_swift_service (+25/-0)
files/nagios/check_swift_storage.py (+136/-0)
files/sudo/swift-storage (+1/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+219/-0)
hooks/charmhelpers/contrib/charmsupport/volumes.py (+159/-0)
hooks/swift_storage_hooks.py (+58/-2)
hooks/swift_storage_utils.py (+33/-2)
metadata.yaml (+3/-0)
revision (+1/-1)
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 (+2/-1)
To merge this branch: bzr merge lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased
Reviewer Review Type Date Requested Status
James Page Needs Fixing
Review via email: mp+231406@code.launchpad.net

This proposal supersedes a proposal from 2014-07-31.

Description of the change

This change adds nrpe-external-master support to the charm.

Contains:

- re-sync of charmsupport
- renamed basic checks for swift ring hashes and replication
- status checks for swift storage services
- sudo permissions for above status checks

Rebase on -next as of 2014-08-19

To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

> 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)

Revision history for this message
Gareth Woolridge (moon127) wrote : Posted in a previous version of this proposal

James,

Rebase of Jacek's earlier work to apply cleanly to /next as we needed to pull in some other updates to PS4.

Can you respond to Jacek's earlier comment above? The nagios-external-master support and rsync changes go hand in hand.

Revision history for this message
James Page (james-page) wrote :

OK _ sorry it took me a while to get back to this; currently upgrades are broken and one of the unit tests fails due to incorrect ordering in the expected calls list - other than that LGTM.

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

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#642 swift-storage-next for alexlist mp231406
charm_lint_check

This build:
http://10.98.191.181:8080/job/charm_lint_check/642/

MP URL:
https://code.launchpad.net/~alexlist/charms/trusty/swift-storage/nrpe-rebased/+merge/231406

Proposed branch:
lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased

Results summary:
    LINT FAIL: lint-check failed

LINT Results (max last 25 lines) from
/var/lib/jenkins/workspace/charm_lint_check/make-lint.642:
hooks/swift_storage_hooks.py:45:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_hooks.py:104:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_hooks.py:135:22: E251 unexpected spaces around keyword / parameter equals
hooks/swift_storage_hooks.py:135:24: E251 unexpected spaces around keyword / parameter equals
hooks/swift_storage_hooks.py:139:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:96:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:240:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:249:1: W391 blank line at end of file
make: *** [lint] Error 1

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

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

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#459 swift-storage-next for alexlist mp231406
charm_unit_test

This build:
http://10.98.191.181:8080/job/charm_unit_test/459/

MP URL:
https://code.launchpad.net/~alexlist/charms/trusty/swift-storage/nrpe-rebased/+merge/231406

Proposed branch:
lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased

Results summary:
    UNIT FAIL: unit-test failed

UNIT Results (max last 25 lines) from
/var/lib/jenkins/workspace/charm_unit_test/unit-test.459:
 call('/srv/node', owner='swift', group='swift'),
 call('/etc/rsyncd.d', owner='root', group='root')]

======================================================================
FAIL: test_fetch_swift_rings (unit_tests.test_swift_storage_utils.SwiftStorageUtilsTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/lib/jenkins/checkout/swift-storage/unit_tests/test_swift_storage_utils.py", line 99, in test_fetch_swift_rings
    self.assertEquals(wgets, self.check_call.call_args_list)
AssertionError: [call(['wget', 'http://someproxynode/rings/account.ring.gz', '-O', '/etc/swift/account.ring.gz']), call(['wget', 'http://someproxynode/rings/object.ring.gz', '-O', '/etc/swift/object.ring.gz']), call(['wget', 'http://someproxynode/rings/container.ring.gz', '-O', '/etc/swift/container.ring.gz'])] != [call(['wget', 'http://someproxynode/rings/account.ring.gz', '--retry-connrefused', '-t', '10', '-O', '/etc/swift/account.ring.gz']),
 call(['wget', 'http://someproxynode/rings/object.ring.gz', '--retry-connrefused', '-t', '10', '-O', '/etc/swift/object.ring.gz']),
 call(['wget', 'http://someproxynode/rings/container.ring.gz', '--retry-connrefused', '-t', '10', '-O', '/etc/swift/container.ring.gz'])]

Name Stmts Miss Cover Missing
-----------------------------------------------------
swift_storage_context 40 0 100%
swift_storage_hooks 70 16 77% 72, 77, 107-137
swift_storage_utils 111 8 93% 241-248
-----------------------------------------------------
TOTAL 221 24 89%
----------------------------------------------------------------------
Ran 31 tests in 1.384s

FAILED (failures=2)
make: *** [unit_test] Error 1

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

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

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#653 swift-storage-next for alexlist mp231406
charm_lint_check

This build:
http://10.98.191.181:8080/job/charm_lint_check/653/

MP URL:
https://code.launchpad.net/~alexlist/charms/trusty/swift-storage/nrpe-rebased/+merge/231406

Proposed branch:
lp:~alexlist/charms/trusty/swift-storage/nrpe-rebased

Results summary:
    LINT FAIL: lint-check failed

LINT Results (max last 25 lines) from
/var/lib/jenkins/workspace/charm_lint_check/make-lint.653:
hooks/swift_storage_hooks.py:45:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_hooks.py:104:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_hooks.py:135:22: E251 unexpected spaces around keyword / parameter equals
hooks/swift_storage_hooks.py:135:24: E251 unexpected spaces around keyword / parameter equals
hooks/swift_storage_hooks.py:139:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:96:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:240:1: E302 expected 2 blank lines, found 1
hooks/swift_storage_utils.py:249:1: W391 blank line at end of file
make: *** [lint] Error 1

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

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

charm_lint_check #4234 swift-storage-next for alexlist mp231406
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.

Full lint test output: http://paste.ubuntu.com/10965097/
Build: http://10.245.162.77:8080/job/charm_lint_check/4234/

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

charm_lint_check #4251 swift-storage-next for alexlist mp231406
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.

Full lint test output: http://paste.ubuntu.com/10965294/
Build: http://10.245.162.77:8080/job/charm_lint_check/4251/

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

charm_unit_test #3977 swift-storage-next for alexlist mp231406
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
make: *** [unit_test] Error 1
ERROR:root:Make target returned non-zero.

Full unit test output: http://paste.ubuntu.com/10965295/
Build: http://10.245.162.77:8080/job/charm_unit_test/3977/

Unmerged revisions

45. By Alexander List

[alexlist] move Nagios checks from scripts/ to files/, add service checks, add sudo permissions for service checks

44. By Alexander List

[alexlist] Initial support for nrpe-external-master, fix rsync configuration to support /etc/rsync.d

43. By Alexander List

[alexlist] re-sync charm-helpers, import charmsupport

42. By Alexander List

[alexlist] add config parameters for nrpe-external-master support

41. By Alexander List

[alexlist] add charmsupport dependency

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-hooks.yaml'
--- charm-helpers-hooks.yaml 2014-07-28 12:08:48 +0000
+++ charm-helpers-hooks.yaml 2014-08-19 15:54:23 +0000
@@ -10,3 +10,4 @@
10 - cluster10 - cluster
11 - payload.execd11 - payload.execd
12 - contrib.network.ip12 - contrib.network.ip
13 - contrib.charmsupport
1314
=== modified file 'config.yaml'
--- config.yaml 2014-06-19 08:40:58 +0000
+++ config.yaml 2014-08-19 15:54:23 +0000
@@ -63,3 +63,17 @@
63 to not use a per-disk thread pool. It is recommended to keep this value63 to not use a per-disk thread pool. It is recommended to keep this value
64 small, as large values can result in high read latencies due to large64 small, as large values can result in high read latencies due to large
65 queue depths. A good starting point is 4 threads per disk.65 queue depths. A good starting point is 4 threads per disk.
66 nagios-check-params:
67 default: "-m -r 60 180 10 20"
68 type: string
69 description: String appended to nagios check
70 nagios_context:
71 default: "juju"
72 type: string
73 description: |
74 Used by the nrpe-external-master subordinate charm.
75 A string that will be prepended to instance name to set the host name
76 in nagios. So for instance the hostname would be something like:
77 juju-myservice-0
78 If you're running multiple environments with the same services in them
79 this allows you to differentiate between them.
6680
=== added directory 'files'
=== added directory 'files/nagios'
=== added file 'files/nagios/check_swift_service'
--- files/nagios/check_swift_service 1970-01-01 00:00:00 +0000
+++ files/nagios/check_swift_service 2014-08-19 15:54:23 +0000
@@ -0,0 +1,25 @@
1#!/bin/sh
2
3#
4# check_swift_service --- exactly what it says on the tin
5#
6# Copyright 2013 Canonical Ltd.
7#
8# Authors:
9# Paul Collins <paul.collins@canonical.com>
10#
11
12STATUS=$(sudo -u swift swift-init status "$1" 2>&1)
13
14case $? in
15 0)
16 echo "OK: ${STATUS}"
17 exit 0
18 ;;
19 *)
20 echo "CRITICAL: ${STATUS}"
21 exit 2
22 ;;
23esac
24
25exit 1
026
=== added file 'files/nagios/check_swift_storage.py'
--- files/nagios/check_swift_storage.py 1970-01-01 00:00:00 +0000
+++ files/nagios/check_swift_storage.py 2014-08-19 15:54:23 +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 directory 'files/sudo'
=== added file 'files/sudo/swift-storage'
--- files/sudo/swift-storage 1970-01-01 00:00:00 +0000
+++ files/sudo/swift-storage 2014-08-19 15:54:23 +0000
@@ -0,0 +1,1 @@
1nagios ALL=(swift) NOPASSWD:/usr/bin/swift-init status *
02
=== 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-08-19 15:54:23 +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-08-19 15:54:23 +0000
@@ -0,0 +1,159 @@
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
7 volume-ephemeral:
8 type: boolean
9 default: true
10 description: >
11 If false, a volume is mounted as sepecified in "volume-map"
12 If true, ephemeral storage will be used, meaning that log data
13 will only exist as long as the machine. YOU HAVE BEEN WARNED.
14 volume-map:
15 type: string
16 default: {}
17 description: >
18 YAML map of units to device names, e.g:
19 "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
20 Service units will raise a configure-error if volume-ephemeral
21 is 'true' and no volume-map value is set. Use 'juju set' to set a
22 value and 'juju resolved' to complete configuration.
23
24Usage::
25
26 from charmsupport.volumes import configure_volume, VolumeConfigurationError
27 from charmsupport.hookenv import log, ERROR
28 def post_mount_hook():
29 stop_service('myservice')
30 def post_mount_hook():
31 start_service('myservice')
32
33 if __name__ == '__main__':
34 try:
35 configure_volume(before_change=pre_mount_hook,
36 after_change=post_mount_hook)
37 except VolumeConfigurationError:
38 log('Storage could not be configured', ERROR)
39
40'''
41
42# XXX: Known limitations
43# - fstab is neither consulted nor updated
44
45import os
46from charmhelpers.core import hookenv
47from charmhelpers.core import host
48import yaml
49
50
51MOUNT_BASE = '/srv/juju/volumes'
52
53
54class VolumeConfigurationError(Exception):
55 '''Volume configuration data is missing or invalid'''
56 pass
57
58
59def get_config():
60 '''Gather and sanity-check volume configuration data'''
61 volume_config = {}
62 config = hookenv.config()
63
64 errors = False
65
66 if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
67 volume_config['ephemeral'] = True
68 else:
69 volume_config['ephemeral'] = False
70
71 try:
72 volume_map = yaml.safe_load(config.get('volume-map', '{}'))
73 except yaml.YAMLError as e:
74 hookenv.log("Error parsing YAML volume-map: {}".format(e),
75 hookenv.ERROR)
76 errors = True
77 if volume_map is None:
78 # probably an empty string
79 volume_map = {}
80 elif not isinstance(volume_map, dict):
81 hookenv.log("Volume-map should be a dictionary, not {}".format(
82 type(volume_map)))
83 errors = True
84
85 volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
86 if volume_config['device'] and volume_config['ephemeral']:
87 # asked for ephemeral storage but also defined a volume ID
88 hookenv.log('A volume is defined for this unit, but ephemeral '
89 'storage was requested', hookenv.ERROR)
90 errors = True
91 elif not volume_config['device'] and not volume_config['ephemeral']:
92 # asked for permanent storage but did not define volume ID
93 hookenv.log('Ephemeral storage was requested, but there is no volume '
94 'defined for this unit.', hookenv.ERROR)
95 errors = True
96
97 unit_mount_name = hookenv.local_unit().replace('/', '-')
98 volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
99
100 if errors:
101 return None
102 return volume_config
103
104
105def mount_volume(config):
106 if os.path.exists(config['mountpoint']):
107 if not os.path.isdir(config['mountpoint']):
108 hookenv.log('Not a directory: {}'.format(config['mountpoint']))
109 raise VolumeConfigurationError()
110 else:
111 host.mkdir(config['mountpoint'])
112 if os.path.ismount(config['mountpoint']):
113 unmount_volume(config)
114 if not host.mount(config['device'], config['mountpoint'], persist=True):
115 raise VolumeConfigurationError()
116
117
118def unmount_volume(config):
119 if os.path.ismount(config['mountpoint']):
120 if not host.umount(config['mountpoint'], persist=True):
121 raise VolumeConfigurationError()
122
123
124def managed_mounts():
125 '''List of all mounted managed volumes'''
126 return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
127
128
129def configure_volume(before_change=lambda: None, after_change=lambda: None):
130 '''Set up storage (or don't) according to the charm's volume configuration.
131 Returns the mount point or "ephemeral". before_change and after_change
132 are optional functions to be called if the volume configuration changes.
133 '''
134
135 config = get_config()
136 if not config:
137 hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
138 raise VolumeConfigurationError()
139
140 if config['ephemeral']:
141 if os.path.ismount(config['mountpoint']):
142 before_change()
143 unmount_volume(config)
144 after_change()
145 return 'ephemeral'
146 else:
147 # persistent storage
148 if os.path.ismount(config['mountpoint']):
149 mounts = dict(managed_mounts())
150 if mounts.get(config['mountpoint']) != config['device']:
151 before_change()
152 unmount_volume(config)
153 mount_volume(config)
154 after_change()
155 else:
156 before_change()
157 mount_volume(config)
158 after_change()
159 return config['mountpoint']
0160
=== 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-08-19 15:54:23 +0000
@@ -6,6 +6,7 @@
6from swift_storage_utils import (6from swift_storage_utils import (
7 PACKAGES,7 PACKAGES,
8 RESTART_MAP,8 RESTART_MAP,
9 SWIFT_SVCS,
9 determine_block_devices,10 determine_block_devices,
10 do_openstack_upgrade,11 do_openstack_upgrade,
11 ensure_swift_directories,12 ensure_swift_directories,
@@ -13,6 +14,7 @@
13 register_configs,14 register_configs,
14 save_script_rc,15 save_script_rc,
15 setup_storage,16 setup_storage,
17 concat_rsync_fragments,
16)18)
1719
18from charmhelpers.core.hookenv import (20from charmhelpers.core.hookenv import (
@@ -21,10 +23,11 @@
21 log,23 log,
22 relation_get,24 relation_get,
23 relation_set,25 relation_set,
26 relations_of_type,
24)27)
2528
26from charmhelpers.fetch import apt_install, apt_update29from charmhelpers.fetch import apt_install, apt_update
27from charmhelpers.core.host import restart_on_change30from charmhelpers.core.host import restart_on_change, rsync
28from charmhelpers.payload.execd import execd_preinstall31from charmhelpers.payload.execd import execd_preinstall
2932
30from charmhelpers.contrib.openstack.utils import (33from charmhelpers.contrib.openstack.utils import (
@@ -32,9 +35,12 @@
32 openstack_upgrade_available,35 openstack_upgrade_available,
33)36)
3437
38from charmhelpers.contrib.charmsupport.nrpe import NRPE
39
35hooks = Hooks()40hooks = Hooks()
36CONFIGS = register_configs()41CONFIGS = register_configs()
3742NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
43SUDOERS_D = '/etc/sudoers.d'
3844
39@hooks.hook()45@hooks.hook()
40def install():46def install():
@@ -52,8 +58,24 @@
52 if openstack_upgrade_available('swift'):58 if openstack_upgrade_available('swift'):
53 do_openstack_upgrade(configs=CONFIGS)59 do_openstack_upgrade(configs=CONFIGS)
54 CONFIGS.write_all()60 CONFIGS.write_all()
61
62 # If basenode is not installed and managing rsyncd.conf, replicate
63 # its core functionality. Otherwise concat files
64 if not os.path.exists('/etc/rsyncd.d/001-basenode'):
65 with open('templates/rsyncd.conf') as _in:
66 rsync_header = _in.read()
67 with open('/etc/rsyncd.d/050-swift-storage') as _in:
68 rsync_fragment = _in.read()
69 with open('/etc/rsyncd.conf', 'w') as out:
70 out.write(rsync_header + rsync_fragment)
71 else:
72 concat_rsync_fragments()
73
55 save_script_rc()74 save_script_rc()
5675
76 if relations_of_type('nrpe-external-master'):
77 nrpe_relation()
78
5779
58@hooks.hook()80@hooks.hook()
59def swift_storage_relation_joined():81def swift_storage_relation_joined():
@@ -79,6 +101,40 @@
79 CONFIGS.write('/etc/swift/swift.conf')101 CONFIGS.write('/etc/swift/swift.conf')
80 fetch_swift_rings(rings_url)102 fetch_swift_rings(rings_url)
81103
104@hooks.hook('nrpe-external-master-relation-joined')
105@hooks.hook('nrpe-external-master-relation-changed')
106def nrpe_relation():
107 log('Refreshing nrpe checks')
108 rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nagios',
109 'check_swift_storage.py'),
110 os.path.join(NAGIOS_PLUGINS, 'check_swift_storage.py'))
111 rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nagios',
112 'check_swift_service'),
113 os.path.join(NAGIOS_PLUGINS, 'check_swift_service'))
114 rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'sudo',
115 'swift-storage'),
116 os.path.join(SUDOERS_D, 'swift-storage'))
117 # Find out if nrpe set nagios_hostname
118 hostname = None
119 for rel in relations_of_type('nrpe-external-master'):
120 if 'nagios_hostname' in rel:
121 hostname = rel['nagios_hostname']
122 break
123 nrpe = NRPE(hostname=hostname)
124 nrpe.add_check(
125 shortname='swift_storage',
126 description='Check swift storage ring hashes and replication',
127 check_cmd='check_swift_storage.py {}'.format(
128 config('nagios-check-params'))
129 )
130 # check services are running
131 for service in SWIFT_SVCS:
132 nrpe.add_check(
133 shortname=service,
134 description='swift-storage %s service' % service,
135 check_cmd = 'check_swift_service %s' % service,
136 )
137 nrpe.write()
82138
83def main():139def main():
84 try:140 try:
85141
=== modified file 'hooks/swift_storage_utils.py'
--- hooks/swift_storage_utils.py 2014-08-11 09:14:47 +0000
+++ hooks/swift_storage_utils.py 2014-08-19 15:54:23 +0000
@@ -70,13 +70,28 @@
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,
77 '/etc/swift/swift.conf': ACCOUNT_SVCS + CONTAINER_SVCS + OBJECT_SVCS77 '/etc/swift/swift.conf': ACCOUNT_SVCS + CONTAINER_SVCS + OBJECT_SVCS
78}78}
7979
80SWIFT_SVCS = [
81 'account-auditor',
82 'account-reaper',
83 'account-replicator',
84 'account-server',
85 'container-auditor',
86 'container-replicator',
87 'container-server',
88 'container-sync',
89 'container-updater',
90 'object-auditor',
91 'object-replicator',
92 'object-server',
93 'object-updater',
94 ]
8095
81def ensure_swift_directories():96def ensure_swift_directories():
82 '''97 '''
@@ -90,6 +105,11 @@
90 ]105 ]
91 [mkdir(d, owner='swift', group='swift') for d in dirs106 [mkdir(d, owner='swift', group='swift') for d in dirs
92 if not os.path.isdir(d)]107 if not os.path.isdir(d)]
108 root_dirs = [
109 '/etc/rsyncd.d',
110 ]
111 [mkdir(d, owner='root', group='root') for d in root_dirs
112 if not os.path.isdir(d)]
93113
94114
95def register_configs():115def register_configs():
@@ -98,7 +118,7 @@
98 openstack_release=release)118 openstack_release=release)
99 configs.register('/etc/swift/swift.conf',119 configs.register('/etc/swift/swift.conf',
100 [SwiftStorageContext()])120 [SwiftStorageContext()])
101 configs.register('/etc/rsyncd.conf',121 configs.register('/etc/rsyncd.d/050-swift-storage',
102 [RsyncContext()])122 [RsyncContext()])
103 for server in ['account', 'object', 'container']:123 for server in ['account', 'object', 'container']:
104 configs.register('/etc/swift/%s-server.conf' % server,124 configs.register('/etc/swift/%s-server.conf' % server,
@@ -216,3 +236,14 @@
216 'OPENSTACK_URL_%s' % svc: url,236 'OPENSTACK_URL_%s' % svc: url,
217 })237 })
218 _save_script_rc(**env_vars)238 _save_script_rc(**env_vars)
239
240def concat_rsync_fragments():
241 log('Concatenating rsyncd.d fragments')
242 rsyncd_dir = '/etc/rsyncd.d'
243 rsyncd_conf = ""
244 for filename in sorted(os.listdir(rsyncd_dir)):
245 with open(os.path.join(rsyncd_dir, filename), 'r') as fragment:
246 rsyncd_conf += fragment.read()
247 with open('/etc/rsyncd.conf', 'w') as f:
248 f.write(rsyncd_conf)
249
219250
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-07-11 20:45:19 +0000
+++ metadata.yaml 2014-08-19 15:54:23 +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-08-19 15:54:23 +0000
@@ -1,1 +1,1 @@
1901101
22
=== removed directory 'scripts'
=== added file 'templates/050-swift-storage'
--- templates/050-swift-storage 1970-01-01 00:00:00 +0000
+++ templates/050-swift-storage 2014-08-19 15:54:23 +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-08-19 15:54:23 +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 2014-06-19 08:40:58 +0000
+++ unit_tests/test_swift_storage_relations.py 2014-08-19 15:54:23 +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',
@@ -61,13 +62,19 @@
6162
62 def test_config_changed_no_upgrade_available(self):63 def test_config_changed_no_upgrade_available(self):
63 self.openstack_upgrade_available.return_value = False64 self.openstack_upgrade_available.return_value = False
64 hooks.config_changed()65 self.relations_of_type.return_value = False
66 with patch_open() as (_open, _file):
67 _file.read.return_value = "foo"
68 hooks.config_changed()
65 self.assertFalse(self.do_openstack_upgrade.called)69 self.assertFalse(self.do_openstack_upgrade.called)
66 self.assertTrue(self.CONFIGS.write_all.called)70 self.assertTrue(self.CONFIGS.write_all.called)
6771
68 def test_config_changed_upgrade_available(self):72 def test_config_changed_upgrade_available(self):
69 self.openstack_upgrade_available.return_value = True73 self.openstack_upgrade_available.return_value = True
70 hooks.config_changed()74 self.relations_of_type.return_value = False
75 with patch_open() as (_open, _file):
76 _file.read.return_value = "foo"
77 hooks.config_changed()
71 self.assertTrue(self.do_openstack_upgrade.called)78 self.assertTrue(self.do_openstack_upgrade.called)
72 self.assertTrue(self.CONFIGS.write_all.called)79 self.assertTrue(self.CONFIGS.write_all.called)
7380
7481
=== modified file 'unit_tests/test_swift_storage_utils.py'
--- unit_tests/test_swift_storage_utils.py 2014-06-19 08:40:58 +0000
+++ unit_tests/test_swift_storage_utils.py 2014-08-19 15:54:23 +0000
@@ -75,6 +75,7 @@
75 ex_dirs = [75 ex_dirs = [
76 call('/etc/swift', owner='swift', group='swift'),76 call('/etc/swift', owner='swift', group='swift'),
77 call('/var/cache/swift', owner='swift', group='swift'),77 call('/var/cache/swift', owner='swift', group='swift'),
78 call('/etc/rsyncd.d', owner='root', group='root'),
78 call('/srv/node', owner='swift', group='swift')79 call('/srv/node', owner='swift', group='swift')
79 ]80 ]
80 self.assertEquals(ex_dirs, self.mkdir.call_args_list)81 self.assertEquals(ex_dirs, self.mkdir.call_args_list)
@@ -211,7 +212,7 @@
211 openstack_release='grizzly')212 openstack_release='grizzly')
212 ex = [213 ex = [
213 call('/etc/swift/swift.conf', ['swift_server_context']),214 call('/etc/swift/swift.conf', ['swift_server_context']),
214 call('/etc/rsyncd.conf', ['rsync_context']),215 call('/etc/rsyncd.d/050-swift-storage', ['rsync_context']),
215 call('/etc/swift/account-server.conf', ['swift_context']),216 call('/etc/swift/account-server.conf', ['swift_context']),
216 call('/etc/swift/object-server.conf', ['swift_context']),217 call('/etc/swift/object-server.conf', ['swift_context']),
217 call('/etc/swift/container-server.conf', ['swift_context'])218 call('/etc/swift/container-server.conf', ['swift_context'])

Subscribers

People subscribed via source and target branches