Merge lp:~stub/charms/trusty/postgresql/rewrite-charmhelpers into lp:charms/trusty/postgresql

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 130
Proposed branch: lp:~stub/charms/trusty/postgresql/rewrite-charmhelpers
Merge into: lp:charms/trusty/postgresql
Diff against target: 1113 lines (+902/-25)
16 files modified
charm-helpers.yaml (+3/-0)
hooks/charmhelpers/context.py (+206/-0)
hooks/charmhelpers/contrib/__init__.py (+15/-0)
hooks/charmhelpers/contrib/charmsupport/__init__.py (+15/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+360/-0)
hooks/charmhelpers/core/files.py (+45/-0)
hooks/charmhelpers/core/hookenv.py (+17/-1)
hooks/charmhelpers/core/host.py (+47/-5)
hooks/charmhelpers/core/services/helpers.py (+2/-2)
hooks/charmhelpers/core/templating.py (+5/-1)
hooks/charmhelpers/fetch/__init__.py (+23/-14)
hooks/charmhelpers/fetch/archiveurl.py (+7/-1)
hooks/charmhelpers/fetch/giturl.py (+1/-1)
hooks/charmhelpers/payload/__init__.py (+17/-0)
hooks/charmhelpers/payload/archive.py (+73/-0)
hooks/charmhelpers/payload/execd.py (+66/-0)
To merge this branch: bzr merge lp:~stub/charms/trusty/postgresql/rewrite-charmhelpers
Reviewer Review Type Date Requested Status
Charles Butler (community) Approve
Review via email: mp+267645@code.launchpad.net

Description of the change

Charmhelpers sync for lp:~stub/charms/trusty/postgresql/rewrite.

This branch contains charmhelpers updates, and not worth reviewing. It serves as a dependant branch to remove the noise from the main merge proposal.

To post a comment you must log in.
Revision history for this message
Charles Butler (lazypower) wrote :

Stub,

I did a quick sanity check and found some proof/lint errors that were pre-existing. Unrelated to this merge +1 LGTM.

