~ionutbalutoiu/charms/trusty/neutron-api/next

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/openstack/utils.py

  • Committer: Liam Young
  • Date: 2015-09-28 12:41:31 UTC
  • mfrom: (142.2.1 trunk)
  • Revision ID: liam.young@canonical.com-20150928124131-hzlbsw4n1ligxsm2
[hopem, r=gnuoy] Sync charmhelpers to get fix for bug 1499643

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import re
26
26
 
27
27
import six
 
28
import traceback
28
29
import yaml
29
30
 
30
31
from charmhelpers.contrib.network import ip
34
35
)
35
36
 
36
37
from charmhelpers.core.hookenv import (
 
38
    action_fail,
 
39
    action_set,
37
40
    config,
38
41
    log as juju_log,
39
42
    charm_dir,
40
43
    INFO,
41
44
    relation_ids,
42
 
    relation_set
 
45
    relation_set,
 
46
    status_set,
 
47
    hook_name
43
48
)
44
49
 
45
50
from charmhelpers.contrib.storage.linux.lvm import (
49
54
)
50
55
 
51
56
from charmhelpers.contrib.network.ip import (
52
 
    get_ipv6_addr
 
57
    get_ipv6_addr,
 
58
    is_ipv6,
53
59
)
54
60
 
55
61
from charmhelpers.contrib.python.packages import (
114
120
    ('2.2.1', 'kilo'),
115
121
    ('2.2.2', 'kilo'),
116
122
    ('2.3.0', 'liberty'),
 
123
    ('2.4.0', 'liberty'),
117
124
])
118
125
 
119
126
# >= Liberty version->codename mapping
142
149
    'glance-common': OrderedDict([
143
150
        ('11.0.0', 'liberty'),
144
151
    ]),
 
152
    'openstack-dashboard': OrderedDict([
 
153
        ('8.0.0', 'liberty'),
 
154
    ]),
145
155
}
146
156
 
147
157
DEFAULT_LOOPBACK_SIZE = '5G'
510
520
                                      relation_prefix=None):
511
521
    hosts = get_ipv6_addr(dynamic_only=False)
512
522
 
 
523
    if config('vip'):
 
524
        vips = config('vip').split()
 
525
        for vip in vips:
 
526
            if vip and is_ipv6(vip):
 
527
                hosts.append(vip)
 
528
 
513
529
    kwargs = {'database': database,
514
530
              'username': database_user,
515
531
              'hostname': json.dumps(hosts)}
745
761
        return projects[key]
746
762
 
747
763
    return None
 
764
 
 
765
 
 
766
def os_workload_status(configs, required_interfaces, charm_func=None):
 
767
    """
 
768
    Decorator to set workload status based on complete contexts
 
769
    """
 
770
    def wrap(f):
 
771
        @wraps(f)
 
772
        def wrapped_f(*args, **kwargs):
 
773
            # Run the original function first
 
774
            f(*args, **kwargs)
 
775
            # Set workload status now that contexts have been
 
776
            # acted on
 
777
            set_os_workload_status(configs, required_interfaces, charm_func)
 
778
        return wrapped_f
 
779
    return wrap
 
780
 
 
781
 
 
782
def set_os_workload_status(configs, required_interfaces, charm_func=None):
 
783
    """
 
784
    Set workload status based on complete contexts.
 
785
    status-set missing or incomplete contexts
 
786
    and juju-log details of missing required data.
 
787
    charm_func is a charm specific function to run checking
 
788
    for charm specific requirements such as a VIP setting.
 
789
    """
 
790
    incomplete_rel_data = incomplete_relation_data(configs, required_interfaces)
 
791
    state = 'active'
 
792
    missing_relations = []
 
793
    incomplete_relations = []
 
794
    message = None
 
795
    charm_state = None
 
796
    charm_message = None
 
797
 
 
798
    for generic_interface in incomplete_rel_data.keys():
 
