Merge lp:~le-charmers/charms/trusty/keystone/leadership-election into lp:~openstack-charmers-archive/charms/trusty/keystone/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 151
Proposed branch: lp:~le-charmers/charms/trusty/keystone/leadership-election
Merge into: lp:~openstack-charmers-archive/charms/trusty/keystone/next
Diff against target: 1683 lines (+597/-235)
17 files modified
charm-helpers-tests.yaml (+1/-1)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+37/-2)
hooks/charmhelpers/contrib/openstack/neutron.py (+10/-5)
hooks/charmhelpers/contrib/openstack/templates/section-zeromq (+0/-14)
hooks/charmhelpers/contrib/openstack/utils.py (+65/-18)
hooks/charmhelpers/contrib/peerstorage/__init__.py (+123/-3)
hooks/charmhelpers/contrib/python/packages.py (+28/-5)
hooks/charmhelpers/contrib/unison/__init__.py (+5/-4)
hooks/charmhelpers/core/hookenv.py (+147/-10)
hooks/charmhelpers/core/host.py (+1/-1)
hooks/charmhelpers/core/services/base.py (+32/-11)
hooks/charmhelpers/fetch/__init__.py (+1/-1)
hooks/charmhelpers/fetch/giturl.py (+7/-5)
hooks/keystone_hooks.py (+16/-12)
hooks/keystone_ssl.py (+3/-27)
hooks/keystone_utils.py (+88/-65)
unit_tests/test_keystone_hooks.py (+33/-51)
To merge this branch: bzr merge lp:~le-charmers/charms/trusty/keystone/leadership-election
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+255016@code.launchpad.net
To post a comment you must log in.
158. By Edward Hope-Morley

synced /next

159. By Edward Hope-Morley

make sync

160. By Liam Young

Merged trunk in + LE charmhelper sync

161. By Liam Young

Resync le charm helpers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-tests.yaml'
--- charm-helpers-tests.yaml 2015-05-13 09:42:17 +0000
+++ charm-helpers-tests.yaml 2015-06-04 08:44:44 +0000
@@ -1,4 +1,4 @@
1branch: lp:charm-helpers1branch: lp:charm-helpers
2destination: tests/charmhelpers2destination: tests/charmhelpers
3include:3include:
4 - contrib.amulet4 - contrib.amulet
55
=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-03-18 18:59:03 +0000
+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-06-04 08:44:44 +0000
@@ -44,6 +44,7 @@
44 ERROR,44 ERROR,
45 WARNING,45 WARNING,
46 unit_get,46 unit_get,
47 is_leader as juju_is_leader
47)48)
48from charmhelpers.core.decorators import (49from charmhelpers.core.decorators import (
49 retry_on_exception,50 retry_on_exception,
@@ -52,6 +53,8 @@
52 bool_from_string,53 bool_from_string,
53)54)
5455
56DC_RESOURCE_NAME = 'DC'
57
5558
56class HAIncompleteConfig(Exception):59class HAIncompleteConfig(Exception):
57 pass60 pass
@@ -66,12 +69,21 @@
66 Returns True if the charm executing this is the elected cluster leader.69 Returns True if the charm executing this is the elected cluster leader.
6770
68 It relies on two mechanisms to determine leadership:71 It relies on two mechanisms to determine leadership:
69 1. If the charm is part of a corosync cluster, call corosync to72 1. If juju is sufficiently new and leadership election is supported,
73 the is_leader command will be used.
74 2. If the charm is part of a corosync cluster, call corosync to
70 determine leadership.75 determine leadership.
71 2. If the charm is not part of a corosync cluster, the leader is76 3. If the charm is not part of a corosync cluster, the leader is
72 determined as being "the alive unit with the lowest unit numer". In77 determined as being "the alive unit with the lowest unit numer". In
73 other words, the oldest surviving unit.78 other words, the oldest surviving unit.
74 """79 """
80 try:
81 return juju_is_leader()
82 except NotImplementedError:
83 log('Juju leadership election feature not enabled'
84 ', using fallback support',
85 level=WARNING)
86
75 if is_clustered():87 if is_clustered():
76 if not is_crm_leader(resource):88 if not is_crm_leader(resource):
77 log('Deferring action to CRM leader.', level=INFO)89 log('Deferring action to CRM leader.', level=INFO)
@@ -95,6 +107,27 @@
95 return False107 return False
96108
97109
110def is_crm_dc():
111 """
112 Determine leadership by querying the pacemaker Designated Controller
113 """
114 cmd = ['crm', 'status']
115 try:
116 status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
117 if not isinstance(status, six.text_type):
118 status = six.text_type(status, "utf-8")
119 except subprocess.CalledProcessError:
120 return False
121 current_dc = ''
122 for line in status.split('\n'):
123 if line.startswith('Current DC'):
124 # Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum
125 current_dc = line.split(':')[1].split()[0]
126 if current_dc == get_unit_hostname():
127 return True
128 return False
129
130
98@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)131@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
99def is_crm_leader(resource, retry=False):132def is_crm_leader(resource, retry=False):
100 """133 """
@@ -104,6 +137,8 @@
104 We allow this operation to be retried to avoid the possibility of getting a137 We allow this operation to be retried to avoid the possibility of getting a
105 false negative. See LP #1396246 for more info.138 false negative. See LP #1396246 for more info.
106 """139 """
140 if resource == DC_RESOURCE_NAME:
141 return is_crm_dc()
107 cmd = ['crm', 'resource', 'show', resource]142 cmd = ['crm', 'resource', 'show', resource]
108 try:143 try:
109 status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)144 status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
110145
=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
--- hooks/charmhelpers/contrib/openstack/neutron.py 2015-04-16 19:55:16 +0000
+++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-04 08:44:44 +0000
@@ -256,11 +256,14 @@
256def parse_mappings(mappings):256def parse_mappings(mappings):
257 parsed = {}257 parsed = {}
258 if mappings:258 if mappings:
259 mappings = mappings.split(' ')259 mappings = mappings.split()
260 for m in mappings:260 for m in mappings:
261 p = m.partition(':')261 p = m.partition(':')
262 if p[1] == ':':262 key = p[0].strip()
263 parsed[p[0].strip()] = p[2].strip()263 if p[1]:
264 parsed[key] = p[2].strip()
265 else:
266 parsed[key] = ''
264267
265 return parsed268 return parsed
266269
@@ -283,13 +286,13 @@
283 Returns dict of the form {bridge:port}.286 Returns dict of the form {bridge:port}.
284 """287 """
285 _mappings = parse_mappings(mappings)288 _mappings = parse_mappings(mappings)
286 if not _mappings:289 if not _mappings or list(_mappings.values()) == ['']:
287 if not mappings:290 if not mappings:
288 return {}291 return {}
289292
290 # For backwards-compatibility we need to support port-only provided in293 # For backwards-compatibility we need to support port-only provided in
291 # config.294 # config.
292 _mappings = {default_bridge: mappings.split(' ')[0]}295 _mappings = {default_bridge: mappings.split()[0]}
293296
294 bridges = _mappings.keys()297 bridges = _mappings.keys()
295 ports = _mappings.values()298 ports = _mappings.values()
@@ -309,6 +312,8 @@
309312
310 Mappings must be a space-delimited list of provider:start:end mappings.313 Mappings must be a space-delimited list of provider:start:end mappings.
311314
315 The start:end range is optional and may be omitted.
316
312 Returns dict of the form {provider: (start, end)}.317 Returns dict of the form {provider: (start, end)}.
313 """318 """
314 _mappings = parse_mappings(mappings)319 _mappings = parse_mappings(mappings)
315320
=== added file 'hooks/charmhelpers/contrib/openstack/templates/section-zeromq'
--- hooks/charmhelpers/contrib/openstack/templates/section-zeromq 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/section-zeromq 2015-06-04 08:44:44 +0000
@@ -0,0 +1,14 @@
1{% if zmq_host -%}
2# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
3rpc_backend = zmq
4rpc_zmq_host = {{ zmq_host }}
5{% if zmq_redis_address -%}
6rpc_zmq_matchmaker = redis
7matchmaker_heartbeat_freq = 15
8matchmaker_heartbeat_ttl = 30
9[matchmaker_redis]
10host = {{ zmq_redis_address }}
11{% else -%}
12rpc_zmq_matchmaker = ring
13{% endif -%}
14{% endif -%}
015
=== removed file 'hooks/charmhelpers/contrib/openstack/templates/section-zeromq'
--- hooks/charmhelpers/contrib/openstack/templates/section-zeromq 2015-04-09 02:17:36 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/section-zeromq 1970-01-01 00:00:00 +0000
@@ -1,14 +0,0 @@
1{% if zmq_host -%}
2# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
3rpc_backend = zmq
4rpc_zmq_host = {{ zmq_host }}
5{% if zmq_redis_address -%}
6rpc_zmq_matchmaker = redis
7matchmaker_heartbeat_freq = 15
8matchmaker_heartbeat_ttl = 30
9[matchmaker_redis]
10host = {{ zmq_redis_address }}
11{% else -%}
12rpc_zmq_matchmaker = ring
13{% endif -%}
14{% endif -%}
150
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2015-04-16 14:09:47 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-06-04 08:44:44 +0000
@@ -53,9 +53,13 @@
53 get_ipv6_addr53 get_ipv6_addr
54)54)
5555
56from charmhelpers.contrib.python.packages import (
57 pip_create_virtualenv,
58 pip_install,
59)
60
56from charmhelpers.core.host import lsb_release, mounts, umount61from charmhelpers.core.host import lsb_release, mounts, umount
57from charmhelpers.fetch import apt_install, apt_cache, install_remote62from charmhelpers.fetch import apt_install, apt_cache, install_remote
58from charmhelpers.contrib.python.packages import pip_install
59from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk63from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
60from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device64from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
6165
@@ -497,7 +501,17 @@
497requirements_dir = None501requirements_dir = None
498502
499503
500def git_clone_and_install(projects_yaml, core_project):504def _git_yaml_load(projects_yaml):
505 """
506 Load the specified yaml into a dictionary.
507 """
508 if not projects_yaml:
509 return None
510
511 return yaml.load(projects_yaml)
512
513
514def git_clone_and_install(projects_yaml, core_project, depth=1):
501 """515 """
502 Clone/install all specified OpenStack repositories.516 Clone/install all specified OpenStack repositories.
503517
@@ -510,23 +524,22 @@
510 repository: 'git://git.openstack.org/openstack/requirements.git',524 repository: 'git://git.openstack.org/openstack/requirements.git',
511 branch: 'stable/icehouse'}525 branch: 'stable/icehouse'}
512 directory: /mnt/openstack-git526 directory: /mnt/openstack-git
513 http_proxy: http://squid.internal:3128527 http_proxy: squid-proxy-url
514 https_proxy: https://squid.internal:3128528 https_proxy: squid-proxy-url
515529
516 The directory, http_proxy, and https_proxy keys are optional.530 The directory, http_proxy, and https_proxy keys are optional.
517 """531 """
518 global requirements_dir532 global requirements_dir
519 parent_dir = '/mnt/openstack-git'533 parent_dir = '/mnt/openstack-git'
520534 http_proxy = None
521 if not projects_yaml:535
522 return536 projects = _git_yaml_load(projects_yaml)
523
524 projects = yaml.load(projects_yaml)
525 _git_validate_projects_yaml(projects, core_project)537 _git_validate_projects_yaml(projects, core_project)
526538
527 old_environ = dict(os.environ)539 old_environ = dict(os.environ)
528540
529 if 'http_proxy' in projects.keys():541 if 'http_proxy' in projects.keys():
542 http_proxy = projects['http_proxy']
530 os.environ['http_proxy'] = projects['http_proxy']543 os.environ['http_proxy'] = projects['http_proxy']
531 if 'https_proxy' in projects.keys():544 if 'https_proxy' in projects.keys():
532 os.environ['https_proxy'] = projects['https_proxy']545 os.environ['https_proxy'] = projects['https_proxy']
@@ -534,15 +547,19 @@
534 if 'directory' in projects.keys():547 if 'directory' in projects.keys():
535 parent_dir = projects['directory']548 parent_dir = projects['directory']
536549
550 pip_create_virtualenv(os.path.join(parent_dir, 'venv'))
551
537 for p in projects['repositories']:552 for p in projects['repositories']:
538 repo = p['repository']553 repo = p['repository']
539 branch = p['branch']554 branch = p['branch']
540 if p['name'] == 'requirements':555 if p['name'] == 'requirements':
541 repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,556 repo_dir = _git_clone_and_install_single(repo, branch, depth,
557 parent_dir, http_proxy,
542 update_requirements=False)558 update_requirements=False)
543 requirements_dir = repo_dir559 requirements_dir = repo_dir
544 else:560 else:
545 repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,561 repo_dir = _git_clone_and_install_single(repo, branch, depth,
562 parent_dir, http_proxy,
546 update_requirements=True)563 update_requirements=True)
547564
548 os.environ = old_environ565 os.environ = old_environ
@@ -574,7 +591,8 @@
574 error_out('openstack-origin-git key \'{}\' is missing'.format(key))591 error_out('openstack-origin-git key \'{}\' is missing'.format(key))
575592
576593
577def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):594def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
595 update_requirements):
578 """596 """
579 Clone and install a single git repository.597 Clone and install a single git repository.
580 """598 """
@@ -587,7 +605,8 @@
587605
588 if not os.path.exists(dest_dir):606 if not os.path.exists(dest_dir):
589 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))607 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
590 repo_dir = install_remote(repo, dest=parent_dir, branch=branch)608 repo_dir = install_remote(repo, dest=parent_dir, branch=branch,
609 depth=depth)
591 else:610 else:
592 repo_dir = dest_dir611 repo_dir = dest_dir
593612
@@ -598,7 +617,12 @@
598 _git_update_requirements(repo_dir, requirements_dir)617 _git_update_requirements(repo_dir, requirements_dir)
599618
600 juju_log('Installing git repo from dir: {}'.format(repo_dir))619 juju_log('Installing git repo from dir: {}'.format(repo_dir))
601 pip_install(repo_dir)620 if http_proxy:
621 pip_install(repo_dir, proxy=http_proxy,
622 venv=os.path.join(parent_dir, 'venv'))
623 else:
624 pip_install(repo_dir,
625 venv=os.path.join(parent_dir, 'venv'))
602626
603 return repo_dir627 return repo_dir
604628
@@ -621,16 +645,27 @@
621 os.chdir(orig_dir)645 os.chdir(orig_dir)
622646
623647
648def git_pip_venv_dir(projects_yaml):
649 """
650 Return the pip virtualenv path.
651 """
652 parent_dir = '/mnt/openstack-git'
653
654 projects = _git_yaml_load(projects_yaml)
655
656 if 'directory' in projects.keys():
657 parent_dir = projects['directory']
658
659 return os.path.join(parent_dir, 'venv')
660
661
624def git_src_dir(projects_yaml, project):662def git_src_dir(projects_yaml, project):
625 """663 """
626 Return the directory where the specified project's source is located.664 Return the directory where the specified project's source is located.
627 """665 """
628 parent_dir = '/mnt/openstack-git'666 parent_dir = '/mnt/openstack-git'
629667
630 if not projects_yaml:668 projects = _git_yaml_load(projects_yaml)
631 return
632
633 projects = yaml.load(projects_yaml)
634669
635 if 'directory' in projects.keys():670 if 'directory' in projects.keys():
636 parent_dir = projects['directory']671 parent_dir = projects['directory']
@@ -640,3 +675,15 @@
640 return os.path.join(parent_dir, os.path.basename(p['repository']))675 return os.path.join(parent_dir, os.path.basename(p['repository']))
641676
642 return None677 return None
678
679
680def git_yaml_value(projects_yaml, key):
681 """
682 Return the value in projects_yaml for the specified key.
683 """
684 projects = _git_yaml_load(projects_yaml)
685
686 if key in projects.keys():
687 return projects[key]
688
689 return None
643690
=== modified file 'hooks/charmhelpers/contrib/peerstorage/__init__.py'
--- hooks/charmhelpers/contrib/peerstorage/__init__.py 2015-03-11 11:45:09 +0000
+++ hooks/charmhelpers/contrib/peerstorage/__init__.py 2015-06-04 08:44:44 +0000
@@ -14,14 +14,19 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import json
17import six18import six
19
18from charmhelpers.core.hookenv import relation_id as current_relation_id20from charmhelpers.core.hookenv import relation_id as current_relation_id
19from charmhelpers.core.hookenv import (21from charmhelpers.core.hookenv import (
20 is_relation_made,22 is_relation_made,
21 relation_ids,23 relation_ids,
22 relation_get,24 relation_get as _relation_get,
23 local_unit,25 local_unit,
24 relation_set,26 relation_set as _relation_set,
27 leader_get as _leader_get,
28 leader_set,
29 is_leader,
25)30)
2631
2732
@@ -54,6 +59,105 @@
54"""59"""
5560
5661
62def leader_get(attribute=None):
63 """Wrapper to ensure that settings are migrated from the peer relation.
64
65 This is to support upgrading an environment that does not support
66 Juju leadership election to one that does.
67
68 If a setting is not extant in the leader-get but is on the relation-get
69 peer rel, it is migrated and marked as such so that it is not re-migrated.
70 """
71 migration_key = '__leader_get_migrated_settings__'
72 if not is_leader():
73 return _leader_get(attribute=attribute)
74
75 settings_migrated = False
76 leader_settings = _leader_get(attribute=attribute)
77 previously_migrated = _leader_get(attribute=migration_key)
78
79 if previously_migrated:
80 migrated = set(json.loads(previously_migrated))
81 else:
82 migrated = set([])
83
84 try:
85 if migration_key in leader_settings:
86 del leader_settings[migration_key]
87 except TypeError:
88 pass
89
90 if attribute:
91 if attribute in migrated:
92 return leader_settings
93
94 # If attribute not present in leader db, check if this unit has set
95 # the attribute in the peer relation
96 if not leader_settings:
97 peer_setting = relation_get(attribute=attribute, unit=local_unit())
98 if peer_setting:
99 leader_set(settings={attribute: peer_setting})
100 leader_settings = peer_setting
101
102 if leader_settings:
103 settings_migrated = True
104 migrated.add(attribute)
105 else:
106 r_settings = relation_get(unit=local_unit())
107 if r_settings:
108 for key in set(r_settings.keys()).difference(migrated):
109 # Leader setting wins
110 if not leader_settings.get(key):
111 leader_settings[key] = r_settings[key]
112
113 settings_migrated = True
114 migrated.add(key)
115
116 if settings_migrated:
117 leader_set(**leader_settings)
118
119 if migrated and settings_migrated:
120 migrated = json.dumps(list(migrated))
121 leader_set(settings={migration_key: migrated})
122
123 return leader_settings
124
125
126def relation_set(relation_id=None, relation_settings=None, **kwargs):
127 """Attempt to use leader-set if supported in the current version of Juju,
128 otherwise falls back on relation-set.
129
130 Note that we only attempt to use leader-set if the provided relation_id is
131 a peer relation id or no relation id is provided (in which case we assume
132 we are within the peer relation context).
133 """
134 try:
135 if relation_id in relation_ids('cluster'):
136 return leader_set(settings=relation_settings, **kwargs)
137 else:
138 raise NotImplementedError
139 except NotImplementedError:
140 return _relation_set(relation_id=relation_id,
141 relation_settings=relation_settings, **kwargs)
142
143
144def relation_get(attribute=None, unit=None, rid=None):
145 """Attempt to use leader-get if supported in the current version of Juju,
146 otherwise falls back on relation-get.
147
148 Note that we only attempt to use leader-get if the provided rid is a peer
149 relation id or no relation id is provided (in which case we assume we are
150 within the peer relation context).
151 """
152 try:
153 if rid in relation_ids('cluster'):
154 return leader_get(attribute)
155 else:
156 raise NotImplementedError
157 except NotImplementedError:
158 return _relation_get(attribute=attribute, rid=rid, unit=unit)
159
160
57def peer_retrieve(key, relation_name='cluster'):161def peer_retrieve(key, relation_name='cluster'):
58 """Retrieve a named key from peer relation `relation_name`."""162 """Retrieve a named key from peer relation `relation_name`."""
59 cluster_rels = relation_ids(relation_name)163 cluster_rels = relation_ids(relation_name)
@@ -73,6 +177,8 @@
73 exc_list = exc_list if exc_list else []177 exc_list = exc_list if exc_list else []
74 peerdb_settings = peer_retrieve('-', relation_name=relation_name)178 peerdb_settings = peer_retrieve('-', relation_name=relation_name)
75 matched = {}179 matched = {}
180 if peerdb_settings is None:
181 return matched
76 for k, v in peerdb_settings.items():182 for k, v in peerdb_settings.items():
77 full_prefix = prefix + delimiter183 full_prefix = prefix + delimiter
78 if k.startswith(full_prefix):184 if k.startswith(full_prefix):
@@ -96,12 +202,26 @@
96 'peer relation {}'.format(relation_name))202 'peer relation {}'.format(relation_name))
97203
98204
99def peer_echo(includes=None):205def peer_echo(includes=None, force=False):
100 """Echo filtered attributes back onto the same relation for storage.206 """Echo filtered attributes back onto the same relation for storage.
101207
102 This is a requirement to use the peerstorage module - it needs to be called208 This is a requirement to use the peerstorage module - it needs to be called
103 from the peer relation's changed hook.209 from the peer relation's changed hook.
210
211 If Juju leader support exists this will be a noop unless force is True.
104 """212 """
213 try:
214 is_leader()
215 except NotImplementedError:
216 pass
217 else:
218 if not force:
219 return # NOOP if leader-election is supported
220
221 # Use original non-leader calls
222 relation_get = _relation_get
223 relation_set = _relation_set
224
105 rdata = relation_get()225 rdata = relation_get()
106 echo_data = {}226 echo_data = {}
107 if includes is None:227 if includes is None:
108228
=== modified file 'hooks/charmhelpers/contrib/python/packages.py'
--- hooks/charmhelpers/contrib/python/packages.py 2015-03-11 11:45:09 +0000
+++ hooks/charmhelpers/contrib/python/packages.py 2015-06-04 08:44:44 +0000
@@ -17,8 +17,11 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1919
20import os
21import subprocess
22
20from charmhelpers.fetch import apt_install, apt_update23from charmhelpers.fetch import apt_install, apt_update
21from charmhelpers.core.hookenv import log24from charmhelpers.core.hookenv import charm_dir, log
2225
23try:26try:
24 from pip import main as pip_execute27 from pip import main as pip_execute
@@ -51,11 +54,15 @@
51 pip_execute(command)54 pip_execute(command)
5255
5356
54def pip_install(package, fatal=False, upgrade=False, **options):57def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
55 """Install a python package"""58 """Install a python package"""
56 command = ["install"]59 if venv:
60 venv_python = os.path.join(venv, 'bin/pip')
61 command = [venv_python, "install"]
62 else:
63 command = ["install"]
5764
58 available_options = ('proxy', 'src', 'log', "index-url", )65 available_options = ('proxy', 'src', 'log', 'index-url', )
59 for option in parse_options(options, available_options):66 for option in parse_options(options, available_options):
60 command.append(option)67 command.append(option)
6168
@@ -69,7 +76,10 @@
6976
70 log("Installing {} package with options: {}".format(package,77 log("Installing {} package with options: {}".format(package,
71 command))78 command))
72 pip_execute(command)79 if venv:
80 subprocess.check_call(command)
81 else:
82 pip_execute(command)
7383
7484
75def pip_uninstall(package, **options):85def pip_uninstall(package, **options):
@@ -94,3 +104,16 @@
94 """Returns the list of current python installed packages104 """Returns the list of current python installed packages
95 """105 """
96 return pip_execute(["list"])106 return pip_execute(["list"])
107
108
109def pip_create_virtualenv(path=None):
110 """Create an isolated Python environment."""
111 apt_install('python-virtualenv')
112
113 if path:
114 venv_path = path
115 else:
116 venv_path = os.path.join(charm_dir(), 'venv')
117
118 if not os.path.exists(venv_path):
119 subprocess.check_call(['virtualenv', venv_path])
97120
=== modified file 'hooks/charmhelpers/contrib/unison/__init__.py'
--- hooks/charmhelpers/contrib/unison/__init__.py 2015-03-18 18:59:03 +0000
+++ hooks/charmhelpers/contrib/unison/__init__.py 2015-06-04 08:44:44 +0000
@@ -63,6 +63,7 @@
63from charmhelpers.core.host import (63from charmhelpers.core.host import (
64 adduser,64 adduser,
65 add_user_to_group,65 add_user_to_group,
66 pwgen,
66)67)
6768
68from charmhelpers.core.hookenv import (69from charmhelpers.core.hookenv import (
@@ -140,7 +141,7 @@
140 ssh_dir = os.path.join(home_dir, '.ssh')141 ssh_dir = os.path.join(home_dir, '.ssh')
141 auth_keys = os.path.join(ssh_dir, 'authorized_keys')142 auth_keys = os.path.join(ssh_dir, 'authorized_keys')
142 log('Syncing authorized_keys @ %s.' % auth_keys)143 log('Syncing authorized_keys @ %s.' % auth_keys)
143 with open(auth_keys, 'wb') as out:144 with open(auth_keys, 'w') as out:
144 for k in keys:145 for k in keys:
145 out.write('%s\n' % k)146 out.write('%s\n' % k)
146147
@@ -152,16 +153,16 @@
152 khosts = []153 khosts = []
153 for host in hosts:154 for host in hosts:
154 cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]155 cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]
155 remote_key = check_output(cmd).strip()156 remote_key = check_output(cmd, universal_newlines=True).strip()
156 khosts.append(remote_key)157 khosts.append(remote_key)
157 log('Syncing known_hosts @ %s.' % known_hosts)158 log('Syncing known_hosts @ %s.' % known_hosts)
158 with open(known_hosts, 'wb') as out:159 with open(known_hosts, 'w') as out:
159 for host in khosts:160 for host in khosts:
160 out.write('%s\n' % host)161 out.write('%s\n' % host)
161162
162163
163def ensure_user(user, group=None):164def ensure_user(user, group=None):
164 adduser(user)165 adduser(user, pwgen())
165 if group:166 if group:
166 add_user_to_group(user, group)167 add_user_to_group(user, group)
167168
168169
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-04-15 15:21:50 +0000
+++ hooks/charmhelpers/core/hookenv.py 2015-06-04 08:44:44 +0000
@@ -21,12 +21,14 @@
21# Charm Helpers Developers <juju@lists.ubuntu.com>21# Charm Helpers Developers <juju@lists.ubuntu.com>
2222
23from __future__ import print_function23from __future__ import print_function
24from functools import wraps
24import os25import os
25import json26import json
26import yaml27import yaml
27import subprocess28import subprocess
28import sys29import sys
29import errno30import errno
31import tempfile
30from subprocess import CalledProcessError32from subprocess import CalledProcessError
3133
32import six34import six
@@ -58,15 +60,17 @@
5860
59 will cache the result of unit_get + 'test' for future calls.61 will cache the result of unit_get + 'test' for future calls.
60 """62 """
63 @wraps(func)
61 def wrapper(*args, **kwargs):64 def wrapper(*args, **kwargs):
62 global cache65 global cache
63 key = str((func, args, kwargs))66 key = str((func, args, kwargs))
64 try:67 try:
65 return cache[key]68 return cache[key]
66 except KeyError:69 except KeyError:
67 res = func(*args, **kwargs)70 pass # Drop out of the exception handler scope.
68 cache[key] = res71 res = func(*args, **kwargs)
69 return res72 cache[key] = res
73 return res
70 return wrapper74 return wrapper
7175
7276
@@ -178,7 +182,7 @@
178182
179def remote_unit():183def remote_unit():
180 """The remote unit for the current relation hook"""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)
182186
183187
184def service_name():188def service_name():
@@ -250,6 +254,12 @@
250 except KeyError:254 except KeyError:
251 return (self._prev_dict or {})[key]255 return (self._prev_dict or {})[key]
252256
257 def get(self, key, default=None):
258 try:
259 return self[key]
260 except KeyError:
261 return default
262
253 def keys(self):263 def keys(self):
254 prev_keys = []264 prev_keys = []
255 if self._prev_dict is not None:265 if self._prev_dict is not None:
@@ -353,18 +363,49 @@
353 """Set relation information for the current unit"""363 """Set relation information for the current unit"""
354 relation_settings = relation_settings if relation_settings else {}364 relation_settings = relation_settings if relation_settings else {}
355 relation_cmd_line = ['relation-set']365 relation_cmd_line = ['relation-set']
366 accepts_file = "--file" in subprocess.check_output(
367 relation_cmd_line + ["--help"], universal_newlines=True)
356 if relation_id is not None:368 if relation_id is not None:
357 relation_cmd_line.extend(('-r', relation_id))369 relation_cmd_line.extend(('-r', relation_id))
358 for k, v in (list(relation_settings.items()) + list(kwargs.items())):370 settings = relation_settings.copy()
359 if v is None:371 settings.update(kwargs)
360 relation_cmd_line.append('{}='.format(k))372 for key, value in settings.items():
361 else:373 # Force value to be a string: it always should, but some call
362 relation_cmd_line.append('{}={}'.format(k, v))374 # sites pass in things like dicts or numbers.
363 subprocess.check_call(relation_cmd_line)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 # Flush cache of any relation-gets for local unit394 # Flush cache of any relation-gets for local unit
365 flush(local_unit())395 flush(local_unit())
366396
367397
398def 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@cached409@cached
369def relation_ids(reltype=None):410def relation_ids(reltype=None):
370 """A list of relation_ids"""411 """A list of relation_ids"""
@@ -509,6 +550,11 @@
509 return None550 return None
510551
511552
553def unit_public_ip():
554 """Get this unit's public IP address"""
555 return unit_get('public-address')
556
557
512def unit_private_ip():558def unit_private_ip():
513 """Get this unit's private IP address"""559 """Get this unit's private IP address"""
514 return unit_get('private-address')560 return unit_get('private-address')
@@ -605,3 +651,94 @@
605651
606 The results set by action_set are preserved."""652 The results set by action_set are preserved."""
607 subprocess.check_call(['action-fail', message])653 subprocess.check_call(['action-fail', message])
654
655
656def 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
684def 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
702def 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)
716def 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)
726def 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)
733def 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)
608745
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-03-30 11:43:06 +0000
+++ hooks/charmhelpers/core/host.py 2015-06-04 08:44:44 +0000
@@ -90,7 +90,7 @@
90 ['service', service_name, 'status'],90 ['service', service_name, 'status'],
91 stderr=subprocess.STDOUT).decode('UTF-8')91 stderr=subprocess.STDOUT).decode('UTF-8')
92 except subprocess.CalledProcessError as e:92 except subprocess.CalledProcessError as e:
93 return 'unrecognized service' not in e.output93 return b'unrecognized service' not in e.output
94 else:94 else:
95 return True95 return True
9696
9797
=== modified file 'hooks/charmhelpers/core/services/base.py'
--- hooks/charmhelpers/core/services/base.py 2015-03-11 11:45:09 +0000
+++ hooks/charmhelpers/core/services/base.py 2015-06-04 08:44:44 +0000
@@ -15,9 +15,9 @@
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import os17import os
18import re
19import json18import json
20from collections import Iterable19from inspect import getargspec
20from collections import Iterable, OrderedDict
2121
22from charmhelpers.core import host22from charmhelpers.core import host
23from charmhelpers.core import hookenv23from charmhelpers.core import hookenv
@@ -119,7 +119,7 @@
119 """119 """
120 self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')120 self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
121 self._ready = None121 self._ready = None
122 self.services = {}122 self.services = OrderedDict()
123 for service in services or []:123 for service in services or []:
124 service_name = service['service']124 service_name = service['service']
125 self.services[service_name] = service125 self.services[service_name] = service
@@ -132,8 +132,8 @@
132 if hook_name == 'stop':132 if hook_name == 'stop':
133 self.stop_services()133 self.stop_services()
134 else:134 else:
135 self.reconfigure_services()
135 self.provide_data()136 self.provide_data()
136 self.reconfigure_services()
137 cfg = hookenv.config()137 cfg = hookenv.config()
138 if cfg.implicit_save:138 if cfg.implicit_save:
139 cfg.save()139 cfg.save()
@@ -145,15 +145,36 @@
145 A provider must have a `name` attribute, which indicates which relation145 A provider must have a `name` attribute, which indicates which relation
146 to set data on, and a `provide_data()` method, which returns a dict of146 to set data on, and a `provide_data()` method, which returns a dict of
147 data to set.147 data to set.
148
149 The `provide_data()` method can optionally accept two parameters:
150
151 * ``remote_service`` The name of the remote service that the data will
152 be provided to. The `provide_data()` method will be called once
153 for each connected service (not unit). This allows the method to
154 tailor its data to the given service.
155 * ``service_ready`` Whether or not the service definition had all of
156 its requirements met, and thus the ``data_ready`` callbacks run.
157
158 Note that the ``provided_data`` methods are now called **after** the
159 ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks
160 a chance to generate any data necessary for the providing to the remote
161 services.
148 """162 """
149 hook_name = hookenv.hook_name()163 for service_name, service in self.services.items():
150 for service in self.services.values():164 service_ready = self.is_ready(service_name)
151 for provider in service.get('provided_data', []):165 for provider in service.get('provided_data', []):
152 if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name):166 for relid in hookenv.relation_ids(provider.name):
153 data = provider.provide_data()167 units = hookenv.related_units(relid)
154 _ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data168 if not units:
155 if _ready:169 continue
156 hookenv.relation_set(None, data)170 remote_service = units[0].split('/')[0]
171 argspec = getargspec(provider.provide_data)
172 if len(argspec.args) > 1:
173 data = provider.provide_data(remote_service, service_ready)
174 else:
175 data = provider.provide_data()
176 if data:
177 hookenv.relation_set(relid, data)
157178
158 def reconfigure_services(self, *service_names):179 def reconfigure_services(self, *service_names):
159 """180 """
160181
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2015-03-11 11:45:09 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2015-06-04 08:44:44 +0000
@@ -158,7 +158,7 @@
158158
159def apt_cache(in_memory=True):159def apt_cache(in_memory=True):
160 """Build and return an apt cache"""160 """Build and return an apt cache"""
161 import apt_pkg161 from apt import apt_pkg
162 apt_pkg.init()162 apt_pkg.init()
163 if in_memory:163 if in_memory:
164 apt_pkg.config.set("Dir::Cache::pkgcache", "")164 apt_pkg.config.set("Dir::Cache::pkgcache", "")
165165
=== modified file 'hooks/charmhelpers/fetch/giturl.py'
--- hooks/charmhelpers/fetch/giturl.py 2015-03-11 11:45:09 +0000
+++ hooks/charmhelpers/fetch/giturl.py 2015-06-04 08:44:44 +0000
@@ -45,14 +45,16 @@
45 else:45 else:
46 return True46 return True
4747
48 def clone(self, source, dest, branch):48 def clone(self, source, dest, branch, depth=None):
49 if not self.can_handle(source):49 if not self.can_handle(source):
50 raise UnhandledSource("Cannot handle {}".format(source))50 raise UnhandledSource("Cannot handle {}".format(source))
5151
52 repo = Repo.clone_from(source, dest)52 if depth:
53 repo.git.checkout(branch)53 Repo.clone_from(source, dest, branch=branch, depth=depth)
54 else:
55 Repo.clone_from(source, dest, branch=branch)
5456
55 def install(self, source, branch="master", dest=None):57 def install(self, source, branch="master", dest=None, depth=None):
56 url_parts = self.parse_url(source)58 url_parts = self.parse_url(source)
57 branch_name = url_parts.path.strip("/").split("/")[-1]59 branch_name = url_parts.path.strip("/").split("/")[-1]
58 if dest:60 if dest:
@@ -63,7 +65,7 @@
63 if not os.path.exists(dest_dir):65 if not os.path.exists(dest_dir):
64 mkdir(dest_dir, perms=0o755)66 mkdir(dest_dir, perms=0o755)
65 try:67 try:
66 self.clone(source, dest_dir, branch)68 self.clone(source, dest_dir, branch, depth)
67 except GitCommandError as e:69 except GitCommandError as e:
68 raise UnhandledSource(e.message)70 raise UnhandledSource(e.message)
69 except OSError as e:71 except OSError as e:
7072
=== modified file 'hooks/keystone_hooks.py'
--- hooks/keystone_hooks.py 2015-04-01 14:39:21 +0000
+++ hooks/keystone_hooks.py 2015-06-04 08:44:44 +0000
@@ -2,7 +2,6 @@
2import hashlib2import hashlib
3import json3import json
4import os4import os
5import stat
6import sys5import sys
76
8from subprocess import check_call7from subprocess import check_call
@@ -68,18 +67,18 @@
68 setup_ipv6,67 setup_ipv6,
69 send_notifications,68 send_notifications,
70 check_peer_actions,69 check_peer_actions,
71 CA_CERT_PATH,
72 ensure_permissions,
73 get_ssl_sync_request_units,70 get_ssl_sync_request_units,
74 is_ssl_cert_master,71 is_ssl_cert_master,
75 is_db_ready,72 is_db_ready,
76 clear_ssl_synced_units,73 clear_ssl_synced_units,
77 is_db_initialised,74 is_db_initialised,
75 update_certs_if_available,
78 is_pki_enabled,76 is_pki_enabled,
79 ensure_ssl_dir,77 ensure_ssl_dir,
80 ensure_pki_dir_permissions,78 ensure_pki_dir_permissions,
81 force_ssl_sync,79 force_ssl_sync,
82 filter_null,80 filter_null,
81 ensure_ssl_dirs,
83)82)
8483
85from charmhelpers.contrib.hahelpers.cluster import (84from charmhelpers.contrib.hahelpers.cluster import (
@@ -149,13 +148,7 @@
149148
150 check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])149 check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
151150
152 # Ensure unison can write to certs dir.151 ensure_ssl_dirs()
153 # FIXME: need to a better way around this e.g. move cert to it's own dir
154 # and give that unison permissions.
155 path = os.path.dirname(CA_CERT_PATH)
156 perms = int(oct(stat.S_IMODE(os.stat(path).st_mode) |
157 (stat.S_IWGRP | stat.S_IXGRP)), base=8)
158 ensure_permissions(path, group='keystone', perms=perms)
159152
160 save_script_rc()153 save_script_rc()
161 configure_https()154 configure_https()
@@ -423,6 +416,7 @@
423@hooks.hook('cluster-relation-changed',416@hooks.hook('cluster-relation-changed',
424 'cluster-relation-departed')417 'cluster-relation-departed')
425@restart_on_change(restart_map(), stopstart=True)418@restart_on_change(restart_map(), stopstart=True)
419@update_certs_if_available
426def cluster_changed():420def cluster_changed():
427 unison.ssh_authorized_peers(user=SSH_USER,421 unison.ssh_authorized_peers(user=SSH_USER,
428 group='juju_keystone',422 group='juju_keystone',
@@ -430,9 +424,9 @@
430 ensure_local_user=True)424 ensure_local_user=True)
431 # NOTE(jamespage) re-echo passwords for peer storage425 # NOTE(jamespage) re-echo passwords for peer storage
432 echo_whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',426 echo_whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',
433 'db-initialised']427 'db-initialised', 'ssl-cert-available-updates']
434 log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG)428 log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG)
435 peer_echo(includes=echo_whitelist)429 peer_echo(includes=echo_whitelist, force=True)
436430
437 check_peer_actions()431 check_peer_actions()
438432
@@ -466,6 +460,14 @@
466 CONFIGS.write_all()460 CONFIGS.write_all()
467461
468462
463@hooks.hook('leader-settings-changed')
464def leader_settings_changed():
465 log('Firing identity_changed hook for all related services.')
466 for rid in relation_ids('identity-service'):
467 for unit in related_units(rid):
468 identity_changed(relation_id=rid, remote_unit=unit)
469
470
469@hooks.hook('ha-relation-joined')471@hooks.hook('ha-relation-joined')
470def ha_joined(relation_id=None):472def ha_joined(relation_id=None):
471 cluster_config = get_hacluster_config()473 cluster_config = get_hacluster_config()
@@ -575,6 +577,8 @@
575 peer_interface='cluster',577 peer_interface='cluster',
576 ensure_local_user=True)578 ensure_local_user=True)
577579
580 ensure_ssl_dirs()
581
578 CONFIGS.write_all()582 CONFIGS.write_all()
579 update_nrpe_config()583 update_nrpe_config()
580584
581585
=== modified file 'hooks/keystone_ssl.py'
--- hooks/keystone_ssl.py 2015-02-18 17:20:23 +0000
+++ hooks/keystone_ssl.py 2015-06-04 08:44:44 +0000
@@ -5,12 +5,10 @@
5import subprocess5import subprocess
6import tarfile6import tarfile
7import tempfile7import tempfile
8import time
98
10from charmhelpers.core.hookenv import (9from charmhelpers.core.hookenv import (
11 log,10 log,
12 DEBUG,11 DEBUG,
13 WARNING,
14)12)
1513
16CA_EXPIRY = '365'14CA_EXPIRY = '365'
@@ -312,31 +310,9 @@
312 if os.path.isfile(crtpath):310 if os.path.isfile(crtpath):
313 log('Found existing certificate for %s.' % common_name,311 log('Found existing certificate for %s.' % common_name,
314 level=DEBUG)312 level=DEBUG)
315 max_retries = 3313 crt = open(crtpath, 'r').read()
316 while True:314 key = open(keypath, 'r').read()
317 mtime = os.path.getmtime(crtpath)315 return crt, key
318
319 crt = open(crtpath, 'r').read()
320 try:
321 key = open(keypath, 'r').read()
322 except:
323 msg = ('Could not load ssl private key for %s from %s' %
324 (common_name, keypath))
325 raise Exception(msg)
326
327 # Ensure we are not reading a file that is being written to
328 if mtime != os.path.getmtime(crtpath):
329 max_retries -= 1
330 if max_retries == 0:
331 msg = ("crt contents changed during read - retry "
332 "failed")
333 raise Exception(msg)
334
335 log("crt contents changed during read - re-reading",
336 level=WARNING)
337 time.sleep(1)
338 else:
339 return crt, key
340316
341 crt, key = self._create_certificate(common_name, common_name)317 crt, key = self._create_certificate(common_name, common_name)
342 return open(crt, 'r').read(), open(key, 'r').read()318 return open(crt, 'r').read(), open(key, 'r').read()
343319
=== modified file 'hooks/keystone_utils.py'
--- hooks/keystone_utils.py 2015-05-08 11:43:00 +0000
+++ hooks/keystone_utils.py 2015-06-04 08:44:44 +0000
@@ -8,6 +8,7 @@
8import re8import re
9import shutil9import shutil
10import subprocess10import subprocess
11import tarfile
11import threading12import threading
12import time13import time
13import urlparse14import urlparse
@@ -71,7 +72,6 @@
71 DEBUG,72 DEBUG,
72 INFO,73 INFO,
73 WARNING,74 WARNING,
74 ERROR,
75)75)
7676
77from charmhelpers.fetch import (77from charmhelpers.fetch import (
@@ -160,6 +160,8 @@
160160
161APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'161APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
162SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'162SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
163SYNC_DIR = '/var/lib/keystone/juju_sync/'
164SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
163SSL_DIR = '/var/lib/keystone/juju_ssl/'165SSL_DIR = '/var/lib/keystone/juju_ssl/'
164PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')166PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
165SSL_CA_NAME = 'Ubuntu Cloud'167SSL_CA_NAME = 'Ubuntu Cloud'
@@ -382,26 +384,20 @@
382 return384 return
383385
384386
385def set_db_initialised():
386 for rid in relation_ids('cluster'):
387 relation_set(relation_settings={'db-initialised': 'True'},
388 relation_id=rid)
389
390
391def is_db_initialised():387def is_db_initialised():
392 for rid in relation_ids('cluster'):388 if relation_ids('cluster'):
393 units = related_units(rid) + [local_unit()]389 inited = peer_retrieve('db-initialised')
394 for unit in units:390 if inited and bool_from_string(inited):
395 db_initialised = relation_get(attribute='db-initialised',391 log("Database is initialised", level=DEBUG)
396 unit=unit, rid=rid)392 return True
397 if db_initialised:
398 log("Database is initialised", level=DEBUG)
399 return True
400393
401 log("Database is NOT initialised", level=DEBUG)394 log("Database is NOT initialised", level=DEBUG)
402 return False395 return False
403396
404397
398# NOTE(jamespage): Retry deals with sync issues during one-shot HA deploys.
399# mysql might be restarting or suchlike.
400@retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
405def migrate_database():401def migrate_database():
406 """Runs keystone-manage to initialize a new database or migrate existing"""402 """Runs keystone-manage to initialize a new database or migrate existing"""
407 log('Migrating the keystone database.', level=INFO)403 log('Migrating the keystone database.', level=INFO)
@@ -413,7 +409,7 @@
413 subprocess.check_output(cmd)409 subprocess.check_output(cmd)
414 service_start('keystone')410 service_start('keystone')
415 time.sleep(10)411 time.sleep(10)
416 set_db_initialised()412 peer_store('db-initialised', 'True')
417413
418# OLD414# OLD
419415
@@ -768,6 +764,16 @@
768 return passwd764 return passwd
769765
770766
767def ensure_ssl_dirs():
768 """Ensure unison has access to these dirs."""
769 for path in [SYNC_FLAGS_DIR, SYNC_DIR]:
770 if not os.path.isdir(path):
771 mkdir(path, SSH_USER, 'juju_keystone', 0o775)
772 else:
773 ensure_permissions(path, user=SSH_USER, group='keystone',
774 perms=0o755)
775
776
771def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,777def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,
772 maxdepth=50):778 maxdepth=50):
773 """Set chownand chmod for path779 """Set chownand chmod for path
@@ -864,7 +870,7 @@
864 service.strip(), action))870 service.strip(), action))
865 log("Creating action %s" % (flagfile), level=DEBUG)871 log("Creating action %s" % (flagfile), level=DEBUG)
866 write_file(flagfile, content='', owner=SSH_USER, group='keystone',872 write_file(flagfile, content='', owner=SSH_USER, group='keystone',
867 perms=0o644)873 perms=0o744)
868874
869875
870def create_peer_actions(actions):876def create_peer_actions(actions):
@@ -873,7 +879,7 @@
873 flagfile = os.path.join(SYNC_FLAGS_DIR, action)879 flagfile = os.path.join(SYNC_FLAGS_DIR, action)
874 log("Creating action %s" % (flagfile), level=DEBUG)880 log("Creating action %s" % (flagfile), level=DEBUG)
875 write_file(flagfile, content='', owner=SSH_USER, group='keystone',881 write_file(flagfile, content='', owner=SSH_USER, group='keystone',
876 perms=0o644)882 perms=0o744)
877883
878884
879@retry_on_exception(3, base_delay=2, exc_type=subprocess.CalledProcessError)885@retry_on_exception(3, base_delay=2, exc_type=subprocess.CalledProcessError)
@@ -1011,6 +1017,22 @@
1011 return True1017 return True
10121018
10131019
1020def stage_paths_for_sync(paths):
1021 shutil.rmtree(SYNC_DIR)
1022 ensure_ssl_dirs()
1023 with tarfile.open(SSL_SYNC_ARCHIVE, 'w') as fd:
1024 for path in paths:
1025 if os.path.exists(path):
1026 log("Adding path '%s' sync tarball" % (path), level=DEBUG)
1027 fd.add(path)
1028 else:
1029 log("Path '%s' does not exist - not adding to sync "
1030 "tarball" % (path), level=INFO)
1031
1032 ensure_permissions(SYNC_DIR, user=SSH_USER, group='keystone',
1033 perms=0o755, recurse=True)
1034
1035
1014def is_pki_enabled():1036def is_pki_enabled():
1015 enable_pki = config('enable-pki')1037 enable_pki = config('enable-pki')
1016 if enable_pki and bool_from_string(enable_pki):1038 if enable_pki and bool_from_string(enable_pki):
@@ -1025,6 +1047,33 @@
1025 perms=0o755, recurse=True)1047 perms=0o755, recurse=True)
10261048
10271049
1050def update_certs_if_available(f):
1051 def _inner_update_certs_if_available(*args, **kwargs):
1052 path = None
1053 for rid in relation_ids('cluster'):
1054 path = relation_get(attribute='ssl-cert-available-updates',
1055 rid=rid, unit=local_unit())
1056
1057 if path and os.path.exists(path):
1058 log("Updating certs from '%s'" % (path), level=DEBUG)
1059 with tarfile.open(path) as fd:
1060 files = ["/%s" % m.name for m in fd.getmembers()]
1061 fd.extractall(path='/')
1062
1063 for syncfile in files:
1064 ensure_permissions(syncfile, user='keystone', group='keystone',
1065 perms=0o744, recurse=True)
1066
1067 # Mark as complete
1068 os.rename(path, "%s.complete" % (path))
1069 else:
1070 log("No cert updates available", level=DEBUG)
1071
1072 return f(*args, **kwargs)
1073
1074 return _inner_update_certs_if_available
1075
1076
1028def synchronize_ca(fatal=False):1077def synchronize_ca(fatal=False):
1029 """Broadcast service credentials to peers.1078 """Broadcast service credentials to peers.
10301079
@@ -1038,7 +1087,7 @@
10381087
1039 Returns a dictionary of settings to be set on the cluster relation.1088 Returns a dictionary of settings to be set on the cluster relation.
1040 """1089 """
1041 paths_to_sync = [SYNC_FLAGS_DIR]1090 paths_to_sync = []
1042 peer_service_actions = {'restart': []}1091 peer_service_actions = {'restart': []}
1043 peer_actions = []1092 peer_actions = []
10441093
@@ -1068,9 +1117,6 @@
1068 paths_to_sync.append(PKI_CERTS_DIR)1117 paths_to_sync.append(PKI_CERTS_DIR)
1069 peer_actions.append('ensure-pki-permissions')1118 peer_actions.append('ensure-pki-permissions')
10701119
1071 # Ensure unique
1072 paths_to_sync = list(set(paths_to_sync))
1073
1074 if not paths_to_sync:1120 if not paths_to_sync:
1075 log("Nothing to sync - skipping", level=DEBUG)1121 log("Nothing to sync - skipping", level=DEBUG)
1076 return {}1122 return {}
@@ -1083,50 +1129,27 @@
10831129
1084 create_peer_actions(peer_actions)1130 create_peer_actions(peer_actions)
10851131
1086 cluster_rel_settings = {}1132 paths_to_sync = list(set(paths_to_sync))
10871133 stage_paths_for_sync(paths_to_sync)
1088 retries = 31134
1089 while True:1135 hash1 = hashlib.sha256()
1090 hash1 = hashlib.sha256()1136 for path in paths_to_sync:
1091 for path in paths_to_sync:1137 update_hash_from_path(hash1, path)
1092 update_hash_from_path(hash1, path)1138
10931139 cluster_rel_settings = {'ssl-cert-available-updates': SSL_SYNC_ARCHIVE,
1094 try:1140 'sync-hash': hash1.hexdigest()}
1095 synced_units = unison_sync(paths_to_sync)1141
1096 if synced_units:1142 synced_units = unison_sync([SSL_SYNC_ARCHIVE, SYNC_FLAGS_DIR])
1097 # Format here needs to match that used when peers request sync1143 if synced_units:
1098 synced_units = [u.replace('/', '-') for u in synced_units]1144 # Format here needs to match that used when peers request sync
1099 cluster_rel_settings['ssl-synced-units'] = \1145 synced_units = [u.replace('/', '-') for u in synced_units]
1100 json.dumps(synced_units)1146 cluster_rel_settings['ssl-synced-units'] = \
1101 except Exception as exc:1147 json.dumps(synced_units)
1102 if fatal:1148
1103 raise1149 trigger = str(uuid.uuid4())
1104 else:1150 log("Sending restart-services-trigger=%s to all peers" % (trigger),
1105 log("Sync failed but fatal=False - %s" % (exc), level=INFO)
1106 return {}
1107
1108 hash2 = hashlib.sha256()
1109 for path in paths_to_sync:
1110 update_hash_from_path(hash2, path)
1111
1112 # Detect whether someone else has synced to this unit while we did our
1113 # transfer.
1114 if hash1.hexdigest() != hash2.hexdigest():
1115 retries -= 1
1116 if retries > 0:
1117 log("SSL dir contents changed during sync - retrying unison "
1118 "sync %s more times" % (retries), level=WARNING)
1119 else:
1120 log("SSL dir contents changed during sync - retries failed",
1121 level=ERROR)
1122 return {}
1123 else:
1124 break
1125
1126 hash = hash1.hexdigest()
1127 log("Sending restart-services-trigger=%s to all peers" % (hash),
1128 level=DEBUG)1151 level=DEBUG)
1129 cluster_rel_settings['restart-services-trigger'] = hash1152 cluster_rel_settings['restart-services-trigger'] = trigger
11301153
1131 log("Sync complete", level=DEBUG)1154 log("Sync complete", level=DEBUG)
1132 return cluster_rel_settings1155 return cluster_rel_settings
11331156
=== added symlink 'hooks/leader-settings-changed'
=== target is u'keystone_hooks.py'
=== modified file 'unit_tests/test_keystone_hooks.py'
--- unit_tests/test_keystone_hooks.py 2015-04-17 12:10:54 +0000
+++ unit_tests/test_keystone_hooks.py 2015-06-04 08:44:44 +0000
@@ -59,6 +59,9 @@
59 'add_service_to_keystone',59 'add_service_to_keystone',
60 'synchronize_ca_if_changed',60 'synchronize_ca_if_changed',
61 'update_nrpe_config',61 'update_nrpe_config',
62 'ensure_ssl_dirs',
63 'is_db_initialised',
64 'is_db_ready',
62 # other65 # other
63 'check_call',66 'check_call',
64 'execd_preinstall',67 'execd_preinstall',
@@ -237,18 +240,15 @@
237 configs.write = MagicMock()240 configs.write = MagicMock()
238 hooks.pgsql_db_changed()241 hooks.pgsql_db_changed()
239242
240 @patch.object(hooks, 'is_db_initialised')
241 @patch.object(hooks, 'is_db_ready')
242 @patch('keystone_utils.log')243 @patch('keystone_utils.log')
243 @patch('keystone_utils.ensure_ssl_cert_master')244 @patch('keystone_utils.ensure_ssl_cert_master')
244 @patch.object(hooks, 'CONFIGS')245 @patch.object(hooks, 'CONFIGS')
245 @patch.object(hooks, 'identity_changed')246 @patch.object(hooks, 'identity_changed')
246 def test_db_changed_allowed(self, identity_changed, configs,247 def test_db_changed_allowed(self, identity_changed, configs,
247 mock_ensure_ssl_cert_master,248 mock_ensure_ssl_cert_master,
248 mock_log, mock_is_db_ready,249 mock_log):
249 mock_is_db_initialised):250 self.is_db_initialised.return_value = True
250 mock_is_db_initialised.return_value = True251 self.is_db_ready.return_value = True
251 mock_is_db_ready.return_value = True
252 mock_ensure_ssl_cert_master.return_value = False252 mock_ensure_ssl_cert_master.return_value = False
253 self.relation_ids.return_value = ['identity-service:0']253 self.relation_ids.return_value = ['identity-service:0']
254 self.related_units.return_value = ['unit/0']254 self.related_units.return_value = ['unit/0']
@@ -262,15 +262,13 @@
262 relation_id='identity-service:0',262 relation_id='identity-service:0',
263 remote_unit='unit/0')263 remote_unit='unit/0')
264264
265 @patch.object(hooks, 'is_db_ready')
266 @patch('keystone_utils.log')265 @patch('keystone_utils.log')
267 @patch('keystone_utils.ensure_ssl_cert_master')266 @patch('keystone_utils.ensure_ssl_cert_master')
268 @patch.object(hooks, 'CONFIGS')267 @patch.object(hooks, 'CONFIGS')
269 @patch.object(hooks, 'identity_changed')268 @patch.object(hooks, 'identity_changed')
270 def test_db_changed_not_allowed(self, identity_changed, configs,269 def test_db_changed_not_allowed(self, identity_changed, configs,
271 mock_ensure_ssl_cert_master, mock_log,270 mock_ensure_ssl_cert_master, mock_log):
272 mock_is_db_ready):271 self.is_db_ready.return_value = False
273 mock_is_db_ready.return_value = False
274 mock_ensure_ssl_cert_master.return_value = False272 mock_ensure_ssl_cert_master.return_value = False
275 self.relation_ids.return_value = ['identity-service:0']273 self.relation_ids.return_value = ['identity-service:0']
276 self.related_units.return_value = ['unit/0']274 self.related_units.return_value = ['unit/0']
@@ -284,15 +282,12 @@
284282
285 @patch('keystone_utils.log')283 @patch('keystone_utils.log')
286 @patch('keystone_utils.ensure_ssl_cert_master')284 @patch('keystone_utils.ensure_ssl_cert_master')
287 @patch.object(hooks, 'is_db_initialised')
288 @patch.object(hooks, 'is_db_ready')
289 @patch.object(hooks, 'CONFIGS')285 @patch.object(hooks, 'CONFIGS')
290 @patch.object(hooks, 'identity_changed')286 @patch.object(hooks, 'identity_changed')
291 def test_postgresql_db_changed(self, identity_changed, configs,287 def test_postgresql_db_changed(self, identity_changed, configs,
292 mock_is_db_ready, mock_is_db_initialised,
293 mock_ensure_ssl_cert_master, mock_log):288 mock_ensure_ssl_cert_master, mock_log):
294 mock_is_db_initialised.return_value = True289 self.is_db_initialised.return_value = True
295 mock_is_db_ready.return_value = True290 self.is_db_ready.return_value = True
296 mock_ensure_ssl_cert_master.return_value = False291 mock_ensure_ssl_cert_master.return_value = False
297 self.relation_ids.return_value = ['identity-service:0']292 self.relation_ids.return_value = ['identity-service:0']
298 self.related_units.return_value = ['unit/0']293 self.related_units.return_value = ['unit/0']
@@ -309,15 +304,13 @@
309 @patch.object(hooks, 'git_install_requested')304 @patch.object(hooks, 'git_install_requested')
310 @patch('keystone_utils.log')305 @patch('keystone_utils.log')
311 @patch('keystone_utils.ensure_ssl_cert_master')306 @patch('keystone_utils.ensure_ssl_cert_master')
307 @patch('keystone_utils.ensure_ssl_dirs')
312 @patch.object(hooks, 'ensure_pki_dir_permissions')308 @patch.object(hooks, 'ensure_pki_dir_permissions')
313 @patch.object(hooks, 'ensure_ssl_dir')309 @patch.object(hooks, 'ensure_ssl_dir')
314 @patch.object(hooks, 'is_pki_enabled')310 @patch.object(hooks, 'is_pki_enabled')
315 @patch.object(hooks, 'is_ssl_cert_master')311 @patch.object(hooks, 'is_ssl_cert_master')
316 @patch.object(hooks, 'send_ssl_sync_request')312 @patch.object(hooks, 'send_ssl_sync_request')
317 @patch.object(hooks, 'is_db_initialised')
318 @patch.object(hooks, 'is_db_ready')
319 @patch.object(hooks, 'peer_units')313 @patch.object(hooks, 'peer_units')
320 @patch.object(hooks, 'ensure_permissions')
321 @patch.object(hooks, 'admin_relation_changed')314 @patch.object(hooks, 'admin_relation_changed')
322 @patch.object(hooks, 'cluster_joined')315 @patch.object(hooks, 'cluster_joined')
323 @patch.object(unison, 'ensure_user')316 @patch.object(unison, 'ensure_user')
@@ -331,22 +324,20 @@
331 ensure_user,324 ensure_user,
332 cluster_joined,325 cluster_joined,
333 admin_relation_changed,326 admin_relation_changed,
334 ensure_permissions,
335 mock_peer_units,327 mock_peer_units,
336 mock_is_db_ready,
337 mock_is_db_initialised,
338 mock_send_ssl_sync_request,328 mock_send_ssl_sync_request,
339 mock_is_ssl_cert_master,329 mock_is_ssl_cert_master,
340 mock_is_pki_enabled,330 mock_is_pki_enabled,
341 mock_ensure_ssl_dir,331 mock_ensure_ssl_dir,
342 mock_ensure_pki_dir_permissions,332 mock_ensure_pki_dir_permissions,
333 mock_ensure_ssl_dirs,
343 mock_ensure_ssl_cert_master,334 mock_ensure_ssl_cert_master,
344 mock_log, git_requested):335 mock_log, git_requested):
345 git_requested.return_value = False336 git_requested.return_value = False
346 mock_is_pki_enabled.return_value = True337 mock_is_pki_enabled.return_value = True
347 mock_is_ssl_cert_master.return_value = True338 mock_is_ssl_cert_master.return_value = True
348 mock_is_db_initialised.return_value = True339 self.is_db_initialised.return_value = True
349 mock_is_db_ready.return_value = True340 self.is_db_ready.return_value = True
350 self.openstack_upgrade_available.return_value = False341 self.openstack_upgrade_available.return_value = False
351 self.is_elected_leader.return_value = True342 self.is_elected_leader.return_value = True
352 # avoid having to mock syncer343 # avoid having to mock syncer
@@ -374,13 +365,13 @@
374 @patch.object(hooks, 'git_install_requested')365 @patch.object(hooks, 'git_install_requested')
375 @patch('keystone_utils.log')366 @patch('keystone_utils.log')
376 @patch('keystone_utils.ensure_ssl_cert_master')367 @patch('keystone_utils.ensure_ssl_cert_master')
368 @patch('keystone_utils.ensure_ssl_dirs')
377 @patch.object(hooks, 'update_all_identity_relation_units')369 @patch.object(hooks, 'update_all_identity_relation_units')
378 @patch.object(hooks, 'ensure_pki_dir_permissions')370 @patch.object(hooks, 'ensure_pki_dir_permissions')
379 @patch.object(hooks, 'ensure_ssl_dir')371 @patch.object(hooks, 'ensure_ssl_dir')
380 @patch.object(hooks, 'is_pki_enabled')372 @patch.object(hooks, 'is_pki_enabled')
381 @patch.object(hooks, 'peer_units')373 @patch.object(hooks, 'peer_units')
382 @patch.object(hooks, 'is_ssl_cert_master')374 @patch.object(hooks, 'is_ssl_cert_master')
383 @patch.object(hooks, 'ensure_permissions')
384 @patch.object(hooks, 'cluster_joined')375 @patch.object(hooks, 'cluster_joined')
385 @patch.object(unison, 'ensure_user')376 @patch.object(unison, 'ensure_user')
386 @patch.object(unison, 'get_homedir')377 @patch.object(unison, 'get_homedir')
@@ -391,13 +382,13 @@
391 identity_changed,382 identity_changed,
392 configs, get_homedir,383 configs, get_homedir,
393 ensure_user, cluster_joined,384 ensure_user, cluster_joined,
394 ensure_permissions,
395 mock_is_ssl_cert_master,385 mock_is_ssl_cert_master,
396 mock_peer_units,386 mock_peer_units,
397 mock_is_pki_enabled,387 mock_is_pki_enabled,
398 mock_ensure_ssl_dir,388 mock_ensure_ssl_dir,
399 mock_ensure_pki_permissions,389 mock_ensure_pki_permissions,
400 mock_update_all_id_rel_units,390 mock_update_all_id_rel_units,
391 ensure_ssl_dirs,
401 mock_ensure_ssl_cert_master,392 mock_ensure_ssl_cert_master,
402 mock_log, git_requested):393 mock_log, git_requested):
403 git_requested.return_value = False394 git_requested.return_value = False
@@ -423,15 +414,13 @@
423 @patch.object(hooks, 'git_install_requested')414 @patch.object(hooks, 'git_install_requested')
424 @patch('keystone_utils.log')415 @patch('keystone_utils.log')
425 @patch('keystone_utils.ensure_ssl_cert_master')416 @patch('keystone_utils.ensure_ssl_cert_master')
417 @patch('keystone_utils.ensure_ssl_dirs')
426 @patch.object(hooks, 'ensure_pki_dir_permissions')418 @patch.object(hooks, 'ensure_pki_dir_permissions')
427 @patch.object(hooks, 'ensure_ssl_dir')419 @patch.object(hooks, 'ensure_ssl_dir')
428 @patch.object(hooks, 'is_pki_enabled')420 @patch.object(hooks, 'is_pki_enabled')
429 @patch.object(hooks, 'is_ssl_cert_master')421 @patch.object(hooks, 'is_ssl_cert_master')
430 @patch.object(hooks, 'send_ssl_sync_request')422 @patch.object(hooks, 'send_ssl_sync_request')
431 @patch.object(hooks, 'is_db_initialised')
432 @patch.object(hooks, 'is_db_ready')
433 @patch.object(hooks, 'peer_units')423 @patch.object(hooks, 'peer_units')
434 @patch.object(hooks, 'ensure_permissions')
435 @patch.object(hooks, 'admin_relation_changed')424 @patch.object(hooks, 'admin_relation_changed')
436 @patch.object(hooks, 'cluster_joined')425 @patch.object(hooks, 'cluster_joined')
437 @patch.object(unison, 'ensure_user')426 @patch.object(unison, 'ensure_user')
@@ -444,22 +433,20 @@
444 configs, get_homedir,433 configs, get_homedir,
445 ensure_user, cluster_joined,434 ensure_user, cluster_joined,
446 admin_relation_changed,435 admin_relation_changed,
447 ensure_permissions,
448 mock_peer_units,436 mock_peer_units,
449 mock_is_db_ready,
450 mock_is_db_initialised,
451 mock_send_ssl_sync_request,437 mock_send_ssl_sync_request,
452 mock_is_ssl_cert_master,438 mock_is_ssl_cert_master,
453 mock_is_pki_enabled,439 mock_is_pki_enabled,
454 mock_ensure_ssl_dir,440 mock_ensure_ssl_dir,
455 mock_ensure_pki_permissions,441 mock_ensure_pki_permissions,
442 mock_ensure_ssl_dirs,
456 mock_ensure_ssl_cert_master,443 mock_ensure_ssl_cert_master,
457 mock_log, git_requested):444 mock_log, git_requested):
458 git_requested.return_value = False445 git_requested.return_value = False
459 mock_is_pki_enabled.return_value = True446 mock_is_pki_enabled.return_value = True
460 mock_is_ssl_cert_master.return_value = True447 mock_is_ssl_cert_master.return_value = True
461 mock_is_db_ready.return_value = True448 self.is_db_ready.return_value = True
462 mock_is_db_initialised.return_value = True449 self.is_db_initialised.return_value = True
463 self.openstack_upgrade_available.return_value = True450 self.openstack_upgrade_available.return_value = True
464 self.is_elected_leader.return_value = True451 self.is_elected_leader.return_value = True
465 # avoid having to mock syncer452 # avoid having to mock syncer
@@ -496,7 +483,6 @@
496 @patch.object(hooks, 'is_db_initialised')483 @patch.object(hooks, 'is_db_initialised')
497 @patch.object(hooks, 'is_db_ready')484 @patch.object(hooks, 'is_db_ready')
498 @patch.object(hooks, 'peer_units')485 @patch.object(hooks, 'peer_units')
499 @patch.object(hooks, 'ensure_permissions')
500 @patch.object(hooks, 'admin_relation_changed')486 @patch.object(hooks, 'admin_relation_changed')
501 @patch.object(hooks, 'cluster_joined')487 @patch.object(hooks, 'cluster_joined')
502 @patch.object(unison, 'ensure_user')488 @patch.object(unison, 'ensure_user')
@@ -508,7 +494,7 @@
508 identity_changed,494 identity_changed,
509 configs, get_homedir, ensure_user,495 configs, get_homedir, ensure_user,
510 cluster_joined, admin_relation_changed,496 cluster_joined, admin_relation_changed,
511 ensure_permissions, mock_peer_units,497 mock_peer_units,
512 mock_is_db_ready,498 mock_is_db_ready,
513 mock_is_db_initialised,499 mock_is_db_initialised,
514 mock_send_ssl_sync_request,500 mock_send_ssl_sync_request,
@@ -546,18 +532,15 @@
546 self.assertFalse(self.openstack_upgrade_available.called)532 self.assertFalse(self.openstack_upgrade_available.called)
547 self.assertFalse(self.do_openstack_upgrade.called)533 self.assertFalse(self.do_openstack_upgrade.called)
548534
549 @patch.object(hooks, 'is_db_initialised')
550 @patch.object(hooks, 'is_db_ready')
551 @patch('keystone_utils.log')535 @patch('keystone_utils.log')
552 @patch('keystone_utils.ensure_ssl_cert_master')536 @patch('keystone_utils.ensure_ssl_cert_master')
553 @patch.object(hooks, 'hashlib')537 @patch.object(hooks, 'hashlib')
554 @patch.object(hooks, 'send_notifications')538 @patch.object(hooks, 'send_notifications')
555 def test_identity_changed_leader(self, mock_send_notifications,539 def test_identity_changed_leader(self, mock_send_notifications,
556 mock_hashlib, mock_ensure_ssl_cert_master,540 mock_hashlib, mock_ensure_ssl_cert_master,
557 mock_log, mock_is_db_ready,541 mock_log):
558 mock_is_db_initialised):542 self.is_db_initialised.return_value = True
559 mock_is_db_initialised.return_value = True543 self.is_db_ready.return_value = True
560 mock_is_db_ready.return_value = True
561 mock_ensure_ssl_cert_master.return_value = False544 mock_ensure_ssl_cert_master.return_value = False
562 hooks.identity_changed(545 hooks.identity_changed(
563 relation_id='identity-service:0',546 relation_id='identity-service:0',
@@ -597,6 +580,7 @@
597 @patch.object(hooks, 'get_ssl_sync_request_units')580 @patch.object(hooks, 'get_ssl_sync_request_units')
598 @patch.object(hooks, 'is_ssl_cert_master')581 @patch.object(hooks, 'is_ssl_cert_master')
599 @patch.object(hooks, 'peer_units')582 @patch.object(hooks, 'peer_units')
583 @patch('keystone_utils.relation_ids')
600 @patch('keystone_utils.config')584 @patch('keystone_utils.config')
601 @patch('keystone_utils.log')585 @patch('keystone_utils.log')
602 @patch('keystone_utils.ensure_ssl_cert_master')586 @patch('keystone_utils.ensure_ssl_cert_master')
@@ -607,7 +591,8 @@
607 def test_cluster_changed(self, configs, ssh_authorized_peers,591 def test_cluster_changed(self, configs, ssh_authorized_peers,
608 check_peer_actions, mock_synchronize_ca,592 check_peer_actions, mock_synchronize_ca,
609 mock_ensure_ssl_cert_master,593 mock_ensure_ssl_cert_master,
610 mock_log, mock_config, mock_peer_units,594 mock_log, mock_config, mock_relation_ids,
595 mock_peer_units,
611 mock_is_ssl_cert_master,596 mock_is_ssl_cert_master,
612 mock_get_ssl_sync_request_units,597 mock_get_ssl_sync_request_units,
613 mock_update_all_identity_relation_units):598 mock_update_all_identity_relation_units):
@@ -618,6 +603,7 @@
618 mock_is_ssl_cert_master.return_value = False603 mock_is_ssl_cert_master.return_value = False
619 mock_peer_units.return_value = ['unit/0']604 mock_peer_units.return_value = ['unit/0']
620 mock_ensure_ssl_cert_master.return_value = False605 mock_ensure_ssl_cert_master.return_value = False
606 mock_relation_ids.return_value = []
621 self.is_elected_leader.return_value = False607 self.is_elected_leader.return_value = False
622608
623 def fake_rel_get(attribute=None, *args, **kwargs):609 def fake_rel_get(attribute=None, *args, **kwargs):
@@ -632,8 +618,8 @@
632618
633 hooks.cluster_changed()619 hooks.cluster_changed()
634 whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',620 whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',
635 'db-initialised']621 'db-initialised', 'ssl-cert-available-updates']
636 self.peer_echo.assert_called_with(includes=whitelist)622 self.peer_echo.assert_called_with(force=True, includes=whitelist)
637 ssh_authorized_peers.assert_called_with(623 ssh_authorized_peers.assert_called_with(
638 user=self.ssh_user, group='juju_keystone',624 user=self.ssh_user, group='juju_keystone',
639 peer_interface='cluster', ensure_local_user=True)625 peer_interface='cluster', ensure_local_user=True)
@@ -733,18 +719,14 @@
733719
734 @patch('keystone_utils.log')720 @patch('keystone_utils.log')
735 @patch('keystone_utils.ensure_ssl_cert_master')721 @patch('keystone_utils.ensure_ssl_cert_master')
736 @patch.object(hooks, 'is_db_ready')
737 @patch.object(hooks, 'is_db_initialised')
738 @patch.object(hooks, 'identity_changed')722 @patch.object(hooks, 'identity_changed')
739 @patch.object(hooks, 'CONFIGS')723 @patch.object(hooks, 'CONFIGS')
740 def test_ha_relation_changed_clustered_leader(self, configs,724 def test_ha_relation_changed_clustered_leader(self, configs,
741 identity_changed,725 identity_changed,
742 mock_is_db_initialised,
743 mock_is_db_ready,
744 mock_ensure_ssl_cert_master,726 mock_ensure_ssl_cert_master,
745 mock_log):727 mock_log):
746 mock_is_db_initialised.return_value = True728 self.is_db_initialised.return_value = True
747 mock_is_db_ready.return_value = True729 self.is_db_ready.return_value = True
748 mock_ensure_ssl_cert_master.return_value = False730 mock_ensure_ssl_cert_master.return_value = False
749 self.relation_get.return_value = True731 self.relation_get.return_value = True
750 self.is_elected_leader.return_value = True732 self.is_elected_leader.return_value = True
@@ -807,8 +789,8 @@
807 mock_is_elected_leader,789 mock_is_elected_leader,
808 mock_relation_ids,790 mock_relation_ids,
809 mock_log,791 mock_log,
792 mock_is_db_initialised,
810 mock_is_db_ready,793 mock_is_db_ready,
811 mock_is_db_initialised,
812 git_requested):794 git_requested):
813 mock_is_db_initialised.return_value = True795 mock_is_db_initialised.return_value = True
814 mock_is_db_ready.return_value = True796 mock_is_db_ready.return_value = True

Subscribers

People subscribed via source and target branches