Merge lp:~openstack-charmers/charms/precise/quantum-gateway/ods_merge into lp:~charmers/charms/precise/quantum-gateway/trunk

Proposed by Adam Gandelman
Status: Merged
Merged at revision: 40
Proposed branch: lp:~openstack-charmers/charms/precise/quantum-gateway/ods_merge
Merge into: lp:~charmers/charms/precise/quantum-gateway/trunk
Diff against target: 1490 lines (+860/-62)
16 files modified
charm-helpers-sync.yaml (+1/-0)
hooks/charmhelpers/contrib/network/ovs/__init__.py (+4/-1)
hooks/charmhelpers/contrib/openstack/alternatives.py (+17/-0)
hooks/charmhelpers/contrib/openstack/context.py (+20/-1)
hooks/charmhelpers/contrib/openstack/neutron.py (+20/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+81/-10)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+383/-0)
hooks/charmhelpers/contrib/storage/linux/loopback.py (+62/-0)
hooks/charmhelpers/contrib/storage/linux/lvm.py (+88/-0)
hooks/charmhelpers/contrib/storage/linux/utils.py (+25/-0)
hooks/charmhelpers/core/hookenv.py (+78/-23)
hooks/charmhelpers/core/host.py (+15/-9)
hooks/charmhelpers/fetch/__init__.py (+53/-5)
hooks/charmhelpers/fetch/bzrurl.py (+1/-1)
hooks/quantum_utils.py (+3/-6)
unit_tests/test_quantum_utils.py (+9/-6)
To merge this branch: bzr merge lp:~openstack-charmers/charms/precise/quantum-gateway/ods_merge
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Abstain
OpenStack Charmers Pending
Review via email: mp+194065@code.launchpad.net

Description of the change

Adds Neutron NVP support

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

