Merge lp:~james-page/charms/precise/ceph-radosgw/apache24 into lp:~charmers/charms/precise/ceph-radosgw/trunk

Proposed by James Page
Status: Merged
Merged at revision: 15
Proposed branch: lp:~james-page/charms/precise/ceph-radosgw/apache24
Merge into: lp:~charmers/charms/precise/ceph-radosgw/trunk
Diff against target: 1986 lines (+1345/-277)
17 files modified
.project (+17/-0)
.pydevproject (+8/-0)
Makefile (+8/-0)
charm-helpers-sync.yaml (+9/-0)
hooks/ceph.py (+22/-21)
hooks/charmhelpers/contrib/openstack/alternatives.py (+17/-0)
hooks/charmhelpers/contrib/storage/linux/utils.py (+25/-0)
hooks/charmhelpers/core/hookenv.py (+395/-0)
hooks/charmhelpers/core/host.py (+291/-0)
hooks/charmhelpers/fetch/__init__.py (+279/-0)
hooks/charmhelpers/fetch/archiveurl.py (+48/-0)
hooks/charmhelpers/fetch/bzrurl.py (+49/-0)
hooks/charmhelpers/payload/__init__.py (+1/-0)
hooks/charmhelpers/payload/execd.py (+50/-0)
hooks/hooks.py (+109/-90)
hooks/utils.py (+15/-166)
metadata.yaml (+2/-0)
To merge this branch: bzr merge lp:~james-page/charms/precise/ceph-radosgw/apache24
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Approve
OpenStack Charmers Pending
Review via email: mp+203129@code.launchpad.net

Description of the change

1) Fixes for compatibility with apache 2.4

2) General refresh to use charm-helpers inline with other ceph charms

To post a comment you must log in.
19. By James Page

Add pydev stuff

Revision history for this message
Marco Ceppi (marcoceppi) wrote :

Deferring to openstack-charmers

review: Abstain
Revision history for this message
Marco Ceppi (marcoceppi) wrote :

