Merge lp:~hopem/charms/trusty/keystone/stable-backport-lp1415579 into lp:~openstack-charmers-archive/charms/trusty/keystone/trunk

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 117
Proposed branch: lp:~hopem/charms/trusty/keystone/stable-backport-lp1415579
Merge into: lp:~openstack-charmers-archive/charms/trusty/keystone/trunk
Diff against target: 588 lines (+270/-102)
5 files modified
hooks/charmhelpers/contrib/unison/__init__.py (+6/-4)
hooks/charmhelpers/core/sysctl.py (+11/-5)
hooks/keystone_hooks.py (+36/-15)
hooks/keystone_utils.py (+78/-36)
unit_tests/test_keystone_utils.py (+139/-42)
To merge this branch: bzr merge lp:~hopem/charms/trusty/keystone/stable-backport-lp1415579
Reviewer Review Type Date Requested Status
James Page Approve
Review via email: mp+248894@code.launchpad.net
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #1781 keystone for hopem mp248894
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/1781/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #1609 keystone for hopem mp248894
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/1609/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #1801 keystone for hopem mp248894
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/1801/

Revision history for this message
James Page (james-page) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/contrib/unison/__init__.py'
2--- hooks/charmhelpers/contrib/unison/__init__.py 2015-01-26 09:44:47 +0000
3+++ hooks/charmhelpers/contrib/unison/__init__.py 2015-02-06 12:31:31 +0000
4@@ -73,6 +73,7 @@
5 relation_set,
6 relation_get,
7 unit_private_ip,
8+ INFO,
9 ERROR,
10 )
11
12@@ -86,7 +87,7 @@
13 user = pwd.getpwnam(user)
14 return user.pw_dir
15 except KeyError:
16- log('Could not get homedir for user %s: user exists?', ERROR)
17+ log('Could not get homedir for user %s: user exists?' % (user), ERROR)
18 raise Exception
19
20
21@@ -233,14 +234,15 @@
22 rid=r_id, unit=unit)
23
24 if not authed_hosts:
25- log('Peer %s has not authorized *any* hosts yet, skipping.')
26+ log('Peer %s has not authorized *any* hosts yet, skipping.' %
27+ (unit), level=INFO)
28 continue
29
30 if unit_private_ip() in authed_hosts.split(':'):
31 hosts.append(private_addr)
32 else:
33- log('Peer %s has not authorized *this* host yet, skipping.')
34-
35+ log('Peer %s has not authorized *this* host yet, skipping.' %
36+ (unit), level=INFO)
37 return hosts
38
39
40
41=== modified file 'hooks/charmhelpers/core/sysctl.py'
42--- hooks/charmhelpers/core/sysctl.py 2015-01-26 09:44:47 +0000
43+++ hooks/charmhelpers/core/sysctl.py 2015-02-06 12:31:31 +0000
44@@ -26,25 +26,31 @@
45 from charmhelpers.core.hookenv import (
46 log,
47 DEBUG,
48+ ERROR,
49 )
50
51
52 def create(sysctl_dict, sysctl_file):
53 """Creates a sysctl.conf file from a YAML associative array
54
55- :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 }
56- :type sysctl_dict: dict
57+ :param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }"
58+ :type sysctl_dict: str
59 :param sysctl_file: path to the sysctl file to be saved
60 :type sysctl_file: str or unicode
61 :returns: None
62 """
63- sysctl_dict = yaml.load(sysctl_dict)
64+ try:
65+ sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
66+ except yaml.YAMLError:
67+ log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
68+ level=ERROR)
69+ return
70
71 with open(sysctl_file, "w") as fd:
72- for key, value in sysctl_dict.items():
73+ for key, value in sysctl_dict_parsed.items():
74 fd.write("{}={}\n".format(key, value))
75
76- log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict),
77+ log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed),
78 level=DEBUG)
79
80 check_call(["sysctl", "-p", sysctl_file])
81
82=== modified file 'hooks/keystone_hooks.py'
83--- hooks/keystone_hooks.py 2015-01-27 22:21:37 +0000
84+++ hooks/keystone_hooks.py 2015-02-06 12:31:31 +0000
85@@ -67,6 +67,7 @@
86 is_str_true,
87 is_ssl_cert_master,
88 is_db_ready,
89+ clear_ssl_synced_units,
90 )
91
92 from charmhelpers.contrib.hahelpers.cluster import (
93@@ -150,11 +151,8 @@
94 admin_relation_changed(rid)
95
96 # Ensure sync request is sent out (needed for upgrade to ssl from non-ssl)
97- settings = {}
98- append_ssl_sync_request(settings)
99- if settings:
100- for rid in relation_ids('cluster'):
101- relation_set(relation_id=rid, relation_settings=settings)
102+ send_ssl_sync_request()
103+
104 for r_id in relation_ids('ha'):
105 ha_joined(relation_id=r_id)
106
107@@ -283,15 +281,39 @@
108 send_notifications(notifications)
109
110
111-def append_ssl_sync_request(settings):
112- """Add request to be synced to relation settings.
113-
114- This will be consumed by cluster-relation-changed ssl master.
115+def send_ssl_sync_request():
116+ """Set sync request on cluster relation.
117+
118+ Value set equals number of ssl configs currently enabled so that if they
119+ change, we ensure that certs are synced. This setting is consumed by
120+ cluster-relation-changed ssl master. We also clear the 'synced' set to
121+ guarantee that a sync will occur.
122+
123+ Note the we do nothing if the setting is already applied.
124 """
125- if (is_str_true(config('use-https')) or
126- is_str_true(config('https-service-endpoints'))):
127- unit = local_unit().replace('/', '-')
128- settings['ssl-sync-required-%s' % (unit)] = '1'
129+ unit = local_unit().replace('/', '-')
130+ count = 0
131+ if is_str_true(config('use-https')):
132+ count += 1
133+
134+ if is_str_true(config('https-service-endpoints')):
135+ count += 2
136+
137+ if count:
138+ key = 'ssl-sync-required-%s' % (unit)
139+ settings = {key: count}
140+ prev = 0
141+ rid = None
142+ for rid in relation_ids('cluster'):
143+ for unit in related_units(rid):
144+ _prev = relation_get(rid=rid, unit=unit, attribute=key) or 0
145+ if _prev and _prev > prev:
146+ prev = _prev
147+
148+ if rid and prev < count:
149+ clear_ssl_synced_units()
150+ log("Setting %s=%s" % (key, count), level=DEBUG)
151+ relation_set(relation_id=rid, relation_settings=settings)
152
153
154 @hooks.hook('cluster-relation-joined')
155@@ -314,9 +336,8 @@
156 private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
157 settings['private-address'] = private_addr
158
159- append_ssl_sync_request(settings)
160-
161 relation_set(relation_settings=settings)
162+ send_ssl_sync_request()
163
164
165 def apply_echo_filters(settings, echo_whitelist):
166
167=== modified file 'hooks/keystone_utils.py'
168--- hooks/keystone_utils.py 2015-02-02 13:52:05 +0000
169+++ hooks/keystone_utils.py 2015-02-06 12:31:31 +0000
170@@ -21,7 +21,6 @@
171 determine_api_port,
172 https,
173 peer_units,
174- oldest_peer,
175 )
176
177 from charmhelpers.contrib.openstack import context, templating
178@@ -764,14 +763,27 @@
179 def unison_sync(paths_to_sync):
180 """Do unison sync and retry a few times if it fails since peers may not be
181 ready for sync.
182+
183+ Returns list of synced units or None if one or more peers was not synced.
184 """
185 log('Synchronizing CA (%s) to all peers.' % (', '.join(paths_to_sync)),
186 level=INFO)
187 keystone_gid = grp.getgrnam('keystone').gr_gid
188+
189+ # NOTE(dosaboy): This will sync to all peers who have already provided
190+ # their ssh keys. If any existing peers have not provided their keys yet,
191+ # they will be silently ignored.
192 unison.sync_to_peers(peer_interface='cluster', paths=paths_to_sync,
193 user=SSH_USER, verbose=True, gid=keystone_gid,
194 fatal=True)
195
196+ synced_units = peer_units()
197+ if len(unison.collect_authed_hosts('cluster')) != len(synced_units):
198+ log("Not all peer units synced due to missing public keys", level=INFO)
199+ return None
200+ else:
201+ return synced_units
202+
203
204 def get_ssl_sync_request_units():
205 """Get list of units that have requested to be synced.
206@@ -791,14 +803,22 @@
207 return units
208
209
210-def is_ssl_cert_master():
211+def is_ssl_cert_master(votes=None):
212 """Return True if this unit is ssl cert master."""
213 master = None
214 for rid in relation_ids('cluster'):
215 master = relation_get(attribute='ssl-cert-master', rid=rid,
216 unit=local_unit())
217
218- return master == local_unit()
219+ if master == local_unit():
220+ votes = votes or get_ssl_cert_master_votes()
221+ if not peer_units() or (len(votes) == 1 and master in votes):
222+ return True
223+
224+ log("Did not get consensus from peers on who is ssl-cert-master "
225+ "(%s)" % (votes), level=INFO)
226+
227+ return False
228
229
230 def is_ssl_enabled():
231@@ -812,7 +832,21 @@
232 return True
233
234
235-def ensure_ssl_cert_master(use_oldest_peer=False):
236+def get_ssl_cert_master_votes():
237+ """Returns a list of unique votes."""
238+ votes = []
239+ # Gather election results from peers. These will need to be consistent.
240+ for rid in relation_ids('cluster'):
241+ for unit in related_units(rid):
242+ m = relation_get(rid=rid, unit=unit,
243+ attribute='ssl-cert-master')
244+ if m is not None:
245+ votes.append(m)
246+
247+ return list(set(votes))
248+
249+
250+def ensure_ssl_cert_master():
251 """Ensure that an ssl cert master has been elected.
252
253 Normally the cluster leader will take control but we allow for this to be
254@@ -822,31 +856,19 @@
255 if not is_ssl_enabled():
256 return False
257
258- elect = False
259- peers = peer_units()
260 master_override = False
261- if use_oldest_peer:
262- elect = oldest_peer(peers)
263- else:
264- elect = is_elected_leader(CLUSTER_RES)
265+ elect = is_elected_leader(CLUSTER_RES)
266
267 # If no peers we allow this unit to elect itsef as master and do
268 # sync immediately.
269- if not peers and not is_ssl_cert_master():
270+ if not peer_units():
271 elect = True
272 master_override = True
273
274 if elect:
275- masters = []
276- for rid in relation_ids('cluster'):
277- for unit in related_units(rid):
278- m = relation_get(rid=rid, unit=unit,
279- attribute='ssl-cert-master')
280- if m is not None:
281- masters.append(m)
282-
283+ votes = get_ssl_cert_master_votes()
284 # We expect all peers to echo this setting
285- if not masters or 'unknown' in masters:
286+ if not votes or 'unknown' in votes:
287 log("Notifying peers this unit is ssl-cert-master", level=INFO)
288 for rid in relation_ids('cluster'):
289 settings = {'ssl-cert-master': local_unit()}
290@@ -855,10 +877,11 @@
291 # Return now and wait for cluster-relation-changed (peer_echo) for
292 # sync.
293 return master_override
294- elif len(set(masters)) != 1 and local_unit() not in masters:
295- log("Did not get consensus from peers on who is ssl-cert-master "
296- "(%s) - waiting for current master to release before "
297- "self-electing" % (masters), level=INFO)
298+ elif not is_ssl_cert_master(votes):
299+ if not master_override:
300+ log("Conscensus not reached - current master will need to "
301+ "release", level=INFO)
302+
303 return master_override
304
305 if not is_ssl_cert_master():
306@@ -887,15 +910,18 @@
307 log("Syncing all endpoint certs since https-service-endpoints=True",
308 level=DEBUG)
309 paths_to_sync.append(SSL_DIR)
310- paths_to_sync.append(APACHE_SSL_DIR)
311 paths_to_sync.append(CA_CERT_PATH)
312- elif is_str_true(config('use-https')):
313+
314+ if is_str_true(config('use-https')):
315 log("Syncing keystone-endpoint certs since use-https=True",
316 level=DEBUG)
317 paths_to_sync.append(SSL_DIR)
318 paths_to_sync.append(APACHE_SSL_DIR)
319 paths_to_sync.append(CA_CERT_PATH)
320
321+ # Ensure unique
322+ paths_to_sync = list(set(paths_to_sync))
323+
324 if not paths_to_sync:
325 log("Nothing to sync - skipping", level=DEBUG)
326 return {}
327@@ -908,8 +934,7 @@
328 create_peer_service_actions('restart', ['apache2'])
329 create_peer_actions(['update-ca-certificates'])
330
331- # Format here needs to match that used when peers request sync
332- synced_units = [unit.replace('/', '-') for unit in peer_units()]
333+ cluster_rel_settings = {}
334
335 retries = 3
336 while True:
337@@ -918,7 +943,12 @@
338 update_hash_from_path(hash1, path)
339
340 try:
341- unison_sync(paths_to_sync)
342+ synced_units = unison_sync(paths_to_sync)
343+ if synced_units:
344+ # Format here needs to match that used when peers request sync
345+ synced_units = [u.replace('/', '-') for u in synced_units]
346+ cluster_rel_settings['ssl-synced-units'] = \
347+ json.dumps(synced_units)
348 except:
349 if fatal:
350 raise
351@@ -947,10 +977,22 @@
352 hash = hash1.hexdigest()
353 log("Sending restart-services-trigger=%s to all peers" % (hash),
354 level=DEBUG)
355+ cluster_rel_settings['restart-services-trigger'] = hash
356
357 log("Sync complete", level=DEBUG)
358- return {'restart-services-trigger': hash,
359- 'ssl-synced-units': json.dumps(synced_units)}
360+ return cluster_rel_settings
361+
362+
363+def clear_ssl_synced_units():
364+ """Clear the 'synced' units record on the cluster relation.
365+
366+ If new unit sync reauests are set this will ensure that a sync occurs when
367+ the sync master receives the requests.
368+ """
369+ log("Clearing ssl sync units", level=DEBUG)
370+ for rid in relation_ids('cluster'):
371+ relation_set(relation_id=rid,
372+ relation_settings={'ssl-synced-units': None})
373
374
375 def update_hash_from_path(hash, path, recurse_depth=10):
376@@ -1058,11 +1100,11 @@
377 '%s' % SSL_DIR])
378 subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
379
380- # Ensure a master has been elected and prefer this unit. Note that we
381- # prefer oldest peer as predicate since this action i normally only
382- # performed once at deploy time when the oldest peer should be the
383- # first to be ready.
384- ensure_ssl_cert_master(use_oldest_peer=True)
385+ # Ensure a master is elected. This should cover the following cases:
386+ # * single unit == 'oldest' unit is elected as master
387+ # * multi unit + not clustered == 'oldest' unit is elcted as master
388+ # * multi unit + clustered == cluster leader is elected as master
389+ ensure_ssl_cert_master()
390
391 ssl.CA_SINGLETON.append(ca)
392
393
394=== modified file 'unit_tests/test_keystone_utils.py'
395--- unit_tests/test_keystone_utils.py 2015-02-02 13:52:05 +0000
396+++ unit_tests/test_keystone_utils.py 2015-02-06 12:31:31 +0000
397@@ -28,6 +28,7 @@
398 'grant_role',
399 'configure_installation_source',
400 'is_elected_leader',
401+ 'is_ssl_cert_master',
402 'https',
403 'peer_store_and_set',
404 'service_stop',
405@@ -380,45 +381,141 @@
406 self.assertTrue(utils.is_db_ready())
407
408 @patch.object(utils, 'peer_units')
409- @patch.object(utils, 'is_elected_leader')
410- @patch.object(utils, 'oldest_peer')
411- @patch.object(utils, 'is_ssl_enabled')
412- def test_ensure_ssl_cert_master(self, mock_is_str_true, mock_oldest_peer,
413- mock_is_elected_leader, mock_peer_units):
414- self.relation_ids.return_value = ['cluster:0']
415- self.local_unit.return_value = 'unit/0'
416-
417- mock_is_str_true.return_value = False
418- self.assertFalse(utils.ensure_ssl_cert_master())
419- self.assertFalse(self.relation_set.called)
420-
421- mock_is_elected_leader.return_value = False
422- self.assertFalse(utils.ensure_ssl_cert_master())
423- self.assertFalse(self.relation_set.called)
424-
425- mock_is_str_true.return_value = True
426- mock_is_elected_leader.return_value = False
427- mock_peer_units.return_value = ['unit/0']
428- self.assertFalse(utils.ensure_ssl_cert_master())
429- self.assertFalse(self.relation_set.called)
430-
431- mock_peer_units.return_value = []
432- self.assertTrue(utils.ensure_ssl_cert_master())
433- settings = {'ssl-cert-master': 'unit/0'}
434- self.relation_set.assert_called_with(relation_id='cluster:0',
435- relation_settings=settings)
436- self.relation_set.reset_mock()
437-
438- self.assertTrue(utils.ensure_ssl_cert_master(use_oldest_peer=True))
439- settings = {'ssl-cert-master': 'unit/0'}
440- self.relation_set.assert_called_with(relation_id='cluster:0',
441- relation_settings=settings)
442- self.relation_set.reset_mock()
443-
444- mock_peer_units.return_value = ['unit/0']
445- self.assertFalse(utils.ensure_ssl_cert_master())
446- self.assertFalse(utils.ensure_ssl_cert_master(use_oldest_peer=True))
447- settings = {'ssl-cert-master': 'unit/0'}
448- self.relation_set.assert_called_with(relation_id='cluster:0',
449- relation_settings=settings)
450- self.relation_set.reset_mock()
451+ @patch.object(utils, 'is_ssl_enabled')
452+ def test_ensure_ssl_cert_master_no_ssl(self, mock_is_ssl_enabled,
453+ mock_peer_units):
454+ mock_is_ssl_enabled.return_value = False
455+ self.assertFalse(utils.ensure_ssl_cert_master())
456+ self.assertFalse(self.relation_set.called)
457+
458+ @patch.object(utils, 'peer_units')
459+ @patch.object(utils, 'is_ssl_enabled')
460+ def test_ensure_ssl_cert_master_ssl_no_peers(self, mock_is_ssl_enabled,
461+ mock_peer_units):
462+ def mock_rel_get(unit=None, **kwargs):
463+ return None
464+
465+ self.relation_get.side_effect = mock_rel_get
466+ mock_is_ssl_enabled.return_value = True
467+ self.relation_ids.return_value = ['cluster:0']
468+ self.local_unit.return_value = 'unit/0'
469+ self.related_units.return_value = []
470+ mock_peer_units.return_value = []
471+ # This should get ignored since we are overriding
472+ self.is_ssl_cert_master.return_value = False
473+ self.is_elected_leader.return_value = False
474+ self.assertTrue(utils.ensure_ssl_cert_master())
475+ settings = {'ssl-cert-master': 'unit/0'}
476+ self.relation_set.assert_called_with(relation_id='cluster:0',
477+ relation_settings=settings)
478+
479+ @patch.object(utils, 'peer_units')
480+ @patch.object(utils, 'is_ssl_enabled')
481+ def test_ensure_ssl_cert_master_ssl_master_no_peers(self,
482+ mock_is_ssl_enabled,
483+ mock_peer_units):
484+ def mock_rel_get(unit=None, **kwargs):
485+ if unit == 'unit/0':
486+ return 'unit/0'
487+
488+ return None
489+
490+ self.relation_get.side_effect = mock_rel_get
491+ mock_is_ssl_enabled.return_value = True
492+ self.relation_ids.return_value = ['cluster:0']
493+ self.local_unit.return_value = 'unit/0'
494+ self.related_units.return_value = []
495+ mock_peer_units.return_value = []
496+ # This should get ignored since we are overriding
497+ self.is_ssl_cert_master.return_value = False
498+ self.is_elected_leader.return_value = False
499+ self.assertTrue(utils.ensure_ssl_cert_master())
500+ settings = {'ssl-cert-master': 'unit/0'}
501+ self.relation_set.assert_called_with(relation_id='cluster:0',
502+ relation_settings=settings)
503+
504+ @patch.object(utils, 'peer_units')
505+ @patch.object(utils, 'is_ssl_enabled')
506+ def test_ensure_ssl_cert_master_ssl_not_leader(self, mock_is_ssl_enabled,
507+ mock_peer_units):
508+ mock_is_ssl_enabled.return_value = True
509+ self.relation_ids.return_value = ['cluster:0']
510+ self.local_unit.return_value = 'unit/0'
511+ mock_peer_units.return_value = ['unit/1']
512+ self.is_ssl_cert_master.return_value = False
513+ self.is_elected_leader.return_value = False
514+ self.assertFalse(utils.ensure_ssl_cert_master())
515+ self.assertFalse(self.relation_set.called)
516+
517+ @patch.object(utils, 'peer_units')
518+ @patch.object(utils, 'is_ssl_enabled')
519+ def test_ensure_ssl_cert_master_is_leader_new_peer(self,
520+ mock_is_ssl_enabled,
521+ mock_peer_units):
522+ def mock_rel_get(unit=None, **kwargs):
523+ if unit == 'unit/0':
524+ return 'unit/0'
525+
526+ return 'unknown'
527+
528+ self.relation_get.side_effect = mock_rel_get
529+ mock_is_ssl_enabled.return_value = True
530+ self.relation_ids.return_value = ['cluster:0']
531+ self.local_unit.return_value = 'unit/0'
532+ mock_peer_units.return_value = ['unit/1']
533+ self.related_units.return_value = ['unit/1']
534+ self.is_ssl_cert_master.return_value = False
535+ self.is_elected_leader.return_value = True
536+ self.assertFalse(utils.ensure_ssl_cert_master())
537+ settings = {'ssl-cert-master': 'unit/0'}
538+ self.relation_set.assert_called_with(relation_id='cluster:0',
539+ relation_settings=settings)
540+
541+ @patch.object(utils, 'peer_units')
542+ @patch.object(utils, 'is_ssl_enabled')
543+ def test_ensure_ssl_cert_master_is_leader_no_new_peer(self,
544+ mock_is_ssl_enabled,
545+ mock_peer_units):
546+ def mock_rel_get(unit=None, **kwargs):
547+ if unit == 'unit/0':
548+ return 'unit/0'
549+
550+ return 'unit/0'
551+
552+ self.relation_get.side_effect = mock_rel_get
553+ mock_is_ssl_enabled.return_value = True
554+ self.relation_ids.return_value = ['cluster:0']
555+ self.local_unit.return_value = 'unit/0'
556+ mock_peer_units.return_value = ['unit/1']
557+ self.related_units.return_value = ['unit/1']
558+ self.is_ssl_cert_master.return_value = False
559+ self.is_elected_leader.return_value = True
560+ self.assertFalse(utils.ensure_ssl_cert_master())
561+ self.assertFalse(self.relation_set.called)
562+
563+ @patch.object(utils, 'peer_units')
564+ @patch.object(utils, 'is_ssl_enabled')
565+ def test_ensure_ssl_cert_master_is_leader_bad_votes(self,
566+ mock_is_ssl_enabled,
567+ mock_peer_units):
568+ counter = {0: 0}
569+
570+ def mock_rel_get(unit=None, **kwargs):
571+ """Returns a mix of votes."""
572+ if unit == 'unit/0':
573+ return 'unit/0'
574+
575+ ret = 'unit/%d' % (counter[0])
576+ counter[0] += 1
577+ return ret
578+
579+ self.relation_get.side_effect = mock_rel_get
580+ mock_is_ssl_enabled.return_value = True
581+ self.relation_ids.return_value = ['cluster:0']
582+ self.local_unit.return_value = 'unit/0'
583+ mock_peer_units.return_value = ['unit/1']
584+ self.related_units.return_value = ['unit/1']
585+ self.is_ssl_cert_master.return_value = False
586+ self.is_elected_leader.return_value = True
587+ self.assertFalse(utils.ensure_ssl_cert_master())
588+ self.assertFalse(self.relation_set.called)

Subscribers

People subscribed via source and target branches