799
        related_interface = None
 
800
        missing_data = {}
 
801
        # Related or not?
 
802
        for interface in incomplete_rel_data[generic_interface]:
 
803
            if incomplete_rel_data[generic_interface][interface].get('related'):
 
804
                related_interface = interface
 
805
                missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data')
 
806
        # No relation ID for the generic_interface
 
807
        if not related_interface:
 
808
            juju_log("{} relation is missing and must be related for "
 
809
                     "functionality. ".format(generic_interface), 'WARN')
 
810
            state = 'blocked'
 
811
            if generic_interface not in missing_relations:
 
812
                missing_relations.append(generic_interface)
 
813
        else:
 
814
            # Relation ID exists but no related unit
 
815
            if not missing_data:
 
816
                # Edge case relation ID exists but departing
 
817
                if ('departed' in hook_name() or 'broken' in hook_name()) \
 
818
                        and related_interface in hook_name():
 
819
                    state = 'blocked'
 
820
                    if generic_interface not in missing_relations:
 
821
                        missing_relations.append(generic_interface)
 
822
                    juju_log("{} relation's interface, {}, "
 
823
                             "relationship is departed or broken "
 
824
                             "and is required for functionality."
 
825
                             "".format(generic_interface, related_interface), "WARN")
 
826
                # Normal case relation ID exists but no related unit
 
827
                # (joining)
 
828
                else:
 
829
                    juju_log("{} relations's interface, {}, is related but has "
 
830
                             "no units in the relation."
 
831
                             "".format(generic_interface, related_interface), "INFO")
 
832
            # Related unit exists and data missing on the relation
 
833
            else:
 
834
                juju_log("{} relation's interface, {}, is related awaiting "
 
835
                         "the following data from the relationship: {}. "
 
836
                         "".format(generic_interface, related_interface,
 
837
                                   ", ".join(missing_data)), "INFO")
 
838
            if state != 'blocked':
 
839
                state = 'waiting'
 
840
            if generic_interface not in incomplete_relations \
 
841
                    and generic_interface not in missing_relations:
 
842
                incomplete_relations.append(generic_interface)
 
843
 
 
844
    if missing_relations:
 
845
        message = "Missing relations: {}".format(", ".join(missing_relations))
 
846
        if incomplete_relations:
 
847
            message += "; incomplete relations: {}" \
 
848
                       "".format(", ".join(incomplete_relations))
 
849
        state = 'blocked'
 
850
    elif incomplete_relations:
 
851
        message = "Incomplete relations: {}" \
 
852
                  "".format(", ".join(incomplete_relations))
 
853
        state = 'waiting'
 
854
 
 
855
    # Run charm specific checks
 
856
    if charm_func:
 
857
        charm_state, charm_message = charm_func(configs)
 
858
        if charm_state != 'active' and charm_state != 'unknown':
 
859
            state = workload_state_compare(state, charm_state)
 
860
            if message:
 
861
                message = "{} {}".format(message, charm_message)
 
862
            else:
 
863
                message = charm_message
 
864
 
 
865
    # Set to active if all requirements have been met
 
866
    if state == 'active':
 
867
        message = "Unit is ready"
 
868
        juju_log(message, "INFO")
 
869
 
 
870
    status_set(state, message)
 
871
 
 
872
 
 
873
def workload_state_compare(current_workload_state, workload_state):
 
874
    """ Return highest priority of two states"""
 
875
    hierarchy = {'unknown': -1,
 
876
                 'active': 0,
 
877
                 'maintenance': 1,
 
878
                 'waiting': 2,
 
879
                 'blocked': 3,
 
880
                 }
 
881
 
 
882
    if hierarchy.get(workload_state) is None:
 
883
        workload_state = 'unknown'
 
884
    if hierarchy.get(current_workload_state) is None:
 
885
        current_workload_state = 'unknown'
 