LGTM +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.project'
--- .project 1970-01-01 00:00:00 +0000
+++ .project 2014-01-24 17:20:41 +0000
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<projectDescription>
3 <name>ceph-radosgw</name>
4 <comment></comment>
5 <projects>
6 </projects>
7 <buildSpec>
8 <buildCommand>
9 <name>org.python.pydev.PyDevBuilder</name>
10 <arguments>
11 </arguments>
12 </buildCommand>
13 </buildSpec>
14 <natures>
15 <nature>org.python.pydev.pythonNature</nature>
16 </natures>
17</projectDescription>
018
=== added file '.pydevproject'
--- .pydevproject 1970-01-01 00:00:00 +0000
+++ .pydevproject 2014-01-24 17:20:41 +0000
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<?eclipse-pydev version="1.0"?><pydev_project>
3<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
4<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
5<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
6<path>/ceph-radosgw/hooks</path>
7</pydev_pathproperty>
8</pydev_project>
09
=== added file 'Makefile'
--- Makefile 1970-01-01 00:00:00 +0000
+++ Makefile 2014-01-24 17:20:41 +0000
@@ -0,0 +1,8 @@
1#!/usr/bin/make
2
3lint:
4 @flake8 --exclude hooks/charmhelpers hooks
5 @charm proof
6
7sync:
8 @charm-helper-sync -c charm-helpers-sync.yaml
09
=== added file 'charm-helpers-sync.yaml'
--- charm-helpers-sync.yaml 1970-01-01 00:00:00 +0000
+++ charm-helpers-sync.yaml 2014-01-24 17:20:41 +0000
@@ -0,0 +1,9 @@
1branch: lp:charm-helpers
2destination: hooks/charmhelpers
3include:
4 - core
5 - fetch
6 - contrib.storage.linux:
7 - utils
8 - payload.execd
9 - contrib.openstack.alternatives
010
=== modified file 'hooks/ceph.py'
--- hooks/ceph.py 2013-01-11 09:15:51 +0000
+++ hooks/ceph.py 2014-01-24 17:20:41 +0000
@@ -10,23 +10,24 @@
10import json10import json
11import subprocess11import subprocess
12import time12import time
13import utils
14import os13import os
15import apt_pkg as apt14import apt_pkg as apt
1615
16from socket import gethostname as get_unit_hostname
17
17LEADER = 'leader'18LEADER = 'leader'
18PEON = 'peon'19PEON = 'peon'
19QUORUM = [LEADER, PEON]20QUORUM = [LEADER, PEON]
2021
2122
22def is_quorum():23def is_quorum():
23 asok = "/var/run/ceph/ceph-mon.{}.asok".format(utils.get_unit_hostname())24 asok = "/var/run/ceph/ceph-mon.{}.asok".format(get_unit_hostname())
24 cmd = [25 cmd = [
25 "ceph",26 "ceph",
26 "--admin-daemon",27 "--admin-daemon",
27 asok,28 asok,
28 "mon_status"29 "mon_status"
29 ]30 ]
30 if os.path.exists(asok):31 if os.path.exists(asok):
31 try:32 try:
32 result = json.loads(subprocess.check_output(cmd))33 result = json.loads(subprocess.check_output(cmd))
@@ -44,13 +45,13 @@
4445
4546
46def is_leader():47def is_leader():
47 asok = "/var/run/ceph/ceph-mon.{}.asok".format(utils.get_unit_hostname())48 asok = "/var/run/ceph/ceph-mon.{}.asok".format(get_unit_hostname())
48 cmd = [49 cmd = [
49 "ceph",50 "ceph",
50 "--admin-daemon",51 "--admin-daemon",
51 asok,52 asok,
52 "mon_status"53 "mon_status"
53 ]54 ]
54 if os.path.exists(asok):55 if os.path.exists(asok):
55 try:56 try:
56 result = json.loads(subprocess.check_output(cmd))57 result = json.loads(subprocess.check_output(cmd))
@@ -73,14 +74,14 @@
7374
7475
75def add_bootstrap_hint(peer):76def add_bootstrap_hint(peer):
76 asok = "/var/run/ceph/ceph-mon.{}.asok".format(utils.get_unit_hostname())77 asok = "/var/run/ceph/ceph-mon.{}.asok".format(get_unit_hostname())
77 cmd = [78 cmd = [
78 "ceph",79 "ceph",
79 "--admin-daemon",80 "--admin-daemon",
80 asok,81 asok,
81 "add_bootstrap_peer_hint",82 "add_bootstrap_peer_hint",
82 peer83 peer
83 ]84 ]
84 if os.path.exists(asok):85 if os.path.exists(asok):
85 # Ignore any errors for this call86 # Ignore any errors for this call
86 subprocess.call(cmd)87 subprocess.call(cmd)
@@ -89,7 +90,7 @@
89 'xfs',90 'xfs',
90 'ext4',91 'ext4',
91 'btrfs'92 'btrfs'
92 ]93]
9394
9495
95def is_osd_disk(dev):96def is_osd_disk(dev):
@@ -99,7 +100,7 @@
99 for line in info:100 for line in info:
100 if line.startswith(101 if line.startswith(
101 'Partition GUID code: 4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D'102 'Partition GUID code: 4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D'
102 ):103 ):
103 return True104 return True
104 except subprocess.CalledProcessError:105 except subprocess.CalledProcessError:
105 pass106 pass
@@ -110,7 +111,7 @@
110 cmd = [111 cmd = [
111 'udevadm', 'trigger',112 'udevadm', 'trigger',
112 '--subsystem-match=block', '--action=add'113 '--subsystem-match=block', '--action=add'
113 ]114 ]
114115
115 subprocess.call(cmd)116 subprocess.call(cmd)
116117
@@ -140,7 +141,7 @@
140 '--create-keyring',141 '--create-keyring',
141 '--name=client.bootstrap-osd',142 '--name=client.bootstrap-osd',
142 '--add-key={}'.format(key)143 '--add-key={}'.format(key)
143 ]144 ]
144 subprocess.check_call(cmd)145 subprocess.check_call(cmd)
145146
146# OSD caps taken from ceph-create-keys147# OSD caps taken from ceph-create-keys
@@ -148,10 +149,10 @@
148 'mon': [149 'mon': [
149 'allow command osd create ...',150 'allow command osd create ...',
150 'allow command osd crush set ...',151 'allow command osd crush set ...',
151 r'allow command auth add * osd allow\ * mon allow\ rwx',152 r'allow command auth add * osd allow\ * mon allow\ rwx',
152 'allow command mon getmap'153 'allow command mon getmap'
153 ]154 ]
154 }155}
155156
156157
157def get_osd_bootstrap_key():158def get_osd_bootstrap_key():
@@ -169,14 +170,14 @@
169 '--create-keyring',170 '--create-keyring',
170 '--name=client.radosgw.gateway',171 '--name=client.radosgw.gateway',
171 '--add-key={}'.format(key)172 '--add-key={}'.format(key)
172 ]173 ]
173 subprocess.check_call(cmd)174 subprocess.check_call(cmd)
174175
175# OSD caps taken from ceph-create-keys176# OSD caps taken from ceph-create-keys
176_radosgw_caps = {177_radosgw_caps = {
177 'mon': ['allow r'],178 'mon': ['allow r'],
178 'osd': ['allow rwx']179 'osd': ['allow rwx']
179 }180}
180181
181182
182def get_radosgw_key():183def get_radosgw_key():
@@ -186,7 +187,7 @@
186_default_caps = {187_default_caps = {
187 'mon': ['allow r'],188 'mon': ['allow r'],
188 'osd': ['allow rwx']189 'osd': ['allow rwx']
189 }190}
190191
191192
192def get_named_key(name, caps=None):193def get_named_key(name, caps=None):
@@ -196,16 +197,16 @@
196 '--name', 'mon.',197 '--name', 'mon.',
197 '--keyring',198 '--keyring',
198 '/var/lib/ceph/mon/ceph-{}/keyring'.format(199 '/var/lib/ceph/mon/ceph-{}/keyring'.format(
199 utils.get_unit_hostname()200 get_unit_hostname()
200 ),201 ),
201 'auth', 'get-or-create', 'client.{}'.format(name),202 'auth', 'get-or-create', 'client.{}'.format(name),
202 ]203 ]
203 # Add capabilities204 # Add capabilities
204 for subsystem, subcaps in caps.iteritems():205 for subsystem, subcaps in caps.iteritems():
205 cmd.extend([206 cmd.extend([
206 subsystem,207 subsystem,
207 '; '.join(subcaps),208 '; '.join(subcaps),
208 ])209 ])
209 output = subprocess.check_output(cmd).strip() # IGNORE:E1103210 output = subprocess.check_output(cmd).strip() # IGNORE:E1103
210 # get-or-create appears to have different output depending211 # get-or-create appears to have different output depending
211 # on whether its 'get' or 'create'212 # on whether its 'get' or 'create'
212213
=== added directory 'hooks/charmhelpers'
=== added file 'hooks/charmhelpers/__init__.py'
=== added directory 'hooks/charmhelpers/contrib'
=== added file 'hooks/charmhelpers/contrib/__init__.py'
=== added directory 'hooks/charmhelpers/contrib/openstack'
=== added file 'hooks/charmhelpers/contrib/openstack/__init__.py'
=== added file 'hooks/charmhelpers/contrib/openstack/alternatives.py'
--- hooks/charmhelpers/contrib/openstack/alternatives.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/alternatives.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,17 @@
1''' Helper for managing alternatives for file conflict resolution '''
2
3import subprocess
4import shutil
5import os
6
7
8def install_alternative(name, target, source, priority=50):
9 ''' Install alternative configuration '''
10 if (os.path.exists(target) and not os.path.islink(target)):
11 # Move existing file/directory away before installing
12 shutil.move(target, '{}.bak'.format(target))
13 cmd = [
14 'update-alternatives', '--force', '--install',
15 target, name, source, str(priority)
16 ]
17 subprocess.check_call(cmd)
018
=== added directory 'hooks/charmhelpers/contrib/storage'
=== added file 'hooks/charmhelpers/contrib/storage/__init__.py'
=== added directory 'hooks/charmhelpers/contrib/storage/linux'
=== added file 'hooks/charmhelpers/contrib/storage/linux/__init__.py'
=== added file 'hooks/charmhelpers/contrib/storage/linux/utils.py'
--- hooks/charmhelpers/contrib/storage/linux/utils.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/storage/linux/utils.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,25 @@
1from os import stat
2from stat import S_ISBLK
3
4from subprocess import (
5 check_call
6)
7
8
9def is_block_device(path):
10 '''
11 Confirm device at path is a valid block device node.
12
13 :returns: boolean: True if path is a block device, False if not.
14 '''
15 return S_ISBLK(stat(path).st_mode)
16
17
18def zap_disk(block_device):
19 '''
20 Clear a block device of partition table. Relies on sgdisk, which is
21 installed as pat of the 'gdisk' package in Ubuntu.
22
23 :param block_device: str: Full path of block device to clean.
24 '''
25 check_call(['sgdisk', '--zap-all', '--mbrtogpt', block_device])
026
=== added directory 'hooks/charmhelpers/core'
=== added file 'hooks/charmhelpers/core/__init__.py'
=== added file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/hookenv.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,395 @@
1"Interactions with the Juju environment"
2# Copyright 2013 Canonical Ltd.
3#
4# Authors:
5# Charm Helpers Developers <juju@lists.ubuntu.com>
6
7import os
8import json
9import yaml
10import subprocess
11import UserDict
12from subprocess import CalledProcessError
13
14CRITICAL = "CRITICAL"
15ERROR = "ERROR"
16WARNING = "WARNING"
17INFO = "INFO"
18DEBUG = "DEBUG"
19MARKER = object()
20
21cache = {}
22
23
24def cached(func):
25 """Cache return values for multiple executions of func + args
26
27 For example:
28
29 @cached
30 def unit_get(attribute):
31 pass
32
33 unit_get('test')
34
35 will cache the result of unit_get + 'test' for future calls.
36 """
37 def wrapper(*args, **kwargs):
38 global cache
39 key = str((func, args, kwargs))
40 try:
41 return cache[key]
42 except KeyError:
43 res = func(*args, **kwargs)
44 cache[key] = res
45 return res
46 return wrapper
47
48
49def flush(key):
50 """Flushes any entries from function cache where the
51 key is found in the function+args """
52 flush_list = []
53 for item in cache:
54 if key in item:
55 flush_list.append(item)
56 for item in flush_list:
57 del cache[item]
58
59
60def log(message, level=None):
61 """Write a message to the juju log"""
62 command = ['juju-log']
63 if level:
64 command += ['-l', level]
65 command += [message]
66 subprocess.call(command)
67
68
69class Serializable(UserDict.IterableUserDict):
70 """Wrapper, an object that can be serialized to yaml or json"""
71
72 def __init__(self, obj):
73 # wrap the object
74 UserDict.IterableUserDict.__init__(self)
75 self.data = obj
76
77 def __getattr__(self, attr):
78 # See if this object has attribute.
79 if attr in ("json", "yaml", "data"):
80 return self.__dict__[attr]
81 # Check for attribute in wrapped object.
82 got = getattr(self.data, attr, MARKER)
83 if got is not MARKER:
84 return got
85 # Proxy to the wrapped object via dict interface.
86 try:
87 return self.data[attr]
88 except KeyError:
89 raise AttributeError(attr)
90
91 def __getstate__(self):
92 # Pickle as a standard dictionary.
93 return self.data
94
95 def __setstate__(self, state):
96 # Unpickle into our wrapper.
97 self.data = state
98
99 def json(self):
100 """Serialize the object to json"""
101 return json.dumps(self.data)
102
103 def yaml(self):
104 """Serialize the object to yaml"""
105 return yaml.dump(self.data)
106
107
108def execution_environment():
109 """A convenient bundling of the current execution context"""
110 context = {}
111 context['conf'] = config()
112 if relation_id():
113 context['reltype'] = relation_type()
114 context['relid'] = relation_id()
115 context['rel'] = relation_get()
116 context['unit'] = local_unit()
117 context['rels'] = relations()
118 context['env'] = os.environ
119 return context
120
121
122def in_relation_hook():
123 """Determine whether we're running in a relation hook"""
124 return 'JUJU_RELATION' in os.environ
125
126
127def relation_type():
128 """The scope for the current relation hook"""
129 return os.environ.get('JUJU_RELATION', None)
130
131
132def relation_id():
133 """The relation ID for the current relation hook"""
134 return os.environ.get('JUJU_RELATION_ID', None)
135
136
137def local_unit():
138 """Local unit ID"""
139 return os.environ['JUJU_UNIT_NAME']
140
141
142def remote_unit():
143 """The remote unit for the current relation hook"""
144 return os.environ['JUJU_REMOTE_UNIT']
145
146
147def service_name():
148 """The name service group this unit belongs to"""
149 return local_unit().split('/')[0]
150
151
152@cached
153def config(scope=None):
154 """Juju charm configuration"""
155 config_cmd_line = ['config-get']
156 if scope is not None:
157 config_cmd_line.append(scope)
158 config_cmd_line.append('--format=json')
159 try:
160 return json.loads(subprocess.check_output(config_cmd_line))
161 except ValueError:
162 return None
163
164
165@cached
166def relation_get(attribute=None, unit=None, rid=None):
167 """Get relation information"""
168 _args = ['relation-get', '--format=json']
169 if rid:
170 _args.append('-r')
171 _args.append(rid)
172 _args.append(attribute or '-')
173 if unit:
174 _args.append(unit)
175 try:
176 return json.loads(subprocess.check_output(_args))
177 except ValueError:
178 return None
179 except CalledProcessError, e:
180 if e.returncode == 2:
181 return None
182 raise
183
184
185def relation_set(relation_id=None, relation_settings={}, **kwargs):
186 """Set relation information for the current unit"""
187 relation_cmd_line = ['relation-set']
188 if relation_id is not None:
189 relation_cmd_line.extend(('-r', relation_id))
190 for k, v in (relation_settings.items() + kwargs.items()):
191 if v is None:
192 relation_cmd_line.append('{}='.format(k))
193 else:
194 relation_cmd_line.append('{}={}'.format(k, v))
195 subprocess.check_call(relation_cmd_line)
196 # Flush cache of any relation-gets for local unit
197 flush(local_unit())
198
199
200@cached
201def relation_ids(reltype=None):
202 """A list of relation_ids"""
203 reltype = reltype or relation_type()
204 relid_cmd_line = ['relation-ids', '--format=json']
205 if reltype is not None:
206 relid_cmd_line.append(reltype)
207 return json.loads(subprocess.check_output(relid_cmd_line)) or []
208 return []
209
210
211@cached
212def related_units(relid=None):
213 """A list of related units"""
214 relid = relid or relation_id()
215 units_cmd_line = ['relation-list', '--format=json']
216 if relid is not None:
217 units_cmd_line.extend(('-r', relid))
218 return json.loads(subprocess.check_output(units_cmd_line)) or []
219
220
221@cached
222def relation_for_unit(unit=None, rid=None):
223 """Get the json represenation of a unit's relation"""
224 unit = unit or remote_unit()
225 relation = relation_get(unit=unit, rid=rid)
226 for key in relation:
227 if key.endswith('-list'):
228 relation[key] = relation[key].split()
229 relation['__unit__'] = unit
230 return relation
231
232
233@cached
234def relations_for_id(relid=None):
235 """Get relations of a specific relation ID"""
236 relation_data = []
237 relid = relid or relation_ids()
238 for unit in related_units(relid):
239 unit_data = relation_for_unit(unit, relid)
240 unit_data['__relid__'] = relid
241 relation_data.append(unit_data)
242 return relation_data
243
244
245@cached
246def relations_of_type(reltype=None):
247 """Get relations of a specific type"""
248 relation_data = []
249 reltype = reltype or relation_type()
250 for relid in relation_ids(reltype):
251 for relation in relations_for_id(relid):
252 relation['__relid__'] = relid
253 relation_data.append(relation)
254 return relation_data
255
256
257@cached
258def relation_types():
259 """Get a list of relation types supported by this charm"""
260 charmdir = os.environ.get('CHARM_DIR', '')
261 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
262 md = yaml.safe_load(mdf)
263 rel_types = []
264 for key in ('provides', 'requires', 'peers'):
265 section = md.get(key)
266 if section:
267 rel_types.extend(section.keys())
268 mdf.close()
269 return rel_types
270
271
272@cached
273def relations():
274 """Get a nested dictionary of relation data for all related units"""
275 rels = {}
276 for reltype in relation_types():
277 relids = {}
278 for relid in relation_ids(reltype):
279 units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
280 for unit in related_units(relid):
281 reldata = relation_get(unit=unit, rid=relid)
282 units[unit] = reldata
283 relids[relid] = units
284 rels[reltype] = relids
285 return rels
286
287
288@cached
289def is_relation_made(relation, keys='private-address'):
290 '''
291 Determine whether a relation is established by checking for
292 presence of key(s). If a list of keys is provided, they
293 must all be present for the relation to be identified as made
294 '''
295 if isinstance(keys, str):
296 keys = [keys]
297 for r_id in relation_ids(relation):
298 for unit in related_units(r_id):
299 context = {}
300 for k in keys:
301 context[k] = relation_get(k, rid=r_id,
302 unit=unit)
303 if None not in context.values():
304 return True
305 return False
306
307
308def open_port(port, protocol="TCP"):
309 """Open a service network port"""
310 _args = ['open-port']
311 _args.append('{}/{}'.format(port, protocol))
312 subprocess.check_call(_args)
313
314
315def close_port(port, protocol="TCP"):
316 """Close a service network port"""
317 _args = ['close-port']
318 _args.append('{}/{}'.format(port, protocol))
319 subprocess.check_call(_args)
320
321
322@cached
323def unit_get(attribute):
324 """Get the unit ID for the remote unit"""
325 _args = ['unit-get', '--format=json', attribute]
326 try:
327 return json.loads(subprocess.check_output(_args))
328 except ValueError:
329 return None
330
331
332def unit_private_ip():
333 """Get this unit's private IP address"""
334 return unit_get('private-address')
335
336
337class UnregisteredHookError(Exception):
338 """Raised when an undefined hook is called"""
339 pass
340
341
342class Hooks(object):
343 """A convenient handler for hook functions.
344
345 Example:
346 hooks = Hooks()
347
348 # register a hook, taking its name from the function name
349 @hooks.hook()
350 def install():
351 ...
352
353 # register a hook, providing a custom hook name
354 @hooks.hook("config-changed")
355 def config_changed():
356 ...
357
358 if __name__ == "__main__":
359 # execute a hook based on the name the program is called by
360 hooks.execute(sys.argv)
361 """
362
363 def __init__(self):
364 super(Hooks, self).__init__()
365 self._hooks = {}
366
367 def register(self, name, function):
368 """Register a hook"""
369 self._hooks[name] = function
370
371 def execute(self, args):
372 """Execute a registered hook based on args[0]"""
373 hook_name = os.path.basename(args[0])
374 if hook_name in self._hooks:
375 self._hooks[hook_name]()
376 else:
377 raise UnregisteredHookError(hook_name)
378
379 def hook(self, *hook_names):
380 """Decorator, registering them as hooks"""
381 def wrapper(decorated):
382 for hook_name in hook_names:
383 self.register(hook_name, decorated)
384 else:
385 self.register(decorated.__name__, decorated)
386 if '_' in decorated.__name__:
387 self.register(
388 decorated.__name__.replace('_', '-'), decorated)
389 return decorated
390 return wrapper
391
392
393def charm_dir():
394 """Return the root directory of the current charm"""
395 return os.environ.get('CHARM_DIR')
0396
=== added file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/host.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,291 @@
1"""Tools for working with the host system"""
2# Copyright 2012 Canonical Ltd.
3#
4# Authors:
5# Nick Moffitt <nick.moffitt@canonical.com>
6# Matthew Wedgwood <matthew.wedgwood@canonical.com>
7
8import os
9import pwd
10import grp
11import random
12import string
13import subprocess
14import hashlib
15
16from collections import OrderedDict
17
18from hookenv import log
19
20
21def service_start(service_name):
22 """Start a system service"""
23 return service('start', service_name)
24
25
26def service_stop(service_name):
27 """Stop a system service"""
28 return service('stop', service_name)
29
30
31def service_restart(service_name):
32 """Restart a system service"""
33 return service('restart', service_name)
34
35
36def service_reload(service_name, restart_on_failure=False):
37 """Reload a system service, optionally falling back to restart if reload fails"""
38 service_result = service('reload', service_name)
39 if not service_result and restart_on_failure:
40 service_result = service('restart', service_name)
41 return service_result
42
43
44def service(action, service_name):
45 """Control a system service"""
46 cmd = ['service', service_name, action]
47 return subprocess.call(cmd) == 0
48
49
50def service_running(service):
51 """Determine whether a system service is running"""
52 try:
53 output = subprocess.check_output(['service', service, 'status'])
54 except subprocess.CalledProcessError:
55 return False
56 else:
57 if ("start/running" in output or "is running" in output):
58 return True
59 else:
60 return False
61
62
63def adduser(username, password=None, shell='/bin/bash', system_user=False):
64 """Add a user to the system"""
65 try:
66 user_info = pwd.getpwnam(username)
67 log('user {0} already exists!'.format(username))
68 except KeyError:
69 log('creating user {0}'.format(username))
70 cmd = ['useradd']
71 if system_user or password is None:
72 cmd.append('--system')
73 else:
74 cmd.extend([
75 '--create-home',
76 '--shell', shell,
77 '--password', password,
78 ])
79 cmd.append(username)
80 subprocess.check_call(cmd)
81 user_info = pwd.getpwnam(username)
82 return user_info
83
84
85def add_user_to_group(username, group):
86 """Add a user to a group"""
87 cmd = [
88 'gpasswd', '-a',
89 username,
90 group
91 ]
92 log("Adding user {} to group {}".format(username, group))
93 subprocess.check_call(cmd)
94
95
96def rsync(from_path, to_path, flags='-r', options=None):
97 """Replicate the contents of a path"""
98 options = options or ['--delete', '--executability']
99 cmd = ['/usr/bin/rsync', flags]
100 cmd.extend(options)
101 cmd.append(from_path)
102 cmd.append(to_path)
103 log(" ".join(cmd))
104 return subprocess.check_output(cmd).strip()
105
106
107def symlink(source, destination):
108 """Create a symbolic link"""
109 log("Symlinking {} as {}".format(source, destination))
110 cmd = [
111 'ln',
112 '-sf',
113 source,
114 destination,
115 ]
116 subprocess.check_call(cmd)
117
118
119def mkdir(path, owner='root', group='root', perms=0555, force=False):
120 """Create a directory"""
121 log("Making dir {} {}:{} {:o}".format(path, owner, group,
122 perms))
123 uid = pwd.getpwnam(owner).pw_uid
124 gid = grp.getgrnam(group).gr_gid
125 realpath = os.path.abspath(path)
126 if os.path.exists(realpath):
127 if force and not os.path.isdir(realpath):
128 log("Removing non-directory file {} prior to mkdir()".format(path))
129 os.unlink(realpath)
130 else:
131 os.makedirs(realpath, perms)
132 os.chown(realpath, uid, gid)
133
134
135def write_file(path, content, owner='root', group='root', perms=0444):
136 """Create or overwrite a file with the contents of a string"""
137 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
138 uid = pwd.getpwnam(owner).pw_uid
139 gid = grp.getgrnam(group).gr_gid
140 with open(path, 'w') as target:
141 os.fchown(target.fileno(), uid, gid)
142 os.fchmod(target.fileno(), perms)
143 target.write(content)
144
145
146def mount(device, mountpoint, options=None, persist=False):
147 """Mount a filesystem at a particular mountpoint"""
148 cmd_args = ['mount']
149 if options is not None:
150 cmd_args.extend(['-o', options])
151 cmd_args.extend([device, mountpoint])
152 try:
153 subprocess.check_output(cmd_args)
154 except subprocess.CalledProcessError, e:
155 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
156 return False
157 if persist:
158 # TODO: update fstab
159 pass
160 return True
161
162
163def umount(mountpoint, persist=False):
164 """Unmount a filesystem"""
165 cmd_args = ['umount', mountpoint]
166 try:
167 subprocess.check_output(cmd_args)
168 except subprocess.CalledProcessError, e:
169 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
170 return False
171 if persist:
172 # TODO: update fstab
173 pass
174 return True
175
176
177def mounts():
178 """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
179 with open('/proc/mounts') as f:
180 # [['/mount/point','/dev/path'],[...]]
181 system_mounts = [m[1::-1] for m in [l.strip().split()
182 for l in f.readlines()]]
183 return system_mounts
184
185
186def file_hash(path):
187 """Generate a md5 hash of the contents of 'path' or None if not found """
188 if os.path.exists(path):
189 h = hashlib.md5()
190 with open(path, 'r') as source:
191 h.update(source.read()) # IGNORE:E1101 - it does have update
192 return h.hexdigest()
193 else:
194 return None
195
196
197def restart_on_change(restart_map):
198 """Restart services based on configuration files changing
199
200 This function is used a decorator, for example
201
202 @restart_on_change({
203 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
204 })
205 def ceph_client_changed():
206 ...
207
208 In this example, the cinder-api and cinder-volume services
209 would be restarted if /etc/ceph/ceph.conf is changed by the
210 ceph_client_changed function.
211 """
212 def wrap(f):
213 def wrapped_f(*args):
214 checksums = {}
215 for path in restart_map:
216 checksums[path] = file_hash(path)
217 f(*args)
218 restarts = []
219 for path in restart_map:
220 if checksums[path] != file_hash(path):
221 restarts += restart_map[path]
222 for service_name in list(OrderedDict.fromkeys(restarts)):
223 service('restart', service_name)
224 return wrapped_f
225 return wrap
226
227
228def lsb_release():
229 """Return /etc/lsb-release in a dict"""
230 d = {}
231 with open('/etc/lsb-release', 'r') as lsb:
232 for l in lsb:
233 k, v = l.split('=')
234 d[k.strip()] = v.strip()
235 return d
236
237
238def pwgen(length=None):
239 """Generate a random pasword."""
240 if length is None:
241 length = random.choice(range(35, 45))
242 alphanumeric_chars = [
243 l for l in (string.letters + string.digits)
244 if l not in 'l0QD1vAEIOUaeiou']
245 random_chars = [
246 random.choice(alphanumeric_chars) for _ in range(length)]
247 return(''.join(random_chars))
248
249
250def list_nics(nic_type):
251 '''Return a list of nics of given type(s)'''
252 if isinstance(nic_type, basestring):
253 int_types = [nic_type]
254 else:
255 int_types = nic_type
256 interfaces = []
257 for int_type in int_types:
258 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
259 ip_output = subprocess.check_output(cmd).split('\n')
260 ip_output = (line for line in ip_output if line)
261 for line in ip_output:
262 if line.split()[1].startswith(int_type):
263 interfaces.append(line.split()[1].replace(":", ""))
264 return interfaces
265
266
267def set_nic_mtu(nic, mtu):
268 '''Set MTU on a network interface'''
269 cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
270 subprocess.check_call(cmd)
271
272
273def get_nic_mtu(nic):
274 cmd = ['ip', 'addr', 'show', nic]
275 ip_output = subprocess.check_output(cmd).split('\n')
276 mtu = ""
277 for line in ip_output:
278 words = line.split()
279 if 'mtu' in words:
280 mtu = words[words.index("mtu") + 1]
281 return mtu
282
283
284def get_nic_hwaddr(nic):
285 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
286 ip_output = subprocess.check_output(cmd)
287 hwaddr = ""
288 words = ip_output.split()
289 if 'link/ether' in words:
290 hwaddr = words[words.index('link/ether') + 1]
291 return hwaddr
0292
=== added directory 'hooks/charmhelpers/fetch'
=== added file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,279 @@
1import importlib
2from yaml import safe_load
3from charmhelpers.core.host import (
4 lsb_release
5)
6from urlparse import (
7 urlparse,
8 urlunparse,
9)
10import subprocess
11from charmhelpers.core.hookenv import (
12 config,
13 log,
14)
15import apt_pkg
16import os
17
18CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
19deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
20"""
21PROPOSED_POCKET = """# Proposed
22deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
23"""
24CLOUD_ARCHIVE_POCKETS = {
25 # Folsom
26 'folsom': 'precise-updates/folsom',
27 'precise-folsom': 'precise-updates/folsom',
28 'precise-folsom/updates': 'precise-updates/folsom',
29 'precise-updates/folsom': 'precise-updates/folsom',
30 'folsom/proposed': 'precise-proposed/folsom',
31 'precise-folsom/proposed': 'precise-proposed/folsom',
32 'precise-proposed/folsom': 'precise-proposed/folsom',
33 # Grizzly
34 'grizzly': 'precise-updates/grizzly',
35 'precise-grizzly': 'precise-updates/grizzly',
36 'precise-grizzly/updates': 'precise-updates/grizzly',
37 'precise-updates/grizzly': 'precise-updates/grizzly',
38 'grizzly/proposed': 'precise-proposed/grizzly',
39 'precise-grizzly/proposed': 'precise-proposed/grizzly',
40 'precise-proposed/grizzly': 'precise-proposed/grizzly',
41 # Havana
42 'havana': 'precise-updates/havana',
43 'precise-havana': 'precise-updates/havana',
44 'precise-havana/updates': 'precise-updates/havana',
45 'precise-updates/havana': 'precise-updates/havana',
46 'havana/proposed': 'precise-proposed/havana',
47 'precise-havana/proposed': 'precise-proposed/havana',
48 'precise-proposed/havana': 'precise-proposed/havana',
49 # Icehouse
50 'icehouse': 'precise-updates/icehouse',
51 'precise-icehouse': 'precise-updates/icehouse',
52 'precise-icehouse/updates': 'precise-updates/icehouse',
53 'precise-updates/icehouse': 'precise-updates/icehouse',
54 'icehouse/proposed': 'precise-proposed/icehouse',
55 'precise-icehouse/proposed': 'precise-proposed/icehouse',
56 'precise-proposed/icehouse': 'precise-proposed/icehouse',
57}
58
59
60def filter_installed_packages(packages):
61 """Returns a list of packages that require installation"""
62 apt_pkg.init()
63 cache = apt_pkg.Cache()
64 _pkgs = []
65 for package in packages:
66 try:
67 p = cache[package]
68 p.current_ver or _pkgs.append(package)
69 except KeyError:
70 log('Package {} has no installation candidate.'.format(package),
71 level='WARNING')
72 _pkgs.append(package)
73 return _pkgs
74
75
76def apt_install(packages, options=None, fatal=False):
77 """Install one or more packages"""
78 if options is None:
79 options = ['--option=Dpkg::Options::=--force-confold']
80
81 cmd = ['apt-get', '--assume-yes']
82 cmd.extend(options)
83 cmd.append('install')
84 if isinstance(packages, basestring):
85 cmd.append(packages)
86 else:
87 cmd.extend(packages)
88 log("Installing {} with options: {}".format(packages,
89 options))
90 env = os.environ.copy()
91 if 'DEBIAN_FRONTEND' not in env:
92 env['DEBIAN_FRONTEND'] = 'noninteractive'
93
94 if fatal:
95 subprocess.check_call(cmd, env=env)
96 else:
97 subprocess.call(cmd, env=env)
98
99
100def apt_update(fatal=False):
101 """Update local apt cache"""
102 cmd = ['apt-get', 'update']
103 if fatal:
104 subprocess.check_call(cmd)
105 else:
106 subprocess.call(cmd)
107
108
109def apt_purge(packages, fatal=False):
110 """Purge one or more packages"""
111 cmd = ['apt-get', '--assume-yes', 'purge']
112 if isinstance(packages, basestring):
113 cmd.append(packages)
114 else:
115 cmd.extend(packages)
116 log("Purging {}".format(packages))
117 if fatal:
118 subprocess.check_call(cmd)
119 else:
120 subprocess.call(cmd)
121
122
123def apt_hold(packages, fatal=False):
124 """Hold one or more packages"""
125 cmd = ['apt-mark', 'hold']
126 if isinstance(packages, basestring):
127 cmd.append(packages)
128 else:
129 cmd.extend(packages)
130 log("Holding {}".format(packages))
131 if fatal:
132 subprocess.check_call(cmd)
133 else:
134 subprocess.call(cmd)
135
136
137def add_source(source, key=None):
138 if (source.startswith('ppa:') or
139 source.startswith('http:') or
140 source.startswith('deb ') or
141 source.startswith('cloud-archive:')):
142 subprocess.check_call(['add-apt-repository', '--yes', source])
143 elif source.startswith('cloud:'):
144 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
145 fatal=True)
146 pocket = source.split(':')[-1]
147 if pocket not in CLOUD_ARCHIVE_POCKETS:
148 raise SourceConfigError(
149 'Unsupported cloud: source option %s' %
150 pocket)
151 actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
152 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
153 apt.write(CLOUD_ARCHIVE.format(actual_pocket))
154 elif source == 'proposed':
155 release = lsb_release()['DISTRIB_CODENAME']
156 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
157 apt.write(PROPOSED_POCKET.format(release))
158 if key:
159 subprocess.check_call(['apt-key', 'import', key])
160
161
162class SourceConfigError(Exception):
163 pass
164
165
166def configure_sources(update=False,
167 sources_var='install_sources',
168 keys_var='install_keys'):
169 """
170 Configure multiple sources from charm configuration
171
172 Example config:
173 install_sources:
174 - "ppa:foo"
175 - "http://example.com/repo precise main"
176 install_keys:
177 - null
178 - "a1b2c3d4"
179
180 Note that 'null' (a.k.a. None) should not be quoted.
181 """
182 sources = safe_load(config(sources_var))
183 keys = config(keys_var)
184 if keys is not None:
185 keys = safe_load(keys)
186 if isinstance(sources, basestring) and (
187 keys is None or isinstance(keys, basestring)):
188 add_source(sources, keys)
189 else:
190 if not len(sources) == len(keys):
191 msg = 'Install sources and keys lists are different lengths'
192 raise SourceConfigError(msg)
193 for src_num in range(len(sources)):
194 add_source(sources[src_num], keys[src_num])
195 if update:
196 apt_update(fatal=True)
197
198# The order of this list is very important. Handlers should be listed in from
199# least- to most-specific URL matching.
200FETCH_HANDLERS = (
201 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
202 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
203)
204
205
206class UnhandledSource(Exception):
207 pass
208
209
210def install_remote(source):
211 """
212 Install a file tree from a remote source
213
214 The specified source should be a url of the form:
215 scheme://[host]/path[#[option=value][&...]]
216
217 Schemes supported are based on this modules submodules
218 Options supported are submodule-specific"""
219 # We ONLY check for True here because can_handle may return a string
220 # explaining why it can't handle a given source.
221 handlers = [h for h in plugins() if h.can_handle(source) is True]
222 installed_to = None
223 for handler in handlers:
224 try:
225 installed_to = handler.install(source)
226 except UnhandledSource:
227 pass
228 if not installed_to:
229 raise UnhandledSource("No handler found for source {}".format(source))
230 return installed_to
231
232
233def install_from_config(config_var_name):
234 charm_config = config()
235 source = charm_config[config_var_name]
236 return install_remote(source)
237
238
239class BaseFetchHandler(object):
240
241 """Base class for FetchHandler implementations in fetch plugins"""
242
243 def can_handle(self, source):
244 """Returns True if the source can be handled. Otherwise returns
245 a string explaining why it cannot"""
246 return "Wrong source type"
247
248 def install(self, source):
249 """Try to download and unpack the source. Return the path to the
250 unpacked files or raise UnhandledSource."""
251 raise UnhandledSource("Wrong source type {}".format(source))
252
253 def parse_url(self, url):
254 return urlparse(url)
255
256 def base_url(self, url):
257 """Return url without querystring or fragment"""
258 parts = list(self.parse_url(url))
259 parts[4:] = ['' for i in parts[4:]]
260 return urlunparse(parts)
261
262
263def plugins(fetch_handlers=None):
264 if not fetch_handlers:
265 fetch_handlers = FETCH_HANDLERS
266 plugin_list = []
267 for handler_name in fetch_handlers:
268 package, classname = handler_name.rsplit('.', 1)
269 try:
270 handler_class = getattr(
271 importlib.import_module(package),
272 classname)
273 plugin_list.append(handler_class())
274 except (ImportError, AttributeError):
275 # Skip missing plugins so that they can be ommitted from
276 # installation if desired
277 log("FetchHandler {} not found, skipping plugin".format(
278 handler_name))
279 return plugin_list
0280
=== added file 'hooks/charmhelpers/fetch/archiveurl.py'
--- hooks/charmhelpers/fetch/archiveurl.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/fetch/archiveurl.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,48 @@
1import os
2import urllib2
3from charmhelpers.fetch import (
4 BaseFetchHandler,
5 UnhandledSource
6)
7from charmhelpers.payload.archive import (
8 get_archive_handler,
9 extract,
10)
11from charmhelpers.core.host import mkdir
12
13
14class ArchiveUrlFetchHandler(BaseFetchHandler):
15 """Handler for archives via generic URLs"""
16 def can_handle(self, source):
17 url_parts = self.parse_url(source)
18 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
19 return "Wrong source type"
20 if get_archive_handler(self.base_url(source)):
21 return True
22 return False
23
24 def download(self, source, dest):
25 # propogate all exceptions
26 # URLError, OSError, etc
27 response = urllib2.urlopen(source)
28 try:
29 with open(dest, 'w') as dest_file:
30 dest_file.write(response.read())
31 except Exception as e:
32 if os.path.isfile(dest):
33 os.unlink(dest)
34 raise e
35
36 def install(self, source):
37 url_parts = self.parse_url(source)
38 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
39 if not os.path.exists(dest_dir):
40 mkdir(dest_dir, perms=0755)
41 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
42 try:
43 self.download(source, dld_file)
44 except urllib2.URLError as e:
45 raise UnhandledSource(e.reason)
46 except OSError as e:
47 raise UnhandledSource(e.strerror)
48 return extract(dld_file)
049
=== added file 'hooks/charmhelpers/fetch/bzrurl.py'
--- hooks/charmhelpers/fetch/bzrurl.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,49 @@
1import os
2from charmhelpers.fetch import (
3 BaseFetchHandler,
4 UnhandledSource
5)
6from charmhelpers.core.host import mkdir
7
8try:
9 from bzrlib.branch import Branch
10except ImportError:
11 from charmhelpers.fetch import apt_install
12 apt_install("python-bzrlib")
13 from bzrlib.branch import Branch
14
15
16class BzrUrlFetchHandler(BaseFetchHandler):
17 """Handler for bazaar branches via generic and lp URLs"""
18 def can_handle(self, source):
19 url_parts = self.parse_url(source)
20 if url_parts.scheme not in ('bzr+ssh', 'lp'):
21 return False
22 else:
23 return True
24
25 def branch(self, source, dest):
26 url_parts = self.parse_url(source)
27 # If we use lp:branchname scheme we need to load plugins
28 if not self.can_handle(source):
29 raise UnhandledSource("Cannot handle {}".format(source))
30 if url_parts.scheme == "lp":
31 from bzrlib.plugin import load_plugins
32 load_plugins()
33 try:
34 remote_branch = Branch.open(source)
35 remote_branch.bzrdir.sprout(dest).open_branch()
36 except Exception as e:
37 raise e
38
39 def install(self, source):
40 url_parts = self.parse_url(source)
41 branch_name = url_parts.path.strip("/").split("/")[-1]
42 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", branch_name)
43 if not os.path.exists(dest_dir):
44 mkdir(dest_dir, perms=0755)
45 try:
46 self.branch(source, dest_dir)
47 except OSError as e:
48 raise UnhandledSource(e.strerror)
49 return dest_dir
050
=== added directory 'hooks/charmhelpers/payload'
=== added file 'hooks/charmhelpers/payload/__init__.py'
--- hooks/charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/payload/__init__.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,1 @@
1"Tools for working with files injected into a charm just before deployment."
02
=== added file 'hooks/charmhelpers/payload/execd.py'
--- hooks/charmhelpers/payload/execd.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/payload/execd.py 2014-01-24 17:20:41 +0000
@@ -0,0 +1,50 @@
1#!/usr/bin/env python
2
3import os
4import sys
5import subprocess
6from charmhelpers.core import hookenv
7
8
9def default_execd_dir():
10 return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
11
12
13def execd_module_paths(execd_dir=None):
14 """Generate a list of full paths to modules within execd_dir."""
15 if not execd_dir:
16 execd_dir = default_execd_dir()
17
18 if not os.path.exists(execd_dir):
19 return
20
21 for subpath in os.listdir(execd_dir):
22 module = os.path.join(execd_dir, subpath)
23 if os.path.isdir(module):
24 yield module
25
26
27def execd_submodule_paths(command, execd_dir=None):
28 """Generate a list of full paths to the specified command within exec_dir.
29 """
30 for module_path in execd_module_paths(execd_dir):
31 path = os.path.join(module_path, command)
32 if os.access(path, os.X_OK) and os.path.isfile(path):
33 yield path
34
35
36def execd_run(command, execd_dir=None, die_on_error=False, stderr=None):
37 """Run command for each module within execd_dir which defines it."""
38 for submodule_path in execd_submodule_paths(command, execd_dir):
39 try:
40 subprocess.check_call(submodule_path, shell=True, stderr=stderr)
41 except subprocess.CalledProcessError as e:
42 hookenv.log("Error ({}) running {}. Output: {}".format(
43 e.returncode, e.cmd, e.output))
44 if die_on_error:
45 sys.exit(e.returncode)
46
47
48def execd_preinstall(execd_dir=None):
49 """Run charm-pre-install for each module within execd_dir."""
50 execd_run('charm-pre-install', execd_dir=execd_dir)
051
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2013-01-11 09:15:51 +0000
+++ hooks/hooks.py 2014-01-24 17:20:41 +0000
@@ -14,7 +14,33 @@
14import os14import os
15import ceph15import ceph
1616
17import utils17from charmhelpers.core.hookenv import (
18 relation_get,
19 relation_ids,
20 related_units,
21 config,
22 unit_get,
23 open_port,
24 relation_set,
25 log,
26 Hooks, UnregisteredHookError,
27)
28from charmhelpers.fetch import (
29 apt_update,
30 apt_install,
31 add_source,
32)
33from utils import (
34 render_template,
35 get_host_ip,
36 enable_pocket,
37 is_apache_24
38)
39
40from charmhelpers.payload.execd import execd_preinstall
41from socket import gethostname as get_unit_hostname
42
43hooks = Hooks()
1844
1945
20def install_www_scripts():46def install_www_scripts():
@@ -22,19 +48,20 @@
22 shutil.copy(x, '/var/www/')48 shutil.copy(x, '/var/www/')
2349
2450
25NSS_DIR='/var/lib/ceph/nss'51NSS_DIR = '/var/lib/ceph/nss'
2652
2753
54@hooks.hook('install')
28def install():55def install():
29 utils.juju_log('INFO', 'Begin install hook.')56 execd_preinstall()
30 utils.enable_pocket('multiverse')57 enable_pocket('multiverse')
31 utils.configure_source()58 add_source(config('source'), config('key'))
32 utils.install('radosgw',59 apt_update(fatal=True)
33 'libapache2-mod-fastcgi',60 apt_install(['radosgw',
34 'apache2',61 'libapache2-mod-fastcgi',
35 'ntp')62 'apache2',
63 'ntp'], fatal=True)
36 os.makedirs(NSS_DIR)64 os.makedirs(NSS_DIR)
37 utils.juju_log('INFO', 'End install hook.')
3865
3966
40def emit_cephconf():67def emit_cephconf():
@@ -45,68 +72,70 @@
45 cephcontext = {72 cephcontext = {
46 'auth_supported': get_auth() or 'none',73 'auth_supported': get_auth() or 'none',
47 'mon_hosts': ' '.join(get_mon_hosts()),74 'mon_hosts': ' '.join(get_mon_hosts()),
48 'hostname': utils.get_unit_hostname(),75 'hostname': get_unit_hostname(),
49 'version': ceph.get_ceph_version('radosgw')76 'version': ceph.get_ceph_version('radosgw')
50 }77 }
51 78
52 # Check to ensure that correct version of ceph is 79 # Check to ensure that correct version of ceph is
53 # in use80 # in use
54 if ceph.get_ceph_version('radosgw') >= "0.55": 81 if ceph.get_ceph_version('radosgw') >= "0.55":
55 # Add keystone configuration if found82 # Add keystone configuration if found
56 ks_conf = get_keystone_conf()83 ks_conf = get_keystone_conf()
57 if ks_conf:84 if ks_conf:
58 cephcontext.update(ks_conf)85 cephcontext.update(ks_conf)
5986
60 with open('/etc/ceph/ceph.conf', 'w') as cephconf:87 with open('/etc/ceph/ceph.conf', 'w') as cephconf:
61 cephconf.write(utils.render_template('ceph.conf', cephcontext))88 cephconf.write(render_template('ceph.conf', cephcontext))
6289
6390
64def emit_apacheconf():91def emit_apacheconf():
65 apachecontext = {92 apachecontext = {
66 "hostname": utils.unit_get('private-address')93 "hostname": unit_get('private-address')
67 }94 }
68 with open('/etc/apache2/sites-available/rgw', 'w') as apacheconf:95 site_conf = '/etc/apache2/sites-available/rgw'
69 apacheconf.write(utils.render_template('rgw', apachecontext))96 if is_apache_24():
97 site_conf = '/etc/apache2/sites-available/rgw.conf'
98 with open(site_conf, 'w') as apacheconf:
99 apacheconf.write(render_template('rgw', apachecontext))
70100
71101
72def apache_sites():102def apache_sites():
73 utils.juju_log('INFO', 'Begin apache_sites.')103 if is_apache_24():
74 subprocess.check_call(['a2dissite', 'default'])104 subprocess.check_call(['a2dissite', '000-default'])
105 else:
106 subprocess.check_call(['a2dissite', 'default'])
75 subprocess.check_call(['a2ensite', 'rgw'])107 subprocess.check_call(['a2ensite', 'rgw'])
76 utils.juju_log('INFO', 'End apache_sites.')
77108
78109
79def apache_modules():110def apache_modules():
80 utils.juju_log('INFO', 'Begin apache_sites.')
81 subprocess.check_call(['a2enmod', 'fastcgi'])111 subprocess.check_call(['a2enmod', 'fastcgi'])
82 subprocess.check_call(['a2enmod', 'rewrite'])112 subprocess.check_call(['a2enmod', 'rewrite'])
83 utils.juju_log('INFO', 'End apache_sites.')
84113
85114
86def apache_reload():115def apache_reload():
87 subprocess.call(['service', 'apache2', 'reload'])116 subprocess.call(['service', 'apache2', 'reload'])
88117
89118
119@hooks.hook('upgrade-charm',
120 'config-changed')
90def config_changed():121def config_changed():
91 utils.juju_log('INFO', 'Begin config-changed hook.')
92 emit_cephconf()122 emit_cephconf()
93 emit_apacheconf()123 emit_apacheconf()
94 install_www_scripts()124 install_www_scripts()
95 apache_sites()125 apache_sites()
96 apache_modules()126 apache_modules()
97 apache_reload()127 apache_reload()
98 utils.juju_log('INFO', 'End config-changed hook.')
99128
100129
101def get_mon_hosts():130def get_mon_hosts():
102 hosts = []131 hosts = []
103 for relid in utils.relation_ids('mon'):132 for relid in relation_ids('mon'):
104 for unit in utils.relation_list(relid):133 for unit in related_units(relid):
105 hosts.append(134 hosts.append(
106 '{}:6789'.format(utils.get_host_ip(135 '{}:6789'.format(get_host_ip(
107 utils.relation_get('private-address',136 relation_get('private-address',
108 unit, relid)))137 unit, relid)))
109 )138 )
110139
111 hosts.sort()140 hosts.sort()
112 return hosts141 return hosts
@@ -117,100 +146,90 @@
117146
118147
119def get_conf(name):148def get_conf(name):
120 for relid in utils.relation_ids('mon'):149 for relid in relation_ids('mon'):
121 for unit in utils.relation_list(relid):150 for unit in related_units(relid):
122 conf = utils.relation_get(name,151 conf = relation_get(name,
123 unit, relid)152 unit, relid)
124 if conf:153 if conf:
125 return conf154 return conf
126 return None155 return None
127156
157
128def get_keystone_conf():158def get_keystone_conf():
129 for relid in utils.relation_ids('identity-service'):159 for relid in relation_ids('identity-service'):
130 for unit in utils.relation_list(relid):160 for unit in related_units(relid):
131 ks_auth = {161 ks_auth = {
132 'auth_type': 'keystone',162 'auth_type': 'keystone',
133 'auth_protocol': 'http',163 'auth_protocol': 'http',
134 'auth_host': utils.relation_get('auth_host', unit, relid),164 'auth_host': relation_get('auth_host', unit, relid),
135 'auth_port': utils.relation_get('auth_port', unit, relid),165 'auth_port': relation_get('auth_port', unit, relid),
136 'admin_token': utils.relation_get('admin_token', unit, relid),166 'admin_token': relation_get('admin_token', unit, relid),
137 'user_roles': utils.config_get('operator-roles'),167 'user_roles': config('operator-roles'),
138 'cache_size': utils.config_get('cache-size'),168 'cache_size': config('cache-size'),
139 'revocation_check_interval': utils.config_get('revocation-check-interval')169 'revocation_check_interval':
170 config('revocation-check-interval')
140 }171 }
141 if None not in ks_auth.itervalues():172 if None not in ks_auth.itervalues():
142 return ks_auth173 return ks_auth
143 return None174 return None
144175
145176
177@hooks.hook('mon-relation-departed',
178 'mon-relation-changed')
146def mon_relation():179def mon_relation():
147 utils.juju_log('INFO', 'Begin mon-relation hook.')
148 emit_cephconf()180 emit_cephconf()
149 key = utils.relation_get('radosgw_key')181 key = relation_get('radosgw_key')
150 if key:182 if key:
151 ceph.import_radosgw_key(key)183 ceph.import_radosgw_key(key)
152 restart() # TODO figure out a better way todo this184 restart() # TODO figure out a better way todo this
153 utils.juju_log('INFO', 'End mon-relation hook.')185
154186
155187@hooks.hook('gateway-relation-joined')
156def gateway_relation():188def gateway_relation():
157 utils.juju_log('INFO', 'Begin gateway-relation hook.')189 relation_set(hostname=unit_get('private-address'),
158 utils.relation_set(hostname=utils.unit_get('private-address'),190 port=80)
159 port=80)
160 utils.juju_log('INFO', 'Begin gateway-relation hook.')
161
162
163def upgrade_charm():
164 utils.juju_log('INFO', 'Begin upgrade-charm hook.')
165 utils.juju_log('INFO', 'End upgrade-charm hook.')
166191
167192
168def start():193def start():
169 subprocess.call(['service', 'radosgw', 'start'])194 subprocess.call(['service', 'radosgw', 'start'])
170 utils.expose(port=80)195 open_port(port=80)
171196
172197
173def stop():198def stop():
174 subprocess.call(['service', 'radosgw', 'stop'])199 subprocess.call(['service', 'radosgw', 'stop'])
175 utils.expose(port=80)200 open_port(port=80)
176201
177202
178def restart():203def restart():
179 subprocess.call(['service', 'radosgw', 'restart'])204 subprocess.call(['service', 'radosgw', 'restart'])
180 utils.expose(port=80)205 open_port(port=80)
181206
182207
208@hooks.hook('identity-service-relation-joined',
209 'identity-service-relation-changed')
183def identity_joined(relid=None):210def identity_joined(relid=None):
184 if ceph.get_ceph_version('radosgw') < "0.55":211 if ceph.get_ceph_version('radosgw') < "0.55":
185 utils.juju_log('ERROR',212 log('Integration with keystone requires ceph >= 0.55')
186 'Integration with keystone requires ceph >= 0.55')
187 sys.exit(1)213 sys.exit(1)
188214
189 hostname = utils.unit_get('private-address')215 hostname = unit_get('private-address')
190 admin_url = 'http://{}:80/swift'.format(hostname)216 admin_url = 'http://{}:80/swift'.format(hostname)
191 internal_url = public_url = '{}/v1'.format(admin_url)217 internal_url = public_url = '{}/v1'.format(admin_url)
192 utils.relation_set(service='swift',218 relation_set(service='swift',
193 region=utils.config_get('region'),219 region=config('region'),
194 public_url=public_url, internal_url=internal_url,220 public_url=public_url, internal_url=internal_url,
195 admin_url=admin_url,221 admin_url=admin_url,
196 requested_roles=utils.config_get('operator-roles'),222 requested_roles=config('operator-roles'),
197 rid=relid)223 rid=relid)
198224
199225
200def identity_changed():226def identity_changed():
201 emit_cephconf()227 emit_cephconf()
202 restart() 228 restart()
203229
204230
205utils.do_hooks({231if __name__ == '__main__':
206 'install': install,232 try:
207 'config-changed': config_changed,233 hooks.execute(sys.argv)
208 'mon-relation-departed': mon_relation,234 except UnregisteredHookError as e:
209 'mon-relation-changed': mon_relation,235 log('Unknown hook {} - skipping.'.format(e))
210 'gateway-relation-joined': gateway_relation,
211 'upgrade-charm': config_changed, # same function ATM
212 'identity-service-relation-joined': identity_joined,
213 'identity-service-relation-changed': identity_changed
214 })
215
216sys.exit(0)
217236
=== modified file 'hooks/utils.py'
--- hooks/utils.py 2013-09-24 11:29:07 +0000
+++ hooks/utils.py 2014-01-24 17:20:41 +0000
@@ -7,97 +7,36 @@
7# Paul Collins <paul.collins@canonical.com>7# Paul Collins <paul.collins@canonical.com>
8#8#
99
10import os
11import subprocess
12import socket10import socket
13import sys
14import re11import re
1512import os
1613
17def do_hooks(hooks):14from charmhelpers.core.hookenv import unit_get
18 hook = os.path.basename(sys.argv[0])15from charmhelpers.fetch import apt_install
19
20 try:
21 hook_func = hooks[hook]
22 except KeyError:
23 juju_log('INFO',
24 "This charm doesn't know how to handle '{}'.".format(hook))
25 else:
26 hook_func()
27
28
29def install(*pkgs):
30 cmd = [
31 'apt-get',
32 '-y',
33 'install'
34 ]
35 for pkg in pkgs:
36 cmd.append(pkg)
37 subprocess.check_call(cmd)
3816
39TEMPLATES_DIR = 'templates'17TEMPLATES_DIR = 'templates'
4018
41try:19try:
42 import jinja220 import jinja2
43except ImportError:21except ImportError:
44 install('python-jinja2')22 apt_install('python-jinja2', fatal=True)
45 import jinja223 import jinja2
4624
47try:25try:
48 import dns.resolver26 import dns.resolver
49except ImportError:27except ImportError:
50 install('python-dnspython')28 apt_install('python-dnspython', fatal=True)
51 import dns.resolver29 import dns.resolver
5230
5331
54def render_template(template_name, context, template_dir=TEMPLATES_DIR):32def render_template(template_name, context, template_dir=TEMPLATES_DIR):
55 templates = jinja2.Environment(33 templates = jinja2.Environment(
56 loader=jinja2.FileSystemLoader(template_dir)34 loader=jinja2.FileSystemLoader(template_dir)
57 )35 )
58 template = templates.get_template(template_name)36 template = templates.get_template(template_name)
59 return template.render(context)37 return template.render(context)
6038
6139
62CLOUD_ARCHIVE = \
63""" # Ubuntu Cloud Archive
64deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
65"""
66
67
68def configure_source():
69 source = str(config_get('source'))
70 if not source:
71 return
72 if source.startswith('ppa:'):
73 cmd = [
74 'add-apt-repository',
75 source
76 ]
77 subprocess.check_call(cmd)
78 if source.startswith('cloud:'):
79 install('ubuntu-cloud-keyring')
80 pocket = source.split(':')[1]
81 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
82 apt.write(CLOUD_ARCHIVE.format(pocket))
83 if source.startswith('http:'):
84 with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt:
85 apt.write("deb " + source + "\n")
86 key = config_get('key')
87 if key:
88 cmd = [
89 'apt-key',
90 'adv', '--keyserver keyserver.ubuntu.com',
91 '--recv-keys', key
92 ]
93 subprocess.check_call(cmd)
94 cmd = [
95 'apt-get',
96 'update'
97 ]
98 subprocess.check_call(cmd)
99
100
101def enable_pocket(pocket):40def enable_pocket(pocket):
102 apt_sources = "/etc/apt/sources.list"41 apt_sources = "/etc/apt/sources.list"
103 with open(apt_sources, "r") as sources:42 with open(apt_sources, "r") as sources:
@@ -109,103 +48,6 @@
109 else:48 else:
110 sources.write(line)49 sources.write(line)
11150
112# Protocols
113TCP = 'TCP'
114UDP = 'UDP'
115
116
117def expose(port, protocol='TCP'):
118 cmd = [
119 'open-port',
120 '{}/{}'.format(port, protocol)
121 ]
122 subprocess.check_call(cmd)
123
124
125def juju_log(severity, message):
126 cmd = [
127 'juju-log',
128 '--log-level', severity,
129 message
130 ]
131 subprocess.check_call(cmd)
132
133
134def relation_ids(relation):
135 cmd = [
136 'relation-ids',
137 relation
138 ]
139 return subprocess.check_output(cmd).split() # IGNORE:E1103
140
141
142def relation_list(rid):
143 cmd = [
144 'relation-list',
145 '-r', rid,
146 ]
147 return subprocess.check_output(cmd).split() # IGNORE:E1103
148
149
150def relation_get(attribute, unit=None, rid=None):
151 cmd = [
152 'relation-get',
153 ]
154 if rid:
155 cmd.append('-r')
156 cmd.append(rid)
157 cmd.append(attribute)
158 if unit:
159 cmd.append(unit)
160 value = str(subprocess.check_output(cmd)).strip()
161 if value == "":
162 return None
163 else:
164 return value
165
166
167def relation_set(**kwargs):
168 cmd = [
169 'relation-set'
170 ]
171 args = []
172 for k, v in kwargs.items():
173 if k == 'rid' and v:
174 cmd.append('-r')
175 cmd.append(v)
176 elif k != 'rid':
177 args.append('{}={}'.format(k, v))
178 cmd += args
179 subprocess.check_call(cmd)
180
181
182def unit_get(attribute):
183 cmd = [
184 'unit-get',
185 attribute
186 ]
187 value = str(subprocess.check_output(cmd)).strip()
188 if value == "":
189 return None
190 else:
191 return value
192
193
194def config_get(attribute):
195 cmd = [
196 'config-get',
197 attribute
198 ]
199 value = str(subprocess.check_output(cmd)).strip()
200 if value == "":
201 return None
202 else:
203 return value
204
205
206def get_unit_hostname():
207 return socket.gethostname()
208
20951
210def get_host_ip(hostname=unit_get('private-address')):52def get_host_ip(hostname=unit_get('private-address')):
211 try:53 try:
@@ -218,3 +60,10 @@
218 answers = dns.resolver.query(hostname, 'A')60 answers = dns.resolver.query(hostname, 'A')
219 if answers:61 if answers:
220 return answers[0].address62 return answers[0].address
63
64
65def is_apache_24():
66 if os.path.exists('/etc/apache2/conf-available'):
67 return True
68 else:
69 return False
22170
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-01-11 09:15:51 +0000
+++ metadata.yaml 2014-01-24 17:20:41 +0000
@@ -7,6 +7,8 @@
7 .7 .
8 This charm provides the RADOS HTTP gateway supporting S3 and Swift protocols8 This charm provides the RADOS HTTP gateway supporting S3 and Swift protocols
9 for object storage.9 for object storage.
10categories:
11 - misc
10requires:12requires:
11 mon:13 mon:
12 interface: ceph-radosgw14 interface: ceph-radosgw

Subscribers

People subscribed via source and target branches

to all changes: