~1chb1n/charms/trusty/cinder/15.10-stable-flip-tests-helper-syncs

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2015-10-22 13:19:13 UTC
  • Revision ID: james.page@ubuntu.com-20151022131913-02u2l9s2fa0xtbio
Tags: 15.10
15.10 Charm release

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
import json
23
23
import os
24
24
import sys
 
25
import re
25
26
 
26
27
import six
 
28
import traceback
27
29
import yaml
28
30
 
29
31
from charmhelpers.contrib.network import ip
33
35
)
34
36
 
35
37
from charmhelpers.core.hookenv import (
 
38
    action_fail,
 
39
    action_set,
36
40
    config,
37
41
    log as juju_log,
38
42
    charm_dir,
39
43
    INFO,
40
44
    relation_ids,
41
 
    relation_set
 
45
    relation_set,
 
46
    status_set,
 
47
    hook_name
42
48
)
43
49
 
44
50
from charmhelpers.contrib.storage.linux.lvm import (
48
54
)
49
55
 
50
56
from charmhelpers.contrib.network.ip import (
51
 
    get_ipv6_addr
 
57
    get_ipv6_addr,
 
58
    is_ipv6,
52
59
)
53
60
 
54
61
from charmhelpers.contrib.python.packages import (
67
74
DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
68
75
                   'restricted main multiverse universe')
69
76
 
70
 
 
71
77
UBUNTU_OPENSTACK_RELEASE = OrderedDict([
72
78
    ('oneiric', 'diablo'),
73
79
    ('precise', 'essex'),
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
 
 
126
# >= Liberty version->codename mapping
 
127
PACKAGE_CODENAMES = {
 
128
    'nova-common': OrderedDict([
 
129
        ('12.0.0', 'liberty'),
 
130
    ]),
 
131
    'neutron-common': OrderedDict([
 
132
        ('7.0.0', 'liberty'),
 
133
    ]),
 
134
    'cinder-common': OrderedDict([
 
135
        ('7.0.0', 'liberty'),
 
136
    ]),
 
137
    'keystone': OrderedDict([
 
138
        ('8.0.0', 'liberty'),
 
139
    ]),
 
140
    'horizon-common': OrderedDict([
 
141
        ('8.0.0', 'liberty'),
 
142
    ]),
 
143
    'ceilometer-common': OrderedDict([
 
144
        ('5.0.0', 'liberty'),
 
145
    ]),
 
146
    'heat-common': OrderedDict([
 
147
        ('5.0.0', 'liberty'),
 
148
    ]),
 
149
    'glance-common': OrderedDict([
 
150
        ('11.0.0', 'liberty'),
 
151
    ]),
 
152
    'openstack-dashboard': OrderedDict([
 
153
        ('8.0.0', 'liberty'),
 
154
    ]),
 
155
}
 
156
 
119
157
DEFAULT_LOOPBACK_SIZE = '5G'
120
158
 
121
159
 
199
237
        error_out(e)
200
238
 
201
239
    vers = apt.upstream_version(pkg.current_ver.ver_str)
 
240
    match = re.match('^(\d+)\.(\d+)\.(\d+)', vers)
 
241
    if match:
 
242
        vers = match.group(0)
202
243
 
203
 
    try:
204
 
        if 'swift' in pkg.name:
205
 
            swift_vers = vers[:5]
206
 
            if swift_vers not in SWIFT_CODENAMES:
207
 
                # Deal with 1.10.0 upward
208
 
                swift_vers = vers[:6]
209
 
            return SWIFT_CODENAMES[swift_vers]
210
 
        else:
211
 
            vers = vers[:6]
212
 
            return OPENSTACK_CODENAMES[vers]
213
 
    except KeyError:
214
 
        e = 'Could not determine OpenStack codename for version %s' % vers
215
 
        error_out(e)
 
244
    # >= Liberty independent project versions
 
245
    if (package in PACKAGE_CODENAMES and
 
246
            vers in PACKAGE_CODENAMES[package]):
 
247
        return PACKAGE_CODENAMES[package][vers]
 
248
    else:
 
249
        # < Liberty co-ordinated project versions
 
250
        try:
 
251
            if 'swift' in pkg.name:
 
252
                swift_vers = vers[:5]
 
253
                if swift_vers not in SWIFT_CODENAMES:
 
254
                    # Deal with 1.10.0 upward
 
255
                    swift_vers = vers[:6]
 
256
                return SWIFT_CODENAMES[swift_vers]
 
257
            else:
 
258
                vers = vers[:6]
 
259
                return OPENSTACK_CODENAMES[vers]
 
260
        except KeyError:
 
261
            if not fatal:
 
262
                return None
 
263
            e = 'Could not determine OpenStack codename for version %s' % vers
 
264
            error_out(e)
216
265
 
217
266
 
218
267
def get_os_version_package(pkg, fatal=True):
471
520
                                      relation_prefix=None):
472
521
    hosts = get_ipv6_addr(dynamic_only=False)
473
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
 
474
529
    kwargs = {'database': database,
475
530
              'username': database_user,
476
531
              'hostname': json.dumps(hosts)}
706
761
        return projects[key]
707
762
 
708
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