Merged and pushed, thanks for breaking apart the noise.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers.yaml'
2--- charm-helpers.yaml 2015-06-25 11:39:19 +0000
3+++ charm-helpers.yaml 2015-08-11 11:19:35 +0000
4@@ -1,6 +1,9 @@
5 destination: hooks/charmhelpers
6 branch: lp:~stub/charm-helpers/integration
7 include:
8+ - context
9 - coordinator
10 - core
11 - fetch
12+ - payload
13+ - contrib.charmsupport.nrpe
14
15=== added file 'hooks/charmhelpers/context.py'
16--- hooks/charmhelpers/context.py 1970-01-01 00:00:00 +0000
17+++ hooks/charmhelpers/context.py 2015-08-11 11:19:35 +0000
18@@ -0,0 +1,206 @@
19+# Copyright 2015 Canonical Limited.
20+#
21+# This file is part of charm-helpers.
22+#
23+# charm-helpers is free software: you can redistribute it and/or modify
24+# it under the terms of the GNU Lesser General Public License version 3 as
25+# published by the Free Software Foundation.
26+#
27+# charm-helpers is distributed in the hope that it will be useful,
28+# but WITHOUT ANY WARRANTY; without even the implied warranty of
29+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+# GNU Lesser General Public License for more details.
31+#
32+# You should have received a copy of the GNU Lesser General Public License
33+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
34+'''
35+A Pythonic API to interact with the charm hook environment.
36+
37+:author: Stuart Bishop <stuart.bishop@canonical.com>
38+'''
39+
40+import six
41+
42+from charmhelpers.core import hookenv
43+
44+from collections import OrderedDict
45+if six.PY3:
46+ from collections import UserDict # pragma: nocover
47+else:
48+ from UserDict import IterableUserDict as UserDict # pragma: nocover
49+
50+
51+class Relations(OrderedDict):
52+ '''Mapping relation name -> relation id -> Relation.
53+
54+ >>> rels = Relations()
55+ >>> rels['sprog']['sprog:12']['client/6']['widget']
56+ 'remote widget'
57+ >>> rels['sprog']['sprog:12'].local['widget'] = 'local widget'
58+ >>> rels['sprog']['sprog:12'].local['widget']
59+ 'local widget'
60+ >>> rels.peer.local['widget']
61+ 'local widget on the peer relation'
62+ '''
63+ def __init__(self):
64+ super(Relations, self).__init__()
65+ for relname in sorted(hookenv.relation_types()):
66+ self[relname] = OrderedDict()
67+ relids = hookenv.relation_ids(relname)
68+ relids.sort(key=lambda x: int(x.split(':', 1)[-1]))
69+ for relid in relids:
70+ self[relname][relid] = Relation(relid)
71+
72+ @property
73+ def peer(self):
74+ peer_relid = hookenv.peer_relation_id()
75+ for rels in self.values():
76+ if peer_relid in rels:
77+ return rels[peer_relid]
78+
79+
80+class Relation(OrderedDict):
81+ '''Mapping of unit -> remote RelationInfo for a relation.
82+
83+ This is an OrderedDict mapping, ordered numerically by
84+ by unit number.
85+
86+ Also provides access to the local RelationInfo, and peer RelationInfo
87+ instances by the 'local' and 'peers' attributes.
88+
89+ >>> r = Relation('sprog:12')
90+ >>> r.keys()
91+ ['client/9', 'client/10'] # Ordered numerically
92+ >>> r['client/10']['widget'] # A remote RelationInfo setting
93+ 'remote widget'
94+ >>> r.local['widget'] # The local RelationInfo setting
95+ 'local widget'
96+ '''
97+ relid = None # The relation id.
98+ relname = None # The relation name (also known as relation type).
99+ service = None # The remote service name, if known.
100+ local = None # The local end's RelationInfo.
101+ peers = None # Map of peer -> RelationInfo. None if no peer relation.
102+
103+ def __init__(self, relid):
104+ remote_units = hookenv.related_units(relid)
105+ remote_units.sort(key=lambda u: int(u.split('/', 1)[-1]))
106+ super(Relation, self).__init__((unit, RelationInfo(relid, unit))
107+ for unit in remote_units)
108+
109+ self.relname = relid.split(':', 1)[0]
110+ self.relid = relid
111+ self.local = RelationInfo(relid, hookenv.local_unit())
112+
113+ for relinfo in self.values():
114+ self.service = relinfo.service
115+ break
116+
117+ # If we have peers, and they have joined both the provided peer
118+ # relation and this relation, we can peek at their data too.
119+ # This is useful for creating consensus without leadership.
120+ peer_relid = hookenv.peer_relation_id()
121+ if peer_relid and peer_relid != relid:
122+ peers = hookenv.related_units(peer_relid)
123+ if peers:
124+ peers.sort(key=lambda u: int(u.split('/', 1)[-1]))
125+ self.peers = OrderedDict((peer, RelationInfo(relid, peer))
126+ for peer in peers)
127+ else:
128+ self.peers = OrderedDict()
129+ else:
130+ self.peers = None
131+
132+ def __str__(self):
133+ return '{} ({})'.format(self.relid, self.service)
134+
135+
136+class RelationInfo(UserDict):
137+ '''The bag of data at an end of a relation.
138+
139+ Every unit participating in a relation has a single bag of
140+ data associated with that relation. This is that bag.
141+
142+ The bag of data for the local unit may be updated. Remote data
143+ is immutable and will remain static for the duration of the hook.
144+
145+ Changes made to the local units relation data only become visible
146+ to other units after the hook completes successfully. If the hook
147+ does not complete successfully, the changes are rolled back.
148+
149+ Unlike standard Python mappings, setting an item to None is the
150+ same as deleting it.
151+
152+ >>> relinfo = RelationInfo('db:12') # Default is the local unit.
153+ >>> relinfo['user'] = 'fred'
154+ >>> relinfo['user']
155+ 'fred'
156+ >>> relinfo['user'] = None
157+ >>> 'fred' in relinfo
158+ False
159+
160+ This class wraps hookenv.relation_get and hookenv.relation_set.
161+ All caching is left up to these two methods to avoid synchronization
162+ issues. Data is only loaded on demand.
163+ '''
164+ relid = None # The relation id.
165+ relname = None # The relation name (also know as the relation type).
166+ unit = None # The unit id.
167+ number = None # The unit number (integer).
168+ service = None # The service name.
169+
170+ def __init__(self, relid, unit):
171+ self.relname = relid.split(':', 1)[0]
172+ self.relid = relid
173+ self.unit = unit
174+ self.service, num = self.unit.split('/', 1)
175+ self.number = int(num)
176+
177+ def __str__(self):
178+ return '{} ({})'.format(self.relid, self.unit)
179+
180+ @property
181+ def data(self):
182+ return hookenv.relation_get(rid=self.relid, unit=self.unit)
183+
184+ def __setitem__(self, key, value):
185+ if self.unit != hookenv.local_unit():
186+ raise TypeError('Attempting to set {} on remote unit {}'
187+ ''.format(key, self.unit))
188+ if value is not None and not isinstance(value, six.string_types):
189+ # We don't do implicit casting. This would cause simple
190+ # types like integers to be read back as strings in subsequent
191+ # hooks, and mutable types would require a lot of wrapping
192+ # to ensure relation-set gets called when they are mutated.
193+ raise ValueError('Only string values allowed')
194+ hookenv.relation_set(self.relid, {key: value})
195+
196+ def __delitem__(self, key):
197+ # Deleting a key and setting it to null is the same thing in
198+ # Juju relations.
199+ self[key] = None
200+
201+
202+class Leader(UserDict):
203+ def __init__(self):
204+ pass # Don't call superclass initializer, as it will nuke self.data
205+
206+ @property
207+ def data(self):
208+ return hookenv.leader_get()
209+
210+ def __setitem__(self, key, value):
211+ if not hookenv.is_leader():
212+ raise TypeError('Not the leader. Cannot change leader settings.')
213+ if value is not None and not isinstance(value, six.string_types):
214+ # We don't do implicit casting. This would cause simple
215+ # types like integers to be read back as strings in subsequent
216+ # hooks, and mutable types would require a lot of wrapping
217+ # to ensure leader-set gets called when they are mutated.
218+ raise ValueError('Only string values allowed')
219+ hookenv.leader_set({key: value})
220+
221+ def __delitem__(self, key):
222+ # Deleting a key and setting it to null is the same thing in
223+ # Juju leadership settings.
224+ self[key] = None
225
226=== added directory 'hooks/charmhelpers/contrib'
227=== added file 'hooks/charmhelpers/contrib/__init__.py'
228--- hooks/charmhelpers/contrib/__init__.py 1970-01-01 00:00:00 +0000
229+++ hooks/charmhelpers/contrib/__init__.py 2015-08-11 11:19:35 +0000
230@@ -0,0 +1,15 @@
231+# Copyright 2014-2015 Canonical Limited.
232+#
233+# This file is part of charm-helpers.
234+#
235+# charm-helpers is free software: you can redistribute it and/or modify
236+# it under the terms of the GNU Lesser General Public License version 3 as
237+# published by the Free Software Foundation.
238+#
239+# charm-helpers is distributed in the hope that it will be useful,
240+# but WITHOUT ANY WARRANTY; without even the implied warranty of
241+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
242+# GNU Lesser General Public License for more details.
243+#
244+# You should have received a copy of the GNU Lesser General Public License
245+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
246
247=== added directory 'hooks/charmhelpers/contrib/charmsupport'
248=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
249--- hooks/charmhelpers/contrib/charmsupport/__init__.py 1970-01-01 00:00:00 +0000
250+++ hooks/charmhelpers/contrib/charmsupport/__init__.py 2015-08-11 11:19:35 +0000
251@@ -0,0 +1,15 @@
252+# Copyright 2014-2015 Canonical Limited.
253+#
254+# This file is part of charm-helpers.
255+#
256+# charm-helpers is free software: you can redistribute it and/or modify
257+# it under the terms of the GNU Lesser General Public License version 3 as
258+# published by the Free Software Foundation.
259+#
260+# charm-helpers is distributed in the hope that it will be useful,
261+# but WITHOUT ANY WARRANTY; without even the implied warranty of
262+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
263+# GNU Lesser General Public License for more details.
264+#
265+# You should have received a copy of the GNU Lesser General Public License
266+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
267
268=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
269--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
270+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-08-11 11:19:35 +0000
271@@ -0,0 +1,360 @@
272+# Copyright 2014-2015 Canonical Limited.
273+#
274+# This file is part of charm-helpers.
275+#
276+# charm-helpers is free software: you can redistribute it and/or modify
277+# it under the terms of the GNU Lesser General Public License version 3 as
278+# published by the Free Software Foundation.
279+#
280+# charm-helpers is distributed in the hope that it will be useful,
281+# but WITHOUT ANY WARRANTY; without even the implied warranty of
282+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
283+# GNU Lesser General Public License for more details.
284+#
285+# You should have received a copy of the GNU Lesser General Public License
286+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
287+
288+"""Compatibility with the nrpe-external-master charm"""
289+# Copyright 2012 Canonical Ltd.
290+#
291+# Authors:
292+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
293+
294+import subprocess
295+import pwd
296+import grp
297+import os
298+import glob
299+import shutil
300+import re
301+import shlex
302+import yaml
303+
304+from charmhelpers.core.hookenv import (
305+ config,
306+ local_unit,
307+ log,
308+ relation_ids,
309+ relation_set,
310+ relations_of_type,
311+)
312+
313+from charmhelpers.core.host import service
314+
315+# This module adds compatibility with the nrpe-external-master and plain nrpe
316+# subordinate charms. To use it in your charm:
317+#
318+# 1. Update metadata.yaml
319+#
320+# provides:
321+# (...)
322+# nrpe-external-master:
323+# interface: nrpe-external-master
324+# scope: container
325+#
326+# and/or
327+#
328+# provides:
329+# (...)
330+# local-monitors:
331+# interface: local-monitors
332+# scope: container
333+
334+#
335+# 2. Add the following to config.yaml
336+#
337+# nagios_context:
338+# default: "juju"
339+# type: string
340+# description: |
341+# Used by the nrpe subordinate charms.
342+# A string that will be prepended to instance name to set the host name
343+# in nagios. So for instance the hostname would be something like:
344+# juju-myservice-0
345+# If you're running multiple environments with the same services in them
346+# this allows you to differentiate between them.
347+# nagios_servicegroups:
348+# default: ""
349+# type: string
350+# description: |
351+# A comma-separated list of nagios servicegroups.
352+# If left empty, the nagios_context will be used as the servicegroup
353+#
354+# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
355+#
356+# 4. Update your hooks.py with something like this:
357+#
358+# from charmsupport.nrpe import NRPE
359+# (...)
360+# def update_nrpe_config():
361+# nrpe_compat = NRPE()
362+# nrpe_compat.add_check(
363+# shortname = "myservice",
364+# description = "Check MyService",
365+# check_cmd = "check_http -w 2 -c 10 http://localhost"
366+# )
367+# nrpe_compat.add_check(
368+# "myservice_other",
369+# "Check for widget failures",
370+# check_cmd = "/srv/myapp/scripts/widget_check"
371+# )
372+# nrpe_compat.write()
373+#
374+# def config_changed():
375+# (...)
376+# update_nrpe_config()
377+#
378+# def nrpe_external_master_relation_changed():
379+# update_nrpe_config()
380+#
381+# def local_monitors_relation_changed():
382+# update_nrpe_config()
383+#
384+# 5. ln -s hooks.py nrpe-external-master-relation-changed
385+# ln -s hooks.py local-monitors-relation-changed
386+
387+
388+class CheckException(Exception):
389+ pass
390+
391+
392+class Check(object):
393+ shortname_re = '[A-Za-z0-9-_]+$'
394+ service_template = ("""
395+#---------------------------------------------------
396+# This file is Juju managed
397+#---------------------------------------------------
398+define service {{
399+ use active-service
400+ host_name {nagios_hostname}
401+ service_description {nagios_hostname}[{shortname}] """
402+ """{description}
403+ check_command check_nrpe!{command}
404+ servicegroups {nagios_servicegroup}
405+}}
406+""")
407+
408+ def __init__(self, shortname, description, check_cmd):
409+ super(Check, self).__init__()
410+ # XXX: could be better to calculate this from the service name
411+ if not re.match(self.shortname_re, shortname):
412+ raise CheckException("shortname must match {}".format(
413+ Check.shortname_re))
414+ self.shortname = shortname
415+ self.command = "check_{}".format(shortname)
416+ # Note: a set of invalid characters is defined by the
417+ # Nagios server config
418+ # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
419+ self.description = description
420+ self.check_cmd = self._locate_cmd(check_cmd)
421+
422+ def _locate_cmd(self, check_cmd):
423+ search_path = (
424+ '/usr/lib/nagios/plugins',
425+ '/usr/local/lib/nagios/plugins',
426+ )
427+ parts = shlex.split(check_cmd)
428+ for path in search_path:
429+ if os.path.exists(os.path.join(path, parts[0])):
430+ command = os.path.join(path, parts[0])
431+ if len(parts) > 1:
432+ command += " " + " ".join(parts[1:])
433+ return command
434+ log('Check command not found: {}'.format(parts[0]))
435+ return ''
436+
437+ def write(self, nagios_context, hostname, nagios_servicegroups):
438+ nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
439+ self.command)
440+ with open(nrpe_check_file, 'w') as nrpe_check_config:
441+ nrpe_check_config.write("# check {}\n".format(self.shortname))
442+ nrpe_check_config.write("command[{}]={}\n".format(
443+ self.command, self.check_cmd))
444+
445+ if not os.path.exists(NRPE.nagios_exportdir):
446+ log('Not writing service config as {} is not accessible'.format(
447+ NRPE.nagios_exportdir))
448+ else:
449+ self.write_service_config(nagios_context, hostname,
450+ nagios_servicegroups)
451+
452+ def write_service_config(self, nagios_context, hostname,
453+ nagios_servicegroups):
454+ for f in os.listdir(NRPE.nagios_exportdir):
455+ if re.search('.*{}.cfg'.format(self.command), f):
456+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
457+
458+ templ_vars = {
459+ 'nagios_hostname': hostname,
460+ 'nagios_servicegroup': nagios_servicegroups,
461+ 'description': self.description,
462+ 'shortname': self.shortname,
463+ 'command': self.command,
464+ }
465+ nrpe_service_text = Check.service_template.format(**templ_vars)
466+ nrpe_service_file = '{}/service__{}_{}.cfg'.format(
467+ NRPE.nagios_exportdir, hostname, self.command)
468+ with open(nrpe_service_file, 'w') as nrpe_service_config:
469+ nrpe_service_config.write(str(nrpe_service_text))
470+
471+ def run(self):
472+ subprocess.call(self.check_cmd)
473+
474+
475+class NRPE(object):
476+ nagios_logdir = '/var/log/nagios'
477+ nagios_exportdir = '/var/lib/nagios/export'
478+ nrpe_confdir = '/etc/nagios/nrpe.d'
479+
480+ def __init__(self, hostname=None):
481+ super(NRPE, self).__init__()
482+ self.config = config()
483+ self.nagios_context = self.config['nagios_context']
484+ if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
485+ self.nagios_servicegroups = self.config['nagios_servicegroups']
486+ else:
487+ self.nagios_servicegroups = self.nagios_context
488+ self.unit_name = local_unit().replace('/', '-')
489+ if hostname:
490+ self.hostname = hostname
491+ else:
492+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
493+ self.checks = []
494+
495+ def add_check(self, *args, **kwargs):
496+ self.checks.append(Check(*args, **kwargs))
497+
498+ def write(self):
499+ try:
500+ nagios_uid = pwd.getpwnam('nagios').pw_uid
501+ nagios_gid = grp.getgrnam('nagios').gr_gid
502+ except:
503+ log("Nagios user not set up, nrpe checks not updated")
504+ return
505+
506+ if not os.path.exists(NRPE.nagios_logdir):
507+ os.mkdir(NRPE.nagios_logdir)
508+ os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
509+
510+ nrpe_monitors = {}
511+ monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
512+ for nrpecheck in self.checks:
513+ nrpecheck.write(self.nagios_context, self.hostname,
514+ self.nagios_servicegroups)
515+ nrpe_monitors[nrpecheck.shortname] = {
516+ "command": nrpecheck.command,
517+ }
518+
519+ service('restart', 'nagios-nrpe-server')
520+
521+ monitor_ids = relation_ids("local-monitors") + \
522+ relation_ids("nrpe-external-master")
523+ for rid in monitor_ids:
524+ relation_set(relation_id=rid, monitors=yaml.dump(monitors))
525+
526+
527+def get_nagios_hostcontext(relation_name='nrpe-external-master'):
528+ """
529+ Query relation with nrpe subordinate, return the nagios_host_context
530+
531+ :param str relation_name: Name of relation nrpe sub joined to
532+ """
533+ for rel in relations_of_type(relation_name):
534+ if 'nagios_hostname' in rel:
535+ return rel['nagios_host_context']
536+
537+
538+def get_nagios_hostname(relation_name='nrpe-external-master'):
539+ """
540+ Query relation with nrpe subordinate, return the nagios_hostname
541+
542+ :param str relation_name: Name of relation nrpe sub joined to
543+ """
544+ for rel in relations_of_type(relation_name):
545+ if 'nagios_hostname' in rel:
546+ return rel['nagios_hostname']
547+
548+
549+def get_nagios_unit_name(relation_name='nrpe-external-master'):
550+ """
551+ Return the nagios unit name prepended with host_context if needed
552+
553+ :param str relation_name: Name of relation nrpe sub joined to
554+ """
555+ host_context = get_nagios_hostcontext(relation_name)
556+ if host_context:
557+ unit = "%s:%s" % (host_context, local_unit())
558+ else:
559+ unit = local_unit()
560+ return unit
561+
562+
563+def add_init_service_checks(nrpe, services, unit_name):
564+ """
565+ Add checks for each service in list
566+
567+ :param NRPE nrpe: NRPE object to add check to
568+ :param list services: List of services to check
569+ :param str unit_name: Unit name to use in check description
570+ """
571+ for svc in services:
572+ upstart_init = '/etc/init/%s.conf' % svc
573+ sysv_init = '/etc/init.d/%s' % svc
574+ if os.path.exists(upstart_init):
575+ nrpe.add_check(
576+ shortname=svc,
577+ description='process check {%s}' % unit_name,
578+ check_cmd='check_upstart_job %s' % svc
579+ )
580+ elif os.path.exists(sysv_init):
581+ cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
582+ cron_file = ('*/5 * * * * root '
583+ '/usr/local/lib/nagios/plugins/check_exit_status.pl '
584+ '-s /etc/init.d/%s status > '
585+ '/var/lib/nagios/service-check-%s.txt\n' % (svc,
586+ svc)
587+ )
588+ f = open(cronpath, 'w')
589+ f.write(cron_file)
590+ f.close()
591+ nrpe.add_check(
592+ shortname=svc,
593+ description='process check {%s}' % unit_name,
594+ check_cmd='check_status_file.py -f '
595+ '/var/lib/nagios/service-check-%s.txt' % svc,
596+ )
597+
598+
599+def copy_nrpe_checks():
600+ """
601+ Copy the nrpe checks into place
602+
603+ """
604+ NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
605+ nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
606+ 'charmhelpers', 'contrib', 'openstack',
607+ 'files')
608+
609+ if not os.path.exists(NAGIOS_PLUGINS):
610+ os.makedirs(NAGIOS_PLUGINS)
611+ for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
612+ if os.path.isfile(fname):
613+ shutil.copy2(fname,
614+ os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
615+
616+
617+def add_haproxy_checks(nrpe, unit_name):
618+ """
619+ Add checks for each service in list
620+
621+ :param NRPE nrpe: NRPE object to add check to
622+ :param str unit_name: Unit name to use in check description
623+ """
624+ nrpe.add_check(
625+ shortname='haproxy_servers',
626+ description='Check HAProxy {%s}' % unit_name,
627+ check_cmd='check_haproxy.sh')
628+ nrpe.add_check(
629+ shortname='haproxy_queue',
630+ description='Check HAProxy queue depth {%s}' % unit_name,
631+ check_cmd='check_haproxy_queue_depth.sh')
632
633=== added file 'hooks/charmhelpers/core/files.py'
634--- hooks/charmhelpers/core/files.py 1970-01-01 00:00:00 +0000
635+++ hooks/charmhelpers/core/files.py 2015-08-11 11:19:35 +0000
636@@ -0,0 +1,45 @@
637+#!/usr/bin/env python
638+# -*- coding: utf-8 -*-
639+
640+# Copyright 2014-2015 Canonical Limited.
641+#
642+# This file is part of charm-helpers.
643+#
644+# charm-helpers is free software: you can redistribute it and/or modify
645+# it under the terms of the GNU Lesser General Public License version 3 as
646+# published by the Free Software Foundation.
647+#
648+# charm-helpers is distributed in the hope that it will be useful,
649+# but WITHOUT ANY WARRANTY; without even the implied warranty of
650+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
651+# GNU Lesser General Public License for more details.
652+#
653+# You should have received a copy of the GNU Lesser General Public License
654+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
655+
656+__author__ = 'Jorge Niedbalski <niedbalski@ubuntu.com>'
657+
658+import os
659+import subprocess
660+
661+
662+def sed(filename, before, after, flags='g'):
663+ """
664+ Search and replaces the given pattern on filename.
665+
666+ :param filename: relative or absolute file path.
667+ :param before: expression to be replaced (see 'man sed')
668+ :param after: expression to replace with (see 'man sed')
669+ :param flags: sed-compatible regex flags in example, to make
670+ the search and replace case insensitive, specify ``flags="i"``.
671+ The ``g`` flag is always specified regardless, so you do not
672+ need to remember to include it when overriding this parameter.
673+ :returns: If the sed command exit code was zero then return,
674+ otherwise raise CalledProcessError.
675+ """
676+ expression = r's/{0}/{1}/{2}'.format(before,
677+ after, flags)
678+
679+ return subprocess.check_call(["sed", "-i", "-r", "-e",
680+ expression,
681+ os.path.expanduser(filename)])
682
683=== modified file 'hooks/charmhelpers/core/hookenv.py'
684--- hooks/charmhelpers/core/hookenv.py 2015-06-25 11:39:19 +0000
685+++ hooks/charmhelpers/core/hookenv.py 2015-08-11 11:19:35 +0000
686@@ -21,6 +21,7 @@
687 # Charm Helpers Developers <juju@lists.ubuntu.com>
688
689 from __future__ import print_function
690+import copy
691 from distutils.version import LooseVersion
692 from functools import wraps
693 import glob
694@@ -263,7 +264,7 @@
695 self.path = path or self.path
696 with open(self.path) as f:
697 self._prev_dict = json.load(f)
698- for k, v in self._prev_dict.items():
699+ for k, v in copy.deepcopy(self._prev_dict).items():
700 if k not in self:
701 self[k] = v
702
703@@ -468,6 +469,19 @@
704
705
706 @cached
707+def peer_relation_id():
708+ '''Get a peer relation id if a peer relation has been joined, else None.'''
709+ md = metadata()
710+ section = md.get('peers')
711+ if section:
712+ for key in section:
713+ relids = relation_ids(key)
714+ if relids:
715+ return relids[0]
716+ return None
717+
718+
719+@cached
720 def charm_name():
721 """Get the name of the current charm as is specified on metadata.yaml"""
722 return metadata().get('name')
723@@ -691,6 +705,7 @@
724
725 def translate_exc(from_exc, to_exc):
726 def inner_translate_exc1(f):
727+ @wraps(f)
728 def inner_translate_exc2(*args, **kwargs):
729 try:
730 return f(*args, **kwargs)
731@@ -761,6 +776,7 @@
732
733 This is useful for modules and classes to perform initialization
734 and inject behavior. In particular:
735+
736 - Run common code before all of your hooks, such as logging
737 the hook name or interesting relation data.
738 - Defer object or module initialization that requires a hook
739
740=== modified file 'hooks/charmhelpers/core/host.py'
741--- hooks/charmhelpers/core/host.py 2015-06-25 07:56:30 +0000
742+++ hooks/charmhelpers/core/host.py 2015-08-11 11:19:35 +0000
743@@ -63,6 +63,36 @@
744 return service_result
745
746
747+def service_pause(service_name, init_dir=None):
748+ """Pause a system service.
749+
750+ Stop it, and prevent it from starting again at boot."""
751+ if init_dir is None:
752+ init_dir = "/etc/init"
753+ stopped = service_stop(service_name)
754+ # XXX: Support systemd too
755+ override_path = os.path.join(
756+ init_dir, '{}.conf.override'.format(service_name))
757+ with open(override_path, 'w') as fh:
758+ fh.write("manual\n")
759+ return stopped
760+
761+
762+def service_resume(service_name, init_dir=None):
763+ """Resume a system service.
764+
765+ Reenable starting again at boot. Start the service"""
766+ # XXX: Support systemd too
767+ if init_dir is None:
768+ init_dir = "/etc/init"
769+ override_path = os.path.join(
770+ init_dir, '{}.conf.override'.format(service_name))
771+ if os.path.exists(override_path):
772+ os.unlink(override_path)
773+ started = service_start(service_name)
774+ return started
775+
776+
777 def service(action, service_name):
778 """Control a system service"""
779 cmd = ['service', service_name, action]
780@@ -140,11 +170,7 @@
781
782 def add_user_to_group(username, group):
783 """Add a user to a group"""
784- cmd = [
785- 'gpasswd', '-a',
786- username,
787- group
788- ]
789+ cmd = ['gpasswd', '-a', username, group]
790 log("Adding user {} to group {}".format(username, group))
791 subprocess.check_call(cmd)
792
793@@ -466,3 +492,19 @@
794
795 def lchownr(path, owner, group):
796 chownr(path, owner, group, follow_links=False)
797+
798+
799+def get_total_ram():
800+ '''The total amount of system RAM in bytes.
801+
802+ This is what is reported by the OS, and may be overcommitted when
803+ there are multiple containers hosted on the same machine.
804+ '''
805+ with open('/proc/meminfo', 'r') as f:
806+ for line in f.readlines():
807+ if line:
808+ key, value, unit = line.split()
809+ if key == 'MemTotal:':
810+ assert unit == 'kB', 'Unknown unit'
811+ return int(value) * 1024 # Classic, not KiB.
812+ raise NotImplementedError()
813
814=== modified file 'hooks/charmhelpers/core/services/helpers.py'
815--- hooks/charmhelpers/core/services/helpers.py 2015-06-25 07:56:30 +0000
816+++ hooks/charmhelpers/core/services/helpers.py 2015-08-11 11:19:35 +0000
817@@ -239,12 +239,12 @@
818 action.
819
820 :param str source: The template source file, relative to
821- `$CHARM_DIR/templates`
822-
823+ `$CHARM_DIR/templates`
824 :param str target: The target to write the rendered template to
825 :param str owner: The owner of the rendered file
826 :param str group: The group of the rendered file
827 :param int perms: The permissions of the rendered file
828+
829 """
830 def __init__(self, source, target,
831 owner='root', group='root', perms=0o444):
832
833=== modified file 'hooks/charmhelpers/core/templating.py'
834--- hooks/charmhelpers/core/templating.py 2015-06-25 07:56:30 +0000
835+++ hooks/charmhelpers/core/templating.py 2015-08-11 11:19:35 +0000
836@@ -64,5 +64,9 @@
837 level=hookenv.ERROR)
838 raise e
839 content = template.render(context)
840- host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
841+ target_dir = os.path.dirname(target)
842+ if not os.path.exists(target_dir):
843+ # This is a terrible default directory permission, as the file
844+ # or its siblings will often contain secrets.
845+ host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
846 host.write_file(target, content.encode(encoding), owner, group, perms)
847
848=== modified file 'hooks/charmhelpers/fetch/__init__.py'
849--- hooks/charmhelpers/fetch/__init__.py 2015-06-25 07:56:30 +0000
850+++ hooks/charmhelpers/fetch/__init__.py 2015-08-11 11:19:35 +0000
851@@ -215,19 +215,27 @@
852 _run_apt_command(cmd, fatal)
853
854
855+def apt_mark(packages, mark, fatal=False):
856+ """Flag one or more packages using apt-mark"""
857+ log("Marking {} as {}".format(packages, mark))
858+ cmd = ['apt-mark', mark]
859+ if isinstance(packages, six.string_types):
860+ cmd.append(packages)
861+ else:
862+ cmd.extend(packages)
863+
864+ if fatal:
865+ subprocess.check_call(cmd, universal_newlines=True)
866+ else:
867+ subprocess.call(cmd, universal_newlines=True)
868+
869+
870 def apt_hold(packages, fatal=False):
871- """Hold one or more packages"""
872- cmd = ['apt-mark', 'hold']
873- if isinstance(packages, six.string_types):
874- cmd.append(packages)
875- else:
876- cmd.extend(packages)
877- log("Holding {}".format(packages))
878-
879- if fatal:
880- subprocess.check_call(cmd)
881- else:
882- subprocess.call(cmd)
883+ return apt_mark(packages, 'hold', fatal=fatal)
884+
885+
886+def apt_unhold(packages, fatal=False):
887+ return apt_mark(packages, 'unhold', fatal=fatal)
888
889
890 def add_source(source, key=None):
891@@ -370,8 +378,9 @@
892 for handler in handlers:
893 try:
894 installed_to = handler.install(source, *args, **kwargs)
895- except UnhandledSource:
896- pass
897+ except UnhandledSource as e:
898+ log('Install source attempt unsuccessful: {}'.format(e),
899+ level='WARNING')
900 if not installed_to:
901 raise UnhandledSource("No handler found for source {}".format(source))
902 return installed_to
903
904=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
905--- hooks/charmhelpers/fetch/archiveurl.py 2015-06-25 07:56:30 +0000
906+++ hooks/charmhelpers/fetch/archiveurl.py 2015-08-11 11:19:35 +0000
907@@ -77,6 +77,8 @@
908 def can_handle(self, source):
909 url_parts = self.parse_url(source)
910 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
911+ # XXX: Why is this returning a boolean and a string? It's
912+ # doomed to fail since "bool(can_handle('foo://'))" will be True.
913 return "Wrong source type"
914 if get_archive_handler(self.base_url(source)):
915 return True
916@@ -155,7 +157,11 @@
917 else:
918 algorithms = hashlib.algorithms_available
919 if key in algorithms:
920- check_hash(dld_file, value, key)
921+ if len(value) != 1:
922+ raise TypeError(
923+ "Expected 1 hash value, not %d" % len(value))
924+ expected = value[0]
925+ check_hash(dld_file, expected, key)
926 if checksum:
927 check_hash(dld_file, checksum, hash_type)
928 return extract(dld_file, dest)
929
930=== modified file 'hooks/charmhelpers/fetch/giturl.py'
931--- hooks/charmhelpers/fetch/giturl.py 2015-06-25 07:56:30 +0000
932+++ hooks/charmhelpers/fetch/giturl.py 2015-08-11 11:19:35 +0000
933@@ -67,7 +67,7 @@
934 try:
935 self.clone(source, dest_dir, branch, depth)
936 except GitCommandError as e:
937- raise UnhandledSource(e.message)
938+ raise UnhandledSource(e)
939 except OSError as e:
940 raise UnhandledSource(e.strerror)
941 return dest_dir
942
943=== added directory 'hooks/charmhelpers/payload'
944=== added file 'hooks/charmhelpers/payload/__init__.py'
945--- hooks/charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000
946+++ hooks/charmhelpers/payload/__init__.py 2015-08-11 11:19:35 +0000
947@@ -0,0 +1,17 @@
948+# Copyright 2014-2015 Canonical Limited.
949+#
950+# This file is part of charm-helpers.
951+#
952+# charm-helpers is free software: you can redistribute it and/or modify
953+# it under the terms of the GNU Lesser General Public License version 3 as
954+# published by the Free Software Foundation.
955+#
956+# charm-helpers is distributed in the hope that it will be useful,
957+# but WITHOUT ANY WARRANTY; without even the implied warranty of
958+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
959+# GNU Lesser General Public License for more details.
960+#
961+# You should have received a copy of the GNU Lesser General Public License
962+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
963+
964+"Tools for working with files injected into a charm just before deployment."
965
966=== added file 'hooks/charmhelpers/payload/archive.py'
967--- hooks/charmhelpers/payload/archive.py 1970-01-01 00:00:00 +0000
968+++ hooks/charmhelpers/payload/archive.py 2015-08-11 11:19:35 +0000
969@@ -0,0 +1,73 @@
970+# Copyright 2014-2015 Canonical Limited.
971+#
972+# This file is part of charm-helpers.
973+#
974+# charm-helpers is free software: you can redistribute it and/or modify
975+# it under the terms of the GNU Lesser General Public License version 3 as
976+# published by the Free Software Foundation.
977+#
978+# charm-helpers is distributed in the hope that it will be useful,
979+# but WITHOUT ANY WARRANTY; without even the implied warranty of
980+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
981+# GNU Lesser General Public License for more details.
982+#
983+# You should have received a copy of the GNU Lesser General Public License
984+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
985+
986+import os
987+import tarfile
988+import zipfile
989+from charmhelpers.core import (
990+ host,
991+ hookenv,
992+)
993+
994+
995+class ArchiveError(Exception):
996+ pass
997+
998+
999+def get_archive_handler(archive_name):
1000+ if os.path.isfile(archive_name):
1001+ if tarfile.is_tarfile(archive_name):
1002+ return extract_tarfile
1003+ elif zipfile.is_zipfile(archive_name):
1004+ return extract_zipfile
1005+ else:
1006+ # look at the file name
1007+ for ext in ('.tar', '.tar.gz', '.tgz', 'tar.bz2', '.tbz2', '.tbz'):
1008+ if archive_name.endswith(ext):
1009+ return extract_tarfile
1010+ for ext in ('.zip', '.jar'):
1011+ if archive_name.endswith(ext):
1012+ return extract_zipfile
1013+
1014+
1015+def archive_dest_default(archive_name):
1016+ archive_file = os.path.basename(archive_name)
1017+ return os.path.join(hookenv.charm_dir(), "archives", archive_file)
1018+
1019+
1020+def extract(archive_name, destpath=None):
1021+ handler = get_archive_handler(archive_name)
1022+ if handler:
1023+ if not destpath:
1024+ destpath = archive_dest_default(archive_name)
1025+ if not os.path.isdir(destpath):
1026+ host.mkdir(destpath)
1027+ handler(archive_name, destpath)
1028+ return destpath
1029+ else:
1030+ raise ArchiveError("No handler for archive")
1031+
1032+
1033+def extract_tarfile(archive_name, destpath):
1034+ "Unpack a tar archive, optionally compressed"
1035+ archive = tarfile.open(archive_name)
1036+ archive.extractall(destpath)
1037+
1038+
1039+def extract_zipfile(archive_name, destpath):
1040+ "Unpack a zip file"
1041+ archive = zipfile.ZipFile(archive_name)
1042+ archive.extractall(destpath)
1043
1044=== added file 'hooks/charmhelpers/payload/execd.py'
1045--- hooks/charmhelpers/payload/execd.py 1970-01-01 00:00:00 +0000
1046+++ hooks/charmhelpers/payload/execd.py 2015-08-11 11:19:35 +0000
1047@@ -0,0 +1,66 @@
1048+#!/usr/bin/env python
1049+
1050+# Copyright 2014-2015 Canonical Limited.
1051+#
1052+# This file is part of charm-helpers.
1053+#
1054+# charm-helpers is free software: you can redistribute it and/or modify
1055+# it under the terms of the GNU Lesser General Public License version 3 as
1056+# published by the Free Software Foundation.
1057+#
1058+# charm-helpers is distributed in the hope that it will be useful,
1059+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1060+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1061+# GNU Lesser General Public License for more details.
1062+#
1063+# You should have received a copy of the GNU Lesser General Public License
1064+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1065+
1066+import os
1067+import sys
1068+import subprocess
1069+from charmhelpers.core import hookenv
1070+
1071+
1072+def default_execd_dir():
1073+ return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
1074+
1075+
1076+def execd_module_paths(execd_dir=None):
1077+ """Generate a list of full paths to modules within execd_dir."""
1078+ if not execd_dir:
1079+ execd_dir = default_execd_dir()
1080+
1081+ if not os.path.exists(execd_dir):
1082+ return
1083+
1084+ for subpath in os.listdir(execd_dir):
1085+ module = os.path.join(execd_dir, subpath)
1086+ if os.path.isdir(module):
1087+ yield module
1088+
1089+
1090+def execd_submodule_paths(command, execd_dir=None):
1091+ """Generate a list of full paths to the specified command within exec_dir.
1092+ """
1093+ for module_path in execd_module_paths(execd_dir):
1094+ path = os.path.join(module_path, command)
1095+ if os.access(path, os.X_OK) and os.path.isfile(path):
1096+ yield path
1097+
1098+
1099+def execd_run(command, execd_dir=None, die_on_error=False, stderr=None):
1100+ """Run command for each module within execd_dir which defines it."""
1101+ for submodule_path in execd_submodule_paths(command, execd_dir):
1102+ try:
1103+ subprocess.check_call(submodule_path, shell=True, stderr=stderr)
1104+ except subprocess.CalledProcessError as e:
1105+ hookenv.log("Error ({}) running {}. Output: {}".format(
1106+ e.returncode, e.cmd, e.output))
1107+ if die_on_error:
1108+ sys.exit(e.returncode)
1109+
1110+
1111+def execd_preinstall(execd_dir=None):
1112+ """Run charm-pre-install for each module within execd_dir."""
1113+ execd_run('charm-pre-install', execd_dir=execd_dir)

Subscribers

People subscribed via source and target branches

to all changes: