Merge lp:~openstack-charmers/charms/precise/nova-compute/ods_merge into lp:~charmers/charms/precise/nova-compute/trunk

Proposed by Adam Gandelman
Status: Merged
Merged at revision: 50
Proposed branch: lp:~openstack-charmers/charms/precise/nova-compute/ods_merge
Merge into: lp:~charmers/charms/precise/nova-compute/trunk
Diff against target: 1306 lines (+444/-83)
22 files modified
.project (+1/-1)
.pydevproject (+2/-2)
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 (+27/-3)
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/nova_compute_context.py (+11/-0)
hooks/nova_compute_hooks.py (+11/-8)
hooks/nova_compute_utils.py (+35/-15)
templates/essex/nova.conf (+3/-0)
templates/folsom/nova.conf (+3/-0)
templates/grizzly/nova.conf (+5/-0)
templates/havana/nova.conf (+5/-0)
unit_tests/test_nova_compute_contexts.py (+14/-0)
unit_tests/test_nova_compute_hooks.py (+34/-4)
unit_tests/test_nova_compute_utils.py (+4/-0)
To merge this branch: bzr merge lp:~openstack-charmers/charms/precise/nova-compute/ods_merge
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Abstain
OpenStack Charmers Pending
Review via email: mp+194063@code.launchpad.net

Description of the change

* Adds Neutron NVP support
* Adds support for deploying alongside ceph via hulk smash

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