886
 
 
887
    # Set workload_state based on hierarchy of statuses
 
888
    if hierarchy.get(current_workload_state) > hierarchy.get(workload_state):
 
889
        return current_workload_state
 
890
    else:
 
891
        return workload_state
 
892
 
 
893
 
 
894
def incomplete_relation_data(configs, required_interfaces):
 
895
    """
 
896
    Check complete contexts against required_interfaces
 
897
    Return dictionary of incomplete relation data.
 
898
 
 
899
    configs is an OSConfigRenderer object with configs registered
 
900
 
 
901
    required_interfaces is a dictionary of required general interfaces
 
902
    with dictionary values of possible specific interfaces.
 
903
    Example:
 
904
    required_interfaces = {'database': ['shared-db', 'pgsql-db']}
 
905
 
 
906
    The interface is said to be satisfied if anyone of the interfaces in the
 
907
    list has a complete context.
 
908
 
 
909
    Return dictionary of incomplete or missing required contexts with relation
 
910
    status of interfaces and any missing data points. Example:
 
911
        {'message':
 
912
             {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True},
 
913
              'zeromq-configuration': {'related': False}},
 
914
         'identity':
 
915
             {'identity-service': {'related': False}},
 
916
         'database':
 
917
             {'pgsql-db': {'related': False},
 
918
              'shared-db': {'related': True}}}
 
919
    """
 
920
    complete_ctxts = configs.complete_contexts()
 
921
    incomplete_relations = []
 
922
    for svc_type in required_interfaces.keys():
 
923
        # Avoid duplicates
 
924
        found_ctxt = False
 
925
        for interface in required_interfaces[svc_type]:
 
926
            if interface in complete_ctxts:
 
927
                found_ctxt = True
 
928
        if not found_ctxt:
 
929
            incomplete_relations.append(svc_type)
 
930
    incomplete_context_data = {}
 
931
    for i in incomplete_relations:
 
932
        incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i])
 
933
    return incomplete_context_data
 
934
 
 
935
 
 
936
def do_action_openstack_upgrade(package, upgrade_callback, configs):
 
937
    """Perform action-managed OpenStack upgrade.
 
938
 
 
939
    Upgrades packages to the configured openstack-origin version and sets
 
940
    the corresponding action status as a result.
 
941
 
 
942
    If the charm was installed from source we cannot upgrade it.
 
943
    For backwards compatibility a config flag (action-managed-upgrade) must
 
944
    be set for this code to run, otherwise a full service level upgrade will
 
945
    fire on config-changed.
 
946
 
 
947
    @param package: package name for determining if upgrade available
 
948
    @param upgrade_callback: function callback to charm's upgrade function
 
949
    @param configs: templating object derived from OSConfigRenderer class
 
950
 
 
951
    @return: True if upgrade successful; False if upgrade failed or skipped
 
952
    """
 
953
    ret = False
 
954
 
 
955
    if git_install_requested():
 
956
        action_set({'outcome': 'installed from source, skipped upgrade.'})
 
957
    else:
 
958
        if openstack_upgrade_available(package):
 
959
            if config('action-managed-upgrade'):
 
960
                juju_log('Upgrading OpenStack release')
 
961
 
 
962
                try:
 
963
                    upgrade_callback(configs=configs)
 
964
                    action_set({'outcome': 'success, upgrade completed.'})
 
965
                    ret = True
 
966
                except:
 
967
                    action_set({'outcome': 'upgrade failed, see traceback.'})
 
968
                    action_set({'traceback': traceback.format_exc()})
 
969
                    action_fail('do_openstack_upgrade resulted in an '
 
970
                                'unexpected error')
 
971
            else:
 
972
                action_set({'outcome': 'action-managed-upgrade config is '
 
973
                                       'False, skipped upgrade.'})
 
974
        else:
 
975
            action_set({'outcome': 'no upgrade available.'})
 
976
 
 
977
    return ret