~openstack-charmers-archive/charms/trusty/cinder/next

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/core/hookenv.py

  • Committer: james.page at ubuntu
  • Date: 2015-06-09 09:57:24 UTC
  • mfrom: (76.1.16 cinder)
  • Revision ID: james.page@ubuntu.com-20150609095724-r3v11i715z0yogxd
Add support for leader-election

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
#  Charm Helpers Developers <juju@lists.ubuntu.com>
22
22
 
23
23
from __future__ import print_function
 
24
from functools import wraps
24
25
import os
25
26
import json
26
27
import yaml
27
28
import subprocess
28
29
import sys
29
30
import errno
 
31
import tempfile
30
32
from subprocess import CalledProcessError
31
33
 
32
34
import six
58
60
 
59
61
    will cache the result of unit_get + 'test' for future calls.
60
62
    """
 
63
    @wraps(func)
61
64
    def wrapper(*args, **kwargs):
62
65
        global cache
63
66
        key = str((func, args, kwargs))
64
67
        try:
65
68
            return cache[key]
66
69
        except KeyError:
67
 
            res = func(*args, **kwargs)
68
 
            cache[key] = res
69
 
            return res
 
70
            pass  # Drop out of the exception handler scope.
 
71
        res = func(*args, **kwargs)
 
72
        cache[key] = res
 
73
        return res
70
74
    return wrapper
71
75
 
72
76
 
178
182
 
179
183
def remote_unit():
180
184
    """The remote unit for the current relation hook"""
181
 
    return os.environ['JUJU_REMOTE_UNIT']
 
185
    return os.environ.get('JUJU_REMOTE_UNIT', None)
182
186
 
183
187
 
184
188
def service_name():
250
254
        except KeyError:
251
255
            return (self._prev_dict or {})[key]
252
256
 
 
257
    def get(self, key, default=None):
 
258
        try:
 
259
            return self[key]
 
260
        except KeyError:
 
261
            return default
 
262
 
253
263
    def keys(self):
254
264
        prev_keys = []
255
265
        if self._prev_dict is not None:
353
363
    """Set relation information for the current unit"""
354
364
    relation_settings = relation_settings if relation_settings else {}
355
365
    relation_cmd_line = ['relation-set']
 
366
    accepts_file = "--file" in subprocess.check_output(
 
367
        relation_cmd_line + ["--help"], universal_newlines=True)
356
368
    if relation_id is not None:
357
369
        relation_cmd_line.extend(('-r', relation_id))
358
 
    for k, v in (list(relation_settings.items()) + list(kwargs.items())):
359
 
        if v is None:
360
 
            relation_cmd_line.append('{}='.format(k))
361
 
        else:
362
 
            relation_cmd_line.append('{}={}'.format(k, v))
363
 
    subprocess.check_call(relation_cmd_line)
 
370
    settings = relation_settings.copy()
 
371
    settings.update(kwargs)
 
372
    for key, value in settings.items():
 
373
        # Force value to be a string: it always should, but some call
 
374
        # sites pass in things like dicts or numbers.
 
375
        if value is not None:
 
376
            settings[key] = "{}".format(value)
 
377
    if accepts_file:
 
378
        # --file was introduced in Juju 1.23.2. Use it by default if
 
379
        # available, since otherwise we'll break if the relation data is
 
380
        # too big. Ideally we should tell relation-set to read the data from
 
381
        # stdin, but that feature is broken in 1.23.2: Bug #1454678.
 
382
        with tempfile.NamedTemporaryFile(delete=False) as settings_file:
 
383
            settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
 
384
        subprocess.check_call(
 
385
            relation_cmd_line + ["--file", settings_file.name])
 
386
        os.remove(settings_file.name)
 
387
    else:
 
388
        for key, value in settings.items():
 
389
            if value is None:
 
390
                relation_cmd_line.append('{}='.format(key))
 
391
            else:
 
392
                relation_cmd_line.append('{}={}'.format(key, value))
 
393
        subprocess.check_call(relation_cmd_line)
364
394
    # Flush cache of any relation-gets for local unit
365
395
    flush(local_unit())
366
396
 
367
397
 
 
398
def relation_clear(r_id=None):
 
399
    ''' Clears any relation data already set on relation r_id '''
 
400
    settings = relation_get(rid=r_id,
 
401
                            unit=local_unit())
 
402
    for setting in settings:
 
403
        if setting not in ['public-address', 'private-address']:
 
404
            settings[setting] = None
 
405
    relation_set(relation_id=r_id,
 
406
                 **settings)
 
407
 
 
408
 
368
409
@cached
369
410
def relation_ids(reltype=None):
370
411
    """A list of relation_ids"""
509
550
        return None
510
551
 
511
552
 
 
553
def unit_public_ip():
 
554
    """Get this unit's public IP address"""
 