Rebase on trunk

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 '.project'
--- .project 2013-09-20 16:51:57 +0000
+++ .project 2013-11-08 05:49:21 +0000
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<projectDescription>2<projectDescription>
3 <name>nova-compute</name>3 <name>nvp-nova-compute</name>
4 <comment></comment>4 <comment></comment>
5 <projects>5 <projects>
6 </projects>6 </projects>
77
=== modified file '.pydevproject'
--- .pydevproject 2013-09-23 13:23:51 +0000
+++ .pydevproject 2013-11-08 05:49:21 +0000
@@ -3,7 +3,7 @@
3<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>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>4<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
5<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">5<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
6<path>/nova-compute/hooks</path>6<path>/nvp-nova-compute/hooks</path>
7<path>/nova-compute/unit_tests</path>7<path>/nvp-nova-compute/unit_tests</path>
8</pydev_pathproperty>8</pydev_pathproperty>
9</pydev_project>9</pydev_project>
1010
=== modified file 'hooks/charmhelpers/contrib/network/ovs/__init__.py'
--- hooks/charmhelpers/contrib/network/ovs/__init__.py 2013-09-25 09:31:31 +0000
+++ hooks/charmhelpers/contrib/network/ovs/__init__.py 2013-11-08 05:49:21 +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:49:21 +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:49:21 +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:49:21 +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-13 22:51:26 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2013-11-08 05:49:21 +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
=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-09-25 09:31:31 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-08 05:49:21 +0000
@@ -102,8 +102,12 @@
102 Return a list of all Ceph Object Storage Daemons102 Return a list of all Ceph Object Storage Daemons
103 currently in the cluster103 currently in the cluster
104 '''104 '''
105 return json.loads(check_output(['ceph', '--id', service,105 version = ceph_version()
106 'osd', 'ls', '--format=json']))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
107111
108112
109def create_pool(service, name, replicas=2):113def create_pool(service, name, replicas=2):
@@ -114,7 +118,13 @@
114 return118 return
115 # Calculate the number of placement groups based119 # Calculate the number of placement groups based
116 # on upstream recommended best practices.120 # on upstream recommended best practices.
117 pgnum = (len(get_osds(service)) * 100 / replicas)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
118 cmd = [128 cmd = [
119 'ceph', '--id', service,129 'ceph', '--id', service,
120 'osd', 'pool', 'create',130 'osd', 'pool', 'create',
@@ -357,3 +367,17 @@
357 if user and group:367 if user and group:
358 check_call(['chown', '%s.%s' % (user, group), keyring])368 check_call(['chown', '%s.%s' % (user, group), keyring])
359 return True369 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
360384
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2013-07-19 02:37:30 +0000
+++ hooks/charmhelpers/core/hookenv.py 2013-11-08 05:49:21 +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-20 16:40:54 +0000
+++ hooks/charmhelpers/core/host.py 2013-11-08 05:49:21 +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-23 13:23:51 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2013-11-08 05:49:21 +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-23 13:23:51 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-08 05:49:21 +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/nova_compute_context.py'
--- hooks/nova_compute_context.py 2013-10-15 12:04:13 +0000
+++ hooks/nova_compute_context.py 2013-11-08 05:49:21 +0000
@@ -273,6 +273,14 @@
273 self.network_manager)273 self.network_manager)
274 return ctxt274 return ctxt
275275
276 def restart_trigger(self):
277 rt = None
278 for rid in relation_ids('cloud-compute'):
279 for unit in related_units(rid):
280 rt = relation_get('restart_trigger', rid=rid, unit=unit)
281 if rt:
282 return rt
283
276 def __call__(self):284 def __call__(self):
277 rids = relation_ids('cloud-compute')285 rids = relation_ids('cloud-compute')
278 if not rids:286 if not rids:
@@ -289,6 +297,9 @@
289 if vol_service:297 if vol_service:
290 ctxt['volume_service'] = vol_service298 ctxt['volume_service'] = vol_service
291299
300 if self.restart_trigger():
301 ctxt['restart_trigger'] = self.restart_trigger()
302
292 return ctxt303 return ctxt
293304
294305
295306
=== modified file 'hooks/nova_compute_hooks.py'
--- hooks/nova_compute_hooks.py 2013-10-17 19:27:44 +0000
+++ hooks/nova_compute_hooks.py 2013-11-08 05:49:21 +0000
@@ -48,7 +48,7 @@
48 register_configs,48 register_configs,
49 NOVA_CONF,49 NOVA_CONF,
50 QUANTUM_CONF, NEUTRON_CONF,50 QUANTUM_CONF, NEUTRON_CONF,
51 CEPH_CONF, CEPH_SECRET51 ceph_config_file, CEPH_SECRET
52)52)
5353
54from nova_compute_context import CEPH_SECRET_UUID54from nova_compute_context import CEPH_SECRET_UUID
@@ -94,9 +94,10 @@
94 log('amqp relation incomplete. Peer not ready?')94 log('amqp relation incomplete. Peer not ready?')
95 return95 return
96 CONFIGS.write(NOVA_CONF)96 CONFIGS.write(NOVA_CONF)
97 if network_manager() == 'quantum':97
98 if network_manager() == 'quantum' and neutron_plugin() == 'ovs':
98 CONFIGS.write(QUANTUM_CONF)99 CONFIGS.write(QUANTUM_CONF)
99 if network_manager() == 'neutron':100 if network_manager() == 'neutron' and neutron_plugin() == 'ovs':
100 CONFIGS.write(NEUTRON_CONF)101 CONFIGS.write(NEUTRON_CONF)
101102
102103
@@ -106,7 +107,8 @@
106 nova_database=config('database'),107 nova_database=config('database'),
107 nova_username=config('database-user'),108 nova_username=config('database-user'),
108 nova_hostname=unit_get('private-address'))109 nova_hostname=unit_get('private-address'))
109 if network_manager() in ['quantum', 'neutron']:110 if (network_manager() in ['quantum', 'neutron']
111 and neutron_plugin() == 'ovs'):
110 # XXX: Renaming relations from quantum_* to neutron_* here.112 # XXX: Renaming relations from quantum_* to neutron_* here.
111 relation_set(relation_id=rid,113 relation_set(relation_id=rid,
112 neutron_database=config('neutron-database'),114 neutron_database=config('neutron-database'),
@@ -122,8 +124,8 @@
122 return124 return
123 CONFIGS.write(NOVA_CONF)125 CONFIGS.write(NOVA_CONF)
124 nm = network_manager()126 nm = network_manager()
125 if nm in ['quantum', 'neutron']:127 plugin = neutron_plugin()
126 plugin = neutron_plugin()128 if nm in ['quantum', 'neutron'] and plugin == 'ovs':
127 CONFIGS.write(neutron_plugin_attribute(plugin, 'config', nm))129 CONFIGS.write(neutron_plugin_attribute(plugin, 'config', nm))
128130
129131
@@ -157,7 +159,8 @@
157 CONFIGS.write_all()159 CONFIGS.write_all()
158 import_authorized_keys()160 import_authorized_keys()
159 import_keystone_ca_cert()161 import_keystone_ca_cert()
160 if network_manager() in ['quantum', 'neutron']:162 if (network_manager() in ['quantum', 'neutron']
163 and neutron_plugin() == 'ovs'):
161 # in case we already have a database relation, need to request164 # in case we already have a database relation, need to request
162 # access to the additional neutron database.165 # access to the additional neutron database.
163 [db_joined(rid) for rid in relation_ids('shared-db')]166 [db_joined(rid) for rid in relation_ids('shared-db')]
@@ -179,7 +182,7 @@
179 if not ensure_ceph_keyring(service=svc):182 if not ensure_ceph_keyring(service=svc):
180 log('Could not create ceph keyring: peer not ready?')183 log('Could not create ceph keyring: peer not ready?')
181 return184 return
182 CONFIGS.write(CEPH_CONF)185 CONFIGS.write(ceph_config_file())
183 CONFIGS.write(CEPH_SECRET)186 CONFIGS.write(CEPH_SECRET)
184 CONFIGS.write(NOVA_CONF)187 CONFIGS.write(NOVA_CONF)
185188
186189
=== modified file 'hooks/nova_compute_utils.py'
--- hooks/nova_compute_utils.py 2013-09-24 17:11:51 +0000
+++ hooks/nova_compute_utils.py 2013-11-08 05:49:21 +0000
@@ -6,7 +6,7 @@
6from subprocess import check_call, check_output6from subprocess import check_call, check_output
77
8from charmhelpers.fetch import apt_update, apt_install8from charmhelpers.fetch import apt_update, apt_install
99from charmhelpers.core.host import mkdir
10from charmhelpers.core.hookenv import (10from charmhelpers.core.hookenv import (
11 config,11 config,
12 log,12 log,
@@ -14,10 +14,12 @@
14 relation_ids,14 relation_ids,
15 relation_get,15 relation_get,
16 DEBUG,16 DEBUG,
17 service_name
17)18)
1819
19from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute20from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
20from charmhelpers.contrib.openstack import templating, context21from charmhelpers.contrib.openstack import templating, context
22from charmhelpers.contrib.openstack.alternatives import install_alternative
2123
22from charmhelpers.contrib.openstack.utils import (24from charmhelpers.contrib.openstack.utils import (
23 configure_installation_source,25 configure_installation_source,
@@ -72,13 +74,10 @@
72}74}
7375
74CEPH_CONF = '/etc/ceph/ceph.conf'76CEPH_CONF = '/etc/ceph/ceph.conf'
77CHARM_CEPH_CONF = '/var/lib/charm/{}/ceph.conf'
75CEPH_SECRET = '/etc/ceph/secret.xml'78CEPH_SECRET = '/etc/ceph/secret.xml'
7679
77CEPH_RESOURCES = {80CEPH_RESOURCES = {
78 CEPH_CONF: {
79 'contexts': [NovaComputeCephContext()],
80 'services': [],
81 },
82 CEPH_SECRET: {81 CEPH_SECRET: {
83 'contexts': [NovaComputeCephContext()],82 'contexts': [NovaComputeCephContext()],
84 'services': [],83 'services': [],
@@ -114,6 +113,10 @@
114}113}
115114
116115
116def ceph_config_file():
117 return CHARM_CEPH_CONF.format(service_name())
118
119
117def resource_map():120def resource_map():
118 '''121 '''
119 Dynamically generate a map of resources that will be managed for a single122 Dynamically generate a map of resources that will be managed for a single
@@ -122,6 +125,7 @@
122 # TODO: Cache this on first call?125 # TODO: Cache this on first call?
123 resource_map = deepcopy(BASE_RESOURCE_MAP)126 resource_map = deepcopy(BASE_RESOURCE_MAP)
124 net_manager = network_manager()127 net_manager = network_manager()
128 plugin = neutron_plugin()
125129
126 # Network manager gets set late by the cloud-compute interface.130 # Network manager gets set late by the cloud-compute interface.
127 # FlatDHCPManager only requires some extra packages.131 # FlatDHCPManager only requires some extra packages.
@@ -133,17 +137,15 @@
133137
134 # Neutron/quantum requires additional contexts, as well as new resources138 # Neutron/quantum requires additional contexts, as well as new resources
135 # depending on the plugin used.139 # depending on the plugin used.
140 # NOTE(james-page): only required for ovs plugin right now
136 if net_manager in ['neutron', 'quantum']:141 if net_manager in ['neutron', 'quantum']:
137 if net_manager == 'quantum':142 if plugin == 'ovs':
138 nm_rsc = QUANTUM_RESOURCES143 if net_manager == 'quantum':
139 if net_manager == 'neutron':144 nm_rsc = QUANTUM_RESOURCES
140 nm_rsc = NEUTRON_RESOURCES145 if net_manager == 'neutron':
141 resource_map.update(nm_rsc)146 nm_rsc = NEUTRON_RESOURCES
142147 resource_map.update(nm_rsc)
143 resource_map[NOVA_CONF]['contexts'].append(NeutronComputeContext())148
144
145 plugin = neutron_plugin()
146 if plugin:
147 conf = neutron_plugin_attribute(plugin, 'config', net_manager)149 conf = neutron_plugin_attribute(plugin, 'config', net_manager)
148 svcs = neutron_plugin_attribute(plugin, 'services', net_manager)150 svcs = neutron_plugin_attribute(plugin, 'services', net_manager)
149 ctxts = (neutron_plugin_attribute(plugin, 'contexts', net_manager)151 ctxts = (neutron_plugin_attribute(plugin, 'contexts', net_manager)
@@ -156,7 +158,25 @@
156 # associate the plugin agent with main network manager config(s)158 # associate the plugin agent with main network manager config(s)
157 [resource_map[nmc]['services'].extend(svcs) for nmc in nm_rsc]159 [resource_map[nmc]['services'].extend(svcs) for nmc in nm_rsc]
158160
161 resource_map[NOVA_CONF]['contexts'].append(NeutronComputeContext())
162
159 if relation_ids('ceph'):163 if relation_ids('ceph'):
164 # Add charm ceph configuration to resources and
165 # ensure directory actually exists
166 mkdir(os.path.dirname(ceph_config_file()))
167 mkdir(os.path.dirname(CEPH_CONF))
168 # Install ceph config as an alternative for co-location with
169 # ceph and ceph-osd charms - nova-compute ceph.conf will be
170 # lower priority that both of these but thats OK
171 if not os.path.exists(ceph_config_file()):
172 # touch file for pre-templated generation
173 open(ceph_config_file(), 'w').close()
174 install_alternative(os.path.basename(CEPH_CONF),
175 CEPH_CONF, ceph_config_file())
176 CEPH_RESOURCES[ceph_config_file()] = {
177 'contexts': [NovaComputeCephContext()],
178 'services': [],
179 }
160 resource_map.update(CEPH_RESOURCES)180 resource_map.update(CEPH_RESOURCES)
161181
162 return resource_map182 return resource_map
163183
=== modified file 'templates/essex/nova.conf'
--- templates/essex/nova.conf 2013-08-01 23:21:58 +0000
+++ templates/essex/nova.conf 2013-11-08 05:49:21 +0000
@@ -1,6 +1,9 @@
1###############################################################################1###############################################################################
2# [ WARNING ]2# [ WARNING ]
3# Configuration file maintained by Juju. Local changes may be overwritten.3# Configuration file maintained by Juju. Local changes may be overwritten.
4{% if restart_trigger -%}
5# restart trigger: {{ restart_trigger }}
6{% endif -%}
4###############################################################################7###############################################################################
5--dhcpbridge_flagfile=/etc/nova/nova.conf8--dhcpbridge_flagfile=/etc/nova/nova.conf
6--dhcpbridge=/usr/bin/nova-dhcpbridge9--dhcpbridge=/usr/bin/nova-dhcpbridge
710
=== modified file 'templates/folsom/nova.conf'
--- templates/folsom/nova.conf 2013-10-01 11:16:21 +0000
+++ templates/folsom/nova.conf 2013-11-08 05:49:21 +0000
@@ -2,6 +2,9 @@
2###############################################################################2###############################################################################
3# [ WARNING ]3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.4# Configuration file maintained by Juju. Local changes may be overwritten.
5{% if restart_trigger -%}
6# restart trigger: {{ restart_trigger }}
7{% endif -%}
5###############################################################################8###############################################################################
6[DEFAULT]9[DEFAULT]
7dhcpbridge_flagfile=/etc/nova/nova.conf10dhcpbridge_flagfile=/etc/nova/nova.conf
811
=== modified file 'templates/grizzly/nova.conf'
--- templates/grizzly/nova.conf 2013-10-01 11:16:21 +0000
+++ templates/grizzly/nova.conf 2013-11-08 05:49:21 +0000
@@ -2,6 +2,9 @@
2###############################################################################2###############################################################################
3# [ WARNING ]3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.4# Configuration file maintained by Juju. Local changes may be overwritten.
5{% if restart_trigger -%}
6# restart trigger: {{ restart_trigger }}
7{% endif -%}
5###############################################################################8###############################################################################
6[DEFAULT]9[DEFAULT]
7dhcpbridge_flagfile=/etc/nova/nova.conf10dhcpbridge_flagfile=/etc/nova/nova.conf
@@ -52,6 +55,8 @@
5255
53{% if neutron_plugin and neutron_plugin == 'nvp' -%}56{% if neutron_plugin and neutron_plugin == 'nvp' -%}
54libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver57libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver
58security_group_api = quantum
59firewall_driver = nova.virt.firewall.NoopFirewallDriver
55{% endif -%}60{% endif -%}
5661
57{% if network_manager_config -%}62{% if network_manager_config -%}
5863
=== modified file 'templates/havana/nova.conf'
--- templates/havana/nova.conf 2013-10-01 11:16:21 +0000
+++ templates/havana/nova.conf 2013-11-08 05:49:21 +0000
@@ -2,6 +2,9 @@
2###############################################################################2###############################################################################
3# [ WARNING ]3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.4# Configuration file maintained by Juju. Local changes may be overwritten.
5{% if restart_trigger -%}
6# restart trigger: {{ restart_trigger }}
7{% endif -%}
5###############################################################################8###############################################################################
6[DEFAULT]9[DEFAULT]
7dhcpbridge_flagfile=/etc/nova/nova.conf10dhcpbridge_flagfile=/etc/nova/nova.conf
@@ -52,6 +55,8 @@
5255
53{% if neutron_plugin and neutron_plugin == 'nvp' -%}56{% if neutron_plugin and neutron_plugin == 'nvp' -%}
54libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver57libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver
58security_group_api = neutron
59firewall_driver = nova.virt.firewall.NoopFirewallDriver
55{% endif -%}60{% endif -%}
5661
57{% if network_manager_config -%}62{% if network_manager_config -%}
5863
=== modified file 'unit_tests/test_nova_compute_contexts.py'
--- unit_tests/test_nova_compute_contexts.py 2013-09-25 09:01:42 +0000
+++ unit_tests/test_nova_compute_contexts.py 2013-11-08 05:49:21 +0000
@@ -67,6 +67,20 @@
67 self.assertEquals({}, cloud_compute())67 self.assertEquals({}, cloud_compute())
6868
69 @patch.object(context, '_network_manager')69 @patch.object(context, '_network_manager')
70 def test_cloud_compute_context_restart_trigger(self, nm):
71 nm.return_value = None
72 cloud_compute = context.CloudComputeContext()
73 with patch.object(cloud_compute, 'restart_trigger') as rt:
74 rt.return_value = 'footrigger'
75 ctxt = cloud_compute()
76 self.assertEquals(ctxt.get('restart_trigger'), 'footrigger')
77
78 with patch.object(cloud_compute, 'restart_trigger') as rt:
79 rt.return_value = None
80 ctxt = cloud_compute()
81 self.assertEquals(ctxt.get('restart_trigger'), None)
82
83 @patch.object(context, '_network_manager')
70 def test_cloud_compute_volume_context_cinder(self, netman):84 def test_cloud_compute_volume_context_cinder(self, netman):
71 netman.return_value = None85 netman.return_value = None
72 self.relation_ids.return_value = 'cloud-compute:0'86 self.relation_ids.return_value = 'cloud-compute:0'
7387
=== modified file 'unit_tests/test_nova_compute_hooks.py'
--- unit_tests/test_nova_compute_hooks.py 2013-10-17 19:25:15 +0000
+++ unit_tests/test_nova_compute_hooks.py 2013-11-08 05:49:21 +0000
@@ -123,6 +123,7 @@
123 configs.write = MagicMock()123 configs.write = MagicMock()
124 if quantum:124 if quantum:
125 self.network_manager.return_value = 'quantum'125 self.network_manager.return_value = 'quantum'
126 self.neutron_plugin.return_value = 'ovs'
126 hooks.amqp_changed()127 hooks.amqp_changed()
127128
128 @patch.object(hooks, 'CONFIGS')129 @patch.object(hooks, 'CONFIGS')
@@ -147,9 +148,10 @@
147 nova_hostname='nova.foohost.com')148 nova_hostname='nova.foohost.com')
148 self.unit_get.assert_called_with('private-address')149 self.unit_get.assert_called_with('private-address')
149150
150 def test_db_joined_quantum(self):151 def test_db_joined_quantum_ovs(self):
151 self.unit_get.return_value = 'nova.foohost.com'152 self.unit_get.return_value = 'nova.foohost.com'
152 self.network_manager.return_value = 'quantum'153 self.network_manager.return_value = 'quantum'
154 self.neutron_plugin.return_value = 'ovs'
153 hooks.db_joined(rid='shared-db:0')155 hooks.db_joined(rid='shared-db:0')
154 calls = [call(nova_database='nova',156 calls = [call(nova_database='nova',
155 nova_username='nova',157 nova_username='nova',
@@ -163,6 +165,21 @@
163 for c in calls]165 for c in calls]
164 self.unit_get.assert_called_with('private-address')166 self.unit_get.assert_called_with('private-address')
165167
168 def test_db_joined_quantum_nvp(self):
169 self.unit_get.return_value = 'nova.foohost.com'
170 self.network_manager.return_value = 'quantum'
171 self.neutron_plugin.return_value = 'nvp'
172 hooks.db_joined(rid='shared-db:0')
173 calls = [call(nova_database='nova',
174 nova_username='nova',
175 nova_hostname='nova.foohost.com',
176 relation_id='shared-db:0')]
177 # NVP plugin requires no DB access - check it was not
178 # requested
179 [self.assertIn(c, self.relation_set.call_args_list)
180 for c in calls]
181 self.unit_get.assert_called_with('private-address')
182
166 @patch.object(hooks, 'CONFIGS')183 @patch.object(hooks, 'CONFIGS')
167 def test_db_changed_missing_relation_data(self, configs):184 def test_db_changed_missing_relation_data(self, configs):
168 configs.complete_contexts = MagicMock()185 configs.complete_contexts = MagicMock()
@@ -187,13 +204,23 @@
187 configs.write.call_args_list)204 configs.write.call_args_list)
188205
189 @patch.object(hooks, 'CONFIGS')206 @patch.object(hooks, 'CONFIGS')
190 def test_db_changed_with_data_and_quantum(self, configs):207 def test_db_changed_with_data_and_quantum_ovs(self, configs):
191 self.neutron_plugin_attribute.return_value = '/etc/quantum/plugin.conf'208 self.neutron_plugin_attribute.return_value = '/etc/quantum/plugin.conf'
209 self.neutron_plugin.return_value = 'ovs'
192 self._shared_db_test(configs, quantum=True)210 self._shared_db_test(configs, quantum=True)
193 ex = [call('/etc/nova/nova.conf'), call('/etc/quantum/plugin.conf')]211 ex = [call('/etc/nova/nova.conf'), call('/etc/quantum/plugin.conf')]
194 self.assertEquals(ex, configs.write.call_args_list)212 self.assertEquals(ex, configs.write.call_args_list)
195213
196 @patch.object(hooks, 'CONFIGS')214 @patch.object(hooks, 'CONFIGS')
215 def test_db_changed_with_data_and_quantum_nvp(self, configs):
216 self.neutron_plugin_attribute.return_value = '/etc/quantum/plugin.conf'
217 self.neutron_plugin.return_value = 'nvp'
218 self._shared_db_test(configs, quantum=True)
219 ex = [call('/etc/nova/nova.conf')]
220 # NVP has no compute agent for neutron; check no config files generated
221 self.assertEquals(ex, configs.write.call_args_list)
222
223 @patch.object(hooks, 'CONFIGS')
197 def test_image_service_missing_relation_data(self, configs):224 def test_image_service_missing_relation_data(self, configs):
198 configs.complete_contexts = MagicMock()225 configs.complete_contexts = MagicMock()
199 configs.complete_contexts.return_value = []226 configs.complete_contexts.return_value = []
@@ -264,15 +291,18 @@
264 'Could not create ceph keyring: peer not ready?'291 'Could not create ceph keyring: peer not ready?'
265 )292 )
266293
294 @patch.object(utils, 'service_name')
267 @patch.object(hooks, 'CONFIGS')295 @patch.object(hooks, 'CONFIGS')
268 def test_ceph_changed_with_key_and_relation_data(self, configs):296 def test_ceph_changed_with_key_and_relation_data(self, configs,
297 service_name):
269 configs.complete_contexts = MagicMock()298 configs.complete_contexts = MagicMock()
270 configs.complete_contexts.return_value = ['ceph']299 configs.complete_contexts.return_value = ['ceph']
271 configs.write = MagicMock()300 configs.write = MagicMock()
301 service_name.return_value = 'nova-compute'
272 self.ensure_ceph_keyring.return_value = True302 self.ensure_ceph_keyring.return_value = True
273 hooks.ceph_changed()303 hooks.ceph_changed()
274 ex = [304 ex = [
275 call('/etc/ceph/ceph.conf'),305 call('/var/lib/charm/nova-compute/ceph.conf'),
276 call('/etc/ceph/secret.xml'),306 call('/etc/ceph/secret.xml'),
277 call('/etc/nova/nova.conf'),307 call('/etc/nova/nova.conf'),
278 ]308 ]
279309
=== modified file 'unit_tests/test_nova_compute_utils.py'
--- unit_tests/test_nova_compute_utils.py 2013-09-25 09:01:42 +0000
+++ unit_tests/test_nova_compute_utils.py 2013-11-08 05:49:21 +0000
@@ -13,6 +13,9 @@
13 'related_units',13 'related_units',
14 'relation_ids',14 'relation_ids',
15 'relation_get',15 'relation_get',
16 'service_name',
17 'mkdir',
18 'install_alternative'
16]19]
1720
18OVS_PKGS = [21OVS_PKGS = [
@@ -25,6 +28,7 @@
25 def setUp(self):28 def setUp(self):
26 super(NovaComputeUtilsTests, self).setUp(utils, TO_PATCH)29 super(NovaComputeUtilsTests, self).setUp(utils, TO_PATCH)
27 self.config.side_effect = self.test_config.get30 self.config.side_effect = self.test_config.get
31 self.service_name.return_value = 'nova-compute'
2832
29 @patch.object(utils, 'network_manager')33 @patch.object(utils, 'network_manager')
30 def test_determine_packages_nova_network(self, net_man):34 def test_determine_packages_nova_network(self, net_man):

Subscribers

People subscribed via source and target branches