Merge lp:~openstack-charmers/charms/precise/glance/ceilometer-support into lp:~charmers/charms/precise/glance/trunk

Proposed by James Page
Status: Merged
Merged at revision: 41
Proposed branch: lp:~openstack-charmers/charms/precise/glance/ceilometer-support
Merge into: lp:~charmers/charms/precise/glance/trunk
Diff against target: 1065 lines (+403/-99)
15 files modified
config.yaml (+8/-10)
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/glance_contexts.py (+1/-10)
hooks/glance_relations.py (+15/-1)
hooks/glance_utils.py (+1/-0)
metadata.yaml (+2/-0)
templates/folsom/glance-api.conf (+9/-25)
unit_tests/test_glance_relations.py (+72/-1)
To merge this branch: bzr merge lp:~openstack-charmers/charms/precise/glance/ceilometer-support
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+194040@code.launchpad.net

Description of the change

Support for use with ceilometer

To post a comment you must log in.
43. By Adam Gandelman

Sync helpers, use is_relation_made() from helpers.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2013-09-26 09:49:19 +0000
3+++ config.yaml 2013-11-06 04:26:29 +0000
4@@ -70,14 +70,12 @@
5 ssl_key:
6 type: string
7 description: SSL key to use with certificate specified as ssl_cert.
8- ceph-osd-replication-count:
9- default: 2
10- type: int
11- description: |
12- This value dictates the number of replicas ceph must make of any
13- object it stores within the images rbd pool. Of course, this only
14- applies if using Ceph as a backend store. Note that once the images
15- rbd pool has been created, changing this value will not have any
16- effect (although it can be changed in ceph by manually configuring
17- your ceph cluster).
18+ rabbit-user:
19+ default: glance
20+ type: string
21+ description: Username to request access on rabbitmq-server.
22+ rabbit-vhost:
23+ default: openstack
24+ type: string
25+ description: RabbitMQ virtual host to request access on rabbitmq-server.
26
27
28=== added symlink 'hooks/amqp-relation-changed'
29=== target is u'glance_relations.py'
30=== added symlink 'hooks/amqp-relation-joined'
31=== target is u'glance_relations.py'
32=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
33--- hooks/charmhelpers/contrib/openstack/context.py 2013-10-15 01:32:42 +0000
34+++ hooks/charmhelpers/contrib/openstack/context.py 2013-11-06 04:26:29 +0000
35@@ -385,16 +385,33 @@
36 def ovs_ctxt(self):
37 driver = neutron_plugin_attribute(self.plugin, 'driver',
38 self.network_manager)
39-
40+ config = neutron_plugin_attribute(self.plugin, 'config',
41+ self.network_manager)
42 ovs_ctxt = {
43 'core_plugin': driver,
44 'neutron_plugin': 'ovs',
45 'neutron_security_groups': self.neutron_security_groups,
46 'local_ip': unit_private_ip(),
47+ 'config': config
48 }
49
50 return ovs_ctxt
51
52+ def nvp_ctxt(self):
53+ driver = neutron_plugin_attribute(self.plugin, 'driver',
54+ self.network_manager)
55+ config = neutron_plugin_attribute(self.plugin, 'config',
56+ self.network_manager)
57+ nvp_ctxt = {
58+ 'core_plugin': driver,
59+ 'neutron_plugin': 'nvp',
60+ 'neutron_security_groups': self.neutron_security_groups,
61+ 'local_ip': unit_private_ip(),
62+ 'config': config
63+ }
64+
65+ return nvp_ctxt
66+
67 def __call__(self):
68 self._ensure_packages()
69
70@@ -408,6 +425,8 @@
71
72 if self.plugin == 'ovs':
73 ctxt.update(self.ovs_ctxt())
74+ elif self.plugin == 'nvp':
75+ ctxt.update(self.nvp_ctxt())
76
77 self._save_flag_file()
78 return ctxt
79
80=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
81--- hooks/charmhelpers/contrib/openstack/neutron.py 2013-10-15 01:32:42 +0000
82+++ hooks/charmhelpers/contrib/openstack/neutron.py 2013-11-06 04:26:29 +0000
83@@ -34,13 +34,23 @@
84 'services': ['quantum-plugin-openvswitch-agent'],
85 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
86 ['quantum-plugin-openvswitch-agent']],
87+ 'server_packages': ['quantum-server',
88+ 'quantum-plugin-openvswitch'],
89+ 'server_services': ['quantum-server']
90 },
91 'nvp': {
92 'config': '/etc/quantum/plugins/nicira/nvp.ini',
93 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
94 'QuantumPlugin.NvpPluginV2',
95+ 'contexts': [
96+ context.SharedDBContext(user=config('neutron-database-user'),
97+ database=config('neutron-database'),
98+ relation_prefix='neutron')],
99 'services': [],
100 'packages': [],
101+ 'server_packages': ['quantum-server',
102+ 'quantum-plugin-nicira'],
103+ 'server_services': ['quantum-server']
104 }
105 }
106
107@@ -60,13 +70,23 @@
108 'services': ['neutron-plugin-openvswitch-agent'],
109 'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
110 ['quantum-plugin-openvswitch-agent']],
111+ 'server_packages': ['neutron-server',
112+ 'neutron-plugin-openvswitch'],
113+ 'server_services': ['neutron-server']
114 },
115 'nvp': {
116 'config': '/etc/neutron/plugins/nicira/nvp.ini',
117 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'
118 'NeutronPlugin.NvpPluginV2',
119+ 'contexts': [
120+ context.SharedDBContext(user=config('neutron-database-user'),
121+ database=config('neutron-database'),
122+ relation_prefix='neutron')],
123 'services': [],
124 'packages': [],
125+ 'server_packages': ['neutron-server',
126+ 'neutron-plugin-nicira'],
127+ 'server_services': ['neutron-server']
128 }
129 }
130
131
132=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
133--- hooks/charmhelpers/contrib/openstack/utils.py 2013-10-15 01:32:42 +0000
134+++ hooks/charmhelpers/contrib/openstack/utils.py 2013-11-06 04:26:29 +0000
135@@ -13,19 +13,28 @@
136 config,
137 log as juju_log,
138 charm_dir,
139-)
140-
141-from charmhelpers.core.host import (
142- lsb_release,
143-)
144-
145-from charmhelpers.fetch import (
146- apt_install,
147-)
148+ ERROR,
149+ INFO
150+)
151+
152+from charmhelpers.contrib.storage.linux.lvm import (
153+ deactivate_lvm_volume_group,
154+ is_lvm_physical_volume,
155+ remove_lvm_physical_volume,
156+)
157+
158+from charmhelpers.core.host import lsb_release, mounts, umount
159+from charmhelpers.fetch import apt_install
160+from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
161+from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
162
163 CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
164 CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
165
166+DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
167+ 'restricted main multiverse universe')
168+
169+
170 UBUNTU_OPENSTACK_RELEASE = OrderedDict([
171 ('oneiric', 'diablo'),
172 ('precise', 'essex'),
173@@ -57,6 +66,8 @@
174 ('1.9.0', 'havana'),
175 ])
176
177+DEFAULT_LOOPBACK_SIZE = '5G'
178+
179
180 def error_out(msg):
181 juju_log("FATAL ERROR: %s" % msg, level='ERROR')
182@@ -67,7 +78,7 @@
183 '''Derive OpenStack release codename from a given installation source.'''
184 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
185 rel = ''
186- if src == 'distro':
187+ if src in ['distro', 'distro-proposed']:
188 try:
189 rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
190 except KeyError:
191@@ -202,6 +213,10 @@
192 '''Configure apt installation source.'''
193 if rel == 'distro':
194 return
195+ elif rel == 'distro-proposed':
196+ ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
197+ with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
198+ f.write(DISTRO_PROPOSED % ubuntu_rel)
199 elif rel[:4] == "ppa:":
200 src = rel
201 subprocess.check_call(["add-apt-repository", "-y", src])
202@@ -299,6 +314,62 @@
203 return apt.version_compare(available_vers, cur_vers) == 1
204
205
206+def ensure_block_device(block_device):
207+ '''
208+ Confirm block_device, create as loopback if necessary.
209+
210+ :param block_device: str: Full path of block device to ensure.
211+
212+ :returns: str: Full path of ensured block device.
213+ '''
214+ _none = ['None', 'none', None]
215+ if (block_device in _none):
216+ error_out('prepare_storage(): Missing required input: '
217+ 'block_device=%s.' % block_device, level=ERROR)
218+
219+ if block_device.startswith('/dev/'):
220+ bdev = block_device
221+ elif block_device.startswith('/'):
222+ _bd = block_device.split('|')
223+ if len(_bd) == 2:
224+ bdev, size = _bd
225+ else:
226+ bdev = block_device
227+ size = DEFAULT_LOOPBACK_SIZE
228+ bdev = ensure_loopback_device(bdev, size)
229+ else:
230+ bdev = '/dev/%s' % block_device
231+
232+ if not is_block_device(bdev):
233+ error_out('Failed to locate valid block device at %s' % bdev,
234+ level=ERROR)
235+
236+ return bdev
237+
238+
239+def clean_storage(block_device):
240+ '''
241+ Ensures a block device is clean. That is:
242+ - unmounted
243+ - any lvm volume groups are deactivated
244+ - any lvm physical device signatures removed
245+ - partition table wiped
246+
247+ :param block_device: str: Full path to block device to clean.
248+ '''
249+ for mp, d in mounts():
250+ if d == block_device:
251+ juju_log('clean_storage(): %s is mounted @ %s, unmounting.' %
252+ (d, mp), level=INFO)
253+ umount(mp, persist=True)
254+
255+ if is_lvm_physical_volume(block_device):
256+ deactivate_lvm_volume_group(block_device)
257+ remove_lvm_physical_volume(block_device)
258+ else:
259+ zap_disk(block_device)
260+
261+
262 def is_ip(address):
263 """
264 Returns True if address is a valid IP address.
265
266=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
267--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-09-24 14:54:12 +0000
268+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-06 04:26:29 +0000
269@@ -102,8 +102,12 @@
270 Return a list of all Ceph Object Storage Daemons
271 currently in the cluster
272 '''
273- return json.loads(check_output(['ceph', '--id', service,
274- 'osd', 'ls', '--format=json']))
275+ version = ceph_version()
276+ if version and version >= '0.56':
277+ return json.loads(check_output(['ceph', '--id', service,
278+ 'osd', 'ls', '--format=json']))
279+ else:
280+ return None
281
282
283 def create_pool(service, name, replicas=2):
284@@ -114,7 +118,13 @@
285 return
286 # Calculate the number of placement groups based
287 # on upstream recommended best practices.
288- pgnum = (len(get_osds(service)) * 100 / replicas)
289+ osds = get_osds(service)
290+ if osds:
291+ pgnum = (len(osds) * 100 / replicas)
292+ else:
293+ # NOTE(james-page): Default to 200 for older ceph versions
294+ # which don't support OSD query from cli
295+ pgnum = 200
296 cmd = [
297 'ceph', '--id', service,
298 'osd', 'pool', 'create',
299@@ -357,3 +367,17 @@
300 if user and group:
301 check_call(['chown', '%s.%s' % (user, group), keyring])
302 return True
303+
304+
305+def ceph_version():
306+ ''' Retrieve the local version of ceph '''
307+ if os.path.exists('/usr/bin/ceph'):
308+ cmd = ['ceph', '-v']
309+ output = check_output(cmd)
310+ output = output.split()
311+ if len(output) > 3:
312+ return output[2]
313+ else:
314+ return None
315+ else:
316+ return None
317
318=== modified file 'hooks/charmhelpers/core/hookenv.py'
319--- hooks/charmhelpers/core/hookenv.py 2013-08-14 22:31:44 +0000
320+++ hooks/charmhelpers/core/hookenv.py 2013-11-06 04:26:29 +0000
321@@ -9,6 +9,7 @@
322 import yaml
323 import subprocess
324 import UserDict
325+from subprocess import CalledProcessError
326
327 CRITICAL = "CRITICAL"
328 ERROR = "ERROR"
329@@ -21,7 +22,7 @@
330
331
332 def cached(func):
333- ''' Cache return values for multiple executions of func + args
334+ """Cache return values for multiple executions of func + args
335
336 For example:
337
338@@ -32,7 +33,7 @@
339 unit_get('test')
340
341 will cache the result of unit_get + 'test' for future calls.
342- '''
343+ """
344 def wrapper(*args, **kwargs):
345 global cache
346 key = str((func, args, kwargs))
347@@ -46,8 +47,8 @@
348
349
350 def flush(key):
351- ''' Flushes any entries from function cache where the
352- key is found in the function+args '''
353+ """Flushes any entries from function cache where the
354+ key is found in the function+args """
355 flush_list = []
356 for item in cache:
357 if key in item:
358@@ -57,7 +58,7 @@
359
360
361 def log(message, level=None):
362- "Write a message to the juju log"
363+ """Write a message to the juju log"""
364 command = ['juju-log']
365 if level:
366 command += ['-l', level]
367@@ -66,7 +67,7 @@
368
369
370 class Serializable(UserDict.IterableUserDict):
371- "Wrapper, an object that can be serialized to yaml or json"
372+ """Wrapper, an object that can be serialized to yaml or json"""
373
374 def __init__(self, obj):
375 # wrap the object
376@@ -96,11 +97,11 @@
377 self.data = state
378
379 def json(self):
380- "Serialize the object to json"
381+ """Serialize the object to json"""
382 return json.dumps(self.data)
383
384 def yaml(self):
385- "Serialize the object to yaml"
386+ """Serialize the object to yaml"""
387 return yaml.dump(self.data)
388
389
390@@ -119,38 +120,38 @@
391
392
393 def in_relation_hook():
394- "Determine whether we're running in a relation hook"
395+ """Determine whether we're running in a relation hook"""
396 return 'JUJU_RELATION' in os.environ
397
398
399 def relation_type():
400- "The scope for the current relation hook"
401+ """The scope for the current relation hook"""
402 return os.environ.get('JUJU_RELATION', None)
403
404
405 def relation_id():
406- "The relation ID for the current relation hook"
407+ """The relation ID for the current relation hook"""
408 return os.environ.get('JUJU_RELATION_ID', None)
409
410
411 def local_unit():
412- "Local unit ID"
413+ """Local unit ID"""
414 return os.environ['JUJU_UNIT_NAME']
415
416
417 def remote_unit():
418- "The remote unit for the current relation hook"
419+ """The remote unit for the current relation hook"""
420 return os.environ['JUJU_REMOTE_UNIT']
421
422
423 def service_name():
424- "The name service group this unit belongs to"
425+ """The name service group this unit belongs to"""
426 return local_unit().split('/')[0]
427
428
429 @cached
430 def config(scope=None):
431- "Juju charm configuration"
432+ """Juju charm configuration"""
433 config_cmd_line = ['config-get']
434 if scope is not None:
435 config_cmd_line.append(scope)
436@@ -163,6 +164,7 @@
437
438 @cached
439 def relation_get(attribute=None, unit=None, rid=None):
440+ """Get relation information"""
441 _args = ['relation-get', '--format=json']
442 if rid:
443 _args.append('-r')
444@@ -174,9 +176,14 @@
445 return json.loads(subprocess.check_output(_args))
446 except ValueError:
447 return None
448+ except CalledProcessError, e:
449+ if e.returncode == 2:
450+ return None
451+ raise
452
453
454 def relation_set(relation_id=None, relation_settings={}, **kwargs):
455+ """Set relation information for the current unit"""
456 relation_cmd_line = ['relation-set']
457 if relation_id is not None:
458 relation_cmd_line.extend(('-r', relation_id))
459@@ -192,7 +199,7 @@
460
461 @cached
462 def relation_ids(reltype=None):
463- "A list of relation_ids"
464+ """A list of relation_ids"""
465 reltype = reltype or relation_type()
466 relid_cmd_line = ['relation-ids', '--format=json']
467 if reltype is not None:
468@@ -203,7 +210,7 @@
469
470 @cached
471 def related_units(relid=None):
472- "A list of related units"
473+ """A list of related units"""
474 relid = relid or relation_id()
475 units_cmd_line = ['relation-list', '--format=json']
476 if relid is not None:
477@@ -213,7 +220,7 @@
478
479 @cached
480 def relation_for_unit(unit=None, rid=None):
481- "Get the json represenation of a unit's relation"
482+ """Get the json represenation of a unit's relation"""
483 unit = unit or remote_unit()
484 relation = relation_get(unit=unit, rid=rid)
485 for key in relation:
486@@ -225,7 +232,7 @@
487
488 @cached
489 def relations_for_id(relid=None):
490- "Get relations of a specific relation ID"
491+ """Get relations of a specific relation ID"""
492 relation_data = []
493 relid = relid or relation_ids()
494 for unit in related_units(relid):
495@@ -237,7 +244,7 @@
496
497 @cached
498 def relations_of_type(reltype=None):
499- "Get relations of a specific type"
500+ """Get relations of a specific type"""
501 relation_data = []
502 reltype = reltype or relation_type()
503 for relid in relation_ids(reltype):
504@@ -249,7 +256,7 @@
505
506 @cached
507 def relation_types():
508- "Get a list of relation types supported by this charm"
509+ """Get a list of relation types supported by this charm"""
510 charmdir = os.environ.get('CHARM_DIR', '')
511 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
512 md = yaml.safe_load(mdf)
513@@ -264,6 +271,7 @@
514
515 @cached
516 def relations():
517+ """Get a nested dictionary of relation data for all related units"""
518 rels = {}
519 for reltype in relation_types():
520 relids = {}
521@@ -277,15 +285,35 @@
522 return rels
523
524
525+@cached
526+def is_relation_made(relation, keys='private-address'):
527+ '''
528+ Determine whether a relation is established by checking for
529+ presence of key(s). If a list of keys is provided, they
530+ must all be present for the relation to be identified as made
531+ '''
532+ if isinstance(keys, str):
533+ keys = [keys]
534+ for r_id in relation_ids(relation):
535+ for unit in related_units(r_id):
536+ context = {}
537+ for k in keys:
538+ context[k] = relation_get(k, rid=r_id,
539+ unit=unit)
540+ if None not in context.values():
541+ return True
542+ return False
543+
544+
545 def open_port(port, protocol="TCP"):
546- "Open a service network port"
547+ """Open a service network port"""
548 _args = ['open-port']
549 _args.append('{}/{}'.format(port, protocol))
550 subprocess.check_call(_args)
551
552
553 def close_port(port, protocol="TCP"):
554- "Close a service network port"
555+ """Close a service network port"""
556 _args = ['close-port']
557 _args.append('{}/{}'.format(port, protocol))
558 subprocess.check_call(_args)
559@@ -293,6 +321,7 @@
560
561 @cached
562 def unit_get(attribute):
563+ """Get the unit ID for the remote unit"""
564 _args = ['unit-get', '--format=json', attribute]
565 try:
566 return json.loads(subprocess.check_output(_args))
567@@ -301,22 +330,46 @@
568
569
570 def unit_private_ip():
571+ """Get this unit's private IP address"""
572 return unit_get('private-address')
573
574
575 class UnregisteredHookError(Exception):
576+ """Raised when an undefined hook is called"""
577 pass
578
579
580 class Hooks(object):
581+ """A convenient handler for hook functions.
582+
583+ Example:
584+ hooks = Hooks()
585+
586+ # register a hook, taking its name from the function name
587+ @hooks.hook()
588+ def install():
589+ ...
590+
591+ # register a hook, providing a custom hook name
592+ @hooks.hook("config-changed")
593+ def config_changed():
594+ ...
595+
596+ if __name__ == "__main__":
597+ # execute a hook based on the name the program is called by
598+ hooks.execute(sys.argv)
599+ """
600+
601 def __init__(self):
602 super(Hooks, self).__init__()
603 self._hooks = {}
604
605 def register(self, name, function):
606+ """Register a hook"""
607 self._hooks[name] = function
608
609 def execute(self, args):
610+ """Execute a registered hook based on args[0]"""
611 hook_name = os.path.basename(args[0])
612 if hook_name in self._hooks:
613 self._hooks[hook_name]()
614@@ -324,6 +377,7 @@
615 raise UnregisteredHookError(hook_name)
616
617 def hook(self, *hook_names):
618+ """Decorator, registering them as hooks"""
619 def wrapper(decorated):
620 for hook_name in hook_names:
621 self.register(hook_name, decorated)
622@@ -337,4 +391,5 @@
623
624
625 def charm_dir():
626+ """Return the root directory of the current charm"""
627 return os.environ.get('CHARM_DIR')
628
629=== modified file 'hooks/charmhelpers/core/host.py'
630--- hooks/charmhelpers/core/host.py 2013-09-20 15:52:45 +0000
631+++ hooks/charmhelpers/core/host.py 2013-11-06 04:26:29 +0000
632@@ -19,18 +19,22 @@
633
634
635 def service_start(service_name):
636+ """Start a system service"""
637 return service('start', service_name)
638
639
640 def service_stop(service_name):
641+ """Stop a system service"""
642 return service('stop', service_name)
643
644
645 def service_restart(service_name):
646+ """Restart a system service"""
647 return service('restart', service_name)
648
649
650 def service_reload(service_name, restart_on_failure=False):
651+ """Reload a system service, optionally falling back to restart if reload fails"""
652 service_result = service('reload', service_name)
653 if not service_result and restart_on_failure:
654 service_result = service('restart', service_name)
655@@ -38,11 +42,13 @@
656
657
658 def service(action, service_name):
659+ """Control a system service"""
660 cmd = ['service', service_name, action]
661 return subprocess.call(cmd) == 0
662
663
664 def service_running(service):
665+ """Determine whether a system service is running"""
666 try:
667 output = subprocess.check_output(['service', service, 'status'])
668 except subprocess.CalledProcessError:
669@@ -55,7 +61,7 @@
670
671
672 def adduser(username, password=None, shell='/bin/bash', system_user=False):
673- """Add a user"""
674+ """Add a user to the system"""
675 try:
676 user_info = pwd.getpwnam(username)
677 log('user {0} already exists!'.format(username))
678@@ -138,7 +144,7 @@
679
680
681 def mount(device, mountpoint, options=None, persist=False):
682- '''Mount a filesystem'''
683+ """Mount a filesystem at a particular mountpoint"""
684 cmd_args = ['mount']
685 if options is not None:
686 cmd_args.extend(['-o', options])
687@@ -155,7 +161,7 @@
688
689
690 def umount(mountpoint, persist=False):
691- '''Unmount a filesystem'''
692+ """Unmount a filesystem"""
693 cmd_args = ['umount', mountpoint]
694 try:
695 subprocess.check_output(cmd_args)
696@@ -169,7 +175,7 @@
697
698
699 def mounts():
700- '''List of all mounted volumes as [[mountpoint,device],[...]]'''
701+ """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
702 with open('/proc/mounts') as f:
703 # [['/mount/point','/dev/path'],[...]]
704 system_mounts = [m[1::-1] for m in [l.strip().split()
705@@ -178,7 +184,7 @@
706
707
708 def file_hash(path):
709- ''' Generate a md5 hash of the contents of 'path' or None if not found '''
710+ """Generate a md5 hash of the contents of 'path' or None if not found """
711 if os.path.exists(path):
712 h = hashlib.md5()
713 with open(path, 'r') as source:
714@@ -189,7 +195,7 @@
715
716
717 def restart_on_change(restart_map):
718- ''' Restart services based on configuration files changing
719+ """Restart services based on configuration files changing
720
721 This function is used a decorator, for example
722
723@@ -202,7 +208,7 @@
724 In this example, the cinder-api and cinder-volume services
725 would be restarted if /etc/ceph/ceph.conf is changed by the
726 ceph_client_changed function.
727- '''
728+ """
729 def wrap(f):
730 def wrapped_f(*args):
731 checksums = {}
732@@ -220,7 +226,7 @@
733
734
735 def lsb_release():
736- '''Return /etc/lsb-release in a dict'''
737+ """Return /etc/lsb-release in a dict"""
738 d = {}
739 with open('/etc/lsb-release', 'r') as lsb:
740 for l in lsb:
741@@ -230,7 +236,7 @@
742
743
744 def pwgen(length=None):
745- '''Generate a random pasword.'''
746+ """Generate a random pasword."""
747 if length is None:
748 length = random.choice(range(35, 45))
749 alphanumeric_chars = [
750
751=== modified file 'hooks/charmhelpers/fetch/__init__.py'
752--- hooks/charmhelpers/fetch/__init__.py 2013-09-23 13:22:13 +0000
753+++ hooks/charmhelpers/fetch/__init__.py 2013-11-06 04:26:29 +0000
754@@ -20,6 +20,32 @@
755 PROPOSED_POCKET = """# Proposed
756 deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
757 """
758+CLOUD_ARCHIVE_POCKETS = {
759+ # Folsom
760+ 'folsom': 'precise-updates/folsom',
761+ 'precise-folsom': 'precise-updates/folsom',
762+ 'precise-folsom/updates': 'precise-updates/folsom',
763+ 'precise-updates/folsom': 'precise-updates/folsom',
764+ 'folsom/proposed': 'precise-proposed/folsom',
765+ 'precise-folsom/proposed': 'precise-proposed/folsom',
766+ 'precise-proposed/folsom': 'precise-proposed/folsom',
767+ # Grizzly
768+ 'grizzly': 'precise-updates/grizzly',
769+ 'precise-grizzly': 'precise-updates/grizzly',
770+ 'precise-grizzly/updates': 'precise-updates/grizzly',
771+ 'precise-updates/grizzly': 'precise-updates/grizzly',
772+ 'grizzly/proposed': 'precise-proposed/grizzly',
773+ 'precise-grizzly/proposed': 'precise-proposed/grizzly',
774+ 'precise-proposed/grizzly': 'precise-proposed/grizzly',
775+ # Havana
776+ 'havana': 'precise-updates/havana',
777+ 'precise-havana': 'precise-updates/havana',
778+ 'precise-havana/updates': 'precise-updates/havana',
779+ 'precise-updates/havana': 'precise-updates/havana',
780+ 'havana/proposed': 'precise-proposed/havana',
781+ 'precies-havana/proposed': 'precise-proposed/havana',
782+ 'precise-proposed/havana': 'precise-proposed/havana',
783+}
784
785
786 def filter_installed_packages(packages):
787@@ -79,16 +105,35 @@
788 subprocess.call(cmd)
789
790
791+def apt_hold(packages, fatal=False):
792+ """Hold one or more packages"""
793+ cmd = ['apt-mark', 'hold']
794+ if isinstance(packages, basestring):
795+ cmd.append(packages)
796+ else:
797+ cmd.extend(packages)
798+ log("Holding {}".format(packages))
799+ if fatal:
800+ subprocess.check_call(cmd)
801+ else:
802+ subprocess.call(cmd)
803+
804+
805 def add_source(source, key=None):
806- if ((source.startswith('ppa:') or
807- source.startswith('http:'))):
808+ if (source.startswith('ppa:') or
809+ source.startswith('http:') or
810+ source.startswith('deb ') or
811+ source.startswith('cloud-archive:')):
812 subprocess.check_call(['add-apt-repository', '--yes', source])
813 elif source.startswith('cloud:'):
814 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
815 fatal=True)
816 pocket = source.split(':')[-1]
817+ if pocket not in CLOUD_ARCHIVE_POCKETS:
818+ raise SourceConfigError('Unsupported cloud: source option %s' % pocket)
819+ actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
820 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
821- apt.write(CLOUD_ARCHIVE.format(pocket))
822+ apt.write(CLOUD_ARCHIVE.format(actual_pocket))
823 elif source == 'proposed':
824 release = lsb_release()['DISTRIB_CODENAME']
825 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
826@@ -118,8 +163,11 @@
827 Note that 'null' (a.k.a. None) should not be quoted.
828 """
829 sources = safe_load(config(sources_var))
830- keys = safe_load(config(keys_var))
831- if isinstance(sources, basestring) and isinstance(keys, basestring):
832+ keys = config(keys_var)
833+ if keys is not None:
834+ keys = safe_load(keys)
835+ if isinstance(sources, basestring) and (
836+ keys is None or isinstance(keys, basestring)):
837 add_source(sources, keys)
838 else:
839 if not len(sources) == len(keys):
840
841=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
842--- hooks/charmhelpers/fetch/bzrurl.py 2013-09-23 13:22:13 +0000
843+++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-06 04:26:29 +0000
844@@ -12,6 +12,7 @@
845 apt_install("python-bzrlib")
846 from bzrlib.branch import Branch
847
848+
849 class BzrUrlFetchHandler(BaseFetchHandler):
850 """Handler for bazaar branches via generic and lp URLs"""
851 def can_handle(self, source):
852@@ -46,4 +47,3 @@
853 except OSError as e:
854 raise UnhandledSource(e.strerror)
855 return dest_dir
856-
857
858=== modified file 'hooks/glance_contexts.py'
859--- hooks/glance_contexts.py 2013-10-19 21:01:07 +0000
860+++ hooks/glance_contexts.py 2013-11-06 04:26:29 +0000
861@@ -1,4 +1,5 @@
862 from charmhelpers.core.hookenv import (
863+ is_relation_made,
864 relation_ids,
865 related_units,
866 relation_get,
867@@ -16,16 +17,6 @@
868 )
869
870
871-# TODO: switch to charmhelpers once landed
872-# NOTE: zero units tests - done in charmhelpers
873-def is_relation_made(relation, key='private-address'):
874- for r_id in relation_ids(relation):
875- for unit in related_units(r_id):
876- if relation_get(key, rid=r_id, unit=unit):
877- return True
878- return False
879-
880-
881 class CephGlanceContext(OSContextGenerator):
882 interfaces = ['ceph-glance']
883
884
885=== modified file 'hooks/glance_relations.py'
886--- hooks/glance_relations.py 2013-10-19 21:01:07 +0000
887+++ hooks/glance_relations.py 2013-11-06 04:26:29 +0000
888@@ -313,8 +313,22 @@
889 image_service_joined(relation_id=r_id)
890
891
892+@hooks.hook('amqp-relation-joined')
893+def amqp_joined():
894+ conf = config()
895+ relation_set(username=conf['rabbit-user'], vhost=conf['rabbit-vhost'])
896+
897+
898+@hooks.hook('amqp-relation-changed')
899+@restart_on_change(restart_map())
900+def amqp_changed():
901+ if 'amqp' not in CONFIGS.complete_contexts():
902+ juju_log('amqp relation incomplete. Peer not ready?')
903+ return
904+ CONFIGS.write(GLANCE_API_CONF)
905+
906 if __name__ == '__main__':
907 try:
908 hooks.execute(sys.argv)
909 except UnregisteredHookError as e:
910- juju_log('Unknown hook {} - skiping.'.format(e))
911+ juju_log('Unknown hook {} - skipping.'.format(e))
912
913=== modified file 'hooks/glance_utils.py'
914--- hooks/glance_utils.py 2013-10-19 21:01:07 +0000
915+++ hooks/glance_utils.py 2013-11-06 04:26:29 +0000
916@@ -68,6 +68,7 @@
917 }),
918 (GLANCE_API_CONF, {
919 'hook_contexts': [context.SharedDBContext(),
920+ context.AMQPContext(),
921 context.IdentityServiceContext(),
922 glance_contexts.CephGlanceContext(),
923 glance_contexts.ObjectStoreContext(),
924
925=== modified file 'metadata.yaml'
926--- metadata.yaml 2013-09-26 09:47:58 +0000
927+++ metadata.yaml 2013-11-06 04:26:29 +0000
928@@ -14,6 +14,8 @@
929 requires:
930 shared-db:
931 interface: mysql-shared
932+ amqp:
933+ interface: rabbitmq
934 object-store:
935 interface: swift-proxy
936 identity-service:
937
938=== modified file 'templates/folsom/glance-api.conf'
939--- templates/folsom/glance-api.conf 2013-10-07 14:48:52 +0000
940+++ templates/folsom/glance-api.conf 2013-11-06 04:26:29 +0000
941@@ -29,31 +29,15 @@
942 registry_host = 0.0.0.0
943 registry_port = 9191
944 registry_client_protocol = http
945-notifier_strategy = noop
946-rabbit_host = localhost
947-rabbit_port = 5672
948-rabbit_use_ssl = false
949-rabbit_userid = guest
950-rabbit_password = guest
951-rabbit_virtual_host = /
952-rabbit_notification_exchange = glance
953-rabbit_notification_topic = glance_notifications
954-rabbit_durable_queues = False
955-qpid_notification_exchange = glance
956-qpid_notification_topic = glance_notifications
957-qpid_host = localhost
958-qpid_port = 5672
959-qpid_username =
960-qpid_password =
961-qpid_sasl_mechanisms =
962-qpid_reconnect_timeout = 0
963-qpid_reconnect_limit = 0
964-qpid_reconnect_interval_min = 0
965-qpid_reconnect_interval_max = 0
966-qpid_reconnect_interval = 0
967-qpid_heartbeat = 5
968-qpid_protocol = tcp
969-qpid_tcp_nodelay = True
970+
971+{% if rabbitmq_host -%}
972+notifier_strategy = rabbit
973+rabbit_host = {{ rabbitmq_host }}
974+rabbit_userid = {{ rabbitmq_user }}
975+rabbit_password = {{ rabbitmq_password }}
976+rabbit_virtual_host = {{ rabbitmq_virtual_host }}
977+{% endif -%}
978+
979 filesystem_store_datadir = /var/lib/glance/images/
980
981 {% if swift_store %}
982
983=== modified file 'unit_tests/test_glance_relations.py'
984--- unit_tests/test_glance_relations.py 2013-10-20 18:35:42 +0000
985+++ unit_tests/test_glance_relations.py 2013-11-06 04:26:29 +0000
986@@ -370,7 +370,78 @@
987 def test_ha_relation_changed_not_clustered(self):
988 self.relation_get.return_value = False
989 relations.ha_relation_changed()
990- self.assertTrue(self.juju_log.called)
991+ self.juju_log.assert_called_with(
992+ 'ha_changed: hacluster subordinate is not fully clustered.'
993+ )
994+
995+ @patch.object(relations, 'keystone_joined')
996+ @patch.object(relations, 'CONFIGS')
997+ def test_configure_https_enable_with_identity_service(self, configs, keystone_joined):
998+ configs.complete_contexts = MagicMock()
999+ configs.complete_contexts.return_value = ['https']
1000+ configs.write = MagicMock()
1001+ self.relation_ids.return_value = ['identity-service:0']
1002+ relations.configure_https()
1003+ cmd = ['a2ensite', 'openstack_https_frontend']
1004+ self.check_call.assert_called_with(cmd)
1005+ keystone_joined.assert_called_with(relation_id='identity-service:0')
1006+
1007+ @patch.object(relations, 'keystone_joined')
1008+ @patch.object(relations, 'CONFIGS')
1009+ def test_configure_https_disable_with_keystone_joined(self, configs, keystone_joined):
1010+ configs.complete_contexts = MagicMock()
1011+ configs.complete_contexts.return_value = ['']
1012+ configs.write = MagicMock()
1013+ self.relation_ids.return_value = ['identity-service:0']
1014+ relations.configure_https()
1015+ cmd = ['a2dissite', 'openstack_https_frontend']
1016+ self.check_call.assert_called_with(cmd)
1017+ keystone_joined.assert_called_with(relation_id='identity-service:0')
1018+
1019+ @patch.object(relations, 'image_service_joined')
1020+ @patch.object(relations, 'CONFIGS')
1021+ def test_configure_https_enable_with_image_service(self, configs, image_service_joined):
1022+ configs.complete_contexts = MagicMock()
1023+ configs.complete_contexts.return_value = ['https']
1024+ configs.write = MagicMock()
1025+ self.relation_ids.return_value = ['image-service:0']
1026+ relations.configure_https()
1027+ cmd = ['a2ensite', 'openstack_https_frontend']
1028+ self.check_call.assert_called_with(cmd)
1029+ image_service_joined.assert_called_with(relation_id='image-service:0')
1030+
1031+ @patch.object(relations, 'image_service_joined')
1032+ @patch.object(relations, 'CONFIGS')
1033+ def test_configure_https_disable_with_image_service(self, configs, image_service_joined):
1034+ configs.complete_contexts = MagicMock()
1035+ configs.complete_contexts.return_value = ['']
1036+ configs.write = MagicMock()
1037+ self.relation_ids.return_value = ['image-service:0']
1038+ relations.configure_https()
1039+ cmd = ['a2dissite', 'openstack_https_frontend']
1040+ self.check_call.assert_called_with(cmd)
1041+ image_service_joined.assert_called_with(relation_id='image-service:0')
1042+
1043+ def test_amqp_joined(self):
1044+ relations.amqp_joined()
1045+ self.relation_set.assert_called_with(username='glance', vhost='openstack')
1046+
1047+ @patch.object(relations, 'CONFIGS')
1048+ def test_amqp_changed_missing_relation_data(self, configs):
1049+ configs.complete_contexts = MagicMock()
1050+ configs.complete_contexts.return_value = []
1051+ relations.amqp_changed()
1052+ self.juju_log.assert_called()
1053+
1054+ @patch.object(relations, 'CONFIGS')
1055+ def test_amqp_changed_relation_data(self, configs):
1056+ configs.complete_contexts = MagicMock()
1057+ configs.complete_contexts.return_value = ['amqp']
1058+ configs.write = MagicMock()
1059+ relations.amqp_changed()
1060+ self.assertEquals([call('/etc/glance/glance-api.conf')],
1061+ configs.write.call_args_list)
1062+ self.assertFalse(self.juju_log.called)
1063
1064 @patch.object(relations, 'keystone_joined')
1065 def test_ha_relation_changed_not_leader(self, joined):

Subscribers

People subscribed via source and target branches