555
    return unit_get('public-address')
 
556
 
 
557
 
512
558
def unit_private_ip():
513
559
    """Get this unit's private IP address"""
514
560
    return unit_get('private-address')
605
651
 
606
652
    The results set by action_set are preserved."""
607
653
    subprocess.check_call(['action-fail', message])
 
654
 
 
655
 
 
656
def status_set(workload_state, message):
 
657
    """Set the workload state with a message
 
658
 
 
659
    Use status-set to set the workload state with a message which is visible
 
660
    to the user via juju status. If the status-set command is not found then
 
661
    assume this is juju < 1.23 and juju-log the message unstead.
 
662
 
 
663
    workload_state -- valid juju workload state.
 
664
    message        -- status update message
 
665
    """
 
666
    valid_states = ['maintenance', 'blocked', 'waiting', 'active']
 
667
    if workload_state not in valid_states:
 
668
        raise ValueError(
 
669
            '{!r} is not a valid workload state'.format(workload_state)
 
670
        )
 
671
    cmd = ['status-set', workload_state, message]
 
672
    try:
 
673
        ret = subprocess.call(cmd)
 
674
        if ret == 0:
 
675
            return
 
676
    except OSError as e:
 
677
        if e.errno != errno.ENOENT:
 
678
            raise
 
679
    log_message = 'status-set failed: {} {}'.format(workload_state,
 
680
                                                    message)
 
681
    log(log_message, level='INFO')
 
682
 
 
683
 
 
684
def status_get():
 
685
    """Retrieve the previously set juju workload state
 
686
 
 
687
    If the status-set command is not found then assume this is juju < 1.23 and
 
688
    return 'unknown'
 
689
    """
 
690
    cmd = ['status-get']
 
691
    try:
 
692
        raw_status = subprocess.check_output(cmd, universal_newlines=True)
 
693
        status = raw_status.rstrip()
 
694
        return status
 
695
    except OSError as e:
 
696
        if e.errno == errno.ENOENT:
 
697
            return 'unknown'
 
698
        else:
 
699
            raise
 
700
 
 
701
 
 
702
def translate_exc(from_exc, to_exc):
 
703
    def inner_translate_exc1(f):
 
704
        def inner_translate_exc2(*args, **kwargs):
 
705
            try:
 
706
                return f(*args, **kwargs)
 
707
            except from_exc:
 
708
                raise to_exc
 
709
 
 
710
        return inner_translate_exc2
 
711
 
 
712
    return inner_translate_exc1
 
713
 
 
714
 
 
715
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
 
716
def is_leader():
 
717
    """Does the current unit hold the juju leadership
 
718
 
 
719
    Uses juju to determine whether the current unit is the leader of its peers
 
720
    """
 
721
    cmd = ['is-leader', '--format=json']
 
722
    return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
 
723
 
 
724
 
 
725
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
 
726
def leader_get(attribute=None):
 
727
    """Juju leader get value(s)"""
 
728
    cmd = ['leader-get', '--format=json'] + [attribute or '-']
 
729
    return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
 
730
 
 
731
 
 
732
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
 
733
def leader_set(settings=None, **kwargs):
 
734
    """Juju leader set value(s)"""
 
735
    log("Juju leader-set '%s'" % (settings), level=DEBUG)
 
736
    cmd = ['leader-set']
 
737
    settings = settings or {}
 
738
    settings.update(kwargs)
 
739
    for k, v in settings.iteritems():
 
740
        if v is None:
 
741
            cmd.append('{}='.format(k))
 
742
        else:
 
743
            cmd.append('{}={}'.format(k, v))
 
744
    subprocess.check_call(cmd)