Merge lp:~brad-marshall/charms/trusty/nova-cloud-controller/add-haproxy-nrpe-fix-servicegroups into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next

Proposed by Brad Marshall
Status: Merged
Merged at revision: 142
Proposed branch: lp:~brad-marshall/charms/trusty/nova-cloud-controller/add-haproxy-nrpe-fix-servicegroups
Merge into: lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
Diff against target: 1247 lines (+861/-47)
21 files modified
config.yaml (+6/-1)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+41/-7)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+5/-1)
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+5/-2)
hooks/charmhelpers/contrib/openstack/files/__init__.py (+18/-0)
hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh (+32/-0)
hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh (+30/-0)
hooks/charmhelpers/contrib/openstack/ip.py (+37/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+1/-0)
hooks/charmhelpers/contrib/python/packages.py (+2/-2)
hooks/charmhelpers/core/fstab.py (+4/-4)
hooks/charmhelpers/core/host.py (+5/-5)
hooks/charmhelpers/core/strutils.py (+42/-0)
hooks/charmhelpers/core/sysctl.py (+13/-7)
hooks/charmhelpers/core/templating.py (+3/-3)
hooks/charmhelpers/core/unitdata.py (+477/-0)
hooks/charmhelpers/fetch/archiveurl.py (+10/-10)
hooks/charmhelpers/fetch/giturl.py (+1/-1)
hooks/nova_cc_hooks.py (+2/-0)
tests/charmhelpers/contrib/amulet/utils.py (+122/-2)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+5/-2)
To merge this branch: bzr merge lp:~brad-marshall/charms/trusty/nova-cloud-controller/add-haproxy-nrpe-fix-servicegroups
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+250704@code.launchpad.net

Description of the change

Synced charmhelpers, added nagios_servicegroup config option, and added haproxy nrpe checks.

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2219 nova-cloud-controller-next for brad-marshall mp250704
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/2219/

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

charm_unit_test #2008 nova-cloud-controller-next for brad-marshall mp250704
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/2008/

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

charm_amulet_test #2165 nova-cloud-controller-next for brad-marshall mp250704
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/10396811/
Build: http://10.245.162.77:8080/job/charm_amulet_test/2165/

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

Approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2015-01-14 08:45:54 +0000
3+++ config.yaml 2015-02-24 06:12:30 +0000
4@@ -320,4 +320,9 @@
5 juju-myservice-0
6 If you're running multiple environments with the same services in them
7 this allows you to differentiate between them.
8-
9+ nagios_servicegroups:
10+ default: ""
11+ type: string
12+ description: |
13+ A comma-separated list of nagios servicegroups.
14+ If left empty, the nagios_context will be used as the servicegroup
15
16=== modified file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
17--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-01-26 09:44:11 +0000
18+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-02-24 06:12:30 +0000
19@@ -24,6 +24,8 @@
20 import pwd
21 import grp
22 import os
23+import glob
24+import shutil
25 import re
26 import shlex
27 import yaml
28@@ -161,7 +163,7 @@
29 log('Check command not found: {}'.format(parts[0]))
30 return ''
31
32- def write(self, nagios_context, hostname, nagios_servicegroups=None):
33+ def write(self, nagios_context, hostname, nagios_servicegroups):
34 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
35 self.command)
36 with open(nrpe_check_file, 'w') as nrpe_check_config:
37@@ -177,14 +179,11 @@
38 nagios_servicegroups)
39
40 def write_service_config(self, nagios_context, hostname,
41- nagios_servicegroups=None):
42+ nagios_servicegroups):
43 for f in os.listdir(NRPE.nagios_exportdir):
44 if re.search('.*{}.cfg'.format(self.command), f):
45 os.remove(os.path.join(NRPE.nagios_exportdir, f))
46
47- if not nagios_servicegroups:
48- nagios_servicegroups = nagios_context
49-
50 templ_vars = {
51 'nagios_hostname': hostname,
52 'nagios_servicegroup': nagios_servicegroups,
53@@ -211,10 +210,10 @@
54 super(NRPE, self).__init__()
55 self.config = config()
56 self.nagios_context = self.config['nagios_context']
57- if 'nagios_servicegroups' in self.config:
58+ if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
59 self.nagios_servicegroups = self.config['nagios_servicegroups']
60 else:
61- self.nagios_servicegroups = 'juju'
62+ self.nagios_servicegroups = self.nagios_context
63 self.unit_name = local_unit().replace('/', '-')
64 if hostname:
65 self.hostname = hostname
66@@ -322,3 +321,38 @@
67 check_cmd='check_status_file.py -f '
68 '/var/lib/nagios/service-check-%s.txt' % svc,
69 )
70+
71+
72+def copy_nrpe_checks():
73+ """
74+ Copy the nrpe checks into place
75+
76+ """
77+ NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
78+ nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
79+ 'charmhelpers', 'contrib', 'openstack',
80+ 'files')
81+
82+ if not os.path.exists(NAGIOS_PLUGINS):
83+ os.makedirs(NAGIOS_PLUGINS)
84+ for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
85+ if os.path.isfile(fname):
86+ shutil.copy2(fname,
87+ os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
88+
89+
90+def add_haproxy_checks(nrpe, unit_name):
91+ """
92+ Add checks for each service in list
93+
94+ :param NRPE nrpe: NRPE object to add check to
95+ :param str unit_name: Unit name to use in check description
96+ """
97+ nrpe.add_check(
98+ shortname='haproxy_servers',
99+ description='Check HAProxy {%s}' % unit_name,
100+ check_cmd='check_haproxy.sh')
101+ nrpe.add_check(
102+ shortname='haproxy_queue',
103+ description='Check HAProxy queue depth {%s}' % unit_name,
104+ check_cmd='check_haproxy_queue_depth.sh')
105
106=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
107--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-01-26 09:44:11 +0000
108+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-02-24 06:12:30 +0000
109@@ -48,6 +48,9 @@
110 from charmhelpers.core.decorators import (
111 retry_on_exception,
112 )
113+from charmhelpers.core.strutils import (
114+ bool_from_string,
115+)
116
117
118 class HAIncompleteConfig(Exception):
119@@ -164,7 +167,8 @@
120 .
121 returns: boolean
122 '''
123- if config_get('use-https') == "yes":
124+ use_https = config_get('use-https')
125+ if use_https and bool_from_string(use_https):
126 return True
127 if config_get('ssl_cert') and config_get('ssl_key'):
128 return True
129
130=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
131--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-01-26 09:44:11 +0000
132+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-02-24 06:12:30 +0000
133@@ -71,16 +71,19 @@
134 services.append(this_service)
135 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
136 'ceph-osd', 'ceph-radosgw']
137+ # Openstack subordinate charms do not expose an origin option as that
138+ # is controlled by the principle
139+ ignore = ['neutron-openvswitch']
140
141 if self.openstack:
142 for svc in services:
143- if svc['name'] not in use_source:
144+ if svc['name'] not in use_source + ignore:
145 config = {'openstack-origin': self.openstack}
146 self.d.configure(svc['name'], config)
147
148 if self.source:
149 for svc in services:
150- if svc['name'] in use_source:
151+ if svc['name'] in use_source and svc['name'] not in ignore:
152 config = {'source': self.source}
153 self.d.configure(svc['name'], config)
154
155
156=== added directory 'hooks/charmhelpers/contrib/openstack/files'
157=== added file 'hooks/charmhelpers/contrib/openstack/files/__init__.py'
158--- hooks/charmhelpers/contrib/openstack/files/__init__.py 1970-01-01 00:00:00 +0000
159+++ hooks/charmhelpers/contrib/openstack/files/__init__.py 2015-02-24 06:12:30 +0000
160@@ -0,0 +1,18 @@
161+# Copyright 2014-2015 Canonical Limited.
162+#
163+# This file is part of charm-helpers.
164+#
165+# charm-helpers is free software: you can redistribute it and/or modify
166+# it under the terms of the GNU Lesser General Public License version 3 as
167+# published by the Free Software Foundation.
168+#
169+# charm-helpers is distributed in the hope that it will be useful,
170+# but WITHOUT ANY WARRANTY; without even the implied warranty of
171+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
172+# GNU Lesser General Public License for more details.
173+#
174+# You should have received a copy of the GNU Lesser General Public License
175+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
176+
177+# dummy __init__.py to fool syncer into thinking this is a syncable python
178+# module
179
180=== added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh'
181--- hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 1970-01-01 00:00:00 +0000
182+++ hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-02-24 06:12:30 +0000
183@@ -0,0 +1,32 @@
184+#!/bin/bash
185+#--------------------------------------------
186+# This file is managed by Juju
187+#--------------------------------------------
188+#
189+# Copyright 2009,2012 Canonical Ltd.
190+# Author: Tom Haddon
191+
192+CRITICAL=0
193+NOTACTIVE=''
194+LOGFILE=/var/log/nagios/check_haproxy.log
195+AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')
196+
197+for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'});
198+do
199+ output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK')
200+ if [ $? != 0 ]; then
201+ date >> $LOGFILE
202+ echo $output >> $LOGFILE
203+ /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1
204+ CRITICAL=1
205+ NOTACTIVE="${NOTACTIVE} $appserver"
206+ fi
207+done
208+
209+if [ $CRITICAL = 1 ]; then
210+ echo "CRITICAL:${NOTACTIVE}"
211+ exit 2
212+fi
213+
214+echo "OK: All haproxy instances looking good"
215+exit 0
216
217=== added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh'
218--- hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 1970-01-01 00:00:00 +0000
219+++ hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 2015-02-24 06:12:30 +0000
220@@ -0,0 +1,30 @@
221+#!/bin/bash
222+#--------------------------------------------
223+# This file is managed by Juju
224+#--------------------------------------------
225+#
226+# Copyright 2009,2012 Canonical Ltd.
227+# Author: Tom Haddon
228+
229+# These should be config options at some stage
230+CURRQthrsh=0
231+MAXQthrsh=100
232+
233+AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')
234+
235+HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v)
236+
237+for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}')
238+do
239+ CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3)
240+ MAXQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 4)
241+
242+ if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then
243+ echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ"
244+ exit 2
245+ fi
246+done
247+
248+echo "OK: All haproxy queue depths looking good"
249+exit 0
250+
251
252=== modified file 'hooks/charmhelpers/contrib/openstack/ip.py'
253--- hooks/charmhelpers/contrib/openstack/ip.py 2015-01-26 09:44:11 +0000
254+++ hooks/charmhelpers/contrib/openstack/ip.py 2015-02-24 06:12:30 +0000
255@@ -26,6 +26,8 @@
256 )
257 from charmhelpers.contrib.hahelpers.cluster import is_clustered
258
259+from functools import partial
260+
261 PUBLIC = 'public'
262 INTERNAL = 'int'
263 ADMIN = 'admin'
264@@ -107,3 +109,38 @@
265 "clustered=%s)" % (net_type, clustered))
266
267 return resolved_address
268+
269+
270+def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC,
271+ override=None):
272+ """Returns the correct endpoint URL to advertise to Keystone.
273+
274+ This method provides the correct endpoint URL which should be advertised to
275+ the keystone charm for endpoint creation. This method allows for the url to
276+ be overridden to force a keystone endpoint to have specific URL for any of
277+ the defined scopes (admin, internal, public).
278+
279+ :param configs: OSTemplateRenderer config templating object to inspect
280+ for a complete https context.
281+ :param url_template: str format string for creating the url template. Only
282+ two values will be passed - the scheme+hostname
283+ returned by the canonical_url and the port.
284+ :param endpoint_type: str endpoint type to resolve.
285+ :param override: str the name of the config option which overrides the
286+ endpoint URL defined by the charm itself. None will
287+ disable any overrides (default).
288+ """
289+ if override:
290+ # Return any user-defined overrides for the keystone endpoint URL.
291+ user_value = config(override)
292+ if user_value:
293+ return user_value.strip()
294+
295+ return url_template % (canonical_url(configs, endpoint_type), port)
296+
297+
298+public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC)
299+
300+internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL)
301+
302+admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN)
303
304=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
305--- hooks/charmhelpers/contrib/openstack/utils.py 2015-01-26 09:44:11 +0000
306+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-02-24 06:12:30 +0000
307@@ -103,6 +103,7 @@
308 ('2.1.0', 'juno'),
309 ('2.2.0', 'juno'),
310 ('2.2.1', 'kilo'),
311+ ('2.2.2', 'kilo'),
312 ])
313
314 DEFAULT_LOOPBACK_SIZE = '5G'
315
316=== modified file 'hooks/charmhelpers/contrib/python/packages.py'
317--- hooks/charmhelpers/contrib/python/packages.py 2015-01-26 09:44:11 +0000
318+++ hooks/charmhelpers/contrib/python/packages.py 2015-02-24 06:12:30 +0000
319@@ -17,8 +17,6 @@
320 # You should have received a copy of the GNU Lesser General Public License
321 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
322
323-__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
324-
325 from charmhelpers.fetch import apt_install, apt_update
326 from charmhelpers.core.hookenv import log
327
328@@ -29,6 +27,8 @@
329 apt_install('python-pip')
330 from pip import main as pip_execute
331
332+__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
333+
334
335 def parse_options(given, available):
336 """Given a set of options, check if available"""
337
338=== modified file 'hooks/charmhelpers/core/fstab.py'
339--- hooks/charmhelpers/core/fstab.py 2015-01-26 09:44:11 +0000
340+++ hooks/charmhelpers/core/fstab.py 2015-02-24 06:12:30 +0000
341@@ -17,11 +17,11 @@
342 # You should have received a copy of the GNU Lesser General Public License
343 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
344
345-__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
346-
347 import io
348 import os
349
350+__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
351+
352
353 class Fstab(io.FileIO):
354 """This class extends file in order to implement a file reader/writer
355@@ -77,7 +77,7 @@
356 for line in self.readlines():
357 line = line.decode('us-ascii')
358 try:
359- if line.strip() and not line.startswith("#"):
360+ if line.strip() and not line.strip().startswith("#"):
361 yield self._hydrate_entry(line)
362 except ValueError:
363 pass
364@@ -104,7 +104,7 @@
365
366 found = False
367 for index, line in enumerate(lines):
368- if not line.startswith("#"):
369+ if line.strip() and not line.strip().startswith("#"):
370 if self._hydrate_entry(line) == entry:
371 found = True
372 break
373
374=== modified file 'hooks/charmhelpers/core/host.py'
375--- hooks/charmhelpers/core/host.py 2015-01-26 09:44:11 +0000
376+++ hooks/charmhelpers/core/host.py 2015-02-24 06:12:30 +0000
377@@ -191,11 +191,11 @@
378
379
380 def write_file(path, content, owner='root', group='root', perms=0o444):
381- """Create or overwrite a file with the contents of a string"""
382+ """Create or overwrite a file with the contents of a byte string."""
383 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
384 uid = pwd.getpwnam(owner).pw_uid
385 gid = grp.getgrnam(group).gr_gid
386- with open(path, 'w') as target:
387+ with open(path, 'wb') as target:
388 os.fchown(target.fileno(), uid, gid)
389 os.fchmod(target.fileno(), perms)
390 target.write(content)
391@@ -305,11 +305,11 @@
392 ceph_client_changed function.
393 """
394 def wrap(f):
395- def wrapped_f(*args):
396+ def wrapped_f(*args, **kwargs):
397 checksums = {}
398 for path in restart_map:
399 checksums[path] = file_hash(path)
400- f(*args)
401+ f(*args, **kwargs)
402 restarts = []
403 for path in restart_map:
404 if checksums[path] != file_hash(path):
405@@ -361,7 +361,7 @@
406 ip_output = (line for line in ip_output if line)
407 for line in ip_output:
408 if line.split()[1].startswith(int_type):
409- matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line)
410+ matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
411 if matched:
412 interface = matched.groups()[0]
413 else:
414
415=== added file 'hooks/charmhelpers/core/strutils.py'
416--- hooks/charmhelpers/core/strutils.py 1970-01-01 00:00:00 +0000
417+++ hooks/charmhelpers/core/strutils.py 2015-02-24 06:12:30 +0000
418@@ -0,0 +1,42 @@
419+#!/usr/bin/env python
420+# -*- coding: utf-8 -*-
421+
422+# Copyright 2014-2015 Canonical Limited.
423+#
424+# This file is part of charm-helpers.
425+#
426+# charm-helpers is free software: you can redistribute it and/or modify
427+# it under the terms of the GNU Lesser General Public License version 3 as
428+# published by the Free Software Foundation.
429+#
430+# charm-helpers is distributed in the hope that it will be useful,
431+# but WITHOUT ANY WARRANTY; without even the implied warranty of
432+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
433+# GNU Lesser General Public License for more details.
434+#
435+# You should have received a copy of the GNU Lesser General Public License
436+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
437+
438+import six
439+
440+
441+def bool_from_string(value):
442+ """Interpret string value as boolean.
443+
444+ Returns True if value translates to True otherwise False.
445+ """
446+ if isinstance(value, six.string_types):
447+ value = six.text_type(value)
448+ else:
449+ msg = "Unable to interpret non-string value '%s' as boolean" % (value)
450+ raise ValueError(msg)
451+
452+ value = value.strip().lower()
453+
454+ if value in ['y', 'yes', 'true', 't']:
455+ return True
456+ elif value in ['n', 'no', 'false', 'f']:
457+ return False
458+
459+ msg = "Unable to interpret string value '%s' as boolean" % (value)
460+ raise ValueError(msg)
461
462=== modified file 'hooks/charmhelpers/core/sysctl.py'
463--- hooks/charmhelpers/core/sysctl.py 2015-01-26 09:44:11 +0000
464+++ hooks/charmhelpers/core/sysctl.py 2015-02-24 06:12:30 +0000
465@@ -17,8 +17,6 @@
466 # You should have received a copy of the GNU Lesser General Public License
467 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
468
469-__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
470-
471 import yaml
472
473 from subprocess import check_call
474@@ -26,25 +24,33 @@
475 from charmhelpers.core.hookenv import (
476 log,
477 DEBUG,
478+ ERROR,
479 )
480
481+__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
482+
483
484 def create(sysctl_dict, sysctl_file):
485 """Creates a sysctl.conf file from a YAML associative array
486
487- :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 }
488- :type sysctl_dict: dict
489+ :param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }"
490+ :type sysctl_dict: str
491 :param sysctl_file: path to the sysctl file to be saved
492 :type sysctl_file: str or unicode
493 :returns: None
494 """
495- sysctl_dict = yaml.load(sysctl_dict)
496+ try:
497+ sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
498+ except yaml.YAMLError:
499+ log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
500+ level=ERROR)
501+ return
502
503 with open(sysctl_file, "w") as fd:
504- for key, value in sysctl_dict.items():
505+ for key, value in sysctl_dict_parsed.items():
506 fd.write("{}={}\n".format(key, value))
507
508- log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict),
509+ log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed),
510 level=DEBUG)
511
512 check_call(["sysctl", "-p", sysctl_file])
513
514=== modified file 'hooks/charmhelpers/core/templating.py'
515--- hooks/charmhelpers/core/templating.py 2015-01-26 09:44:11 +0000
516+++ hooks/charmhelpers/core/templating.py 2015-02-24 06:12:30 +0000
517@@ -21,7 +21,7 @@
518
519
520 def render(source, target, context, owner='root', group='root',
521- perms=0o444, templates_dir=None):
522+ perms=0o444, templates_dir=None, encoding='UTF-8'):
523 """
524 Render a template.
525
526@@ -64,5 +64,5 @@
527 level=hookenv.ERROR)
528 raise e
529 content = template.render(context)
530- host.mkdir(os.path.dirname(target), owner, group)
531- host.write_file(target, content, owner, group, perms)
532+ host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
533+ host.write_file(target, content.encode(encoding), owner, group, perms)
534
535=== added file 'hooks/charmhelpers/core/unitdata.py'
536--- hooks/charmhelpers/core/unitdata.py 1970-01-01 00:00:00 +0000
537+++ hooks/charmhelpers/core/unitdata.py 2015-02-24 06:12:30 +0000
538@@ -0,0 +1,477 @@
539+#!/usr/bin/env python
540+# -*- coding: utf-8 -*-
541+#
542+# Copyright 2014-2015 Canonical Limited.
543+#
544+# This file is part of charm-helpers.
545+#
546+# charm-helpers is free software: you can redistribute it and/or modify
547+# it under the terms of the GNU Lesser General Public License version 3 as
548+# published by the Free Software Foundation.
549+#
550+# charm-helpers is distributed in the hope that it will be useful,
551+# but WITHOUT ANY WARRANTY; without even the implied warranty of
552+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
553+# GNU Lesser General Public License for more details.
554+#
555+# You should have received a copy of the GNU Lesser General Public License
556+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
557+#
558+#
559+# Authors:
560+# Kapil Thangavelu <kapil.foss@gmail.com>
561+#
562+"""
563+Intro
564+-----
565+
566+A simple way to store state in units. This provides a key value
567+storage with support for versioned, transactional operation,
568+and can calculate deltas from previous values to simplify unit logic
569+when processing changes.
570+
571+
572+Hook Integration
573+----------------
574+
575+There are several extant frameworks for hook execution, including
576+
577+ - charmhelpers.core.hookenv.Hooks
578+ - charmhelpers.core.services.ServiceManager
579+
580+The storage classes are framework agnostic, one simple integration is
581+via the HookData contextmanager. It will record the current hook
582+execution environment (including relation data, config data, etc.),
583+setup a transaction and allow easy access to the changes from
584+previously seen values. One consequence of the integration is the
585+reservation of particular keys ('rels', 'unit', 'env', 'config',
586+'charm_revisions') for their respective values.
587+
588+Here's a fully worked integration example using hookenv.Hooks::
589+
590+ from charmhelper.core import hookenv, unitdata
591+
592+ hook_data = unitdata.HookData()
593+ db = unitdata.kv()
594+ hooks = hookenv.Hooks()
595+
596+ @hooks.hook
597+ def config_changed():
598+ # Print all changes to configuration from previously seen
599+ # values.
600+ for changed, (prev, cur) in hook_data.conf.items():
601+ print('config changed', changed,
602+ 'previous value', prev,
603+ 'current value', cur)
604+
605+ # Get some unit specific bookeeping
606+ if not db.get('pkg_key'):
607+ key = urllib.urlopen('https://example.com/pkg_key').read()
608+ db.set('pkg_key', key)
609+
610+ # Directly access all charm config as a mapping.
611+ conf = db.getrange('config', True)
612+
613+ # Directly access all relation data as a mapping
614+ rels = db.getrange('rels', True)
615+
616+ if __name__ == '__main__':
617+ with hook_data():
618+ hook.execute()
619+
620+
621+A more basic integration is via the hook_scope context manager which simply
622+manages transaction scope (and records hook name, and timestamp)::
623+
624+ >>> from unitdata import kv
625+ >>> db = kv()
626+ >>> with db.hook_scope('install'):
627+ ... # do work, in transactional scope.
628+ ... db.set('x', 1)
629+ >>> db.get('x')
630+ 1
631+
632+
633+Usage
634+-----
635+
636+Values are automatically json de/serialized to preserve basic typing
637+and complex data struct capabilities (dicts, lists, ints, booleans, etc).
638+
639+Individual values can be manipulated via get/set::
640+
641+ >>> kv.set('y', True)
642+ >>> kv.get('y')
643+ True
644+
645+ # We can set complex values (dicts, lists) as a single key.
646+ >>> kv.set('config', {'a': 1, 'b': True'})
647+
648+ # Also supports returning dictionaries as a record which
649+ # provides attribute access.
650+ >>> config = kv.get('config', record=True)
651+ >>> config.b
652+ True
653+
654+
655+Groups of keys can be manipulated with update/getrange::
656+
657+ >>> kv.update({'z': 1, 'y': 2}, prefix="gui.")
658+ >>> kv.getrange('gui.', strip=True)
659+ {'z': 1, 'y': 2}
660+
661+When updating values, its very helpful to understand which values
662+have actually changed and how have they changed. The storage
663+provides a delta method to provide for this::
664+
665+ >>> data = {'debug': True, 'option': 2}
666+ >>> delta = kv.delta(data, 'config.')
667+ >>> delta.debug.previous
668+ None
669+ >>> delta.debug.current
670+ True
671+ >>> delta
672+ {'debug': (None, True), 'option': (None, 2)}
673+
674+Note the delta method does not persist the actual change, it needs to
675+be explicitly saved via 'update' method::
676+
677+ >>> kv.update(data, 'config.')
678+
679+Values modified in the context of a hook scope retain historical values
680+associated to the hookname.
681+
682+ >>> with db.hook_scope('config-changed'):
683+ ... db.set('x', 42)
684+ >>> db.gethistory('x')
685+ [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'),
686+ (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')]
687+
688+"""
689+
690+import collections
691+import contextlib
692+import datetime
693+import json
694+import os
695+import pprint
696+import sqlite3
697+import sys
698+
699+__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>'
700+
701+
702+class Storage(object):
703+ """Simple key value database for local unit state within charms.
704+
705+ Modifications are automatically committed at hook exit. That's
706+ currently regardless of exit code.
707+
708+ To support dicts, lists, integer, floats, and booleans values
709+ are automatically json encoded/decoded.
710+ """
711+ def __init__(self, path=None):
712+ self.db_path = path
713+ if path is None:
714+ self.db_path = os.path.join(
715+ os.environ.get('CHARM_DIR', ''), '.unit-state.db')
716+ self.conn = sqlite3.connect('%s' % self.db_path)
717+ self.cursor = self.conn.cursor()
718+ self.revision = None
719+ self._closed = False
720+ self._init()
721+
722+ def close(self):
723+ if self._closed:
724+ return
725+ self.flush(False)
726+ self.cursor.close()
727+ self.conn.close()
728+ self._closed = True
729+
730+ def _scoped_query(self, stmt, params=None):
731+ if params is None:
732+ params = []
733+ return stmt, params
734+
735+ def get(self, key, default=None, record=False):
736+ self.cursor.execute(
737+ *self._scoped_query(
738+ 'select data from kv where key=?', [key]))
739+ result = self.cursor.fetchone()
740+ if not result:
741+ return default
742+ if record:
743+ return Record(json.loads(result[0]))
744+ return json.loads(result[0])
745+
746+ def getrange(self, key_prefix, strip=False):
747+ stmt = "select key, data from kv where key like '%s%%'" % key_prefix
748+ self.cursor.execute(*self._scoped_query(stmt))
749+ result = self.cursor.fetchall()
750+
751+ if not result:
752+ return None
753+ if not strip:
754+ key_prefix = ''
755+ return dict([
756+ (k[len(key_prefix):], json.loads(v)) for k, v in result])
757+
758+ def update(self, mapping, prefix=""):
759+ for k, v in mapping.items():
760+ self.set("%s%s" % (prefix, k), v)
761+
762+ def unset(self, key):
763+ self.cursor.execute('delete from kv where key=?', [key])
764+ if self.revision and self.cursor.rowcount:
765+ self.cursor.execute(
766+ 'insert into kv_revisions values (?, ?, ?)',
767+ [key, self.revision, json.dumps('DELETED')])
768+
769+ def set(self, key, value):
770+ serialized = json.dumps(value)
771+
772+ self.cursor.execute(
773+ 'select data from kv where key=?', [key])
774+ exists = self.cursor.fetchone()
775+
776+ # Skip mutations to the same value
777+ if exists:
778+ if exists[0] == serialized:
779+ return value
780+
781+ if not exists:
782+ self.cursor.execute(
783+ 'insert into kv (key, data) values (?, ?)',
784+ (key, serialized))
785+ else:
786+ self.cursor.execute('''
787+ update kv
788+ set data = ?
789+ where key = ?''', [serialized, key])
790+
791+ # Save
792+ if not self.revision:
793+ return value
794+
795+ self.cursor.execute(
796+ 'select 1 from kv_revisions where key=? and revision=?',
797+ [key, self.revision])
798+ exists = self.cursor.fetchone()
799+
800+ if not exists:
801+ self.cursor.execute(
802+ '''insert into kv_revisions (
803+ revision, key, data) values (?, ?, ?)''',
804+ (self.revision, key, serialized))
805+ else:
806+ self.cursor.execute(
807+ '''
808+ update kv_revisions
809+ set data = ?
810+ where key = ?
811+ and revision = ?''',
812+ [serialized, key, self.revision])
813+
814+ return value
815+
816+ def delta(self, mapping, prefix):
817+ """
818+ return a delta containing values that have changed.
819+ """
820+ previous = self.getrange(prefix, strip=True)
821+ if not previous:
822+ pk = set()
823+ else:
824+ pk = set(previous.keys())
825+ ck = set(mapping.keys())
826+ delta = DeltaSet()
827+
828+ # added
829+ for k in ck.difference(pk):
830+ delta[k] = Delta(None, mapping[k])
831+
832+ # removed
833+ for k in pk.difference(ck):
834+ delta[k] = Delta(previous[k], None)
835+
836+ # changed
837+ for k in pk.intersection(ck):
838+ c = mapping[k]
839+ p = previous[k]
840+ if c != p:
841+ delta[k] = Delta(p, c)
842+
843+ return delta
844+
845+ @contextlib.contextmanager
846+ def hook_scope(self, name=""):
847+ """Scope all future interactions to the current hook execution
848+ revision."""
849+ assert not self.revision
850+ self.cursor.execute(
851+ 'insert into hooks (hook, date) values (?, ?)',
852+ (name or sys.argv[0],
853+ datetime.datetime.utcnow().isoformat()))
854+ self.revision = self.cursor.lastrowid
855+ try:
856+ yield self.revision
857+ self.revision = None
858+ except:
859+ self.flush(False)
860+ self.revision = None
861+ raise
862+ else:
863+ self.flush()
864+
865+ def flush(self, save=True):
866+ if save:
867+ self.conn.commit()
868+ elif self._closed:
869+ return
870+ else:
871+ self.conn.rollback()
872+
873+ def _init(self):
874+ self.cursor.execute('''
875+ create table if not exists kv (
876+ key text,
877+ data text,
878+ primary key (key)
879+ )''')
880+ self.cursor.execute('''
881+ create table if not exists kv_revisions (
882+ key text,
883+ revision integer,
884+ data text,
885+ primary key (key, revision)
886+ )''')
887+ self.cursor.execute('''
888+ create table if not exists hooks (
889+ version integer primary key autoincrement,
890+ hook text,
891+ date text
892+ )''')
893+ self.conn.commit()
894+
895+ def gethistory(self, key, deserialize=False):
896+ self.cursor.execute(
897+ '''
898+ select kv.revision, kv.key, kv.data, h.hook, h.date
899+ from kv_revisions kv,
900+ hooks h
901+ where kv.key=?
902+ and kv.revision = h.version
903+ ''', [key])
904+ if deserialize is False:
905+ return self.cursor.fetchall()
906+ return map(_parse_history, self.cursor.fetchall())
907+
908+ def debug(self, fh=sys.stderr):
909+ self.cursor.execute('select * from kv')
910+ pprint.pprint(self.cursor.fetchall(), stream=fh)
911+ self.cursor.execute('select * from kv_revisions')
912+ pprint.pprint(self.cursor.fetchall(), stream=fh)
913+
914+
915+def _parse_history(d):
916+ return (d[0], d[1], json.loads(d[2]), d[3],
917+ datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f"))
918+
919+
920+class HookData(object):
921+ """Simple integration for existing hook exec frameworks.
922+
923+ Records all unit information, and stores deltas for processing
924+ by the hook.
925+
926+ Sample::
927+
928+ from charmhelper.core import hookenv, unitdata
929+
930+ changes = unitdata.HookData()
931+ db = unitdata.kv()
932+ hooks = hookenv.Hooks()
933+
934+ @hooks.hook
935+ def config_changed():
936+ # View all changes to configuration
937+ for changed, (prev, cur) in changes.conf.items():
938+ print('config changed', changed,
939+ 'previous value', prev,
940+ 'current value', cur)
941+
942+ # Get some unit specific bookeeping
943+ if not db.get('pkg_key'):
944+ key = urllib.urlopen('https://example.com/pkg_key').read()
945+ db.set('pkg_key', key)
946+
947+ if __name__ == '__main__':
948+ with changes():
949+ hook.execute()
950+
951+ """
952+ def __init__(self):
953+ self.kv = kv()
954+ self.conf = None
955+ self.rels = None
956+
957+ @contextlib.contextmanager
958+ def __call__(self):
959+ from charmhelpers.core import hookenv
960+ hook_name = hookenv.hook_name()
961+
962+ with self.kv.hook_scope(hook_name):
963+ self._record_charm_version(hookenv.charm_dir())
964+ delta_config, delta_relation = self._record_hook(hookenv)
965+ yield self.kv, delta_config, delta_relation
966+
967+ def _record_charm_version(self, charm_dir):
968+ # Record revisions.. charm revisions are meaningless
969+ # to charm authors as they don't control the revision.
970+ # so logic dependnent on revision is not particularly
971+ # useful, however it is useful for debugging analysis.
972+ charm_rev = open(
973+ os.path.join(charm_dir, 'revision')).read().strip()
974+ charm_rev = charm_rev or '0'
975+ revs = self.kv.get('charm_revisions', [])
976+ if charm_rev not in revs:
977+ revs.append(charm_rev.strip() or '0')
978+ self.kv.set('charm_revisions', revs)
979+
980+ def _record_hook(self, hookenv):
981+ data = hookenv.execution_environment()
982+ self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
983+ self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
984+ self.kv.set('env', data['env'])
985+ self.kv.set('unit', data['unit'])
986+ self.kv.set('relid', data.get('relid'))
987+ return conf_delta, rels_delta
988+
989+
990+class Record(dict):
991+
992+ __slots__ = ()
993+
994+ def __getattr__(self, k):
995+ if k in self:
996+ return self[k]
997+ raise AttributeError(k)
998+
999+
1000+class DeltaSet(Record):
1001+
1002+ __slots__ = ()
1003+
1004+
1005+Delta = collections.namedtuple('Delta', ['previous', 'current'])
1006+
1007+
1008+_KV = None
1009+
1010+
1011+def kv():
1012+ global _KV
1013+ if _KV is None:
1014+ _KV = Storage()
1015+ return _KV
1016
1017=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
1018--- hooks/charmhelpers/fetch/archiveurl.py 2015-01-26 09:44:11 +0000
1019+++ hooks/charmhelpers/fetch/archiveurl.py 2015-02-24 06:12:30 +0000
1020@@ -18,6 +18,16 @@
1021 import hashlib
1022 import re
1023
1024+from charmhelpers.fetch import (
1025+ BaseFetchHandler,
1026+ UnhandledSource
1027+)
1028+from charmhelpers.payload.archive import (
1029+ get_archive_handler,
1030+ extract,
1031+)
1032+from charmhelpers.core.host import mkdir, check_hash
1033+
1034 import six
1035 if six.PY3:
1036 from urllib.request import (
1037@@ -35,16 +45,6 @@
1038 )
1039 from urlparse import urlparse, urlunparse, parse_qs
1040
1041-from charmhelpers.fetch import (
1042- BaseFetchHandler,
1043- UnhandledSource
1044-)
1045-from charmhelpers.payload.archive import (
1046- get_archive_handler,
1047- extract,
1048-)
1049-from charmhelpers.core.host import mkdir, check_hash
1050-
1051
1052 def splituser(host):
1053 '''urllib.splituser(), but six's support of this seems broken'''
1054
1055=== modified file 'hooks/charmhelpers/fetch/giturl.py'
1056--- hooks/charmhelpers/fetch/giturl.py 2015-01-26 09:44:11 +0000
1057+++ hooks/charmhelpers/fetch/giturl.py 2015-02-24 06:12:30 +0000
1058@@ -32,7 +32,7 @@
1059 apt_install("python-git")
1060 from git import Repo
1061
1062-from git.exc import GitCommandError
1063+from git.exc import GitCommandError # noqa E402
1064
1065
1066 class GitUrlFetchHandler(BaseFetchHandler):
1067
1068=== modified file 'hooks/nova_cc_hooks.py'
1069--- hooks/nova_cc_hooks.py 2015-01-13 14:44:54 +0000
1070+++ hooks/nova_cc_hooks.py 2015-02-24 06:12:30 +0000
1071@@ -866,7 +866,9 @@
1072 hostname = nrpe.get_nagios_hostname()
1073 current_unit = nrpe.get_nagios_unit_name()
1074 nrpe_setup = nrpe.NRPE(hostname=hostname)
1075+ nrpe.copy_nrpe_checks()
1076 nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)
1077+ nrpe.add_haproxy_checks(nrpe_setup, current_unit)
1078 nrpe_setup.write()
1079
1080
1081
1082=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
1083--- tests/charmhelpers/contrib/amulet/utils.py 2015-01-26 09:44:11 +0000
1084+++ tests/charmhelpers/contrib/amulet/utils.py 2015-02-24 06:12:30 +0000
1085@@ -169,8 +169,13 @@
1086 cmd = 'pgrep -o -f {}'.format(service)
1087 else:
1088 cmd = 'pgrep -o {}'.format(service)
1089- proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip())
1090- return self._get_dir_mtime(sentry_unit, proc_dir)
1091+ cmd = cmd + ' | grep -v pgrep || exit 0'
1092+ cmd_out = sentry_unit.run(cmd)
1093+ self.log.debug('CMDout: ' + str(cmd_out))
1094+ if cmd_out[0]:
1095+ self.log.debug('Pid for %s %s' % (service, str(cmd_out[0])))
1096+ proc_dir = '/proc/{}'.format(cmd_out[0].strip())
1097+ return self._get_dir_mtime(sentry_unit, proc_dir)
1098
1099 def service_restarted(self, sentry_unit, service, filename,
1100 pgrep_full=False, sleep_time=20):
1101@@ -187,6 +192,121 @@
1102 else:
1103 return False
1104
1105+ def service_restarted_since(self, sentry_unit, mtime, service,
1106+ pgrep_full=False, sleep_time=20,
1107+ retry_count=2):
1108+ """Check if service was been started after a given time.
1109+
1110+ Args:
1111+ sentry_unit (sentry): The sentry unit to check for the service on
1112+ mtime (float): The epoch time to check against
1113+ service (string): service name to look for in process table
1114+ pgrep_full (boolean): Use full command line search mode with pgrep
1115+ sleep_time (int): Seconds to sleep before looking for process
1116+ retry_count (int): If service is not found, how many times to retry
1117+
1118+ Returns:
1119+ bool: True if service found and its start time it newer than mtime,
1120+ False if service is older than mtime or if service was
1121+ not found.
1122+ """
1123+ self.log.debug('Checking %s restarted since %s' % (service, mtime))
1124+ time.sleep(sleep_time)
1125+ proc_start_time = self._get_proc_start_time(sentry_unit, service,
1126+ pgrep_full)
1127+ while retry_count > 0 and not proc_start_time:
1128+ self.log.debug('No pid file found for service %s, will retry %i '
1129+ 'more times' % (service, retry_count))
1130+ time.sleep(30)
1131+ proc_start_time = self._get_proc_start_time(sentry_unit, service,
1132+ pgrep_full)
1133+ retry_count = retry_count - 1
1134+
1135+ if not proc_start_time:
1136+ self.log.warn('No proc start time found, assuming service did '
1137+ 'not start')
1138+ return False
1139+ if proc_start_time >= mtime:
1140+ self.log.debug('proc start time is newer than provided mtime'
1141+ '(%s >= %s)' % (proc_start_time, mtime))
1142+ return True
1143+ else:
1144+ self.log.warn('proc start time (%s) is older than provided mtime '
1145+ '(%s), service did not restart' % (proc_start_time,
1146+ mtime))
1147+ return False
1148+
1149+ def config_updated_since(self, sentry_unit, filename, mtime,
1150+ sleep_time=20):
1151+ """Check if file was modified after a given time.
1152+
1153+ Args:
1154+ sentry_unit (sentry): The sentry unit to check the file mtime on
1155+ filename (string): The file to check mtime of
1156+ mtime (float): The epoch time to check against
1157+ sleep_time (int): Seconds to sleep before looking for process
1158+
1159+ Returns:
1160+ bool: True if file was modified more recently than mtime, False if
1161+ file was modified before mtime,
1162+ """
1163+ self.log.debug('Checking %s updated since %s' % (filename, mtime))
1164+ time.sleep(sleep_time)
1165+ file_mtime = self._get_file_mtime(sentry_unit, filename)
1166+ if file_mtime >= mtime:
1167+ self.log.debug('File mtime is newer than provided mtime '
1168+ '(%s >= %s)' % (file_mtime, mtime))
1169+ return True
1170+ else:
1171+ self.log.warn('File mtime %s is older than provided mtime %s'
1172+ % (file_mtime, mtime))
1173+ return False
1174+
1175+ def validate_service_config_changed(self, sentry_unit, mtime, service,
1176+ filename, pgrep_full=False,
1177+ sleep_time=20, retry_count=2):
1178+ """Check service and file were updated after mtime
1179+
1180+ Args:
1181+ sentry_unit (sentry): The sentry unit to check for the service on
1182+ mtime (float): The epoch time to check against
1183+ service (string): service name to look for in process table
1184+ filename (string): The file to check mtime of
1185+ pgrep_full (boolean): Use full command line search mode with pgrep
1186+ sleep_time (int): Seconds to sleep before looking for process
1187+ retry_count (int): If service is not found, how many times to retry
1188+
1189+ Typical Usage:
1190+ u = OpenStackAmuletUtils(ERROR)
1191+ ...
1192+ mtime = u.get_sentry_time(self.cinder_sentry)
1193+ self.d.configure('cinder', {'verbose': 'True', 'debug': 'True'})
1194+ if not u.validate_service_config_changed(self.cinder_sentry,
1195+ mtime,
1196+ 'cinder-api',
1197+ '/etc/cinder/cinder.conf')
1198+ amulet.raise_status(amulet.FAIL, msg='update failed')
1199+ Returns:
1200+ bool: True if both service and file where updated/restarted after
1201+ mtime, False if service is older than mtime or if service was
1202+ not found or if filename was modified before mtime.
1203+ """
1204+ self.log.debug('Checking %s restarted since %s' % (service, mtime))
1205+ time.sleep(sleep_time)
1206+ service_restart = self.service_restarted_since(sentry_unit, mtime,
1207+ service,
1208+ pgrep_full=pgrep_full,
1209+ sleep_time=0,
1210+ retry_count=retry_count)
1211+ config_update = self.config_updated_since(sentry_unit, filename, mtime,
1212+ sleep_time=0)
1213+ return service_restart and config_update
1214+
1215+ def get_sentry_time(self, sentry_unit):
1216+ """Return current epoch time on a sentry"""
1217+ cmd = "date +'%s'"
1218+ return float(sentry_unit.run(cmd)[0])
1219+
1220 def relation_error(self, name, data):
1221 return 'unexpected relation data in {} - {}'.format(name, data)
1222
1223
1224=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
1225--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-01-26 09:44:11 +0000
1226+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-02-24 06:12:30 +0000
1227@@ -71,16 +71,19 @@
1228 services.append(this_service)
1229 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
1230 'ceph-osd', 'ceph-radosgw']
1231+ # Openstack subordinate charms do not expose an origin option as that
1232+ # is controlled by the principle
1233+ ignore = ['neutron-openvswitch']
1234
1235 if self.openstack:
1236 for svc in services:
1237- if svc['name'] not in use_source:
1238+ if svc['name'] not in use_source + ignore:
1239 config = {'openstack-origin': self.openstack}
1240 self.d.configure(svc['name'], config)
1241
1242 if self.source:
1243 for svc in services:
1244- if svc['name'] in use_source:
1245+ if svc['name'] in use_source and svc['name'] not in ignore:
1246 config = {'source': self.source}
1247 self.d.configure(svc['name'], config)
1248

Subscribers

People subscribed via source and target branches