~hopem/charms/trusty/keystone/reloads

« back to all changes in this revision

Viewing changes to hooks/keystone_utils.py

  • Committer: Edward Hope-Morley
  • Date: 2015-01-10 14:56:22 UTC
  • Revision ID: edward.hope-morley@canonical.com-20150110145622-dmss29v1klazhp54
Fixed a few race issues and switched to using decorators

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
import glob
3
3
import grp
4
 
import subprocess
 
4
import hashlib
5
5
import os
6
6
import pwd
7
 
import uuid
8
 
import urlparse
9
7
import shutil
 
8
import subprocess
 
9
import threading
10
10
import time
 
11
import urlparse
 
12
import uuid
11
13
 
12
14
from base64 import b64encode
13
15
from collections import OrderedDict
56
58
    config,
57
59
    log,
58
60
    local_unit,
 
61
    related_units,
59
62
    relation_get,
60
63
    relation_set,
61
64
    relation_ids,
62
 
    unit_get,
63
65
    DEBUG,
64
66
    INFO,
65
67
    WARNING,
130
132
SSL_CA_NAME = 'Ubuntu Cloud'
131
133
CLUSTER_RES = 'grp_ks_vips'
132
134
SSH_USER = 'juju_keystone'
 
135
SSL_SYNC_SEMAPHORE = threading.Semaphore()
133
136
 