Rebase on trunk, resync helpers and add missing deps

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-sync.yaml'
--- charm-helpers-sync.yaml 2013-10-15 01:32:42 +0000
+++ charm-helpers-sync.yaml 2013-11-08 05:56:36 +0000
@@ -6,4 +6,5 @@
6 - contrib.openstack6 - contrib.openstack
7 - contrib.hahelpers7 - contrib.hahelpers
8 - contrib.network.ovs8 - contrib.network.ovs
9 - contrib.storage.linux
9 - payload.execd10 - payload.execd
1011
=== modified file 'hooks/charmhelpers/contrib/network/ovs/__init__.py'
--- hooks/charmhelpers/contrib/network/ovs/__init__.py 2013-07-10 14:52:15 +0000
+++ hooks/charmhelpers/contrib/network/ovs/__init__.py 2013-11-08 05:56:36 +0000
@@ -69,4 +69,7 @@
6969
70def full_restart():70def full_restart():
71 ''' Full restart and reload of openvswitch '''71 ''' Full restart and reload of openvswitch '''
72 service('force-reload-kmod', 'openvswitch-switch')72 if os.path.exists('/etc/init/openvswitch-force-reload-kmod.conf'):
73 service('start', 'openvswitch-force-reload-kmod')
74 else:
75 service('force-reload-kmod', 'openvswitch-switch')
7376
=== 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 2013-11-08 05:56:36 +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
=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
--- hooks/charmhelpers/contrib/openstack/context.py 2013-10-15 01:32:42 +0000
+++ hooks/charmhelpers/contrib/openstack/context.py 2013-11-08 05:56:36 +0000
@@ -385,16 +385,33 @@
385 def ovs_ctxt(self):385 def ovs_ctxt(self):
386 driver = neutron_plugin_attribute(self.plugin, 'driver',386 driver = neutron_plugin_attribute(self.plugin, 'driver',
387 self.network_manager)387 self.network_manager)
388388 config = neutron_plugin_attribute(self.plugin, 'config',
389 self.network_manager)
389 ovs_ctxt = {390 ovs_ctxt = {
390 'core_plugin': driver,391 'core_plugin': driver,
391 'neutron_plugin': 'ovs',392 'neutron_plugin': 'ovs',
392 'neutron_security_groups': self.neutron_security_groups,393 'neutron_security_groups': self.neutron_security_groups,
393 'local_ip': unit_private_ip(),394 'local_ip': unit_private_ip(),
395 'config': config
394 }396 }
395397
396 return ovs_ctxt398 return ovs_ctxt
397399
400 def nvp_ctxt(self):
401 driver = neutron_plugin_attribute(self.plugin, 'driver',
402 self.network_manager)
403 config = neutron_plugin_attribute(self.plugin, 'config',
404 self.network_manager)
405 nvp_ctxt = {
406 'core_plugin': driver,
407 'neutron_plugin': 'nvp',
408 'neutron_security_groups': self.neutron_security_groups,
409 'local_ip': unit_private_ip(),
410 'config': config
411 }
412
413 return nvp_ctxt
414
398 def __call__(self):415 def __call__(self):
399 self._ensure_packages()416 self._ensure_packages()
400417
@@ -408,6 +425,8 @@
408425
409 if self.plugin == 'ovs':426 if self.plugin == 'ovs':
410 ctxt.update(self.ovs_ctxt())427 ctxt.update(self.ovs_ctxt())
428 elif self.plugin == 'nvp':
429 ctxt.update(self.nvp_ctxt())
411430
412 self._save_flag_file()431 self._save_flag_file()
413 return ctxt432 return ctxt
414433
=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
--- hooks/charmhelpers/contrib/openstack/neutron.py 2013-10-15 01:32:42 +0000
+++ hooks/charmhelpers/contrib/openstack/neutron.py 2013-11-08 05:56:36 +0000
@@ -34,13 +34,23 @@
34 'services': ['quantum-plugin-openvswitch-agent'],34 'services': ['quantum-plugin-openvswitch-agent'],
35 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],35 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
36 ['quantum-plugin-openvswitch-agent']],36 ['quantum-plugin-openvswitch-agent']],
37 'server_packages': ['quantum-server',
38 'quantum-plugin-openvswitch'],
39 'server_services': ['quantum-server']
37 },40 },
38 'nvp': {41 'nvp': {
39 'config': '/etc/quantum/plugins/nicira/nvp.ini',42 'config': '/etc/quantum/plugins/nicira/nvp.ini',
40 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'43 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
41 'QuantumPlugin.NvpPluginV2',44 'QuantumPlugin.NvpPluginV2',
45 'contexts': [
46 context.SharedDBContext(user=config('neutron-database-user'),
47 database=config('neutron-database'),
48 relation_prefix='neutron')],
42 'services': [],49 'services': [],
43 'packages': [],50 'packages': [],
51 'server_packages': ['quantum-server',
52 'quantum-plugin-nicira'],
53 'server_services': ['quantum-server']
44 }54 }
45 }55 }
4656
@@ -60,13 +70,23 @@
60 'services': ['neutron-plugin-openvswitch-agent'],70 'services': ['neutron-plugin-openvswitch-agent'],
61 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],71 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
62 ['quantum-plugin-openvswitch-agent']],72 ['quantum-plugin-openvswitch-agent']],
73 'server_packages': ['neutron-server',
74 'neutron-plugin-openvswitch'],
75 'server_services': ['neutron-server']
63 },76 },
64 'nvp': {77 'nvp': {
65 'config': '/etc/neutron/plugins/nicira/nvp.ini',78 'config': '/etc/neutron/plugins/nicira/nvp.ini',
66 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'79 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'
67 'NeutronPlugin.NvpPluginV2',80 'NeutronPlugin.NvpPluginV2',
81 'contexts': [
82 context.SharedDBContext(user=config('neutron-database-user'),
83 database=config('neutron-database'),
84 relation_prefix='neutron')],
68 'services': [],85 'services': [],
69 'packages': [],86 'packages': [],
87 'server_packages': ['neutron-server',
88 'neutron-plugin-nicira'],
89 'server_services': ['neutron-server']
70 }90 }
71 }91 }
7292
7393
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2013-10-15 01:32:42 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2013-11-08 05:56:36 +0000
@@ -13,19 +13,28 @@
13 config,13 config,
14 log as juju_log,14 log as juju_log,
15 charm_dir,15 charm_dir,
16)16 ERROR,
1717 INFO
18from charmhelpers.core.host import (18)
19 lsb_release,19
20)20from charmhelpers.contrib.storage.linux.lvm import (
2121 deactivate_lvm_volume_group,
22from charmhelpers.fetch import (22 is_lvm_physical_volume,
23 apt_install,23 remove_lvm_physical_volume,
24)24)
25
26from charmhelpers.core.host import lsb_release, mounts, umount
27from charmhelpers.fetch import apt_install
28from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
29from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
2530
26CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"31CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
27CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'32CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
2833
34DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
35 'restricted main multiverse universe')
36
37
29UBUNTU_OPENSTACK_RELEASE = OrderedDict([38UBUNTU_OPENSTACK_RELEASE = OrderedDict([
30 ('oneiric', 'diablo'),39 ('oneiric', 'diablo'),
31 ('precise', 'essex'),40 ('precise', 'essex'),
@@ -57,6 +66,8 @@
57 ('1.9.0', 'havana'),66 ('1.9.0', 'havana'),
58])67])
5968
69DEFAULT_LOOPBACK_SIZE = '5G'
70
6071
61def error_out(msg):72def error_out(msg):
62 juju_log("FATAL ERROR: %s" % msg, level='ERROR')73 juju_log("FATAL ERROR: %s" % msg, level='ERROR')
@@ -67,7 +78,7 @@
67 '''Derive OpenStack release codename from a given installation source.'''78 '''Derive OpenStack release codename from a given installation source.'''
68 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']79 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
69 rel = ''80 rel = ''
70 if src == 'distro':81 if src in ['distro', 'distro-proposed']:
71 try:82 try:
72 rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]83 rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
73 except KeyError:84 except KeyError:
@@ -202,6 +213,10 @@
202 '''Configure apt installation source.'''213 '''Configure apt installation source.'''
203 if rel == 'distro':214 if rel == 'distro':
204 return215 return
216 elif rel == 'distro-proposed':
217 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
218 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
219 f.write(DISTRO_PROPOSED % ubuntu_rel)
205 elif rel[:4] == "ppa:":220 elif rel[:4] == "ppa:":
206 src = rel221 src = rel
207 subprocess.check_call(["add-apt-repository", "-y", src])222 subprocess.check_call(["add-apt-repository", "-y", src])
@@ -299,6 +314,62 @@
299 return apt.version_compare(available_vers, cur_vers) == 1314 return apt.version_compare(available_vers, cur_vers) == 1
300315
301316
317def ensure_block_device(block_device):
318 '''
319 Confirm block_device, create as loopback if necessary.
320
321 :param block_device: str: Full path of block device to ensure.
322
323 :returns: str: Full path of ensured block device.
324 '''
325 _none = ['None', 'none', None]
326 if (block_device in _none):
327 error_out('prepare_storage(): Missing required input: '
328 'block_device=%s.' % block_device, level=ERROR)
329
330 if block_device.startswith('/dev/'):
331 bdev = block_device
332 elif block_device.startswith('/'):
333 _bd = block_device.split('|')
334 if len(_bd) == 2:
335 bdev, size = _bd
336 else:
337 bdev = block_device
338 size = DEFAULT_LOOPBACK_SIZE
339 bdev = ensure_loopback_device(bdev, size)
340 else:
341 bdev = '/dev/%s' % block_device
342
343 if not is_block_device(bdev):
344 error_out('Failed to locate valid block device at %s' % bdev,
345 level=ERROR)
346
347 return bdev
348
349
350def clean_storage(block_device):
351 '''
352 Ensures a block device is clean. That is:
353 - unmounted
354 - any lvm volume groups are deactivated
355 - any lvm physical device signatures removed
356 - partition table wiped
357
358 :param block_device: str: Full path to block device to clean.
359 '''
360 for mp, d in mounts():
361 if d == block_device:
362 juju_log('clean_storage(): %s is mounted @ %s, unmounting.' %
363 (d, mp), level=INFO)
364 umount(mp, persist=True)
365
366 if is_lvm_physical_volume(block_device):
367 deactivate_lvm_volume_group(block_device)
368 remove_lvm_physical_volume(block_device)
369 else:
370 zap_disk(block_device)
371
372
302def is_ip(address):373def is_ip(address):
303 """374 """
304 Returns True if address is a valid IP address.375 Returns True if address is a valid IP address.
305376
=== 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/ceph.py'
--- hooks/charmhelpers/contrib/storage/linux/ceph.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-08 05:56:36 +0000
@@ -0,0 +1,383 @@
1#
2# Copyright 2012 Canonical Ltd.
3#
4# This file is sourced from lp:openstack-charm-helpers
5#
6# Authors:
7# James Page <james.page@ubuntu.com>
8# Adam Gandelman <adamg@ubuntu.com>
9#
10
11import os
12import shutil
13import json
14import time
15
16from subprocess import (
17 check_call,
18 check_output,
19 CalledProcessError
20)
21
22from charmhelpers.core.hookenv import (
23 relation_get,
24 relation_ids,
25 related_units,
26 log,
27 INFO,
28 WARNING,
29 ERROR
30)
31
32from charmhelpers.core.host import (
33 mount,
34 mounts,
35 service_start,
36 service_stop,
37 service_running,
38 umount,
39)
40
41from charmhelpers.fetch import (
42 apt_install,
43)
44
45KEYRING = '/etc/ceph/ceph.client.{}.keyring'
46KEYFILE = '/etc/ceph/ceph.client.{}.key'
47
48CEPH_CONF = """[global]
49 auth supported = {auth}
50 keyring = {keyring}
51 mon host = {mon_hosts}
52"""
53
54
55def install():
56 ''' Basic Ceph client installation '''
57 ceph_dir = "/etc/ceph"
58 if not os.path.exists(ceph_dir):
59 os.mkdir(ceph_dir)
60 apt_install('ceph-common', fatal=True)
61
62
63def rbd_exists(service, pool, rbd_img):
64 ''' Check to see if a RADOS block device exists '''
65 try:
66 out = check_output(['rbd', 'list', '--id', service,
67 '--pool', pool])
68 except CalledProcessError:
69 return False
70 else:
71 return rbd_img in out
72
73
74def create_rbd_image(service, pool, image, sizemb):
75 ''' Create a new RADOS block device '''
76 cmd = [
77 'rbd',
78 'create',
79 image,
80 '--size',
81 str(sizemb),
82 '--id',
83 service,
84 '--pool',
85 pool
86 ]
87 check_call(cmd)
88
89
90def pool_exists(service, name):
91 ''' Check to see if a RADOS pool already exists '''
92 try:
93 out = check_output(['rados', '--id', service, 'lspools'])
94 except CalledProcessError:
95 return False
96 else:
97 return name in out
98
99
100def get_osds(service):
101 '''
102 Return a list of all Ceph Object Storage Daemons
103 currently in the cluster
104 '''
105 version = ceph_version()
106 if version and version >= '0.56':
107 return json.loads(check_output(['ceph', '--id', service,
108 'osd', 'ls', '--format=json']))
109 else:
110 return None
111
112
113def create_pool(service, name, replicas=2):
114 ''' Create a new RADOS pool '''
115 if pool_exists(service, name):
116 log("Ceph pool {} already exists, skipping creation".format(name),
117 level=WARNING)
118 return
119 # Calculate the number of placement groups based
120 # on upstream recommended best practices.
121 osds = get_osds(service)
122 if osds:
123 pgnum = (len(osds) * 100 / replicas)
124 else:
125 # NOTE(james-page): Default to 200 for older ceph versions
126 # which don't support OSD query from cli
127 pgnum = 200
128 cmd = [
129 'ceph', '--id', service,
130 'osd', 'pool', 'create',
131 name, str(pgnum)
132 ]
133 check_call(cmd)
134 cmd = [
135 'ceph', '--id', service,
136 'osd', 'pool', 'set', name,
137 'size', str(replicas)
138 ]
139 check_call(cmd)
140
141
142def delete_pool(service, name):
143 ''' Delete a RADOS pool from ceph '''
144 cmd = [
145 'ceph', '--id', service,
146 'osd', 'pool', 'delete',
147 name, '--yes-i-really-really-mean-it'
148 ]
149 check_call(cmd)
150
151
152def _keyfile_path(service):
153 return KEYFILE.format(service)
154
155
156def _keyring_path(service):
157 return KEYRING.format(service)
158
159
160def create_keyring(service, key):
161 ''' Create a new Ceph keyring containing key'''
162 keyring = _keyring_path(service)
163 if os.path.exists(keyring):
164 log('ceph: Keyring exists at %s.' % keyring, level=WARNING)
165 return
166 cmd = [
167 'ceph-authtool',
168 keyring,
169 '--create-keyring',
170 '--name=client.{}'.format(service),
171 '--add-key={}'.format(key)
172 ]
173 check_call(cmd)
174 log('ceph: Created new ring at %s.' % keyring, level=INFO)
175
176
177def create_key_file(service, key):
178 ''' Create a file containing key '''
179 keyfile = _keyfile_path(service)
180 if os.path.exists(keyfile):
181 log('ceph: Keyfile exists at %s.' % keyfile, level=WARNING)
182 return
183 with open(keyfile, 'w') as fd:
184 fd.write(key)
185 log('ceph: Created new keyfile at %s.' % keyfile, level=INFO)
186
187
188def get_ceph_nodes():
189 ''' Query named relation 'ceph' to detemine current nodes '''
190 hosts = []
191 for r_id in relation_ids('ceph'):
192 for unit in related_units(r_id):
193 hosts.append(relation_get('private-address', unit=unit, rid=r_id))
194 return hosts
195
196
197def configure(service, key, auth):
198 ''' Perform basic configuration of Ceph '''
199 create_keyring(service, key)
200 create_key_file(service, key)
201 hosts = get_ceph_nodes()
202 with open('/etc/ceph/ceph.conf', 'w') as ceph_conf:
203 ceph_conf.write(CEPH_CONF.format(auth=auth,
204 keyring=_keyring_path(service),
205 mon_hosts=",".join(map(str, hosts))))
206 modprobe('rbd')
207
208
209def image_mapped(name):
210 ''' Determine whether a RADOS block device is mapped locally '''
211 try:
212 out = check_output(['rbd', 'showmapped'])
213 except CalledProcessError:
214 return False
215 else:
216 return name in out
217
218
219def map_block_storage(service, pool, image):
220 ''' Map a RADOS block device for local use '''
221 cmd = [
222 'rbd',
223 'map',
224 '{}/{}'.format(pool, image),
225 '--user',
226 service,
227 '--secret',
228 _keyfile_path(service),
229 ]
230 check_call(cmd)
231
232
233def filesystem_mounted(fs):
234 ''' Determine whether a filesytems is already mounted '''
235 return fs in [f for f, m in mounts()]
236
237
238def make_filesystem(blk_device, fstype='ext4', timeout=10):
239 ''' Make a new filesystem on the specified block device '''
240 count = 0
241 e_noent = os.errno.ENOENT
242 while not os.path.exists(blk_device):
243 if count >= timeout:
244 log('ceph: gave up waiting on block device %s' % blk_device,
245 level=ERROR)
246 raise IOError(e_noent, os.strerror(e_noent), blk_device)
247 log('ceph: waiting for block device %s to appear' % blk_device,
248 level=INFO)
249 count += 1
250 time.sleep(1)
251 else:
252 log('ceph: Formatting block device %s as filesystem %s.' %
253 (blk_device, fstype), level=INFO)
254 check_call(['mkfs', '-t', fstype, blk_device])
255
256
257def place_data_on_block_device(blk_device, data_src_dst):
258 ''' Migrate data in data_src_dst to blk_device and then remount '''
259 # mount block device into /mnt
260 mount(blk_device, '/mnt')
261 # copy data to /mnt
262 copy_files(data_src_dst, '/mnt')
263 # umount block device
264 umount('/mnt')
265 # Grab user/group ID's from original source
266 _dir = os.stat(data_src_dst)
267 uid = _dir.st_uid
268 gid = _dir.st_gid
269 # re-mount where the data should originally be
270 # TODO: persist is currently a NO-OP in core.host
271 mount(blk_device, data_src_dst, persist=True)
272 # ensure original ownership of new mount.
273 os.chown(data_src_dst, uid, gid)
274
275
276# TODO: re-use
277def modprobe(module):
278 ''' Load a kernel module and configure for auto-load on reboot '''
279 log('ceph: Loading kernel module', level=INFO)
280 cmd = ['modprobe', module]
281 check_call(cmd)
282 with open('/etc/modules', 'r+') as modules:
283 if module not in modules.read():
284 modules.write(module)
285
286
287def copy_files(src, dst, symlinks=False, ignore=None):
288 ''' Copy files from src to dst '''
289 for item in os.listdir(src):
290 s = os.path.join(src, item)
291 d = os.path.join(dst, item)
292 if os.path.isdir(s):
293 shutil.copytree(s, d, symlinks, ignore)
294 else:
295 shutil.copy2(s, d)
296
297
298def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
299 blk_device, fstype, system_services=[]):
300 """
301 NOTE: This function must only be called from a single service unit for
302 the same rbd_img otherwise data loss will occur.
303
304 Ensures given pool and RBD image exists, is mapped to a block device,
305 and the device is formatted and mounted at the given mount_point.
306
307 If formatting a device for the first time, data existing at mount_point
308 will be migrated to the RBD device before being re-mounted.
309
310 All services listed in system_services will be stopped prior to data
311 migration and restarted when complete.
312 """
313 # Ensure pool, RBD image, RBD mappings are in place.
314 if not pool_exists(service, pool):
315 log('ceph: Creating new pool {}.'.format(pool))
316 create_pool(service, pool)
317
318 if not rbd_exists(service, pool, rbd_img):
319 log('ceph: Creating RBD image ({}).'.format(rbd_img))
320 create_rbd_image(service, pool, rbd_img, sizemb)
321
322 if not image_mapped(rbd_img):
323 log('ceph: Mapping RBD Image {} as a Block Device.'.format(rbd_img))
324 map_block_storage(service, pool, rbd_img)
325
326 # make file system
327 # TODO: What happens if for whatever reason this is run again and
328 # the data is already in the rbd device and/or is mounted??
329 # When it is mounted already, it will fail to make the fs
330 # XXX: This is really sketchy! Need to at least add an fstab entry
331 # otherwise this hook will blow away existing data if its executed
332 # after a reboot.
333 if not filesystem_mounted(mount_point):
334 make_filesystem(blk_device, fstype)
335
336 for svc in system_services:
337 if service_running(svc):
338 log('ceph: Stopping services {} prior to migrating data.'
339 .format(svc))
340 service_stop(svc)
341
342 place_data_on_block_device(blk_device, mount_point)
343
344 for svc in system_services:
345 log('ceph: Starting service {} after migrating data.'
346 .format(svc))
347 service_start(svc)
348
349
350def ensure_ceph_keyring(service, user=None, group=None):
351 '''
352 Ensures a ceph keyring is created for a named service
353 and optionally ensures user and group ownership.
354
355 Returns False if no ceph key is available in relation state.
356 '''
357 key = None
358 for rid in relation_ids('ceph'):
359 for unit in related_units(rid):
360 key = relation_get('key', rid=rid, unit=unit)
361 if key:
362 break
363 if not key:
364 return False
365 create_keyring(service=service, key=key)
366 keyring = _keyring_path(service)
367 if user and group:
368 check_call(['chown', '%s.%s' % (user, group), keyring])
369 return True
370
371
372def ceph_version():
373 ''' Retrieve the local version of ceph '''
374 if os.path.exists('/usr/bin/ceph'):
375 cmd = ['ceph', '-v']
376 output = check_output(cmd)
377 output = output.split()
378 if len(output) > 3:
379 return output[2]
380 else:
381 return None
382 else:
383 return None
0384
=== added file 'hooks/charmhelpers/contrib/storage/linux/loopback.py'
--- hooks/charmhelpers/contrib/storage/linux/loopback.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/storage/linux/loopback.py 2013-11-08 05:56:36 +0000
@@ -0,0 +1,62 @@
1
2import os
3import re
4
5from subprocess import (
6 check_call,
7 check_output,
8)
9
10
11##################################################
12# loopback device helpers.
13##################################################
14def loopback_devices():
15 '''
16 Parse through 'losetup -a' output to determine currently mapped
17 loopback devices. Output is expected to look like:
18
19 /dev/loop0: [0807]:961814 (/tmp/my.img)
20
21 :returns: dict: a dict mapping {loopback_dev: backing_file}
22 '''
23 loopbacks = {}
24 cmd = ['losetup', '-a']
25 devs = [d.strip().split(' ') for d in
26 check_output(cmd).splitlines() if d != '']
27 for dev, _, f in devs:
28 loopbacks[dev.replace(':', '')] = re.search('\((\S+)\)', f).groups()[0]
29 return loopbacks
30
31
32def create_loopback(file_path):
33 '''
34 Create a loopback device for a given backing file.
35
36 :returns: str: Full path to new loopback device (eg, /dev/loop0)
37 '''
38 file_path = os.path.abspath(file_path)
39 check_call(['losetup', '--find', file_path])
40 for d, f in loopback_devices().iteritems():
41 if f == file_path:
42 return d
43
44
45def ensure_loopback_device(path, size):
46 '''
47 Ensure a loopback device exists for a given backing file path and size.
48 If it a loopback device is not mapped to file, a new one will be created.
49
50 TODO: Confirm size of found loopback device.
51
52 :returns: str: Full path to the ensured loopback device (eg, /dev/loop0)
53 '''
54 for d, f in loopback_devices().iteritems():
55 if f == path:
56 return d
57
58 if not os.path.exists(path):
59 cmd = ['truncate', '--size', size, path]
60 check_call(cmd)
61
62 return create_loopback(path)
063
=== added file 'hooks/charmhelpers/contrib/storage/linux/lvm.py'
--- hooks/charmhelpers/contrib/storage/linux/lvm.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/storage/linux/lvm.py 2013-11-08 05:56:36 +0000
@@ -0,0 +1,88 @@
1from subprocess import (
2 CalledProcessError,
3 check_call,
4 check_output,
5 Popen,
6 PIPE,
7)
8
9
10##################################################
11# LVM helpers.
12##################################################
13def deactivate_lvm_volume_group(block_device):
14 '''
15 Deactivate any volume gruop associated with an LVM physical volume.
16
17 :param block_device: str: Full path to LVM physical volume
18 '''
19 vg = list_lvm_volume_group(block_device)
20 if vg:
21 cmd = ['vgchange', '-an', vg]
22 check_call(cmd)
23
24
25def is_lvm_physical_volume(block_device):
26 '''
27 Determine whether a block device is initialized as an LVM PV.
28
29 :param block_device: str: Full path of block device to inspect.
30
31 :returns: boolean: True if block device is a PV, False if not.
32 '''
33 try:
34 check_output(['pvdisplay', block_device])
35 return True
36 except CalledProcessError:
37 return False
38
39
40def remove_lvm_physical_volume(block_device):
41 '''
42 Remove LVM PV signatures from a given block device.
43
44 :param block_device: str: Full path of block device to scrub.
45 '''
46 p = Popen(['pvremove', '-ff', block_device],
47 stdin=PIPE)
48 p.communicate(input='y\n')
49
50
51def list_lvm_volume_group(block_device):
52 '''
53 List LVM volume group associated with a given block device.
54
55 Assumes block device is a valid LVM PV.
56
57 :param block_device: str: Full path of block device to inspect.
58
59 :returns: str: Name of volume group associated with block device or None
60 '''
61 vg = None
62 pvd = check_output(['pvdisplay', block_device]).splitlines()
63 for l in pvd:
64 if l.strip().startswith('VG Name'):
65 vg = ' '.join(l.split()).split(' ').pop()
66 return vg
67
68
69def create_lvm_physical_volume(block_device):
70 '''
71 Initialize a block device as an LVM physical volume.
72
73 :param block_device: str: Full path of block device to initialize.
74
75 '''
76 check_call(['pvcreate', block_device])
77
78
79def create_lvm_volume_group(volume_group, block_device):
80 '''
81 Create an LVM volume group backed by a given block device.
82
83 Assumes block device has already been initialized as an LVM PV.
84
85 :param volume_group: str: Name of volume group to create.
86 :block_device: str: Full path of PV-initialized block device.
87 '''
88 check_call(['vgcreate', volume_group, block_device])
089
=== 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 2013-11-08 05:56:36 +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', block_device])
026
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2013-07-19 09:46:25 +0000
+++ hooks/charmhelpers/core/hookenv.py 2013-11-08 05:56:36 +0000
@@ -9,6 +9,7 @@
9import yaml9import yaml
10import subprocess10import subprocess
11import UserDict11import UserDict
12from subprocess import CalledProcessError
1213
13CRITICAL = "CRITICAL"14CRITICAL = "CRITICAL"
14ERROR = "ERROR"15ERROR = "ERROR"
@@ -21,7 +22,7 @@
2122
2223
23def cached(func):24def cached(func):
24 ''' Cache return values for multiple executions of func + args25 """Cache return values for multiple executions of func + args
2526
26 For example:27 For example:
2728
@@ -32,7 +33,7 @@
32 unit_get('test')33 unit_get('test')
3334
34 will cache the result of unit_get + 'test' for future calls.35 will cache the result of unit_get + 'test' for future calls.
35 '''36 """
36 def wrapper(*args, **kwargs):37 def wrapper(*args, **kwargs):
37 global cache38 global cache
38 key = str((func, args, kwargs))39 key = str((func, args, kwargs))
@@ -46,8 +47,8 @@
4647
4748
48def flush(key):49def flush(key):
49 ''' Flushes any entries from function cache where the50 """Flushes any entries from function cache where the
50 key is found in the function+args '''51 key is found in the function+args """
51 flush_list = []52 flush_list = []
52 for item in cache:53 for item in cache:
53 if key in item:54 if key in item:
@@ -57,7 +58,7 @@
5758
5859
59def log(message, level=None):60def log(message, level=None):
60 "Write a message to the juju log"61 """Write a message to the juju log"""
61 command = ['juju-log']62 command = ['juju-log']
62 if level:63 if level:
63 command += ['-l', level]64 command += ['-l', level]
@@ -66,7 +67,7 @@
6667
6768
68class Serializable(UserDict.IterableUserDict):69class Serializable(UserDict.IterableUserDict):
69 "Wrapper, an object that can be serialized to yaml or json"70 """Wrapper, an object that can be serialized to yaml or json"""
7071
71 def __init__(self, obj):72 def __init__(self, obj):
72 # wrap the object73 # wrap the object
@@ -96,11 +97,11 @@
96 self.data = state97 self.data = state
9798
98 def json(self):99 def json(self):
99 "Serialize the object to json"100 """Serialize the object to json"""
100 return json.dumps(self.data)101 return json.dumps(self.data)
101102
102 def yaml(self):103 def yaml(self):
103 "Serialize the object to yaml"104 """Serialize the object to yaml"""
104 return yaml.dump(self.data)105 return yaml.dump(self.data)
105106
106107
@@ -119,38 +120,38 @@
119120
120121
121def in_relation_hook():122def in_relation_hook():
122 "Determine whether we're running in a relation hook"123 """Determine whether we're running in a relation hook"""
123 return 'JUJU_RELATION' in os.environ124 return 'JUJU_RELATION' in os.environ
124125
125126
126def relation_type():127def relation_type():
127 "The scope for the current relation hook"128 """The scope for the current relation hook"""
128 return os.environ.get('JUJU_RELATION', None)129 return os.environ.get('JUJU_RELATION', None)
129130
130131
131def relation_id():132def relation_id():
132 "The relation ID for the current relation hook"133 """The relation ID for the current relation hook"""
133 return os.environ.get('JUJU_RELATION_ID', None)134 return os.environ.get('JUJU_RELATION_ID', None)
134135
135136
136def local_unit():137def local_unit():
137 "Local unit ID"138 """Local unit ID"""
138 return os.environ['JUJU_UNIT_NAME']139 return os.environ['JUJU_UNIT_NAME']
139140
140141
141def remote_unit():142def remote_unit():
142 "The remote unit for the current relation hook"143 """The remote unit for the current relation hook"""
143 return os.environ['JUJU_REMOTE_UNIT']144 return os.environ['JUJU_REMOTE_UNIT']
144145
145146
146def service_name():147def service_name():
147 "The name service group this unit belongs to"148 """The name service group this unit belongs to"""
148 return local_unit().split('/')[0]149 return local_unit().split('/')[0]
149150
150151
151@cached152@cached
152def config(scope=None):153def config(scope=None):
153 "Juju charm configuration"154 """Juju charm configuration"""
154 config_cmd_line = ['config-get']155 config_cmd_line = ['config-get']
155 if scope is not None:156 if scope is not None:
156 config_cmd_line.append(scope)157 config_cmd_line.append(scope)
@@ -163,6 +164,7 @@
163164
164@cached165@cached
165def relation_get(attribute=None, unit=None, rid=None):166def relation_get(attribute=None, unit=None, rid=None):
167 """Get relation information"""
166 _args = ['relation-get', '--format=json']168 _args = ['relation-get', '--format=json']
167 if rid:169 if rid:
168 _args.append('-r')170 _args.append('-r')
@@ -174,9 +176,14 @@
174 return json.loads(subprocess.check_output(_args))176 return json.loads(subprocess.check_output(_args))
175 except ValueError:177 except ValueError:
176 return None178 return None
179 except CalledProcessError, e:
180 if e.returncode == 2:
181 return None
182 raise
177183
178184
179def relation_set(relation_id=None, relation_settings={}, **kwargs):185def relation_set(relation_id=None, relation_settings={}, **kwargs):
186 """Set relation information for the current unit"""
180 relation_cmd_line = ['relation-set']187 relation_cmd_line = ['relation-set']
181 if relation_id is not None:188 if relation_id is not None:
182 relation_cmd_line.extend(('-r', relation_id))189 relation_cmd_line.extend(('-r', relation_id))
@@ -192,7 +199,7 @@
192199
193@cached200@cached
194def relation_ids(reltype=None):201def relation_ids(reltype=None):
195 "A list of relation_ids"202 """A list of relation_ids"""
196 reltype = reltype or relation_type()203 reltype = reltype or relation_type()
197 relid_cmd_line = ['relation-ids', '--format=json']204 relid_cmd_line = ['relation-ids', '--format=json']
198 if reltype is not None:205 if reltype is not None:
@@ -203,7 +210,7 @@
203210
204@cached211@cached
205def related_units(relid=None):212def related_units(relid=None):
206 "A list of related units"213 """A list of related units"""
207 relid = relid or relation_id()214 relid = relid or relation_id()
208 units_cmd_line = ['relation-list', '--format=json']215 units_cmd_line = ['relation-list', '--format=json']
209 if relid is not None:216 if relid is not None:
@@ -213,7 +220,7 @@
213220
214@cached221@cached
215def relation_for_unit(unit=None, rid=None):222def relation_for_unit(unit=None, rid=None):
216 "Get the json represenation of a unit's relation"223 """Get the json represenation of a unit's relation"""
217 unit = unit or remote_unit()224 unit = unit or remote_unit()
218 relation = relation_get(unit=unit, rid=rid)225 relation = relation_get(unit=unit, rid=rid)
219 for key in relation:226 for key in relation:
@@ -225,7 +232,7 @@
225232
226@cached233@cached
227def relations_for_id(relid=None):234def relations_for_id(relid=None):
228 "Get relations of a specific relation ID"235 """Get relations of a specific relation ID"""
229 relation_data = []236 relation_data = []
230 relid = relid or relation_ids()237 relid = relid or relation_ids()
231 for unit in related_units(relid):238 for unit in related_units(relid):
@@ -237,7 +244,7 @@
237244
238@cached245@cached
239def relations_of_type(reltype=None):246def relations_of_type(reltype=None):
240 "Get relations of a specific type"247 """Get relations of a specific type"""
241 relation_data = []248 relation_data = []
242 reltype = reltype or relation_type()249 reltype = reltype or relation_type()
243 for relid in relation_ids(reltype):250 for relid in relation_ids(reltype):
@@ -249,7 +256,7 @@
249256
250@cached257@cached
251def relation_types():258def relation_types():
252 "Get a list of relation types supported by this charm"259 """Get a list of relation types supported by this charm"""
253 charmdir = os.environ.get('CHARM_DIR', '')260 charmdir = os.environ.get('CHARM_DIR', '')
254 mdf = open(os.path.join(charmdir, 'metadata.yaml'))261 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
255 md = yaml.safe_load(mdf)262 md = yaml.safe_load(mdf)
@@ -264,6 +271,7 @@
264271
265@cached272@cached
266def relations():273def relations():
274 """Get a nested dictionary of relation data for all related units"""
267 rels = {}275 rels = {}
268 for reltype in relation_types():276 for reltype in relation_types():
269 relids = {}277 relids = {}
@@ -277,15 +285,35 @@
277 return rels285 return rels
278286
279287
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
280def open_port(port, protocol="TCP"):308def open_port(port, protocol="TCP"):
281 "Open a service network port"309 """Open a service network port"""
282 _args = ['open-port']310 _args = ['open-port']
283 _args.append('{}/{}'.format(port, protocol))311 _args.append('{}/{}'.format(port, protocol))
284 subprocess.check_call(_args)312 subprocess.check_call(_args)
285313
286314
287def close_port(port, protocol="TCP"):315def close_port(port, protocol="TCP"):
288 "Close a service network port"316 """Close a service network port"""
289 _args = ['close-port']317 _args = ['close-port']
290 _args.append('{}/{}'.format(port, protocol))318 _args.append('{}/{}'.format(port, protocol))
291 subprocess.check_call(_args)319 subprocess.check_call(_args)
@@ -293,6 +321,7 @@
293321
294@cached322@cached
295def unit_get(attribute):323def unit_get(attribute):
324 """Get the unit ID for the remote unit"""
296 _args = ['unit-get', '--format=json', attribute]325 _args = ['unit-get', '--format=json', attribute]
297 try:326 try:
298 return json.loads(subprocess.check_output(_args))327 return json.loads(subprocess.check_output(_args))
@@ -301,22 +330,46 @@
301330
302331
303def unit_private_ip():332def unit_private_ip():
333 """Get this unit's private IP address"""
304 return unit_get('private-address')334 return unit_get('private-address')
305335
306336
307class UnregisteredHookError(Exception):337class UnregisteredHookError(Exception):
338 """Raised when an undefined hook is called"""
308 pass339 pass
309340
310341
311class Hooks(object):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
312 def __init__(self):363 def __init__(self):
313 super(Hooks, self).__init__()364 super(Hooks, self).__init__()
314 self._hooks = {}365 self._hooks = {}
315366
316 def register(self, name, function):367 def register(self, name, function):
368 """Register a hook"""
317 self._hooks[name] = function369 self._hooks[name] = function
318370
319 def execute(self, args):371 def execute(self, args):
372 """Execute a registered hook based on args[0]"""
320 hook_name = os.path.basename(args[0])373 hook_name = os.path.basename(args[0])
321 if hook_name in self._hooks:374 if hook_name in self._hooks:
322 self._hooks[hook_name]()375 self._hooks[hook_name]()
@@ -324,6 +377,7 @@
324 raise UnregisteredHookError(hook_name)377 raise UnregisteredHookError(hook_name)
325378
326 def hook(self, *hook_names):379 def hook(self, *hook_names):
380 """Decorator, registering them as hooks"""
327 def wrapper(decorated):381 def wrapper(decorated):
328 for hook_name in hook_names:382 for hook_name in hook_names:
329 self.register(hook_name, decorated)383 self.register(hook_name, decorated)
@@ -337,4 +391,5 @@
337391
338392
339def charm_dir():393def charm_dir():
394 """Return the root directory of the current charm"""
340 return os.environ.get('CHARM_DIR')395 return os.environ.get('CHARM_DIR')
341396
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2013-09-02 16:14:27 +0000
+++ hooks/charmhelpers/core/host.py 2013-11-08 05:56:36 +0000
@@ -19,18 +19,22 @@
1919
2020
21def service_start(service_name):21def service_start(service_name):
22 """Start a system service"""
22 return service('start', service_name)23 return service('start', service_name)
2324
2425
25def service_stop(service_name):26def service_stop(service_name):
27 """Stop a system service"""
26 return service('stop', service_name)28 return service('stop', service_name)
2729
2830
29def service_restart(service_name):31def service_restart(service_name):
32 """Restart a system service"""
30 return service('restart', service_name)33 return service('restart', service_name)
3134
3235
33def service_reload(service_name, restart_on_failure=False):36def service_reload(service_name, restart_on_failure=False):
37 """Reload a system service, optionally falling back to restart if reload fails"""
34 service_result = service('reload', service_name)38 service_result = service('reload', service_name)
35 if not service_result and restart_on_failure:39 if not service_result and restart_on_failure:
36 service_result = service('restart', service_name)40 service_result = service('restart', service_name)
@@ -38,11 +42,13 @@
3842
3943
40def service(action, service_name):44def service(action, service_name):
45 """Control a system service"""
41 cmd = ['service', service_name, action]46 cmd = ['service', service_name, action]
42 return subprocess.call(cmd) == 047 return subprocess.call(cmd) == 0
4348
4449
45def service_running(service):50def service_running(service):
51 """Determine whether a system service is running"""
46 try:52 try:
47 output = subprocess.check_output(['service', service, 'status'])53 output = subprocess.check_output(['service', service, 'status'])
48 except subprocess.CalledProcessError:54 except subprocess.CalledProcessError:
@@ -55,7 +61,7 @@
5561
5662
57def adduser(username, password=None, shell='/bin/bash', system_user=False):63def adduser(username, password=None, shell='/bin/bash', system_user=False):
58 """Add a user"""64 """Add a user to the system"""
59 try:65 try:
60 user_info = pwd.getpwnam(username)66 user_info = pwd.getpwnam(username)
61 log('user {0} already exists!'.format(username))67 log('user {0} already exists!'.format(username))
@@ -138,7 +144,7 @@
138144
139145
140def mount(device, mountpoint, options=None, persist=False):146def mount(device, mountpoint, options=None, persist=False):
141 '''Mount a filesystem'''147 """Mount a filesystem at a particular mountpoint"""
142 cmd_args = ['mount']148 cmd_args = ['mount']
143 if options is not None:149 if options is not None:
144 cmd_args.extend(['-o', options])150 cmd_args.extend(['-o', options])
@@ -155,7 +161,7 @@
155161
156162
157def umount(mountpoint, persist=False):163def umount(mountpoint, persist=False):
158 '''Unmount a filesystem'''164 """Unmount a filesystem"""
159 cmd_args = ['umount', mountpoint]165 cmd_args = ['umount', mountpoint]
160 try:166 try:
161 subprocess.check_output(cmd_args)167 subprocess.check_output(cmd_args)
@@ -169,7 +175,7 @@
169175
170176
171def mounts():177def mounts():
172 '''List of all mounted volumes as [[mountpoint,device],[...]]'''178 """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
173 with open('/proc/mounts') as f:179 with open('/proc/mounts') as f:
174 # [['/mount/point','/dev/path'],[...]]180 # [['/mount/point','/dev/path'],[...]]
175 system_mounts = [m[1::-1] for m in [l.strip().split()181 system_mounts = [m[1::-1] for m in [l.strip().split()
@@ -178,7 +184,7 @@
178184
179185
180def file_hash(path):186def file_hash(path):
181 ''' Generate a md5 hash of the contents of 'path' or None if not found '''187 """Generate a md5 hash of the contents of 'path' or None if not found """
182 if os.path.exists(path):188 if os.path.exists(path):
183 h = hashlib.md5()189 h = hashlib.md5()
184 with open(path, 'r') as source:190 with open(path, 'r') as source:
@@ -189,7 +195,7 @@
189195
190196
191def restart_on_change(restart_map):197def restart_on_change(restart_map):
192 ''' Restart services based on configuration files changing198 """Restart services based on configuration files changing
193199
194 This function is used a decorator, for example200 This function is used a decorator, for example
195201
@@ -202,7 +208,7 @@
202 In this example, the cinder-api and cinder-volume services208 In this example, the cinder-api and cinder-volume services
203 would be restarted if /etc/ceph/ceph.conf is changed by the209 would be restarted if /etc/ceph/ceph.conf is changed by the
204 ceph_client_changed function.210 ceph_client_changed function.
205 '''211 """
206 def wrap(f):212 def wrap(f):
207 def wrapped_f(*args):213 def wrapped_f(*args):
208 checksums = {}214 checksums = {}
@@ -220,7 +226,7 @@
220226
221227
222def lsb_release():228def lsb_release():
223 '''Return /etc/lsb-release in a dict'''229 """Return /etc/lsb-release in a dict"""
224 d = {}230 d = {}
225 with open('/etc/lsb-release', 'r') as lsb:231 with open('/etc/lsb-release', 'r') as lsb:
226 for l in lsb:232 for l in lsb:
@@ -230,7 +236,7 @@
230236
231237
232def pwgen(length=None):238def pwgen(length=None):
233 '''Generate a random pasword.'''239 """Generate a random pasword."""
234 if length is None:240 if length is None:
235 length = random.choice(range(35, 45))241 length = random.choice(range(35, 45))
236 alphanumeric_chars = [242 alphanumeric_chars = [
237243
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2013-09-02 16:36:50 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2013-11-08 05:56:36 +0000
@@ -20,6 +20,32 @@
20PROPOSED_POCKET = """# Proposed20PROPOSED_POCKET = """# Proposed
21deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted21deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
22"""22"""
23CLOUD_ARCHIVE_POCKETS = {
24 # Folsom
25 'folsom': 'precise-updates/folsom',
26 'precise-folsom': 'precise-updates/folsom',
27 'precise-folsom/updates': 'precise-updates/folsom',
28 'precise-updates/folsom': 'precise-updates/folsom',
29 'folsom/proposed': 'precise-proposed/folsom',
30 'precise-folsom/proposed': 'precise-proposed/folsom',
31 'precise-proposed/folsom': 'precise-proposed/folsom',
32 # Grizzly
33 'grizzly': 'precise-updates/grizzly',
34 'precise-grizzly': 'precise-updates/grizzly',
35 'precise-grizzly/updates': 'precise-updates/grizzly',
36 'precise-updates/grizzly': 'precise-updates/grizzly',
37 'grizzly/proposed': 'precise-proposed/grizzly',
38 'precise-grizzly/proposed': 'precise-proposed/grizzly',
39 'precise-proposed/grizzly': 'precise-proposed/grizzly',
40 # Havana
41 'havana': 'precise-updates/havana',
42 'precise-havana': 'precise-updates/havana',
43 'precise-havana/updates': 'precise-updates/havana',
44 'precise-updates/havana': 'precise-updates/havana',
45 'havana/proposed': 'precise-proposed/havana',
46 'precies-havana/proposed': 'precise-proposed/havana',
47 'precise-proposed/havana': 'precise-proposed/havana',
48}
2349
2450
25def filter_installed_packages(packages):51def filter_installed_packages(packages):
@@ -79,16 +105,35 @@
79 subprocess.call(cmd)105 subprocess.call(cmd)
80106
81107
108def apt_hold(packages, fatal=False):
109 """Hold one or more packages"""
110 cmd = ['apt-mark', 'hold']
111 if isinstance(packages, basestring):
112 cmd.append(packages)
113 else:
114 cmd.extend(packages)
115 log("Holding {}".format(packages))
116 if fatal:
117 subprocess.check_call(cmd)
118 else:
119 subprocess.call(cmd)
120
121
82def add_source(source, key=None):122def add_source(source, key=None):
83 if ((source.startswith('ppa:') or123 if (source.startswith('ppa:') or
84 source.startswith('http:'))):124 source.startswith('http:') or
125 source.startswith('deb ') or
126 source.startswith('cloud-archive:')):
85 subprocess.check_call(['add-apt-repository', '--yes', source])127 subprocess.check_call(['add-apt-repository', '--yes', source])
86 elif source.startswith('cloud:'):128 elif source.startswith('cloud:'):
87 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),129 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
88 fatal=True)130 fatal=True)
89 pocket = source.split(':')[-1]131 pocket = source.split(':')[-1]
132 if pocket not in CLOUD_ARCHIVE_POCKETS:
133 raise SourceConfigError('Unsupported cloud: source option %s' % pocket)
134 actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
90 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:135 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
91 apt.write(CLOUD_ARCHIVE.format(pocket))136 apt.write(CLOUD_ARCHIVE.format(actual_pocket))
92 elif source == 'proposed':137 elif source == 'proposed':
93 release = lsb_release()['DISTRIB_CODENAME']138 release = lsb_release()['DISTRIB_CODENAME']
94 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:139 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
@@ -118,8 +163,11 @@
118 Note that 'null' (a.k.a. None) should not be quoted.163 Note that 'null' (a.k.a. None) should not be quoted.
119 """164 """
120 sources = safe_load(config(sources_var))165 sources = safe_load(config(sources_var))
121 keys = safe_load(config(keys_var))166 keys = config(keys_var)
122 if isinstance(sources, basestring) and isinstance(keys, basestring):167 if keys is not None:
168 keys = safe_load(keys)
169 if isinstance(sources, basestring) and (
170 keys is None or isinstance(keys, basestring)):
123 add_source(sources, keys)171 add_source(sources, keys)
124 else:172 else:
125 if not len(sources) == len(keys):173 if not len(sources) == len(keys):
126174
=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
--- hooks/charmhelpers/fetch/bzrurl.py 2013-09-02 16:36:50 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-08 05:56:36 +0000
@@ -12,6 +12,7 @@
12 apt_install("python-bzrlib")12 apt_install("python-bzrlib")
13 from bzrlib.branch import Branch13 from bzrlib.branch import Branch
1414
15
15class BzrUrlFetchHandler(BaseFetchHandler):16class BzrUrlFetchHandler(BaseFetchHandler):
16 """Handler for bazaar branches via generic and lp URLs"""17 """Handler for bazaar branches via generic and lp URLs"""
17 def can_handle(self, source):18 def can_handle(self, source):
@@ -46,4 +47,3 @@
46 except OSError as e:47 except OSError as e:
47 raise UnhandledSource(e.strerror)48 raise UnhandledSource(e.strerror)
48 return dest_dir49 return dest_dir
49
5050
=== modified file 'hooks/quantum_utils.py'
--- hooks/quantum_utils.py 2013-10-16 12:19:22 +0000
+++ hooks/quantum_utils.py 2013-11-08 05:56:36 +0000
@@ -80,7 +80,6 @@
80 "nova-api-metadata"80 "nova-api-metadata"
81 ],81 ],
82 NVP: [82 NVP: [
83 "openvswitch-switch",
84 "neutron-dhcp-agent",83 "neutron-dhcp-agent",
85 'python-mysqldb',84 'python-mysqldb',
86 'python-oslo.config', # Force upgrade85 'python-oslo.config', # Force upgrade
@@ -95,7 +94,7 @@
9594
96EARLY_PACKAGES = {95EARLY_PACKAGES = {
97 OVS: ['openvswitch-datapath-dkms'],96 OVS: ['openvswitch-datapath-dkms'],
98 NVP: ['openvswitch-datapath-dkms']97 NVP: []
99}98}
10099
101100
@@ -396,13 +395,11 @@
396395
397396
398def configure_ovs():397def configure_ovs():
399 if not service_running('openvswitch-switch'):
400 full_restart()
401 if config('plugin') == OVS:398 if config('plugin') == OVS:
399 if not service_running('openvswitch-switch'):
400 full_restart()
402 add_bridge(INT_BRIDGE)401 add_bridge(INT_BRIDGE)
403 add_bridge(EXT_BRIDGE)402 add_bridge(EXT_BRIDGE)
404 ext_port = config('ext-port')403 ext_port = config('ext-port')
405 if ext_port:404 if ext_port:
406 add_bridge_port(EXT_BRIDGE, ext_port)405 add_bridge_port(EXT_BRIDGE, ext_port)
407 if config('plugin') == NVP:
408 add_bridge(INT_BRIDGE)
409406
=== modified file 'unit_tests/test_quantum_utils.py'
--- unit_tests/test_quantum_utils.py 2013-10-19 15:19:41 +0000
+++ unit_tests/test_quantum_utils.py 2013-11-08 05:56:36 +0000
@@ -6,6 +6,11 @@
66
7import quantum_utils7import quantum_utils
88
9try:
10 import neutronclient
11except ImportError:
12 neutronclient = None
13
9from test_utils import (14from test_utils import (
10 CharmTestCase15 CharmTestCase
11)16)
@@ -60,7 +65,7 @@
60 self.config.return_value = 'nvp'65 self.config.return_value = 'nvp'
61 self.assertEquals(66 self.assertEquals(
62 quantum_utils.get_early_packages(),67 quantum_utils.get_early_packages(),
63 ['openvswitch-datapath-dkms', 'linux-headers-2.6.18'])68 [])
6469
65 @patch.object(quantum_utils, 'EARLY_PACKAGES')70 @patch.object(quantum_utils, 'EARLY_PACKAGES')
66 def test_get_early_packages_no_dkms(self, _early_packages):71 def test_get_early_packages_no_dkms(self, _early_packages):
@@ -76,6 +81,7 @@
76 self.assertNotEqual(quantum_utils.get_packages(), [])81 self.assertNotEqual(quantum_utils.get_packages(), [])
7782
78 def test_configure_ovs_starts_service_if_required(self):83 def test_configure_ovs_starts_service_if_required(self):
84 self.config.return_value = 'ovs'
79 self.service_running.return_value = False85 self.service_running.return_value = False
80 quantum_utils.configure_ovs()86 quantum_utils.configure_ovs()
81 self.assertTrue(self.full_restart.called)87 self.assertTrue(self.full_restart.called)
@@ -96,11 +102,6 @@
96 ])102 ])
97 self.add_bridge_port.assert_called_with('br-ex', 'eth0')103 self.add_bridge_port.assert_called_with('br-ex', 'eth0')
98104
99 def test_configure_ovs_nvp(self):
100 self.config.return_value = 'nvp'
101 quantum_utils.configure_ovs()
102 self.add_bridge.assert_called_with('br-int')
103
104 def test_do_openstack_upgrade(self):105 def test_do_openstack_upgrade(self):
105 self.config.side_effect = self.test_config.get106 self.config.side_effect = self.test_config.get
106 self.test_config.set('openstack-origin', 'cloud:precise-havana')107 self.test_config.set('openstack-origin', 'cloud:precise-havana')
@@ -293,6 +294,8 @@
293294
294class TestQuantumAgentReallocation(CharmTestCase):295class TestQuantumAgentReallocation(CharmTestCase):
295 def setUp(self):296 def setUp(self):
297 if not neutronclient:
298 raise self.skipTest('Skipping, no neutronclient installed')
296 super(TestQuantumAgentReallocation, self).setUp(quantum_utils,299 super(TestQuantumAgentReallocation, self).setUp(quantum_utils,
297 TO_PATCH)300 TO_PATCH)
298301

Subscribers

People subscribed via source and target branches