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
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

Subscribers

People subscribed via source and target branches