134
137
BASE_RESOURCE_MAP = OrderedDict([
135
138
    (KEYSTONE_CONF, {
701
704
        master = relation_get(attribute='ssl-cert-master', rid=rid,
702
705
                              unit=local_unit())
703
706
 
704
 
    if master and master == unit_get('private-address'):
 
707
    if master and master == local_unit():
705
708
        return True
706
709
 
707
710
    return False
720
723
                         fatal=True)
721
724
 
722
725
 
 
726
def is_sync_master():
 
727
    if not peer_units():
 
728
        log("Not syncing certs since there are no peer units.", level=INFO)
 
729
        return False
 
730
 
 
731
    # If no ssl master elected and we are cluster leader, elect this unit.
 
732
    if is_elected_leader(CLUSTER_RES):
 
733
        master = []
 
734
        for rid in relation_ids('cluster'):
 
735
            for unit in related_units(rid):
 
736
                m = relation_get(rid=rid, unit=unit,
 
737
                                 attribute='ssl-cert-master')
 
738
                if m is not None:
 
739
                    master.append(m)
 
740
 
 
741
        master = set(master)
 
742
        if not master or ('unknown' in master and len(master) == 1):
 
743
            log("Electing this unit as ssl-cert-master", level=DEBUG)
 
744
            for rid in relation_ids('cluster'):
 
745
                settings = {'ssl-cert-master': local_unit(),
 
746
                            'ssl-synced-units': None}
 
747
                relation_set(relation_id=rid, relation_settings=settings)
 
748
 
 
749
            # Return now and wait for cluster-relation-changed for sync.
 
750
            return False
 
751
 
 
752
    if not is_ssl_cert_master():
 
753
        log("Not ssl cert master - skipping sync", level=INFO)
 
754
        return False
 
755
 
 
756
    return True
 
757
 
 
758
 
723
759
def synchronize_ca(fatal=True):
724
760
    """Broadcast service credentials to peers.
725
761
 
733
769
    """
734
770
    paths_to_sync = [SYNC_FLAGS_DIR]
735
771
 
736
 
    if not peer_units():
737
 
        log("Not syncing certs since there are no peer units.", level=INFO)
738
 
        return
739
 
 
740
 
    # If no ssl master elected and we are cluster leader, elect this unit.
741
 
    if is_elected_leader(CLUSTER_RES):
742
 
        master = relation_get(attribute='ssl-cert-master')
743
 
        if not master or master == 'unknown':
744
 
            log("Electing this unit as ssl-cert-master", level=DEBUG)
745
 
            for rid in relation_ids('cluster'):
746
 
                relation_set(relation_id=rid,
747
 
                             relation_settings={'ssl-cert-master':
748
 
                                                unit_get('private-address'),
749
 
                                                'trigger': str(uuid.uuid4())})
750
 
            # Return now and wait for echo before continuing.
751
 
            return
752
 
 
753
 
    if not is_ssl_cert_master():
754
 
        log("Not ssl cert master - skipping sync", level=INFO)
755
 
        return
756
 
 
757
772
    if str_is_true(config('https-service-endpoints')):
758
773
        log("Syncing all endpoint certs since https-service-endpoints=True",
759
774
            level=DEBUG)
785
800
    # new ssl keys.
786
801
    create_peer_service_actions('restart', ['apache2'])
787
802
    create_service_action('update-ca-certificates')
788
 
 
789
803
    try:
790
804
        unison_sync(paths_to_sync)
791
805
    except:
795
809
            log("Sync failed but fatal=False", level=INFO)
796
810
            return
797
811
 
 
812
    # Cleanup
 
813
    shutil.rmtree(SYNC_FLAGS_DIR)
 
814
    mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
 
815
 
798
816
    trigger = str(uuid.uuid4())
799
817
    log("Sending restart-services-trigger=%s to all peers" % (trigger),
800
818
        level=DEBUG)
801
 
    settings = {'restart-services-trigger': trigger}
802
 
 
803
 
    # Cleanup
804
 
    shutil.rmtree(SYNC_FLAGS_DIR)
805
 
    mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
806
 
 
807
 
    # If we are the sync master but no longer leader then re-elect master.
808
 
    if not is_elected_leader(CLUSTER_RES):
809
 
        log("Re-electing ssl cert master.", level=INFO)
810
 
        settings['ssl-cert-master'] = 'unknown'
811
 
 
812
 
    log("Sync complete - sending peer info", level=DEBUG)
813
 
    for rid in relation_ids('cluster'):
814
 
        relation_set(relation_id=rid, **settings)
 
819
 
 
820
    log("Sync complete", level=DEBUG)
 
821
    return {'restart-services-trigger': trigger,
 
822
            'ssl-synced-units': peer_units()}
 
823
 
 
824
 
 
825
def update_hash_from_path(hash, path, recurse_depth=10):
 
826
    """Recurse through path and update the provided hash for every file found.
 
827
    """
 
828
    if not recurse_depth:
 
829
        log("Max recursion depth (%s) reached for update_hash_from_path() at "
 
830
            "path='%s' - not going any deeper" % (recurse_depth, path),
 
831
            level=WARNING)
 
832
        return sum
 
833
 
 
834
    for p in glob.glob("%s/*" % path):
 
835
        if os.path.isdir(p):
 
836
            update_hash_from_path(hash, p, recurse_depth=recurse_depth - 1)
 
837
        else:
 
838
            with open(p, 'r') as fd:
 
839
                hash.update(fd.read())
 
840
 
 
841
 
 
842
def synchronize_ca_if_changed(force=False, fatal=True):
 
843
    """Decorator to perform ssl cert sync if decorated function modifies them
 
844
    in any way.
 
845
 
 
846
    If force is True a sync is done regardless.
 
847
    """
 
848
    def inner_synchronize_ca_if_changed1(f):
 
849
        def inner_synchronize_ca_if_changed2(*args, **kwargs):
 
850
            if not is_sync_master():
 
851
                return f(*args, **kwargs)
 
852
 
 
853
            peer_settings = {}
 
854
            try:
 
855
                # Ensure we don't do a double sync if we are nested.
 
856
                if not force and SSL_SYNC_SEMAPHORE.acquire(blocking=0):
 
857
                    hash1 = hashlib.sha256()
 
858
                    for path in [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]:
 
859
                        update_hash_from_path(hash1, path)
 
860
 
 
861
                    hash1 = hash1.hexdigest()
 
862
 
 
863
                    ret = f(*args, **kwargs)
 
864
 
 
865
                    hash2 = hashlib.sha256()
 
866
                    for path in [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]:
 
867
                        update_hash_from_path(hash2, path)
 
868
 
 
869
                    hash2 = hash2.hexdigest()
 
870
                    if hash1 != hash2:
 
871
                        log("SSL certs have changed - syncing peers",
 
872
                            level=DEBUG)
 
873
                        peer_settings = synchronize_ca(fatal=fatal)
 
874
                    else:
 
875
                        log("SSL certs have not changed - skipping sync",
 
876
                            level=DEBUG)
 
877
                else:
 
878
                    ret = f(*args, **kwargs)
 
879
                    if force:
 
880
                        log("Doing forced ssl cert sync", level=DEBUG)
 
881
                        peer_settings = synchronize_ca(fatal=fatal)
 
882
 
 
883
                # If we are the sync master but no longer leader then re-elect
 
884
                # master.
 
885
                if not is_elected_leader(CLUSTER_RES):
 
886
                    log("Re-electing ssl cert master.", level=INFO)
 
887
                    peer_settings['ssl-cert-master'] = 'unknown'
 
888
 
 
889
                for rid in relation_ids('cluster'):
 
890
                    relation_set(relation_id=rid,
 
891
                                 relation_settings=peer_settings)
 
892
 
 
893
                return ret
 
894
            finally:
 
895
                SSL_SYNC_SEMAPHORE.release()
 
896
 
 
897
        return inner_synchronize_ca_if_changed2
 
898
 
 
899
    return inner_synchronize_ca_if_changed1
815
900
 
816
901
 
817
902
def get_ca(user='keystone', group='keystone'):
838
923
 
839
924
        # Tag this host as the ssl cert master.
840
925
        if is_clustered() or peer_units():
841
 
            peer_store(key='ssl-cert-master',
842
 
                       value=unit_get('private-address'))
 
926
            for rid in relation_ids('cluster'):
 
927
                relation_set(relation_id=rid,
 
928
                             relation_settings={'ssl-cert-master':
 
929
                                                local_unit(),
 
930
                                                'synced-units': None})
843
931
 
844
932
        ssl.CA_SINGLETON.append(ca)
845
933
 
1149
1237
        level=DEBUG)
1150
1238
    for rid in rel_ids:
1151
1239
        relation_set(relation_id=rid, relation_settings=_notifications)
1152
 
 
1153
 
 
1154
 
def is_pending_clustered():
1155
 
    """If we have HA relations but are not yet 'clustered' return True."""
1156
 
    for r_id in (relation_ids('ha') or []):
1157
 
        for unit in (relation_list(r_id) or []):
1158
 
            if not relation_get('clustered', rid=r_id, unit=unit):
1159
 
                return True
1160
 
 
1161
 
    return False