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

Proposed by Adam Gandelman
Status: Merged
Merged at revision: 56
Proposed branch: lp:~openstack-charmers/charms/precise/nova-cloud-controller/ods_merge
Merge into: lp:~charmers/charms/precise/nova-cloud-controller/trunk
Diff against target: 1536 lines (+596/-100)
23 files modified
charm-helpers.yaml (+0/-1)
config.yaml (+29/-0)
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_cc_context.py (+10/-0)
hooks/nova_cc_hooks.py (+59/-29)
hooks/nova_cc_utils.py (+32/-2)
metadata.yaml (+3/-0)
revision (+1/-1)
templates/folsom/nova.conf (+14/-0)
templates/folsom/quantum-server (+6/-0)
templates/havana/neutron-server (+6/-0)
templates/havana/nvp.ini (+11/-0)
unit_tests/test_nova_cc_hooks.py (+63/-1)
unit_tests/test_nova_cc_utils.py (+47/-14)
unit_tests/test_utils.py (+3/-0)
To merge this branch: bzr merge lp:~openstack-charmers/charms/precise/nova-cloud-controller/ods_merge
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Abstain
OpenStack Charmers Pending
Review via email: mp+194062@code.launchpad.net

Description of the change

* Adds Neutron NVP suppot
* Adds VMware subordinate interface

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

Rebase on trunk

62. By James Page

