Merge lp:~openstack-charmers/charms/precise/nova-cloud-controller/ods_merge into lp:~charmers/charms/precise/nova-cloud-controller/trunk
- Precise Pangolin (12.04)
- ods_merge
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marco Ceppi (community) | Abstain | ||
OpenStack Charmers | Pending | ||
Review via email: mp+194062@code.launchpad.net |
Commit message
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
1 | === modified file 'charm-helpers.yaml' |
2 | --- charm-helpers.yaml 2013-10-15 01:32:42 +0000 |
3 | +++ charm-helpers.yaml 2013-11-08 05:43:26 +0000 |
4 | @@ -7,5 +7,4 @@ |
5 | - contrib.storage |
6 | - contrib.hahelpers: |
7 | - apache |
8 | - - ceph |
9 | - payload.execd |
10 | |
11 | === modified file 'config.yaml' |
12 | --- config.yaml 2013-10-17 19:18:20 +0000 |
13 | +++ config.yaml 2013-11-08 05:43:26 +0000 |
14 | @@ -69,6 +69,7 @@ |
15 | Quantum plugin to use for network management; supports |
16 | . |
17 | ovs - OpenvSwitch Plugin |
18 | + nvp - Nicira Network Virtualization Platform |
19 | . |
20 | This configuration only has context when used with |
21 | network-manager Quantum. |
22 | @@ -125,3 +126,31 @@ |
23 | ssl_key: |
24 | type: string |
25 | description: SSL key to use with certificate specified as ssl_cert. |
26 | + # Neutron NVP Plugin configuration |
27 | + nvp-controllers: |
28 | + type: string |
29 | + description: Space delimited addresses of NVP controllers |
30 | + nvp-username: |
31 | + type: string |
32 | + default: admin |
33 | + description: Username to connect to NVP controllers with |
34 | + nvp-password: |
35 | + type: string |
36 | + default: admin |
37 | + description: Password to connect to NVP controllers with |
38 | + nvp-cluster-name: |
39 | + type: string |
40 | + default: example |
41 | + description: Name of the NVP cluster configuration to create (grizzly only) |
42 | + nvp-tz-uuid: |
43 | + type: string |
44 | + description: | |
45 | + This is uuid of the default NVP Transport zone that will be used for |
46 | + creating tunneled isolated Quantum networks. It needs to be created |
47 | + in NVP before starting Quantum with the nvp plugin. |
48 | + nvp-l3-uuid: |
49 | + type: string |
50 | + description: | |
51 | + This is uuid of the default NVP L3 Gateway Service. |
52 | + # end of NVP configuration |
53 | + |
54 | |
55 | === added file 'hooks/charmhelpers/contrib/openstack/alternatives.py' |
56 | --- hooks/charmhelpers/contrib/openstack/alternatives.py 1970-01-01 00:00:00 +0000 |
57 | +++ hooks/charmhelpers/contrib/openstack/alternatives.py 2013-11-08 05:43:26 +0000 |
58 | @@ -0,0 +1,17 @@ |
59 | +''' Helper for managing alternatives for file conflict resolution ''' |
60 | + |
61 | +import subprocess |
62 | +import shutil |
63 | +import os |
64 | + |
65 | + |
66 | +def install_alternative(name, target, source, priority=50): |
67 | + ''' Install alternative configuration ''' |
68 | + if (os.path.exists(target) and not os.path.islink(target)): |
69 | + # Move existing file/directory away before installing |
70 | + shutil.move(target, '{}.bak'.format(target)) |
71 | + cmd = [ |
72 | + 'update-alternatives', '--force', '--install', |
73 | + target, name, source, str(priority) |
74 | + ] |
75 | + subprocess.check_call(cmd) |
76 | |
77 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' |
78 | --- hooks/charmhelpers/contrib/openstack/context.py 2013-10-15 01:32:42 +0000 |
79 | +++ hooks/charmhelpers/contrib/openstack/context.py 2013-11-08 05:43:26 +0000 |
80 | @@ -385,16 +385,33 @@ |
81 | def ovs_ctxt(self): |
82 | driver = neutron_plugin_attribute(self.plugin, 'driver', |
83 | self.network_manager) |
84 | - |
85 | + config = neutron_plugin_attribute(self.plugin, 'config', |
86 | + self.network_manager) |
87 | ovs_ctxt = { |
88 | 'core_plugin': driver, |
89 | 'neutron_plugin': 'ovs', |
90 | 'neutron_security_groups': self.neutron_security_groups, |
91 | 'local_ip': unit_private_ip(), |
92 | + 'config': config |
93 | } |
94 | |
95 | return ovs_ctxt |
96 | |
97 | + def nvp_ctxt(self): |
98 | + driver = neutron_plugin_attribute(self.plugin, 'driver', |
99 | + self.network_manager) |
100 | + config = neutron_plugin_attribute(self.plugin, 'config', |
101 | + self.network_manager) |
102 | + nvp_ctxt = { |
103 | + 'core_plugin': driver, |
104 | + 'neutron_plugin': 'nvp', |
105 | + 'neutron_security_groups': self.neutron_security_groups, |
106 | + 'local_ip': unit_private_ip(), |
107 | + 'config': config |
108 | + } |
109 | + |
110 | + return nvp_ctxt |
111 | + |
112 | def __call__(self): |
113 | self._ensure_packages() |
114 | |
115 | @@ -408,6 +425,8 @@ |
116 | |
117 | if self.plugin == 'ovs': |
118 | ctxt.update(self.ovs_ctxt()) |
119 | + elif self.plugin == 'nvp': |
120 | + ctxt.update(self.nvp_ctxt()) |
121 | |
122 | self._save_flag_file() |
123 | return ctxt |
124 | |
125 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' |
126 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2013-10-15 01:32:42 +0000 |
127 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2013-11-08 05:43:26 +0000 |
128 | @@ -34,13 +34,23 @@ |
129 | 'services': ['quantum-plugin-openvswitch-agent'], |
130 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], |
131 | ['quantum-plugin-openvswitch-agent']], |
132 | + 'server_packages': ['quantum-server', |
133 | + 'quantum-plugin-openvswitch'], |
134 | + 'server_services': ['quantum-server'] |
135 | }, |
136 | 'nvp': { |
137 | 'config': '/etc/quantum/plugins/nicira/nvp.ini', |
138 | 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.' |
139 | 'QuantumPlugin.NvpPluginV2', |
140 | + 'contexts': [ |
141 | + context.SharedDBContext(user=config('neutron-database-user'), |
142 | + database=config('neutron-database'), |
143 | + relation_prefix='neutron')], |
144 | 'services': [], |
145 | 'packages': [], |
146 | + 'server_packages': ['quantum-server', |
147 | + 'quantum-plugin-nicira'], |
148 | + 'server_services': ['quantum-server'] |
149 | } |
150 | } |
151 | |
152 | @@ -60,13 +70,23 @@ |
153 | 'services': ['neutron-plugin-openvswitch-agent'], |
154 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], |
155 | ['quantum-plugin-openvswitch-agent']], |
156 | + 'server_packages': ['neutron-server', |
157 | + 'neutron-plugin-openvswitch'], |
158 | + 'server_services': ['neutron-server'] |
159 | }, |
160 | 'nvp': { |
161 | 'config': '/etc/neutron/plugins/nicira/nvp.ini', |
162 | 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.' |
163 | 'NeutronPlugin.NvpPluginV2', |
164 | + 'contexts': [ |
165 | + context.SharedDBContext(user=config('neutron-database-user'), |
166 | + database=config('neutron-database'), |
167 | + relation_prefix='neutron')], |
168 | 'services': [], |
169 | 'packages': [], |
170 | + 'server_packages': ['neutron-server', |
171 | + 'neutron-plugin-nicira'], |
172 | + 'server_services': ['neutron-server'] |
173 | } |
174 | } |
175 | |
176 | |
177 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' |
178 | --- hooks/charmhelpers/contrib/openstack/utils.py 2013-10-15 01:32:42 +0000 |
179 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2013-11-08 05:43:26 +0000 |
180 | @@ -13,19 +13,28 @@ |
181 | config, |
182 | log as juju_log, |
183 | charm_dir, |
184 | -) |
185 | - |
186 | -from charmhelpers.core.host import ( |
187 | - lsb_release, |
188 | -) |
189 | - |
190 | -from charmhelpers.fetch import ( |
191 | - apt_install, |
192 | -) |
193 | + ERROR, |
194 | + INFO |
195 | +) |
196 | + |
197 | +from charmhelpers.contrib.storage.linux.lvm import ( |
198 | + deactivate_lvm_volume_group, |
199 | + is_lvm_physical_volume, |
200 | + remove_lvm_physical_volume, |
201 | +) |
202 | + |
203 | +from charmhelpers.core.host import lsb_release, mounts, umount |
204 | +from charmhelpers.fetch import apt_install |
205 | +from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk |
206 | +from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device |
207 | |
208 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" |
209 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' |
210 | |
211 | +DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' |
212 | + 'restricted main multiverse universe') |
213 | + |
214 | + |
215 | UBUNTU_OPENSTACK_RELEASE = OrderedDict([ |
216 | ('oneiric', 'diablo'), |
217 | ('precise', 'essex'), |
218 | @@ -57,6 +66,8 @@ |
219 | ('1.9.0', 'havana'), |
220 | ]) |
221 | |
222 | +DEFAULT_LOOPBACK_SIZE = '5G' |
223 | + |
224 | |
225 | def error_out(msg): |
226 | juju_log("FATAL ERROR: %s" % msg, level='ERROR') |
227 | @@ -67,7 +78,7 @@ |
228 | '''Derive OpenStack release codename from a given installation source.''' |
229 | ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] |
230 | rel = '' |
231 | - if src == 'distro': |
232 | + if src in ['distro', 'distro-proposed']: |
233 | try: |
234 | rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel] |
235 | except KeyError: |
236 | @@ -202,6 +213,10 @@ |
237 | '''Configure apt installation source.''' |
238 | if rel == 'distro': |
239 | return |
240 | + elif rel == 'distro-proposed': |
241 | + ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] |
242 | + with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f: |
243 | + f.write(DISTRO_PROPOSED % ubuntu_rel) |
244 | elif rel[:4] == "ppa:": |
245 | src = rel |
246 | subprocess.check_call(["add-apt-repository", "-y", src]) |
247 | @@ -299,6 +314,62 @@ |
248 | return apt.version_compare(available_vers, cur_vers) == 1 |
249 | |
250 | |
251 | +def ensure_block_device(block_device): |
252 | + ''' |
253 | + Confirm block_device, create as loopback if necessary. |
254 | + |
255 | + :param block_device: str: Full path of block device to ensure. |
256 | + |
257 | + :returns: str: Full path of ensured block device. |
258 | + ''' |
259 | + _none = ['None', 'none', None] |
260 | + if (block_device in _none): |
261 | + error_out('prepare_storage(): Missing required input: ' |
262 | + 'block_device=%s.' % block_device, level=ERROR) |
263 | + |
264 | + if block_device.startswith('/dev/'): |
265 | + bdev = block_device |
266 | + elif block_device.startswith('/'): |
267 | + _bd = block_device.split('|') |
268 | + if len(_bd) == 2: |
269 | + bdev, size = _bd |
270 | + else: |
271 | + bdev = block_device |
272 | + size = DEFAULT_LOOPBACK_SIZE |
273 | + bdev = ensure_loopback_device(bdev, size) |
274 | + else: |
275 | + bdev = '/dev/%s' % block_device |
276 | + |
277 | + if not is_block_device(bdev): |
278 | + error_out('Failed to locate valid block device at %s' % bdev, |
279 | + level=ERROR) |
280 | + |
281 | + return bdev |
282 | + |
283 | + |
284 | +def clean_storage(block_device): |
285 | + ''' |
286 | + Ensures a block device is clean. That is: |
287 | + - unmounted |
288 | + - any lvm volume groups are deactivated |
289 | + - any lvm physical device signatures removed |
290 | + - partition table wiped |
291 | + |
292 | + :param block_device: str: Full path to block device to clean. |
293 | + ''' |
294 | + for mp, d in mounts(): |
295 | + if d == block_device: |
296 | + juju_log('clean_storage(): %s is mounted @ %s, unmounting.' % |
297 | + (d, mp), level=INFO) |
298 | + umount(mp, persist=True) |
299 | + |
300 | + if is_lvm_physical_volume(block_device): |
301 | + deactivate_lvm_volume_group(block_device) |
302 | + remove_lvm_physical_volume(block_device) |
303 | + else: |
304 | + zap_disk(block_device) |
305 | + |
306 | + |
307 | def is_ip(address): |
308 | """ |
309 | Returns True if address is a valid IP address. |
310 | |
311 | === modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py' |
312 | --- hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-09-27 16:18:25 +0000 |
313 | +++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-08 05:43:26 +0000 |
314 | @@ -102,8 +102,12 @@ |
315 | Return a list of all Ceph Object Storage Daemons |
316 | currently in the cluster |
317 | ''' |
318 | - return json.loads(check_output(['ceph', '--id', service, |
319 | - 'osd', 'ls', '--format=json'])) |
320 | + version = ceph_version() |
321 | + if version and version >= '0.56': |
322 | + return json.loads(check_output(['ceph', '--id', service, |
323 | + 'osd', 'ls', '--format=json'])) |
324 | + else: |
325 | + return None |
326 | |
327 | |
328 | def create_pool(service, name, replicas=2): |
329 | @@ -114,7 +118,13 @@ |
330 | return |
331 | # Calculate the number of placement groups based |
332 | # on upstream recommended best practices. |
333 | - pgnum = (len(get_osds(service)) * 100 / replicas) |
334 | + osds = get_osds(service) |
335 | + if osds: |
336 | + pgnum = (len(osds) * 100 / replicas) |
337 | + else: |
338 | + # NOTE(james-page): Default to 200 for older ceph versions |
339 | + # which don't support OSD query from cli |
340 | + pgnum = 200 |
341 | cmd = [ |
342 | 'ceph', '--id', service, |
343 | 'osd', 'pool', 'create', |
344 | @@ -357,3 +367,17 @@ |
345 | if user and group: |
346 | check_call(['chown', '%s.%s' % (user, group), keyring]) |
347 | return True |
348 | + |
349 | + |
350 | +def ceph_version(): |
351 | + ''' Retrieve the local version of ceph ''' |
352 | + if os.path.exists('/usr/bin/ceph'): |
353 | + cmd = ['ceph', '-v'] |
354 | + output = check_output(cmd) |
355 | + output = output.split() |
356 | + if len(output) > 3: |
357 | + return output[2] |
358 | + else: |
359 | + return None |
360 | + else: |
361 | + return None |
362 | |
363 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
364 | --- hooks/charmhelpers/core/hookenv.py 2013-08-02 03:42:16 +0000 |
365 | +++ hooks/charmhelpers/core/hookenv.py 2013-11-08 05:43:26 +0000 |
366 | @@ -9,6 +9,7 @@ |
367 | import yaml |
368 | import subprocess |
369 | import UserDict |
370 | +from subprocess import CalledProcessError |
371 | |
372 | CRITICAL = "CRITICAL" |
373 | ERROR = "ERROR" |
374 | @@ -21,7 +22,7 @@ |
375 | |
376 | |
377 | def cached(func): |
378 | - ''' Cache return values for multiple executions of func + args |
379 | + """Cache return values for multiple executions of func + args |
380 | |
381 | For example: |
382 | |
383 | @@ -32,7 +33,7 @@ |
384 | unit_get('test') |
385 | |
386 | will cache the result of unit_get + 'test' for future calls. |
387 | - ''' |
388 | + """ |
389 | def wrapper(*args, **kwargs): |
390 | global cache |
391 | key = str((func, args, kwargs)) |
392 | @@ -46,8 +47,8 @@ |
393 | |
394 | |
395 | def flush(key): |
396 | - ''' Flushes any entries from function cache where the |
397 | - key is found in the function+args ''' |
398 | + """Flushes any entries from function cache where the |
399 | + key is found in the function+args """ |
400 | flush_list = [] |
401 | for item in cache: |
402 | if key in item: |
403 | @@ -57,7 +58,7 @@ |
404 | |
405 | |
406 | def log(message, level=None): |
407 | - "Write a message to the juju log" |
408 | + """Write a message to the juju log""" |
409 | command = ['juju-log'] |
410 | if level: |
411 | command += ['-l', level] |
412 | @@ -66,7 +67,7 @@ |
413 | |
414 | |
415 | class Serializable(UserDict.IterableUserDict): |
416 | - "Wrapper, an object that can be serialized to yaml or json" |
417 | + """Wrapper, an object that can be serialized to yaml or json""" |
418 | |
419 | def __init__(self, obj): |
420 | # wrap the object |
421 | @@ -96,11 +97,11 @@ |
422 | self.data = state |
423 | |
424 | def json(self): |
425 | - "Serialize the object to json" |
426 | + """Serialize the object to json""" |
427 | return json.dumps(self.data) |
428 | |
429 | def yaml(self): |
430 | - "Serialize the object to yaml" |
431 | + """Serialize the object to yaml""" |
432 | return yaml.dump(self.data) |
433 | |
434 | |
435 | @@ -119,38 +120,38 @@ |
436 | |
437 | |
438 | def in_relation_hook(): |
439 | - "Determine whether we're running in a relation hook" |
440 | + """Determine whether we're running in a relation hook""" |
441 | return 'JUJU_RELATION' in os.environ |
442 | |
443 | |
444 | def relation_type(): |
445 | - "The scope for the current relation hook" |
446 | + """The scope for the current relation hook""" |
447 | return os.environ.get('JUJU_RELATION', None) |
448 | |
449 | |
450 | def relation_id(): |
451 | - "The relation ID for the current relation hook" |
452 | + """The relation ID for the current relation hook""" |
453 | return os.environ.get('JUJU_RELATION_ID', None) |
454 | |
455 | |
456 | def local_unit(): |
457 | - "Local unit ID" |
458 | + """Local unit ID""" |
459 | return os.environ['JUJU_UNIT_NAME'] |
460 | |
461 | |
462 | def remote_unit(): |
463 | - "The remote unit for the current relation hook" |
464 | + """The remote unit for the current relation hook""" |
465 | return os.environ['JUJU_REMOTE_UNIT'] |
466 | |
467 | |
468 | def service_name(): |
469 | - "The name service group this unit belongs to" |
470 | + """The name service group this unit belongs to""" |
471 | return local_unit().split('/')[0] |
472 | |
473 | |
474 | @cached |
475 | def config(scope=None): |
476 | - "Juju charm configuration" |
477 | + """Juju charm configuration""" |
478 | config_cmd_line = ['config-get'] |
479 | if scope is not None: |
480 | config_cmd_line.append(scope) |
481 | @@ -163,6 +164,7 @@ |
482 | |
483 | @cached |
484 | def relation_get(attribute=None, unit=None, rid=None): |
485 | + """Get relation information""" |
486 | _args = ['relation-get', '--format=json'] |
487 | if rid: |
488 | _args.append('-r') |
489 | @@ -174,9 +176,14 @@ |
490 | return json.loads(subprocess.check_output(_args)) |
491 | except ValueError: |
492 | return None |
493 | + except CalledProcessError, e: |
494 | + if e.returncode == 2: |
495 | + return None |
496 | + raise |
497 | |
498 | |
499 | def relation_set(relation_id=None, relation_settings={}, **kwargs): |
500 | + """Set relation information for the current unit""" |
501 | relation_cmd_line = ['relation-set'] |
502 | if relation_id is not None: |
503 | relation_cmd_line.extend(('-r', relation_id)) |
504 | @@ -192,7 +199,7 @@ |
505 | |
506 | @cached |
507 | def relation_ids(reltype=None): |
508 | - "A list of relation_ids" |
509 | + """A list of relation_ids""" |
510 | reltype = reltype or relation_type() |
511 | relid_cmd_line = ['relation-ids', '--format=json'] |
512 | if reltype is not None: |
513 | @@ -203,7 +210,7 @@ |
514 | |
515 | @cached |
516 | def related_units(relid=None): |
517 | - "A list of related units" |
518 | + """A list of related units""" |
519 | relid = relid or relation_id() |
520 | units_cmd_line = ['relation-list', '--format=json'] |
521 | if relid is not None: |
522 | @@ -213,7 +220,7 @@ |
523 | |
524 | @cached |
525 | def relation_for_unit(unit=None, rid=None): |
526 | - "Get the json represenation of a unit's relation" |
527 | + """Get the json represenation of a unit's relation""" |
528 | unit = unit or remote_unit() |
529 | relation = relation_get(unit=unit, rid=rid) |
530 | for key in relation: |
531 | @@ -225,7 +232,7 @@ |
532 | |
533 | @cached |
534 | def relations_for_id(relid=None): |
535 | - "Get relations of a specific relation ID" |
536 | + """Get relations of a specific relation ID""" |
537 | relation_data = [] |
538 | relid = relid or relation_ids() |
539 | for unit in related_units(relid): |
540 | @@ -237,7 +244,7 @@ |
541 | |
542 | @cached |
543 | def relations_of_type(reltype=None): |
544 | - "Get relations of a specific type" |
545 | + """Get relations of a specific type""" |
546 | relation_data = [] |
547 | reltype = reltype or relation_type() |
548 | for relid in relation_ids(reltype): |
549 | @@ -249,7 +256,7 @@ |
550 | |
551 | @cached |
552 | def relation_types(): |
553 | - "Get a list of relation types supported by this charm" |
554 | + """Get a list of relation types supported by this charm""" |
555 | charmdir = os.environ.get('CHARM_DIR', '') |
556 | mdf = open(os.path.join(charmdir, 'metadata.yaml')) |
557 | md = yaml.safe_load(mdf) |
558 | @@ -264,6 +271,7 @@ |
559 | |
560 | @cached |
561 | def relations(): |
562 | + """Get a nested dictionary of relation data for all related units""" |
563 | rels = {} |
564 | for reltype in relation_types(): |
565 | relids = {} |
566 | @@ -277,15 +285,35 @@ |
567 | return rels |
568 | |
569 | |
570 | +@cached |
571 | +def is_relation_made(relation, keys='private-address'): |
572 | + ''' |
573 | + Determine whether a relation is established by checking for |
574 | + presence of key(s). If a list of keys is provided, they |
575 | + must all be present for the relation to be identified as made |
576 | + ''' |
577 | + if isinstance(keys, str): |
578 | + keys = [keys] |
579 | + for r_id in relation_ids(relation): |
580 | + for unit in related_units(r_id): |
581 | + context = {} |
582 | + for k in keys: |
583 | + context[k] = relation_get(k, rid=r_id, |
584 | + unit=unit) |
585 | + if None not in context.values(): |
586 | + return True |
587 | + return False |
588 | + |
589 | + |
590 | def open_port(port, protocol="TCP"): |
591 | - "Open a service network port" |
592 | + """Open a service network port""" |
593 | _args = ['open-port'] |
594 | _args.append('{}/{}'.format(port, protocol)) |
595 | subprocess.check_call(_args) |
596 | |
597 | |
598 | def close_port(port, protocol="TCP"): |
599 | - "Close a service network port" |
600 | + """Close a service network port""" |
601 | _args = ['close-port'] |
602 | _args.append('{}/{}'.format(port, protocol)) |
603 | subprocess.check_call(_args) |
604 | @@ -293,6 +321,7 @@ |
605 | |
606 | @cached |
607 | def unit_get(attribute): |
608 | + """Get the unit ID for the remote unit""" |
609 | _args = ['unit-get', '--format=json', attribute] |
610 | try: |
611 | return json.loads(subprocess.check_output(_args)) |
612 | @@ -301,22 +330,46 @@ |
613 | |
614 | |
615 | def unit_private_ip(): |
616 | + """Get this unit's private IP address""" |
617 | return unit_get('private-address') |
618 | |
619 | |
620 | class UnregisteredHookError(Exception): |
621 | + """Raised when an undefined hook is called""" |
622 | pass |
623 | |
624 | |
625 | class Hooks(object): |
626 | + """A convenient handler for hook functions. |
627 | + |
628 | + Example: |
629 | + hooks = Hooks() |
630 | + |
631 | + # register a hook, taking its name from the function name |
632 | + @hooks.hook() |
633 | + def install(): |
634 | + ... |
635 | + |
636 | + # register a hook, providing a custom hook name |
637 | + @hooks.hook("config-changed") |
638 | + def config_changed(): |
639 | + ... |
640 | + |
641 | + if __name__ == "__main__": |
642 | + # execute a hook based on the name the program is called by |
643 | + hooks.execute(sys.argv) |
644 | + """ |
645 | + |
646 | def __init__(self): |
647 | super(Hooks, self).__init__() |
648 | self._hooks = {} |
649 | |
650 | def register(self, name, function): |
651 | + """Register a hook""" |
652 | self._hooks[name] = function |
653 | |
654 | def execute(self, args): |
655 | + """Execute a registered hook based on args[0]""" |
656 | hook_name = os.path.basename(args[0]) |
657 | if hook_name in self._hooks: |
658 | self._hooks[hook_name]() |
659 | @@ -324,6 +377,7 @@ |
660 | raise UnregisteredHookError(hook_name) |
661 | |
662 | def hook(self, *hook_names): |
663 | + """Decorator, registering them as hooks""" |
664 | def wrapper(decorated): |
665 | for hook_name in hook_names: |
666 | self.register(hook_name, decorated) |
667 | @@ -337,4 +391,5 @@ |
668 | |
669 | |
670 | def charm_dir(): |
671 | + """Return the root directory of the current charm""" |
672 | return os.environ.get('CHARM_DIR') |
673 | |
674 | === modified file 'hooks/charmhelpers/core/host.py' |
675 | --- hooks/charmhelpers/core/host.py 2013-09-20 16:29:50 +0000 |
676 | +++ hooks/charmhelpers/core/host.py 2013-11-08 05:43:26 +0000 |
677 | @@ -19,18 +19,22 @@ |
678 | |
679 | |
680 | def service_start(service_name): |
681 | + """Start a system service""" |
682 | return service('start', service_name) |
683 | |
684 | |
685 | def service_stop(service_name): |
686 | + """Stop a system service""" |
687 | return service('stop', service_name) |
688 | |
689 | |
690 | def service_restart(service_name): |
691 | + """Restart a system service""" |
692 | return service('restart', service_name) |
693 | |
694 | |
695 | def service_reload(service_name, restart_on_failure=False): |
696 | + """Reload a system service, optionally falling back to restart if reload fails""" |
697 | service_result = service('reload', service_name) |
698 | if not service_result and restart_on_failure: |
699 | service_result = service('restart', service_name) |
700 | @@ -38,11 +42,13 @@ |
701 | |
702 | |
703 | def service(action, service_name): |
704 | + """Control a system service""" |
705 | cmd = ['service', service_name, action] |
706 | return subprocess.call(cmd) == 0 |
707 | |
708 | |
709 | def service_running(service): |
710 | + """Determine whether a system service is running""" |
711 | try: |
712 | output = subprocess.check_output(['service', service, 'status']) |
713 | except subprocess.CalledProcessError: |
714 | @@ -55,7 +61,7 @@ |
715 | |
716 | |
717 | def adduser(username, password=None, shell='/bin/bash', system_user=False): |
718 | - """Add a user""" |
719 | + """Add a user to the system""" |
720 | try: |
721 | user_info = pwd.getpwnam(username) |
722 | log('user {0} already exists!'.format(username)) |
723 | @@ -138,7 +144,7 @@ |
724 | |
725 | |
726 | def mount(device, mountpoint, options=None, persist=False): |
727 | - '''Mount a filesystem''' |
728 | + """Mount a filesystem at a particular mountpoint""" |
729 | cmd_args = ['mount'] |
730 | if options is not None: |
731 | cmd_args.extend(['-o', options]) |
732 | @@ -155,7 +161,7 @@ |
733 | |
734 | |
735 | def umount(mountpoint, persist=False): |
736 | - '''Unmount a filesystem''' |
737 | + """Unmount a filesystem""" |
738 | cmd_args = ['umount', mountpoint] |
739 | try: |
740 | subprocess.check_output(cmd_args) |
741 | @@ -169,7 +175,7 @@ |
742 | |
743 | |
744 | def mounts(): |
745 | - '''List of all mounted volumes as [[mountpoint,device],[...]]''' |
746 | + """Get a list of all mounted volumes as [[mountpoint,device],[...]]""" |
747 | with open('/proc/mounts') as f: |
748 | # [['/mount/point','/dev/path'],[...]] |
749 | system_mounts = [m[1::-1] for m in [l.strip().split() |
750 | @@ -178,7 +184,7 @@ |
751 | |
752 | |
753 | def file_hash(path): |
754 | - ''' Generate a md5 hash of the contents of 'path' or None if not found ''' |
755 | + """Generate a md5 hash of the contents of 'path' or None if not found """ |
756 | if os.path.exists(path): |
757 | h = hashlib.md5() |
758 | with open(path, 'r') as source: |
759 | @@ -189,7 +195,7 @@ |
760 | |
761 | |
762 | def restart_on_change(restart_map): |
763 | - ''' Restart services based on configuration files changing |
764 | + """Restart services based on configuration files changing |
765 | |
766 | This function is used a decorator, for example |
767 | |
768 | @@ -202,7 +208,7 @@ |
769 | In this example, the cinder-api and cinder-volume services |
770 | would be restarted if /etc/ceph/ceph.conf is changed by the |
771 | ceph_client_changed function. |
772 | - ''' |
773 | + """ |
774 | def wrap(f): |
775 | def wrapped_f(*args): |
776 | checksums = {} |
777 | @@ -220,7 +226,7 @@ |
778 | |
779 | |
780 | def lsb_release(): |
781 | - '''Return /etc/lsb-release in a dict''' |
782 | + """Return /etc/lsb-release in a dict""" |
783 | d = {} |
784 | with open('/etc/lsb-release', 'r') as lsb: |
785 | for l in lsb: |
786 | @@ -230,7 +236,7 @@ |
787 | |
788 | |
789 | def pwgen(length=None): |
790 | - '''Generate a random pasword.''' |
791 | + """Generate a random pasword.""" |
792 | if length is None: |
793 | length = random.choice(range(35, 45)) |
794 | alphanumeric_chars = [ |
795 | |
796 | === modified file 'hooks/charmhelpers/fetch/__init__.py' |
797 | --- hooks/charmhelpers/fetch/__init__.py 2013-09-23 13:26:04 +0000 |
798 | +++ hooks/charmhelpers/fetch/__init__.py 2013-11-08 05:43:26 +0000 |
799 | @@ -20,6 +20,32 @@ |
800 | PROPOSED_POCKET = """# Proposed |
801 | deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted |
802 | """ |
803 | +CLOUD_ARCHIVE_POCKETS = { |
804 | + # Folsom |
805 | + 'folsom': 'precise-updates/folsom', |
806 | + 'precise-folsom': 'precise-updates/folsom', |
807 | + 'precise-folsom/updates': 'precise-updates/folsom', |
808 | + 'precise-updates/folsom': 'precise-updates/folsom', |
809 | + 'folsom/proposed': 'precise-proposed/folsom', |
810 | + 'precise-folsom/proposed': 'precise-proposed/folsom', |
811 | + 'precise-proposed/folsom': 'precise-proposed/folsom', |
812 | + # Grizzly |
813 | + 'grizzly': 'precise-updates/grizzly', |
814 | + 'precise-grizzly': 'precise-updates/grizzly', |
815 | + 'precise-grizzly/updates': 'precise-updates/grizzly', |
816 | + 'precise-updates/grizzly': 'precise-updates/grizzly', |
817 | + 'grizzly/proposed': 'precise-proposed/grizzly', |
818 | + 'precise-grizzly/proposed': 'precise-proposed/grizzly', |
819 | + 'precise-proposed/grizzly': 'precise-proposed/grizzly', |
820 | + # Havana |
821 | + 'havana': 'precise-updates/havana', |
822 | + 'precise-havana': 'precise-updates/havana', |
823 | + 'precise-havana/updates': 'precise-updates/havana', |
824 | + 'precise-updates/havana': 'precise-updates/havana', |
825 | + 'havana/proposed': 'precise-proposed/havana', |
826 | + 'precies-havana/proposed': 'precise-proposed/havana', |
827 | + 'precise-proposed/havana': 'precise-proposed/havana', |
828 | +} |
829 | |
830 | |
831 | def filter_installed_packages(packages): |
832 | @@ -79,16 +105,35 @@ |
833 | subprocess.call(cmd) |
834 | |
835 | |
836 | +def apt_hold(packages, fatal=False): |
837 | + """Hold one or more packages""" |
838 | + cmd = ['apt-mark', 'hold'] |
839 | + if isinstance(packages, basestring): |
840 | + cmd.append(packages) |
841 | + else: |
842 | + cmd.extend(packages) |
843 | + log("Holding {}".format(packages)) |
844 | + if fatal: |
845 | + subprocess.check_call(cmd) |
846 | + else: |
847 | + subprocess.call(cmd) |
848 | + |
849 | + |
850 | def add_source(source, key=None): |
851 | - if ((source.startswith('ppa:') or |
852 | - source.startswith('http:'))): |
853 | + if (source.startswith('ppa:') or |
854 | + source.startswith('http:') or |
855 | + source.startswith('deb ') or |
856 | + source.startswith('cloud-archive:')): |
857 | subprocess.check_call(['add-apt-repository', '--yes', source]) |
858 | elif source.startswith('cloud:'): |
859 | apt_install(filter_installed_packages(['ubuntu-cloud-keyring']), |
860 | fatal=True) |
861 | pocket = source.split(':')[-1] |
862 | + if pocket not in CLOUD_ARCHIVE_POCKETS: |
863 | + raise SourceConfigError('Unsupported cloud: source option %s' % pocket) |
864 | + actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket] |
865 | with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: |
866 | - apt.write(CLOUD_ARCHIVE.format(pocket)) |
867 | + apt.write(CLOUD_ARCHIVE.format(actual_pocket)) |
868 | elif source == 'proposed': |
869 | release = lsb_release()['DISTRIB_CODENAME'] |
870 | with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt: |
871 | @@ -118,8 +163,11 @@ |
872 | Note that 'null' (a.k.a. None) should not be quoted. |
873 | """ |
874 | sources = safe_load(config(sources_var)) |
875 | - keys = safe_load(config(keys_var)) |
876 | - if isinstance(sources, basestring) and isinstance(keys, basestring): |
877 | + keys = config(keys_var) |
878 | + if keys is not None: |
879 | + keys = safe_load(keys) |
880 | + if isinstance(sources, basestring) and ( |
881 | + keys is None or isinstance(keys, basestring)): |
882 | add_source(sources, keys) |
883 | else: |
884 | if not len(sources) == len(keys): |
885 | |
886 | === modified file 'hooks/charmhelpers/fetch/bzrurl.py' |
887 | --- hooks/charmhelpers/fetch/bzrurl.py 2013-09-23 13:26:04 +0000 |
888 | +++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-08 05:43:26 +0000 |
889 | @@ -12,6 +12,7 @@ |
890 | apt_install("python-bzrlib") |
891 | from bzrlib.branch import Branch |
892 | |
893 | + |
894 | class BzrUrlFetchHandler(BaseFetchHandler): |
895 | """Handler for bazaar branches via generic and lp URLs""" |
896 | def can_handle(self, source): |
897 | @@ -46,4 +47,3 @@ |
898 | except OSError as e: |
899 | raise UnhandledSource(e.strerror) |
900 | return dest_dir |
901 | - |
902 | |
903 | === added symlink 'hooks/nova-vmware-relation-changed' |
904 | === target is u'nova_cc_hooks.py' |
905 | === added symlink 'hooks/nova-vmware-relation-joined' |
906 | === target is u'nova_cc_hooks.py' |
907 | === modified file 'hooks/nova_cc_context.py' |
908 | --- hooks/nova_cc_context.py 2013-09-30 20:46:50 +0000 |
909 | +++ hooks/nova_cc_context.py 2013-11-08 05:43:26 +0000 |
910 | @@ -139,6 +139,16 @@ |
911 | def __call__(self): |
912 | ctxt = super(NeutronCCContext, self).__call__() |
913 | ctxt['external_network'] = config('neutron-external-network') |
914 | + if 'nvp' in [config('quantum-plugin'), config('neutron-plugin')]: |
915 | + _config = config() |
916 | + for k, v in _config.iteritems(): |
917 | + if k.startswith('nvp'): |
918 | + ctxt[k.replace('-', '_')] = v |
919 | + if 'nvp-controllers' in _config: |
920 | + ctxt['nvp_controllers'] = \ |
921 | + ','.join(_config['nvp-controllers'].split()) |
922 | + ctxt['nvp_controllers_list'] = \ |
923 | + _config['nvp-controllers'].split() |
924 | return ctxt |
925 | |
926 | |
927 | |
928 | === modified file 'hooks/nova_cc_hooks.py' |
929 | --- hooks/nova_cc_hooks.py 2013-11-06 00:49:41 +0000 |
930 | +++ hooks/nova_cc_hooks.py 2013-11-08 05:43:26 +0000 |
931 | @@ -3,6 +3,7 @@ |
932 | import os |
933 | import shutil |
934 | import sys |
935 | +import uuid |
936 | |
937 | from subprocess import check_call |
938 | from urlparse import urlparse |
939 | @@ -25,7 +26,7 @@ |
940 | ) |
941 | |
942 | from charmhelpers.fetch import ( |
943 | - apt_install, apt_update, filter_installed_packages |
944 | + apt_install, apt_update |
945 | ) |
946 | |
947 | from charmhelpers.contrib.openstack.utils import ( |
948 | @@ -148,6 +149,9 @@ |
949 | |
950 | if eligible_leader(CLUSTER_RES): |
951 | migrate_database() |
952 | + log('Triggering remote cloud-compute restarts.') |
953 | + [compute_joined(rid=rid, remote_restart=True) |
954 | + for rid in relation_ids('cloud-compute')] |
955 | |
956 | |
957 | @hooks.hook('image-service-relation-changed') |
958 | @@ -184,6 +188,7 @@ |
959 | CONFIGS.write(NEUTRON_CONF) |
960 | [compute_joined(rid) for rid in relation_ids('cloud-compute')] |
961 | [quantum_joined(rid) for rid in relation_ids('quantum-network-service')] |
962 | + [nova_vmware_relation_joined(rid) for rid in relation_ids('nova-vmware')] |
963 | configure_https() |
964 | |
965 | |
966 | @@ -230,8 +235,32 @@ |
967 | out.write('export OS_REGION_NAME=%s\n' % config('region')) |
968 | |
969 | |
970 | +def keystone_compute_settings(): |
971 | + ks_auth_config = _auth_config() |
972 | + rel_settings = {} |
973 | + |
974 | + if network_manager() in ['quantum', 'neutron']: |
975 | + if ks_auth_config: |
976 | + rel_settings.update(ks_auth_config) |
977 | + |
978 | + rel_settings.update({ |
979 | + # XXX: Rename these relations settings? |
980 | + 'quantum_plugin': neutron_plugin(), |
981 | + 'region': config('region'), |
982 | + 'quantum_security_groups': config('quantum-security-groups'), |
983 | + 'quantum_url': (canonical_url(CONFIGS) + ':' + |
984 | + str(api_port('neutron-server'))), |
985 | + }) |
986 | + |
987 | + ks_ca = keystone_ca_cert_b64() |
988 | + if ks_auth_config and ks_ca: |
989 | + rel_settings['ca_cert'] = ks_ca |
990 | + |
991 | + return rel_settings |
992 | + |
993 | + |
994 | @hooks.hook('cloud-compute-relation-joined') |
995 | -def compute_joined(rid=None): |
996 | +def compute_joined(rid=None, remote_restart=False): |
997 | if not eligible_leader(CLUSTER_RES): |
998 | return |
999 | rel_settings = { |
1000 | @@ -242,24 +271,12 @@ |
1001 | 'ec2_host': unit_get('private-address'), |
1002 | } |
1003 | |
1004 | - ks_auth_config = _auth_config() |
1005 | - |
1006 | - if network_manager() in ['quantum', 'neutron']: |
1007 | - if ks_auth_config: |
1008 | - rel_settings.update(ks_auth_config) |
1009 | - |
1010 | - rel_settings.update({ |
1011 | - # XXX: Rename these relations settings? |
1012 | - 'quantum_plugin': neutron_plugin(), |
1013 | - 'region': config('region'), |
1014 | - 'quantum_security_groups': config('quantum-security-groups'), |
1015 | - 'quantum_url': (canonical_url(CONFIGS) + ':' + |
1016 | - str(api_port('neutron-server'))), |
1017 | - }) |
1018 | - |
1019 | - ks_ca = keystone_ca_cert_b64() |
1020 | - if ks_auth_config and ks_ca: |
1021 | - rel_settings['ca_cert'] = ks_ca |
1022 | + # update relation setting if we're attempting to restart remote |
1023 | + # services |
1024 | + if remote_restart: |
1025 | + rel_settings['restart_trigger'] = str(uuid.uuid4()) |
1026 | + |
1027 | + rel_settings.update(keystone_compute_settings()) |
1028 | relation_set(relation_id=rid, **rel_settings) |
1029 | |
1030 | |
1031 | @@ -287,15 +304,6 @@ |
1032 | if not eligible_leader(CLUSTER_RES): |
1033 | return |
1034 | |
1035 | - if network_manager() == 'quantum': |
1036 | - pkg = 'quantum-server' |
1037 | - else: |
1038 | - pkg = 'neutron-server' |
1039 | - |
1040 | - required_pkg = filter_installed_packages([pkg]) |
1041 | - if required_pkg: |
1042 | - apt_install(required_pkg) |
1043 | - |
1044 | url = canonical_url(CONFIGS) + ':9696' |
1045 | # XXX: Can we rename to neutron_*? |
1046 | rel_settings = { |
1047 | @@ -397,6 +405,28 @@ |
1048 | identity_joined(rid=rid) |
1049 | |
1050 | |
1051 | +@hooks.hook() |
1052 | +def nova_vmware_relation_joined(rid=None): |
1053 | + rel_settings = {'network_manager': network_manager()} |
1054 | + |
1055 | + ks_auth = _auth_config() |
1056 | + if ks_auth: |
1057 | + rel_settings.update(ks_auth) |
1058 | + rel_settings.update({ |
1059 | + 'quantum_plugin': neutron_plugin(), |
1060 | + 'quantum_security_groups': config('quantum-security-groups'), |
1061 | + 'quantum_url': (canonical_url(CONFIGS) + ':' + |
1062 | + str(api_port('neutron-server')))}) |
1063 | + |
1064 | + relation_set(relation_id=rid, **rel_settings) |
1065 | + |
1066 | + |
1067 | +@hooks.hook('nova-vmware-relation-changed') |
1068 | +@restart_on_change(restart_map()) |
1069 | +def nova_vmware_relation_changed(): |
1070 | + CONFIGS.write('/etc/nova/nova.conf') |
1071 | + |
1072 | + |
1073 | @hooks.hook('upgrade-charm') |
1074 | def upgrade_charm(): |
1075 | for r_id in relation_ids('amqp'): |
1076 | |
1077 | === modified file 'hooks/nova_cc_utils.py' |
1078 | --- hooks/nova_cc_utils.py 2013-09-25 16:24:36 +0000 |
1079 | +++ hooks/nova_cc_utils.py 2013-11-08 05:43:26 +0000 |
1080 | @@ -76,6 +76,8 @@ |
1081 | HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' |
1082 | APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' |
1083 | APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf' |
1084 | +NEUTRON_DEFAULT = '/etc/default/neutron-server' |
1085 | +QUANTUM_DEFAULT = '/etc/default/quantum-server' |
1086 | |
1087 | BASE_RESOURCE_MAP = OrderedDict([ |
1088 | (NOVA_CONF, { |
1089 | @@ -84,6 +86,11 @@ |
1090 | context.SharedDBContext(relation_prefix='nova'), |
1091 | context.ImageServiceContext(), |
1092 | context.OSConfigFlagContext(), |
1093 | + context.SubordinateConfigContext( |
1094 | + interface='nova-vmware', |
1095 | + service='nova', |
1096 | + config_file=NOVA_CONF, |
1097 | + ), |
1098 | nova_cc_context.HAProxyContext(), |
1099 | nova_cc_context.IdentityServiceContext(), |
1100 | nova_cc_context.VolumeServiceContext(), |
1101 | @@ -100,6 +107,10 @@ |
1102 | nova_cc_context.IdentityServiceContext(), |
1103 | nova_cc_context.NeutronCCContext()], |
1104 | }), |
1105 | + (QUANTUM_DEFAULT, { |
1106 | + 'services': ['quantum-server'], |
1107 | + 'contexts': [nova_cc_context.NeutronCCContext()], |
1108 | + }), |
1109 | (QUANTUM_API_PASTE, { |
1110 | 'services': ['quantum-server'], |
1111 | 'contexts': [nova_cc_context.IdentityServiceContext()], |
1112 | @@ -111,6 +122,10 @@ |
1113 | nova_cc_context.NeutronCCContext(), |
1114 | nova_cc_context.HAProxyContext()], |
1115 | }), |
1116 | + (NEUTRON_DEFAULT, { |
1117 | + 'services': ['neutron-server'], |
1118 | + 'contexts': [nova_cc_context.NeutronCCContext()], |
1119 | + }), |
1120 | (HAPROXY_CONF, { |
1121 | 'contexts': [context.HAProxyContext(), |
1122 | nova_cc_context.HAProxyContext()], |
1123 | @@ -166,11 +181,12 @@ |
1124 | plugin = neutron_plugin() |
1125 | if plugin: |
1126 | conf = neutron_plugin_attribute(plugin, 'config', net_manager) |
1127 | - service = '%s-server' % net_manager |
1128 | ctxts = (neutron_plugin_attribute(plugin, 'contexts', net_manager) |
1129 | or []) |
1130 | + services = neutron_plugin_attribute(plugin, 'server_services', |
1131 | + net_manager) |
1132 | resource_map[conf] = {} |
1133 | - resource_map[conf]['services'] = [service] |
1134 | + resource_map[conf]['services'] = services |
1135 | resource_map[conf]['contexts'] = ctxts |
1136 | resource_map[conf]['contexts'].append( |
1137 | nova_cc_context.NeutronCCContext()) |
1138 | @@ -178,6 +194,16 @@ |
1139 | # nova-conductor for releases >= G. |
1140 | if os_release('nova-common') not in ['essex', 'folsom']: |
1141 | resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor'] |
1142 | + |
1143 | + # also manage any configs that are being updated by subordinates. |
1144 | + vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware', |
1145 | + service='nova', |
1146 | + config_file=NOVA_CONF) |
1147 | + vmware_ctxt = vmware_ctxt() |
1148 | + if vmware_ctxt and 'services' in vmware_ctxt: |
1149 | + for s in vmware_ctxt['services']: |
1150 | + if s not in resource_map[NOVA_CONF]['services']: |
1151 | + resource_map[NOVA_CONF]['services'].append(s) |
1152 | return resource_map |
1153 | |
1154 | |
1155 | @@ -217,6 +243,10 @@ |
1156 | packages = [] + BASE_PACKAGES |
1157 | for k, v in resource_map().iteritems(): |
1158 | packages.extend(v['services']) |
1159 | + if network_manager() in ['neutron', 'quantum']: |
1160 | + pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages', |
1161 | + network_manager()) |
1162 | + packages.extend(pkgs) |
1163 | return list(set(packages)) |
1164 | |
1165 | |
1166 | |
1167 | === modified file 'metadata.yaml' |
1168 | --- metadata.yaml 2013-10-15 13:04:00 +0000 |
1169 | +++ metadata.yaml 2013-11-08 05:43:26 +0000 |
1170 | @@ -29,6 +29,9 @@ |
1171 | ha: |
1172 | interface: hacluster |
1173 | scope: container |
1174 | + nova-vmware: |
1175 | + interface: nova-vmware |
1176 | + scope: container |
1177 | peers: |
1178 | cluster: |
1179 | interface: nova-ha |
1180 | |
1181 | === modified file 'revision' |
1182 | --- revision 2013-11-06 04:34:51 +0000 |
1183 | +++ revision 2013-11-08 05:43:26 +0000 |
1184 | @@ -1,1 +1,1 @@ |
1185 | -307 |
1186 | +311 |
1187 | |
1188 | === modified file 'templates/folsom/nova.conf' |
1189 | --- templates/folsom/nova.conf 2013-09-30 20:46:50 +0000 |
1190 | +++ templates/folsom/nova.conf 2013-11-08 05:43:26 +0000 |
1191 | @@ -57,6 +57,14 @@ |
1192 | {% endif -%} |
1193 | {% endif -%} |
1194 | |
1195 | +{% if neutron_plugin and neutron_plugin == 'nvp' -%} |
1196 | +security_group_api = neutron |
1197 | +nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver |
1198 | +{% if external_network -%} |
1199 | +default_floating_pool = {{ external_network }} |
1200 | +{% endif -%} |
1201 | +{% endif -%} |
1202 | + |
1203 | {% if network_manager_config -%} |
1204 | {% for key, value in network_manager_config.iteritems() -%} |
1205 | {{ key }} = {{ value }} |
1206 | @@ -90,3 +98,9 @@ |
1207 | {{ key }} = {{ value }} |
1208 | {% endfor -%} |
1209 | {% endif -%} |
1210 | + |
1211 | +{% if sections and 'DEFAULT' in sections -%} |
1212 | +{% for key, value in sections['DEFAULT'] -%} |
1213 | +{{ key }} = {{ value }} |
1214 | +{% endfor -%} |
1215 | +{% endif -%} |
1216 | |
1217 | === added file 'templates/folsom/quantum-server' |
1218 | --- templates/folsom/quantum-server 1970-01-01 00:00:00 +0000 |
1219 | +++ templates/folsom/quantum-server 2013-11-08 05:43:26 +0000 |
1220 | @@ -0,0 +1,6 @@ |
1221 | +# quantum |
1222 | +############################################################################### |
1223 | +# [ WARNING ] |
1224 | +# Configuration file maintained by Juju. Local changes may be overwritten. |
1225 | +############################################################################### |
1226 | +QUANTUM_PLUGIN_CONFIG="{{ config }}" |
1227 | \ No newline at end of file |
1228 | |
1229 | === added file 'templates/havana/neutron-server' |
1230 | --- templates/havana/neutron-server 1970-01-01 00:00:00 +0000 |
1231 | +++ templates/havana/neutron-server 2013-11-08 05:43:26 +0000 |
1232 | @@ -0,0 +1,6 @@ |
1233 | +# havana |
1234 | +############################################################################### |
1235 | +# [ WARNING ] |
1236 | +# Configuration file maintained by Juju. Local changes may be overwritten. |
1237 | +############################################################################### |
1238 | +NEUTRON_PLUGIN_CONFIG="{{ config }}" |
1239 | \ No newline at end of file |
1240 | |
1241 | === added file 'templates/havana/nvp.ini' |
1242 | --- templates/havana/nvp.ini 1970-01-01 00:00:00 +0000 |
1243 | +++ templates/havana/nvp.ini 2013-11-08 05:43:26 +0000 |
1244 | @@ -0,0 +1,11 @@ |
1245 | +# havana |
1246 | +############################################################################### |
1247 | +# [ WARNING ] |
1248 | +# Configuration file maintained by Juju. Local changes may be overwritten. |
1249 | +############################################################################### |
1250 | +[DEFAULT] |
1251 | +nvp_user = {{ nvp_username }} |
1252 | +nvp_password = {{ nvp_password }} |
1253 | +nvp_controllers = {{ nvp_controllers }} |
1254 | +default_tz_uuid = {{ nvp_tz_uuid }} |
1255 | +default_l3_gw_service_uuid = {{ nvp_l3_uuid }} |
1256 | |
1257 | === modified file 'unit_tests/test_nova_cc_hooks.py' |
1258 | --- unit_tests/test_nova_cc_hooks.py 2013-09-27 16:18:25 +0000 |
1259 | +++ unit_tests/test_nova_cc_hooks.py 2013-11-08 05:43:26 +0000 |
1260 | @@ -17,8 +17,10 @@ |
1261 | |
1262 | |
1263 | TO_PATCH = [ |
1264 | + 'api_port', |
1265 | 'apt_update', |
1266 | 'apt_install', |
1267 | + 'canonical_url', |
1268 | 'configure_installation_source', |
1269 | 'charm_dir', |
1270 | 'do_openstack_upgrade', |
1271 | @@ -33,11 +35,32 @@ |
1272 | 'ssh_known_hosts_b64', |
1273 | 'ssh_authorized_keys_b64', |
1274 | 'save_script_rc', |
1275 | - 'execd_preinstall' |
1276 | + 'execd_preinstall', |
1277 | + 'network_manager', |
1278 | + 'volume_service', |
1279 | + 'unit_get', |
1280 | + 'eligible_leader', |
1281 | + 'keystone_ca_cert_b64', |
1282 | + 'neutron_plugin', |
1283 | ] |
1284 | |
1285 | |
1286 | +FAKE_KS_AUTH_CFG = { |
1287 | + 'auth_host': 'kshost', |
1288 | + 'auth_port': '5000', |
1289 | + 'service_port': 'token', |
1290 | + 'service_username': 'admin_user', |
1291 | + 'service_password': 'admin_passwd', |
1292 | + 'service_tenant_name': 'admin_tenant', |
1293 | + 'auth_uri': 'http://kshost:5000/v2', |
1294 | + # quantum-gateway interface deviates a bit. |
1295 | + 'keystone_host': 'kshost', |
1296 | + 'service_tenant': 'service_tenant', |
1297 | +} |
1298 | + |
1299 | + |
1300 | class NovaCCHooksTests(CharmTestCase): |
1301 | + |
1302 | def setUp(self): |
1303 | super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH) |
1304 | self.config.side_effect = self.test_config.get |
1305 | @@ -76,3 +99,42 @@ |
1306 | self.ssh_compute_add.assert_called_with('fookey') |
1307 | self.relation_set.assert_called_with(known_hosts='hosts', |
1308 | authorized_keys='keys') |
1309 | + |
1310 | + @patch.object(hooks, '_auth_config') |
1311 | + def test_compute_joined_neutron(self, auth_config): |
1312 | + self.network_manager.return_value = 'neutron' |
1313 | + self.eligible_leader = True |
1314 | + self.keystone_ca_cert_b64.return_value = 'foocert64' |
1315 | + self.volume_service.return_value = 'cinder' |
1316 | + self.unit_get.return_value = 'nova-cc-host1' |
1317 | + self.canonical_url.return_value = 'http://nova-cc-host1' |
1318 | + self.api_port.return_value = '9696' |
1319 | + self.neutron_plugin.return_value = 'nvp' |
1320 | + auth_config.return_value = FAKE_KS_AUTH_CFG |
1321 | + hooks.compute_joined() |
1322 | + |
1323 | + self.relation_set.assert_called_with( |
1324 | + relation_id=None, |
1325 | + quantum_url='http://nova-cc-host1:9696', |
1326 | + ca_cert='foocert64', |
1327 | + quantum_security_groups='no', |
1328 | + region='RegionOne', |
1329 | + volume_service='cinder', |
1330 | + ec2_host='nova-cc-host1', |
1331 | + quantum_plugin='nvp', |
1332 | + network_manager='neutron', **FAKE_KS_AUTH_CFG) |
1333 | + |
1334 | + @patch.object(hooks, '_auth_config') |
1335 | + def test_nova_vmware_joined(self, auth_config): |
1336 | + auth_config.return_value = FAKE_KS_AUTH_CFG |
1337 | + # quantum-security-groups, plugin |
1338 | + self.neutron_plugin.return_value = 'nvp' |
1339 | + self.network_manager.return_value = 'neutron' |
1340 | + self.canonical_url.return_value = 'http://nova-cc-host1' |
1341 | + self.api_port.return_value = '9696' |
1342 | + hooks.nova_vmware_relation_joined() |
1343 | + self.relation_set.assert_called_with( |
1344 | + network_manager='neutron', quantum_security_groups='no', |
1345 | + quantum_url='http://nova-cc-host1:9696', quantum_plugin='nvp', |
1346 | + relation_id=None, |
1347 | + **FAKE_KS_AUTH_CFG) |
1348 | |
1349 | === modified file 'unit_tests/test_nova_cc_utils.py' |
1350 | --- unit_tests/test_nova_cc_utils.py 2013-10-15 13:01:37 +0000 |
1351 | +++ unit_tests/test_nova_cc_utils.py 2013-11-08 05:43:26 +0000 |
1352 | @@ -70,10 +70,11 @@ |
1353 | 'nova-api-ec2', 'nova-api-os-compute' |
1354 | ]), |
1355 | ('/etc/neutron/neutron.conf', ['neutron-server']), |
1356 | + ('/etc/default/neutron-server', ['neutron-server']), |
1357 | ('/etc/haproxy/haproxy.cfg', ['haproxy']), |
1358 | ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']), |
1359 | ('/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini', |
1360 | - ['neutron-server']) |
1361 | + ['quantum-server']) |
1362 | ]) |
1363 | |
1364 | |
1365 | @@ -87,13 +88,17 @@ |
1366 | 'services': ['quantum-plugin-openvswitch-agent'], |
1367 | 'packages': ['quantum-plugin-openvswitch-agent', |
1368 | 'openvswitch-datapath-dkms'], |
1369 | + 'server_packages': ['quantum-server', 'quantum-plugin-openvswitch'], |
1370 | + 'server_services': ['quantum-server'], |
1371 | }, |
1372 | 'nvp': { |
1373 | 'config': '/etc/quantum/plugins/nicira/nvp.ini', |
1374 | 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.' |
1375 | 'QuantumPlugin.NvpPluginV2', |
1376 | 'services': [], |
1377 | - 'packages': ['quantum-plugin-nicira'], |
1378 | + 'packages': [], |
1379 | + 'server_packages': ['quantum-server', 'quantum-plugin-nicria'], |
1380 | + 'server_services': ['quantum-server'], |
1381 | } |
1382 | } |
1383 | |
1384 | @@ -107,6 +112,7 @@ |
1385 | |
1386 | |
1387 | class NovaCCUtilsTests(CharmTestCase): |
1388 | + |
1389 | def setUp(self): |
1390 | super(NovaCCUtilsTests, self).setUp(utils, TO_PATCH) |
1391 | self.config.side_effect = self.test_config.get |
1392 | @@ -119,9 +125,13 @@ |
1393 | self.neutron_plugin_attribute.side_effect = fake_plugin_attribute |
1394 | if volume_manager == 'nova-volume': |
1395 | self.relation_ids.return_value = 'nova-volume-service:0' |
1396 | - return utils.resource_map() |
1397 | + with patch('charmhelpers.contrib.openstack.context.' |
1398 | + 'SubordinateConfigContext'): |
1399 | + _map = utils.resource_map() |
1400 | + return _map |
1401 | |
1402 | - def test_resource_map_quantum(self): |
1403 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1404 | + def test_resource_map_quantum(self, subcontext): |
1405 | self._resource_map(network_manager='quantum') |
1406 | _map = utils.resource_map() |
1407 | confs = [ |
1408 | @@ -131,7 +141,8 @@ |
1409 | ] |
1410 | [self.assertIn(q_conf, _map.keys()) for q_conf in confs] |
1411 | |
1412 | - def test_resource_map_neutron(self): |
1413 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1414 | + def test_resource_map_neutron(self, subcontext): |
1415 | self._resource_map(network_manager='neutron') |
1416 | _map = utils.resource_map() |
1417 | confs = [ |
1418 | @@ -139,7 +150,21 @@ |
1419 | ] |
1420 | [self.assertIn(q_conf, _map.keys()) for q_conf in confs] |
1421 | |
1422 | - def test_resource_map_neutron_no_agent_installed(self): |
1423 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1424 | + def test_resource_map_vmware(self, subcontext): |
1425 | + fake_context = MagicMock() |
1426 | + fake_context.return_value = { |
1427 | + 'sections': [], |
1428 | + 'services': ['nova-compute', 'nova-network'], |
1429 | + |
1430 | + } |
1431 | + subcontext.return_value = fake_context |
1432 | + _map = utils.resource_map() |
1433 | + for s in ['nova-compute', 'nova-network']: |
1434 | + self.assertIn(s, _map['/etc/nova/nova.conf']['services']) |
1435 | + |
1436 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1437 | + def test_resource_map_neutron_no_agent_installed(self, subcontext): |
1438 | self._resource_map(network_manager='neutron') |
1439 | _map = utils.resource_map() |
1440 | services = [] |
1441 | @@ -147,22 +172,25 @@ |
1442 | for svc in services: |
1443 | self.assertNotIn('agent', svc) |
1444 | |
1445 | - def test_resource_map_nova_volume(self): |
1446 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1447 | + def test_resource_map_nova_volume(self, subcontext): |
1448 | self.relation_ids.return_value = ['nova-volume-service:0'] |
1449 | _map = utils.resource_map() |
1450 | self.assertIn('nova-api-os-volume', |
1451 | _map['/etc/nova/nova.conf']['services']) |
1452 | |
1453 | @patch('os.path.exists') |
1454 | - def test_restart_map_api_before_frontends(self, _exists): |
1455 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1456 | + def test_restart_map_api_before_frontends(self, subcontext, _exists): |
1457 | _exists.return_value = False |
1458 | self._resource_map(network_manager='neutron') |
1459 | _map = utils.restart_map() |
1460 | self.assertTrue(isinstance(_map, OrderedDict)) |
1461 | self.assertEquals(_map, RESTART_MAP) |
1462 | |
1463 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1464 | @patch('os.path.exists') |
1465 | - def test_restart_map_apache24(self, _exists): |
1466 | + def test_restart_map_apache24(self, _exists, subcontext): |
1467 | _exists.return_Value = True |
1468 | self._resource_map(network_manager='neutron') |
1469 | _map = utils.restart_map() |
1470 | @@ -171,29 +199,34 @@ |
1471 | self.assertTrue('/etc/apache2/sites-available/' |
1472 | 'openstack_https_frontend' not in _map) |
1473 | |
1474 | - def test_determine_packages_quantum(self): |
1475 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1476 | + def test_determine_packages_quantum(self, subcontext): |
1477 | self._resource_map(network_manager='quantum') |
1478 | pkgs = utils.determine_packages() |
1479 | self.assertIn('quantum-server', pkgs) |
1480 | |
1481 | - def test_determine_packages_neutron(self): |
1482 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1483 | + def test_determine_packages_neutron(self, subcontext): |
1484 | self._resource_map(network_manager='neutron') |
1485 | pkgs = utils.determine_packages() |
1486 | self.assertIn('neutron-server', pkgs) |
1487 | |
1488 | - def test_determine_packages_nova_volume(self): |
1489 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1490 | + def test_determine_packages_nova_volume(self, subcontext): |
1491 | self.relation_ids.return_value = ['nova-volume-service:0'] |
1492 | pkgs = utils.determine_packages() |
1493 | self.assertIn('nova-api-os-volume', pkgs) |
1494 | |
1495 | - def test_determine_packages_base(self): |
1496 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1497 | + def test_determine_packages_base(self, subcontext): |
1498 | self.relation_ids.return_value = [] |
1499 | self.os_release.return_value = 'folsom' |
1500 | pkgs = utils.determine_packages() |
1501 | ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES)) |
1502 | self.assertEquals(ex, pkgs) |
1503 | |
1504 | - def test_determine_packages_base_grizzly_beyond(self): |
1505 | + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') |
1506 | + def test_determine_packages_base_grizzly_beyond(self, subcontext): |
1507 | self.relation_ids.return_value = [] |
1508 | self.os_release.return_value = 'grizzly' |
1509 | pkgs = utils.determine_packages() |
1510 | |
1511 | === modified file 'unit_tests/test_utils.py' |
1512 | --- unit_tests/test_utils.py 2013-08-02 03:42:16 +0000 |
1513 | +++ unit_tests/test_utils.py 2013-11-08 05:43:26 +0000 |
1514 | @@ -45,6 +45,7 @@ |
1515 | |
1516 | |
1517 | class CharmTestCase(unittest.TestCase): |
1518 | + |
1519 | def setUp(self, obj, patches): |
1520 | super(CharmTestCase, self).setUp() |
1521 | self.patches = patches |
1522 | @@ -65,6 +66,7 @@ |
1523 | |
1524 | |
1525 | class TestConfig(object): |
1526 | + |
1527 | def __init__(self): |
1528 | self.config = get_default_config() |
1529 | |
1530 | @@ -86,6 +88,7 @@ |
1531 | |
1532 | |
1533 | class TestRelation(object): |
1534 | + |
1535 | def __init__(self, relation_data={}): |
1536 | self.relation_data = relation_data |
1537 |