Fixup nvp- prefix in pydev files

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.yaml'
--- charm-helpers.yaml 2013-10-15 01:32:42 +0000
+++ charm-helpers.yaml 2013-11-08 05:43:26 +0000
@@ -7,5 +7,4 @@
7 - contrib.storage7 - contrib.storage
8 - contrib.hahelpers:8 - contrib.hahelpers:
9 - apache9 - apache
10 - ceph
11 - payload.execd10 - payload.execd
1211
=== modified file 'config.yaml'
--- config.yaml 2013-10-17 19:18:20 +0000
+++ config.yaml 2013-11-08 05:43:26 +0000
@@ -69,6 +69,7 @@
69 Quantum plugin to use for network management; supports69 Quantum plugin to use for network management; supports
70 .70 .
71 ovs - OpenvSwitch Plugin71 ovs - OpenvSwitch Plugin
72 nvp - Nicira Network Virtualization Platform
72 .73 .
73 This configuration only has context when used with74 This configuration only has context when used with
74 network-manager Quantum.75 network-manager Quantum.
@@ -125,3 +126,31 @@
125 ssl_key:126 ssl_key:
126 type: string127 type: string
127 description: SSL key to use with certificate specified as ssl_cert.128 description: SSL key to use with certificate specified as ssl_cert.
129 # Neutron NVP Plugin configuration
130 nvp-controllers:
131 type: string
132 description: Space delimited addresses of NVP controllers
133 nvp-username:
134 type: string
135 default: admin
136 description: Username to connect to NVP controllers with
137 nvp-password:
138 type: string
139 default: admin
140 description: Password to connect to NVP controllers with
141 nvp-cluster-name:
142 type: string
143 default: example
144 description: Name of the NVP cluster configuration to create (grizzly only)
145 nvp-tz-uuid:
146 type: string
147 description: |
148 This is uuid of the default NVP Transport zone that will be used for
149 creating tunneled isolated Quantum networks. It needs to be created
150 in NVP before starting Quantum with the nvp plugin.
151 nvp-l3-uuid:
152 type: string
153 description: |
154 This is uuid of the default NVP L3 Gateway Service.
155 # end of NVP configuration
156
128157
=== 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:43:26 +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:43:26 +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:43:26 +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:43:26 +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-27 16:18:25 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-08 05:43:26 +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-08-02 03:42:16 +0000
+++ hooks/charmhelpers/core/hookenv.py 2013-11-08 05:43:26 +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:29:50 +0000
+++ hooks/charmhelpers/core/host.py 2013-11-08 05:43:26 +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:26:04 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2013-11-08 05:43:26 +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:26:04 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-08 05:43:26 +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
=== added symlink 'hooks/nova-vmware-relation-changed'
=== target is u'nova_cc_hooks.py'
=== added symlink 'hooks/nova-vmware-relation-joined'
=== target is u'nova_cc_hooks.py'
=== modified file 'hooks/nova_cc_context.py'
--- hooks/nova_cc_context.py 2013-09-30 20:46:50 +0000
+++ hooks/nova_cc_context.py 2013-11-08 05:43:26 +0000
@@ -139,6 +139,16 @@
139 def __call__(self):139 def __call__(self):
140 ctxt = super(NeutronCCContext, self).__call__()140 ctxt = super(NeutronCCContext, self).__call__()
141 ctxt['external_network'] = config('neutron-external-network')141 ctxt['external_network'] = config('neutron-external-network')
142 if 'nvp' in [config('quantum-plugin'), config('neutron-plugin')]:
143 _config = config()
144 for k, v in _config.iteritems():
145 if k.startswith('nvp'):
146 ctxt[k.replace('-', '_')] = v
147 if 'nvp-controllers' in _config:
148 ctxt['nvp_controllers'] = \
149 ','.join(_config['nvp-controllers'].split())
150 ctxt['nvp_controllers_list'] = \
151 _config['nvp-controllers'].split()
142 return ctxt152 return ctxt
143153
144154
145155
=== modified file 'hooks/nova_cc_hooks.py'
--- hooks/nova_cc_hooks.py 2013-11-06 00:49:41 +0000
+++ hooks/nova_cc_hooks.py 2013-11-08 05:43:26 +0000
@@ -3,6 +3,7 @@
3import os3import os
4import shutil4import shutil
5import sys5import sys
6import uuid
67
7from subprocess import check_call8from subprocess import check_call
8from urlparse import urlparse9from urlparse import urlparse
@@ -25,7 +26,7 @@
25)26)
2627
27from charmhelpers.fetch import (28from charmhelpers.fetch import (
28 apt_install, apt_update, filter_installed_packages29 apt_install, apt_update
29)30)
3031
31from charmhelpers.contrib.openstack.utils import (32from charmhelpers.contrib.openstack.utils import (
@@ -148,6 +149,9 @@
148149
149 if eligible_leader(CLUSTER_RES):150 if eligible_leader(CLUSTER_RES):
150 migrate_database()151 migrate_database()
152 log('Triggering remote cloud-compute restarts.')
153 [compute_joined(rid=rid, remote_restart=True)
154 for rid in relation_ids('cloud-compute')]
151155
152156
153@hooks.hook('image-service-relation-changed')157@hooks.hook('image-service-relation-changed')
@@ -184,6 +188,7 @@
184 CONFIGS.write(NEUTRON_CONF)188 CONFIGS.write(NEUTRON_CONF)
185 [compute_joined(rid) for rid in relation_ids('cloud-compute')]189 [compute_joined(rid) for rid in relation_ids('cloud-compute')]
186 [quantum_joined(rid) for rid in relation_ids('quantum-network-service')]190 [quantum_joined(rid) for rid in relation_ids('quantum-network-service')]
191 [nova_vmware_relation_joined(rid) for rid in relation_ids('nova-vmware')]
187 configure_https()192 configure_https()
188193
189194
@@ -230,8 +235,32 @@
230 out.write('export OS_REGION_NAME=%s\n' % config('region'))235 out.write('export OS_REGION_NAME=%s\n' % config('region'))
231236
232237
238def keystone_compute_settings():
239 ks_auth_config = _auth_config()
240 rel_settings = {}
241
242 if network_manager() in ['quantum', 'neutron']:
243 if ks_auth_config:
244 rel_settings.update(ks_auth_config)
245
246 rel_settings.update({
247 # XXX: Rename these relations settings?
248 'quantum_plugin': neutron_plugin(),
249 'region': config('region'),
250 'quantum_security_groups': config('quantum-security-groups'),
251 'quantum_url': (canonical_url(CONFIGS) + ':' +
252 str(api_port('neutron-server'))),
253 })
254
255 ks_ca = keystone_ca_cert_b64()
256 if ks_auth_config and ks_ca:
257 rel_settings['ca_cert'] = ks_ca
258
259 return rel_settings
260
261
233@hooks.hook('cloud-compute-relation-joined')262@hooks.hook('cloud-compute-relation-joined')
234def compute_joined(rid=None):263def compute_joined(rid=None, remote_restart=False):
235 if not eligible_leader(CLUSTER_RES):264 if not eligible_leader(CLUSTER_RES):
236 return265 return
237 rel_settings = {266 rel_settings = {
@@ -242,24 +271,12 @@
242 'ec2_host': unit_get('private-address'),271 'ec2_host': unit_get('private-address'),
243 }272 }
244273
245 ks_auth_config = _auth_config()274 # update relation setting if we're attempting to restart remote
246275 # services
247 if network_manager() in ['quantum', 'neutron']:276 if remote_restart:
248 if ks_auth_config:277 rel_settings['restart_trigger'] = str(uuid.uuid4())
249 rel_settings.update(ks_auth_config)278
250279 rel_settings.update(keystone_compute_settings())
251 rel_settings.update({
252 # XXX: Rename these relations settings?
253 'quantum_plugin': neutron_plugin(),
254 'region': config('region'),
255 'quantum_security_groups': config('quantum-security-groups'),
256 'quantum_url': (canonical_url(CONFIGS) + ':' +
257 str(api_port('neutron-server'))),
258 })
259
260 ks_ca = keystone_ca_cert_b64()
261 if ks_auth_config and ks_ca:
262 rel_settings['ca_cert'] = ks_ca
263 relation_set(relation_id=rid, **rel_settings)280 relation_set(relation_id=rid, **rel_settings)
264281
265282
@@ -287,15 +304,6 @@
287 if not eligible_leader(CLUSTER_RES):304 if not eligible_leader(CLUSTER_RES):
288 return305 return
289306
290 if network_manager() == 'quantum':
291 pkg = 'quantum-server'
292 else:
293 pkg = 'neutron-server'
294
295 required_pkg = filter_installed_packages([pkg])
296 if required_pkg:
297 apt_install(required_pkg)
298
299 url = canonical_url(CONFIGS) + ':9696'307 url = canonical_url(CONFIGS) + ':9696'
300 # XXX: Can we rename to neutron_*?308 # XXX: Can we rename to neutron_*?
301 rel_settings = {309 rel_settings = {
@@ -397,6 +405,28 @@
397 identity_joined(rid=rid)405 identity_joined(rid=rid)
398406
399407
408@hooks.hook()
409def nova_vmware_relation_joined(rid=None):
410 rel_settings = {'network_manager': network_manager()}
411
412 ks_auth = _auth_config()
413 if ks_auth:
414 rel_settings.update(ks_auth)
415 rel_settings.update({
416 'quantum_plugin': neutron_plugin(),
417 'quantum_security_groups': config('quantum-security-groups'),
418 'quantum_url': (canonical_url(CONFIGS) + ':' +
419 str(api_port('neutron-server')))})
420
421 relation_set(relation_id=rid, **rel_settings)
422
423
424@hooks.hook('nova-vmware-relation-changed')
425@restart_on_change(restart_map())
426def nova_vmware_relation_changed():
427 CONFIGS.write('/etc/nova/nova.conf')
428
429
400@hooks.hook('upgrade-charm')430@hooks.hook('upgrade-charm')
401def upgrade_charm():431def upgrade_charm():
402 for r_id in relation_ids('amqp'):432 for r_id in relation_ids('amqp'):
403433
=== modified file 'hooks/nova_cc_utils.py'
--- hooks/nova_cc_utils.py 2013-09-25 16:24:36 +0000
+++ hooks/nova_cc_utils.py 2013-11-08 05:43:26 +0000
@@ -76,6 +76,8 @@
76HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'76HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
77APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'77APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
78APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'78APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
79NEUTRON_DEFAULT = '/etc/default/neutron-server'
80QUANTUM_DEFAULT = '/etc/default/quantum-server'
7981
80BASE_RESOURCE_MAP = OrderedDict([82BASE_RESOURCE_MAP = OrderedDict([
81 (NOVA_CONF, {83 (NOVA_CONF, {
@@ -84,6 +86,11 @@
84 context.SharedDBContext(relation_prefix='nova'),86 context.SharedDBContext(relation_prefix='nova'),
85 context.ImageServiceContext(),87 context.ImageServiceContext(),
86 context.OSConfigFlagContext(),88 context.OSConfigFlagContext(),
89 context.SubordinateConfigContext(
90 interface='nova-vmware',
91 service='nova',
92 config_file=NOVA_CONF,
93 ),
87 nova_cc_context.HAProxyContext(),94 nova_cc_context.HAProxyContext(),
88 nova_cc_context.IdentityServiceContext(),95 nova_cc_context.IdentityServiceContext(),
89 nova_cc_context.VolumeServiceContext(),96 nova_cc_context.VolumeServiceContext(),
@@ -100,6 +107,10 @@
100 nova_cc_context.IdentityServiceContext(),107 nova_cc_context.IdentityServiceContext(),
101 nova_cc_context.NeutronCCContext()],108 nova_cc_context.NeutronCCContext()],
102 }),109 }),
110 (QUANTUM_DEFAULT, {
111 'services': ['quantum-server'],
112 'contexts': [nova_cc_context.NeutronCCContext()],
113 }),
103 (QUANTUM_API_PASTE, {114 (QUANTUM_API_PASTE, {
104 'services': ['quantum-server'],115 'services': ['quantum-server'],
105 'contexts': [nova_cc_context.IdentityServiceContext()],116 'contexts': [nova_cc_context.IdentityServiceContext()],
@@ -111,6 +122,10 @@
111 nova_cc_context.NeutronCCContext(),122 nova_cc_context.NeutronCCContext(),
112 nova_cc_context.HAProxyContext()],123 nova_cc_context.HAProxyContext()],
113 }),124 }),
125 (NEUTRON_DEFAULT, {
126 'services': ['neutron-server'],
127 'contexts': [nova_cc_context.NeutronCCContext()],
128 }),
114 (HAPROXY_CONF, {129 (HAPROXY_CONF, {
115 'contexts': [context.HAProxyContext(),130 'contexts': [context.HAProxyContext(),
116 nova_cc_context.HAProxyContext()],131 nova_cc_context.HAProxyContext()],
@@ -166,11 +181,12 @@
166 plugin = neutron_plugin()181 plugin = neutron_plugin()
167 if plugin:182 if plugin:
168 conf = neutron_plugin_attribute(plugin, 'config', net_manager)183 conf = neutron_plugin_attribute(plugin, 'config', net_manager)
169 service = '%s-server' % net_manager
170 ctxts = (neutron_plugin_attribute(plugin, 'contexts', net_manager)184 ctxts = (neutron_plugin_attribute(plugin, 'contexts', net_manager)
171 or [])185 or [])
186 services = neutron_plugin_attribute(plugin, 'server_services',
187 net_manager)
172 resource_map[conf] = {}188 resource_map[conf] = {}
173 resource_map[conf]['services'] = [service]189 resource_map[conf]['services'] = services
174 resource_map[conf]['contexts'] = ctxts190 resource_map[conf]['contexts'] = ctxts
175 resource_map[conf]['contexts'].append(191 resource_map[conf]['contexts'].append(
176 nova_cc_context.NeutronCCContext())192 nova_cc_context.NeutronCCContext())
@@ -178,6 +194,16 @@
178 # nova-conductor for releases >= G.194 # nova-conductor for releases >= G.
179 if os_release('nova-common') not in ['essex', 'folsom']:195 if os_release('nova-common') not in ['essex', 'folsom']:
180 resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor']196 resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor']
197
198 # also manage any configs that are being updated by subordinates.
199 vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware',
200 service='nova',
201 config_file=NOVA_CONF)
202 vmware_ctxt = vmware_ctxt()
203 if vmware_ctxt and 'services' in vmware_ctxt:
204 for s in vmware_ctxt['services']:
205 if s not in resource_map[NOVA_CONF]['services']:
206 resource_map[NOVA_CONF]['services'].append(s)
181 return resource_map207 return resource_map
182208
183209
@@ -217,6 +243,10 @@
217 packages = [] + BASE_PACKAGES243 packages = [] + BASE_PACKAGES
218 for k, v in resource_map().iteritems():244 for k, v in resource_map().iteritems():
219 packages.extend(v['services'])245 packages.extend(v['services'])
246 if network_manager() in ['neutron', 'quantum']:
247 pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages',
248 network_manager())
249 packages.extend(pkgs)
220 return list(set(packages))250 return list(set(packages))
221251
222252
223253
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-10-15 13:04:00 +0000
+++ metadata.yaml 2013-11-08 05:43:26 +0000
@@ -29,6 +29,9 @@
29 ha:29 ha:
30 interface: hacluster30 interface: hacluster
31 scope: container31 scope: container
32 nova-vmware:
33 interface: nova-vmware
34 scope: container
32peers:35peers:
33 cluster:36 cluster:
34 interface: nova-ha37 interface: nova-ha
3538
=== modified file 'revision'
--- revision 2013-11-06 04:34:51 +0000
+++ revision 2013-11-08 05:43:26 +0000
@@ -1,1 +1,1 @@
13071311
22
=== modified file 'templates/folsom/nova.conf'
--- templates/folsom/nova.conf 2013-09-30 20:46:50 +0000
+++ templates/folsom/nova.conf 2013-11-08 05:43:26 +0000
@@ -57,6 +57,14 @@
57{% endif -%}57{% endif -%}
58{% endif -%}58{% endif -%}
5959
60{% if neutron_plugin and neutron_plugin == 'nvp' -%}
61security_group_api = neutron
62nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
63{% if external_network -%}
64default_floating_pool = {{ external_network }}
65{% endif -%}
66{% endif -%}
67
60{% if network_manager_config -%}68{% if network_manager_config -%}
61{% for key, value in network_manager_config.iteritems() -%}69{% for key, value in network_manager_config.iteritems() -%}
62{{ key }} = {{ value }}70{{ key }} = {{ value }}
@@ -90,3 +98,9 @@
90{{ key }} = {{ value }}98{{ key }} = {{ value }}
91{% endfor -%}99{% endfor -%}
92{% endif -%}100{% endif -%}
101
102{% if sections and 'DEFAULT' in sections -%}
103{% for key, value in sections['DEFAULT'] -%}
104{{ key }} = {{ value }}
105{% endfor -%}
106{% endif -%}
93107
=== added file 'templates/folsom/quantum-server'
--- templates/folsom/quantum-server 1970-01-01 00:00:00 +0000
+++ templates/folsom/quantum-server 2013-11-08 05:43:26 +0000
@@ -0,0 +1,6 @@
1# quantum
2###############################################################################
3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.
5###############################################################################
6QUANTUM_PLUGIN_CONFIG="{{ config }}"
0\ No newline at end of file7\ No newline at end of file
18
=== added file 'templates/havana/neutron-server'
--- templates/havana/neutron-server 1970-01-01 00:00:00 +0000
+++ templates/havana/neutron-server 2013-11-08 05:43:26 +0000
@@ -0,0 +1,6 @@
1# havana
2###############################################################################
3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.
5###############################################################################
6NEUTRON_PLUGIN_CONFIG="{{ config }}"
0\ No newline at end of file7\ No newline at end of file
18
=== added file 'templates/havana/nvp.ini'
--- templates/havana/nvp.ini 1970-01-01 00:00:00 +0000
+++ templates/havana/nvp.ini 2013-11-08 05:43:26 +0000
@@ -0,0 +1,11 @@
1# havana
2###############################################################################
3# [ WARNING ]
4# Configuration file maintained by Juju. Local changes may be overwritten.
5###############################################################################
6[DEFAULT]
7nvp_user = {{ nvp_username }}
8nvp_password = {{ nvp_password }}
9nvp_controllers = {{ nvp_controllers }}
10default_tz_uuid = {{ nvp_tz_uuid }}
11default_l3_gw_service_uuid = {{ nvp_l3_uuid }}
012
=== modified file 'unit_tests/test_nova_cc_hooks.py'
--- unit_tests/test_nova_cc_hooks.py 2013-09-27 16:18:25 +0000
+++ unit_tests/test_nova_cc_hooks.py 2013-11-08 05:43:26 +0000
@@ -17,8 +17,10 @@
1717
1818
19TO_PATCH = [19TO_PATCH = [
20 'api_port',
20 'apt_update',21 'apt_update',
21 'apt_install',22 'apt_install',
23 'canonical_url',
22 'configure_installation_source',24 'configure_installation_source',
23 'charm_dir',25 'charm_dir',
24 'do_openstack_upgrade',26 'do_openstack_upgrade',
@@ -33,11 +35,32 @@
33 'ssh_known_hosts_b64',35 'ssh_known_hosts_b64',
34 'ssh_authorized_keys_b64',36 'ssh_authorized_keys_b64',
35 'save_script_rc',37 'save_script_rc',
36 'execd_preinstall'38 'execd_preinstall',
39 'network_manager',
40 'volume_service',
41 'unit_get',
42 'eligible_leader',
43 'keystone_ca_cert_b64',
44 'neutron_plugin',
37]45]
3846
3947
48FAKE_KS_AUTH_CFG = {
49 'auth_host': 'kshost',
50 'auth_port': '5000',
51 'service_port': 'token',
52 'service_username': 'admin_user',
53 'service_password': 'admin_passwd',
54 'service_tenant_name': 'admin_tenant',
55 'auth_uri': 'http://kshost:5000/v2',
56 # quantum-gateway interface deviates a bit.
57 'keystone_host': 'kshost',
58 'service_tenant': 'service_tenant',
59}
60
61
40class NovaCCHooksTests(CharmTestCase):62class NovaCCHooksTests(CharmTestCase):
63
41 def setUp(self):64 def setUp(self):
42 super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)65 super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)
43 self.config.side_effect = self.test_config.get66 self.config.side_effect = self.test_config.get
@@ -76,3 +99,42 @@
76 self.ssh_compute_add.assert_called_with('fookey')99 self.ssh_compute_add.assert_called_with('fookey')
77 self.relation_set.assert_called_with(known_hosts='hosts',100 self.relation_set.assert_called_with(known_hosts='hosts',
78 authorized_keys='keys')101 authorized_keys='keys')
102
103 @patch.object(hooks, '_auth_config')
104 def test_compute_joined_neutron(self, auth_config):
105 self.network_manager.return_value = 'neutron'
106 self.eligible_leader = True
107 self.keystone_ca_cert_b64.return_value = 'foocert64'
108 self.volume_service.return_value = 'cinder'
109 self.unit_get.return_value = 'nova-cc-host1'
110 self.canonical_url.return_value = 'http://nova-cc-host1'
111 self.api_port.return_value = '9696'
112 self.neutron_plugin.return_value = 'nvp'
113 auth_config.return_value = FAKE_KS_AUTH_CFG
114 hooks.compute_joined()
115
116 self.relation_set.assert_called_with(
117 relation_id=None,
118 quantum_url='http://nova-cc-host1:9696',
119 ca_cert='foocert64',
120 quantum_security_groups='no',
121 region='RegionOne',
122 volume_service='cinder',
123 ec2_host='nova-cc-host1',
124 quantum_plugin='nvp',
125 network_manager='neutron', **FAKE_KS_AUTH_CFG)
126
127 @patch.object(hooks, '_auth_config')
128 def test_nova_vmware_joined(self, auth_config):
129 auth_config.return_value = FAKE_KS_AUTH_CFG
130 # quantum-security-groups, plugin
131 self.neutron_plugin.return_value = 'nvp'
132 self.network_manager.return_value = 'neutron'
133 self.canonical_url.return_value = 'http://nova-cc-host1'
134 self.api_port.return_value = '9696'
135 hooks.nova_vmware_relation_joined()
136 self.relation_set.assert_called_with(
137 network_manager='neutron', quantum_security_groups='no',
138 quantum_url='http://nova-cc-host1:9696', quantum_plugin='nvp',
139 relation_id=None,
140 **FAKE_KS_AUTH_CFG)
79141
=== modified file 'unit_tests/test_nova_cc_utils.py'
--- unit_tests/test_nova_cc_utils.py 2013-10-15 13:01:37 +0000
+++ unit_tests/test_nova_cc_utils.py 2013-11-08 05:43:26 +0000
@@ -70,10 +70,11 @@
70 'nova-api-ec2', 'nova-api-os-compute'70 'nova-api-ec2', 'nova-api-os-compute'
71 ]),71 ]),
72 ('/etc/neutron/neutron.conf', ['neutron-server']),72 ('/etc/neutron/neutron.conf', ['neutron-server']),
73 ('/etc/default/neutron-server', ['neutron-server']),
73 ('/etc/haproxy/haproxy.cfg', ['haproxy']),74 ('/etc/haproxy/haproxy.cfg', ['haproxy']),
74 ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']),75 ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']),
75 ('/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini',76 ('/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini',
76 ['neutron-server'])77 ['quantum-server'])
77])78])
7879
7980
@@ -87,13 +88,17 @@
87 'services': ['quantum-plugin-openvswitch-agent'],88 'services': ['quantum-plugin-openvswitch-agent'],
88 'packages': ['quantum-plugin-openvswitch-agent',89 'packages': ['quantum-plugin-openvswitch-agent',
89 'openvswitch-datapath-dkms'],90 'openvswitch-datapath-dkms'],
91 'server_packages': ['quantum-server', 'quantum-plugin-openvswitch'],
92 'server_services': ['quantum-server'],
90 },93 },
91 'nvp': {94 'nvp': {
92 'config': '/etc/quantum/plugins/nicira/nvp.ini',95 'config': '/etc/quantum/plugins/nicira/nvp.ini',
93 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'96 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
94 'QuantumPlugin.NvpPluginV2',97 'QuantumPlugin.NvpPluginV2',
95 'services': [],98 'services': [],
96 'packages': ['quantum-plugin-nicira'],99 'packages': [],
100 'server_packages': ['quantum-server', 'quantum-plugin-nicria'],
101 'server_services': ['quantum-server'],
97 }102 }
98}103}
99104
@@ -107,6 +112,7 @@
107112
108113
109class NovaCCUtilsTests(CharmTestCase):114class NovaCCUtilsTests(CharmTestCase):
115
110 def setUp(self):116 def setUp(self):
111 super(NovaCCUtilsTests, self).setUp(utils, TO_PATCH)117 super(NovaCCUtilsTests, self).setUp(utils, TO_PATCH)
112 self.config.side_effect = self.test_config.get118 self.config.side_effect = self.test_config.get
@@ -119,9 +125,13 @@
119 self.neutron_plugin_attribute.side_effect = fake_plugin_attribute125 self.neutron_plugin_attribute.side_effect = fake_plugin_attribute
120 if volume_manager == 'nova-volume':126 if volume_manager == 'nova-volume':
121 self.relation_ids.return_value = 'nova-volume-service:0'127 self.relation_ids.return_value = 'nova-volume-service:0'
122 return utils.resource_map()128 with patch('charmhelpers.contrib.openstack.context.'
129 'SubordinateConfigContext'):
130 _map = utils.resource_map()
131 return _map
123132
124 def test_resource_map_quantum(self):133 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
134 def test_resource_map_quantum(self, subcontext):
125 self._resource_map(network_manager='quantum')135 self._resource_map(network_manager='quantum')
126 _map = utils.resource_map()136 _map = utils.resource_map()
127 confs = [137 confs = [
@@ -131,7 +141,8 @@
131 ]141 ]
132 [self.assertIn(q_conf, _map.keys()) for q_conf in confs]142 [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
133143
134 def test_resource_map_neutron(self):144 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
145 def test_resource_map_neutron(self, subcontext):
135 self._resource_map(network_manager='neutron')146 self._resource_map(network_manager='neutron')
136 _map = utils.resource_map()147 _map = utils.resource_map()
137 confs = [148 confs = [
@@ -139,7 +150,21 @@
139 ]150 ]
140 [self.assertIn(q_conf, _map.keys()) for q_conf in confs]151 [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
141152
142 def test_resource_map_neutron_no_agent_installed(self):153 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
154 def test_resource_map_vmware(self, subcontext):
155 fake_context = MagicMock()
156 fake_context.return_value = {
157 'sections': [],
158 'services': ['nova-compute', 'nova-network'],
159
160 }
161 subcontext.return_value = fake_context
162 _map = utils.resource_map()
163 for s in ['nova-compute', 'nova-network']:
164 self.assertIn(s, _map['/etc/nova/nova.conf']['services'])
165
166 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
167 def test_resource_map_neutron_no_agent_installed(self, subcontext):
143 self._resource_map(network_manager='neutron')168 self._resource_map(network_manager='neutron')
144 _map = utils.resource_map()169 _map = utils.resource_map()
145 services = []170 services = []
@@ -147,22 +172,25 @@
147 for svc in services:172 for svc in services:
148 self.assertNotIn('agent', svc)173 self.assertNotIn('agent', svc)
149174
150 def test_resource_map_nova_volume(self):175 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
176 def test_resource_map_nova_volume(self, subcontext):
151 self.relation_ids.return_value = ['nova-volume-service:0']177 self.relation_ids.return_value = ['nova-volume-service:0']
152 _map = utils.resource_map()178 _map = utils.resource_map()
153 self.assertIn('nova-api-os-volume',179 self.assertIn('nova-api-os-volume',
154 _map['/etc/nova/nova.conf']['services'])180 _map['/etc/nova/nova.conf']['services'])
155181
156 @patch('os.path.exists')182 @patch('os.path.exists')
157 def test_restart_map_api_before_frontends(self, _exists):183 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
184 def test_restart_map_api_before_frontends(self, subcontext, _exists):
158 _exists.return_value = False185 _exists.return_value = False
159 self._resource_map(network_manager='neutron')186 self._resource_map(network_manager='neutron')
160 _map = utils.restart_map()187 _map = utils.restart_map()
161 self.assertTrue(isinstance(_map, OrderedDict))188 self.assertTrue(isinstance(_map, OrderedDict))
162 self.assertEquals(_map, RESTART_MAP)189 self.assertEquals(_map, RESTART_MAP)
163190
191 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
164 @patch('os.path.exists')192 @patch('os.path.exists')
165 def test_restart_map_apache24(self, _exists):193 def test_restart_map_apache24(self, _exists, subcontext):
166 _exists.return_Value = True194 _exists.return_Value = True
167 self._resource_map(network_manager='neutron')195 self._resource_map(network_manager='neutron')
168 _map = utils.restart_map()196 _map = utils.restart_map()
@@ -171,29 +199,34 @@
171 self.assertTrue('/etc/apache2/sites-available/'199 self.assertTrue('/etc/apache2/sites-available/'
172 'openstack_https_frontend' not in _map)200 'openstack_https_frontend' not in _map)
173201
174 def test_determine_packages_quantum(self):202 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
203 def test_determine_packages_quantum(self, subcontext):
175 self._resource_map(network_manager='quantum')204 self._resource_map(network_manager='quantum')
176 pkgs = utils.determine_packages()205 pkgs = utils.determine_packages()
177 self.assertIn('quantum-server', pkgs)206 self.assertIn('quantum-server', pkgs)
178207
179 def test_determine_packages_neutron(self):208 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
209 def test_determine_packages_neutron(self, subcontext):
180 self._resource_map(network_manager='neutron')210 self._resource_map(network_manager='neutron')
181 pkgs = utils.determine_packages()211 pkgs = utils.determine_packages()
182 self.assertIn('neutron-server', pkgs)212 self.assertIn('neutron-server', pkgs)
183213
184 def test_determine_packages_nova_volume(self):214 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
215 def test_determine_packages_nova_volume(self, subcontext):
185 self.relation_ids.return_value = ['nova-volume-service:0']216 self.relation_ids.return_value = ['nova-volume-service:0']
186 pkgs = utils.determine_packages()217 pkgs = utils.determine_packages()
187 self.assertIn('nova-api-os-volume', pkgs)218 self.assertIn('nova-api-os-volume', pkgs)
188219
189 def test_determine_packages_base(self):220 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
221 def test_determine_packages_base(self, subcontext):
190 self.relation_ids.return_value = []222 self.relation_ids.return_value = []
191 self.os_release.return_value = 'folsom'223 self.os_release.return_value = 'folsom'
192 pkgs = utils.determine_packages()224 pkgs = utils.determine_packages()
193 ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))225 ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
194 self.assertEquals(ex, pkgs)226 self.assertEquals(ex, pkgs)
195227
196 def test_determine_packages_base_grizzly_beyond(self):228 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
229 def test_determine_packages_base_grizzly_beyond(self, subcontext):
197 self.relation_ids.return_value = []230 self.relation_ids.return_value = []
198 self.os_release.return_value = 'grizzly'231 self.os_release.return_value = 'grizzly'
199 pkgs = utils.determine_packages()232 pkgs = utils.determine_packages()
200233
=== modified file 'unit_tests/test_utils.py'
--- unit_tests/test_utils.py 2013-08-02 03:42:16 +0000
+++ unit_tests/test_utils.py 2013-11-08 05:43:26 +0000
@@ -45,6 +45,7 @@
4545
4646
47class CharmTestCase(unittest.TestCase):47class CharmTestCase(unittest.TestCase):
48
48 def setUp(self, obj, patches):49 def setUp(self, obj, patches):
49 super(CharmTestCase, self).setUp()50 super(CharmTestCase, self).setUp()
50 self.patches = patches51 self.patches = patches
@@ -65,6 +66,7 @@
6566
6667
67class TestConfig(object):68class TestConfig(object):
69
68 def __init__(self):70 def __init__(self):
69 self.config = get_default_config()71 self.config = get_default_config()
7072
@@ -86,6 +88,7 @@
8688
8789
88class TestRelation(object):90class TestRelation(object):
91
89 def __init__(self, relation_data={}):92 def __init__(self, relation_data={}):
90 self.relation_data = relation_data93 self.relation_data = relation_data
9194

Subscribers

People subscribed via source and target branches