Merge lp:~hopem/charms/trusty/keystone/fix-ssl-cert-sync into lp:~openstack-charmers-archive/charms/trusty/keystone/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 109
Proposed branch: lp:~hopem/charms/trusty/keystone/fix-ssl-cert-sync
Merge into: lp:~openstack-charmers-archive/charms/trusty/keystone/next
Diff against target: 1821 lines (+937/-202)
7 files modified
README.md (+46/-10)
hooks/keystone_context.py (+45/-8)
hooks/keystone_hooks.py (+180/-66)
hooks/keystone_ssl.py (+43/-14)
hooks/keystone_utils.py (+443/-50)
unit_tests/test_keystone_hooks.py (+178/-50)
unit_tests/test_keystone_utils.py (+2/-4)
To merge this branch: bzr merge lp:~hopem/charms/trusty/keystone/fix-ssl-cert-sync
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Ryan Beisner (community) Approve
Review via email: mp+245492@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 #528 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #557 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #684 keystone-next for hopem mp245492
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/9676583/
Build: http://10.245.162.77:8080/job/charm_amulet_test/684/

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

charm_lint_check #531 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #560 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #687 keystone-next for hopem mp245492
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/9676857/
Build: http://10.245.162.77:8080/job/charm_amulet_test/687/

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

charm_lint_check #533 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #562 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_lint_check #538 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #567 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #694 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #598 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #627 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #786 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #609 keystone-next for hopem mp245492
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
  unit_tests/test_keystone_hooks.py:537:44: W291 trailing whitespace
  make: *** [lint] Error 1

Full lint test output: http://paste.ubuntu.com/9699419/
Build: http://10.245.162.77:8080/job/charm_lint_check/609/

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

charm_unit_test #638 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=3, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9699422/
Build: http://10.245.162.77:8080/job/charm_unit_test/638/

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

charm_amulet_test #796 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #610 keystone-next for hopem mp245492
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
  unit_tests/test_keystone_hooks.py:537:44: W291 trailing whitespace
  make: *** [lint] Error 1

Full lint test output: http://paste.ubuntu.com/9699605/
Build: http://10.245.162.77:8080/job/charm_lint_check/610/

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

charm_unit_test #639 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=3, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9699606/
Build: http://10.245.162.77:8080/job/charm_unit_test/639/

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

charm_amulet_test #797 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #611 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #640 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=3, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9699828/
Build: http://10.245.162.77:8080/job/charm_unit_test/640/

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

charm_amulet_test #798 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #613 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #642 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=9, failures=2)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9704972/
Build: http://10.245.162.77:8080/job/charm_unit_test/642/

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

charm_amulet_test #799 keystone-next for hopem mp245492
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/9705021/
Build: http://10.245.162.77:8080/job/charm_amulet_test/799/

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

charm_lint_check #614 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #643 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=25)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9705140/
Build: http://10.245.162.77:8080/job/charm_unit_test/643/

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

charm_amulet_test #800 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #615 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #644 keystone-next for hopem mp245492
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
  FAILED (errors=25)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9705469/
Build: http://10.245.162.77:8080/job/charm_unit_test/644/

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

charm_amulet_test #801 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #779 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #808 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #963 keystone-next for hopem mp245492
    AMULET OK: passed

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

Revision history for this message
Ryan Beisner (1chb1n) wrote :

P, T & U deploy tests are all happy!

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

charm_lint_check #800 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #829 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #989 keystone-next for hopem mp245492
    AMULET OK: passed

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

112. By Edward Hope-Morley

updated README

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

charm_lint_check #884 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #913 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #1108 keystone-next for hopem mp245492
    AMULET OK: passed

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

Revision history for this message
Ryan Beisner (1chb1n) wrote :

All deploy tests are happy. P-I, T-I, T-J, U-J

Revision history for this message
Liam Young (gnuoy) wrote :

Approve

Tested using mojo spec dev/keystone_ssl from lp:~ost-maintainers/openstack-mojo-specs/mojo-openstack-specs.

(There are issue with removing units which are now highlighted in the READE)

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

charm_lint_check #935 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #964 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #1157 keystone-next for hopem mp245492
    AMULET OK: passed

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

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

charm_lint_check #938 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #967 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #1160 keystone-next for hopem mp245492
    AMULET OK: passed

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

113. By Edward Hope-Morley

ignore ssl actions if not enabled and improve support for non-ssl -> ssl

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

charm_lint_check #946 keystone-next for hopem mp245492
    LINT OK: passed

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

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

charm_unit_test #975 keystone-next for hopem mp245492
    UNIT OK: passed

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

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

charm_amulet_test #1168 keystone-next for hopem mp245492
    AMULET OK: passed

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

Revision history for this message
Liam Young (gnuoy) wrote :

Approve.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.md'
--- README.md 2014-06-29 21:56:26 +0000
+++ README.md 2015-01-21 16:51:08 +0000
@@ -1,22 +1,30 @@
1Overview
2========
3
1This charm provides Keystone, the Openstack identity service. It's target4This charm provides Keystone, the Openstack identity service. It's target
2platform is Ubuntu Precise + Openstack Essex. This has not been tested5platform is (ideally) Ubuntu LTS + Openstack.
3using Oneiric + Diablo.6
47Usage
5It provides three interfaces.8=====
6 9
10The following interfaces are provided:
11
12 - nrpe-external-master: Used generate Nagios checks.
13
7 - identity-service: Openstack API endpoints request an entry in the 14 - identity-service: Openstack API endpoints request an entry in the
8 Keystone service catalog + endpoint template catalog. When a relation15 Keystone service catalog + endpoint template catalog. When a relation
9 is established, Keystone receives: service name, region, public_url,16 is established, Keystone receives: service name, region, public_url,
10 admin_url and internal_url. It first checks that the requested service17 admin_url and internal_url. It first checks that the requested service
11 is listed as a supported service. This list should stay updated to18 is listed as a supported service. This list should stay updated to
12 support current Openstack core services. If the services is supported,19 support current Openstack core services. If the service is supported,
13 a entry in the service catalog is created, an endpoint template is20 an entry in the service catalog is created, an endpoint template is
14 created and a admin token is generated. The other end of the relation21 created and a admin token is generated. The other end of the relation
15 recieves the token as well as info on which ports Keystone is listening.22 receives the token as well as info on which ports Keystone is listening
23 on.
1624
17 - keystone-service: This is currently only used by Horizon/dashboard25 - keystone-service: This is currently only used by Horizon/dashboard
18 as its interaction with Keystone is different from other Openstack API26 as its interaction with Keystone is different from other Openstack API
19 servicies. That is, Horizon requests a Keystone role and token exists.27 services. That is, Horizon requests a Keystone role and token exists.
20 During a relation, Horizon requests its configured default role and28 During a relation, Horizon requests its configured default role and
21 Keystone responds with a token and the auth + admin ports on which29 Keystone responds with a token and the auth + admin ports on which
22 Keystone is listening.30 Keystone is listening.
@@ -26,9 +34,37 @@
26 provision users, tenants, etc. or that otherwise automate using the34 provision users, tenants, etc. or that otherwise automate using the
27 Openstack cluster deployment.35 Openstack cluster deployment.
2836
37 - identity-notifications: Used to broadcast messages to any services
38 listening on the interface.
39
40Database
41--------
42
29Keystone requires a database. By default, a local sqlite database is used.43Keystone requires a database. By default, a local sqlite database is used.
30The charm supports relations to a shared-db via mysql-shared interface. When44The charm supports relations to a shared-db via mysql-shared interface. When
31a new data store is configured, the charm ensures the minimum administrator45a new data store is configured, the charm ensures the minimum administrator
32credentials exist (as configured via charm configuration)46credentials exist (as configured via charm configuration)
3347
34VIP is only required if you plan on multi-unit clusterming. The VIP becomes a highly-available API endpoint.48HA/Clustering
49-------------
50
51VIP is only required if you plan on multi-unit clustering (requires relating
52with hacluster charm). The VIP becomes a highly-available API endpoint.
53
54SSL/HTTPS
55---------
56
57This charm also supports SSL and HTTPS endpoints. In order to ensure SSL
58certificates are only created once and distributed to all units, one unit gets
59elected as an ssl-cert-master. One side-effect of this is that as units are
60scaled-out the currently elected leader needs to be running in order for nodes
61to sync certificates. This 'feature' is to work around the lack of native
62leadership election via Juju itself, a feature that is due for release some
63time soon but until then we have to rely on this. Also, if a keystone unit does
64go down, it must be removed from Juju i.e.
65
66 juju destroy-unit keystone/<unit-num>
67
68Otherwise it will be assumed that this unit may come back at some point and
69therefore must be know to be in-sync with the rest before continuing.
70
3571
=== modified file 'hooks/keystone_context.py'
--- hooks/keystone_context.py 2015-01-19 10:45:41 +0000
+++ hooks/keystone_context.py 2015-01-21 16:51:08 +0000
@@ -1,3 +1,5 @@
1import os
2
1from charmhelpers.core.hookenv import config3from charmhelpers.core.hookenv import config
24
3from charmhelpers.core.host import mkdir, write_file5from charmhelpers.core.host import mkdir, write_file
@@ -6,13 +8,16 @@
68
7from charmhelpers.contrib.hahelpers.cluster import (9from charmhelpers.contrib.hahelpers.cluster import (
8 determine_apache_port,10 determine_apache_port,
9 determine_api_port11 determine_api_port,
12)
13
14from charmhelpers.core.hookenv import (
15 log,
16 INFO,
10)17)
1118
12from charmhelpers.contrib.hahelpers.apache import install_ca_cert19from charmhelpers.contrib.hahelpers.apache import install_ca_cert
1320
14import os
15
16CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'21CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
1722
1823
@@ -29,20 +34,52 @@
29 return super(ApacheSSLContext, self).__call__()34 return super(ApacheSSLContext, self).__call__()
3035
31 def configure_cert(self, cn):36 def configure_cert(self, cn):
32 from keystone_utils import SSH_USER, get_ca37 from keystone_utils import (
38 SSH_USER,
39 get_ca,
40 ensure_permissions,
41 is_ssl_cert_master,
42 )
43
33 ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)44 ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
34 mkdir(path=ssl_dir)45 perms = 0o755
46 mkdir(path=ssl_dir, owner=SSH_USER, group='keystone', perms=perms)
47 # Ensure accessible by keystone ssh user and group (for sync)
48 ensure_permissions(ssl_dir, user=SSH_USER, group='keystone',
49 perms=perms)
50
51 if not is_ssl_cert_master():
52 log("Not ssl-cert-master - skipping apache cert config",
53 level=INFO)
54 return
55
56 log("Creating apache ssl certs in %s" % (ssl_dir), level=INFO)
57
35 ca = get_ca(user=SSH_USER)58 ca = get_ca(user=SSH_USER)
36 cert, key = ca.get_cert_and_key(common_name=cn)59 cert, key = ca.get_cert_and_key(common_name=cn)
37 write_file(path=os.path.join(ssl_dir, 'cert_{}'.format(cn)),60 write_file(path=os.path.join(ssl_dir, 'cert_{}'.format(cn)),
38 content=cert)61 content=cert, owner=SSH_USER, group='keystone', perms=0o644)
39 write_file(path=os.path.join(ssl_dir, 'key_{}'.format(cn)),62 write_file(path=os.path.join(ssl_dir, 'key_{}'.format(cn)),
40 content=key)63 content=key, owner=SSH_USER, group='keystone', perms=0o644)
4164
42 def configure_ca(self):65 def configure_ca(self):
43 from keystone_utils import SSH_USER, get_ca66 from keystone_utils import (
67 SSH_USER,
68 get_ca,
69 ensure_permissions,
70 is_ssl_cert_master,
71 )
72
73 if not is_ssl_cert_master():
74 log("Not ssl-cert-master - skipping apache cert config",
75 level=INFO)
76 return
77
44 ca = get_ca(user=SSH_USER)78 ca = get_ca(user=SSH_USER)
45 install_ca_cert(ca.get_ca_bundle())79 install_ca_cert(ca.get_ca_bundle())
80 # Ensure accessible by keystone ssh user and group (unison)
81 ensure_permissions(CA_CERT_PATH, user=SSH_USER, group='keystone',
82 perms=0o0644)
4683
47 def canonical_names(self):84 def canonical_names(self):
48 addresses = self.get_network_addresses()85 addresses = self.get_network_addresses()
4986
=== modified file 'hooks/keystone_hooks.py'
--- hooks/keystone_hooks.py 2015-01-21 16:26:50 +0000
+++ hooks/keystone_hooks.py 2015-01-21 16:51:08 +0000
@@ -1,7 +1,9 @@
1#!/usr/bin/python1#!/usr/bin/python
2
3import hashlib2import hashlib
3import json
4import os4import os
5import re
6import stat
5import sys7import sys
6import time8import time
79
@@ -16,6 +18,8 @@
16 is_relation_made,18 is_relation_made,
17 log,19 log,
18 local_unit,20 local_unit,
21 DEBUG,
22 WARNING,
19 ERROR,23 ERROR,
20 relation_get,24 relation_get,
21 relation_ids,25 relation_ids,
@@ -48,9 +52,8 @@
48 get_admin_passwd,52 get_admin_passwd,
49 migrate_database,53 migrate_database,
50 save_script_rc,54 save_script_rc,
51 synchronize_ca,55 synchronize_ca_if_changed,
52 register_configs,56 register_configs,
53 relation_list,
54 restart_map,57 restart_map,
55 services,58 services,
56 CLUSTER_RES,59 CLUSTER_RES,
@@ -58,12 +61,18 @@
58 SSH_USER,61 SSH_USER,
59 setup_ipv6,62 setup_ipv6,
60 send_notifications,63 send_notifications,
64 check_peer_actions,
65 CA_CERT_PATH,
66 ensure_permissions,
67 get_ssl_sync_request_units,
68 is_str_true,
69 is_ssl_cert_master,
61)70)
6271
63from charmhelpers.contrib.hahelpers.cluster import (72from charmhelpers.contrib.hahelpers.cluster import (
64 eligible_leader,73 is_elected_leader,
65 is_leader,
66 get_hacluster_config,74 get_hacluster_config,
75 peer_units,
67)76)
6877
69from charmhelpers.payload.execd import execd_preinstall78from charmhelpers.payload.execd import execd_preinstall
@@ -73,6 +82,7 @@
73)82)
74from charmhelpers.contrib.openstack.ip import (83from charmhelpers.contrib.openstack.ip import (
75 ADMIN,84 ADMIN,
85 PUBLIC,
76 resolve_address,86 resolve_address,
77)87)
78from charmhelpers.contrib.network.ip import (88from charmhelpers.contrib.network.ip import (
@@ -100,12 +110,14 @@
100110
101@hooks.hook('config-changed')111@hooks.hook('config-changed')
102@restart_on_change(restart_map())112@restart_on_change(restart_map())
113@synchronize_ca_if_changed()
103def config_changed():114def config_changed():
104 if config('prefer-ipv6'):115 if config('prefer-ipv6'):
105 setup_ipv6()116 setup_ipv6()
106 sync_db_with_multi_ipv6_addresses(config('database'),117 sync_db_with_multi_ipv6_addresses(config('database'),
107 config('database-user'))118 config('database-user'))
108119
120 unison.ensure_user(user=SSH_USER, group='juju_keystone')
109 unison.ensure_user(user=SSH_USER, group='keystone')121 unison.ensure_user(user=SSH_USER, group='keystone')
110 homedir = unison.get_homedir(SSH_USER)122 homedir = unison.get_homedir(SSH_USER)
111 if not os.path.isdir(homedir):123 if not os.path.isdir(homedir):
@@ -116,25 +128,33 @@
116128
117 check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])129 check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
118130
131 # Ensure unison can write to certs dir.
132 # FIXME: need to a better way around this e.g. move cert to it's own dir
133 # and give that unison permissions.
134 path = os.path.dirname(CA_CERT_PATH)
135 perms = int(oct(stat.S_IMODE(os.stat(path).st_mode) |
136 (stat.S_IWGRP | stat.S_IXGRP)), base=8)
137 ensure_permissions(path, group='keystone', perms=perms)
138
119 save_script_rc()139 save_script_rc()
120 configure_https()140 configure_https()
121 update_nrpe_config()141 update_nrpe_config()
122 CONFIGS.write_all()142 CONFIGS.write_all()
123 if eligible_leader(CLUSTER_RES):143
124 migrate_database()144 # Update relations since SSL may have been configured. If we have peer
125 ensure_initial_admin(config)145 # units we can rely on the sync to do this in cluster relation.
126 log('Firing identity_changed hook for all related services.')146 if is_elected_leader(CLUSTER_RES) and not peer_units():
127 # HTTPS may have been set - so fire all identity relations147 update_all_identity_relation_units()
128 # again
129 for r_id in relation_ids('identity-service'):
130 for unit in relation_list(r_id):
131 identity_changed(relation_id=r_id,
132 remote_unit=unit)
133148
134 for rid in relation_ids('identity-admin'):149 for rid in relation_ids('identity-admin'):
135 admin_relation_changed(rid)150 admin_relation_changed(rid)
136 for rid in relation_ids('cluster'):151
137 cluster_joined(rid)152 # Ensure sync request is sent out (needed for upgrade to ssl from non-ssl)
153 settings = {}
154 append_ssl_sync_request(settings)
155 if settings:
156 for rid in relation_ids('cluster'):
157 relation_set(relation_id=rid, relation_settings=settings)
138158
139159
140@hooks.hook('shared-db-relation-joined')160@hooks.hook('shared-db-relation-joined')
@@ -167,14 +187,35 @@
167 relation_set(database=config('database'))187 relation_set(database=config('database'))
168188
169189
190def update_all_identity_relation_units():
191 CONFIGS.write_all()
192 try:
193 migrate_database()
194 except Exception as exc:
195 log("Database initialisation failed (%s) - db not ready?" % (exc),
196 level=WARNING)
197 else:
198 ensure_initial_admin(config)
199 log('Firing identity_changed hook for all related services.')
200 for rid in relation_ids('identity-service'):
201 for unit in related_units(rid):
202 identity_changed(relation_id=rid, remote_unit=unit)
203
204
205@synchronize_ca_if_changed(force=True)
206def update_all_identity_relation_units_force_sync():
207 update_all_identity_relation_units()
208
209
170@hooks.hook('shared-db-relation-changed')210@hooks.hook('shared-db-relation-changed')
171@restart_on_change(restart_map())211@restart_on_change(restart_map())
212@synchronize_ca_if_changed()
172def db_changed():213def db_changed():
173 if 'shared-db' not in CONFIGS.complete_contexts():214 if 'shared-db' not in CONFIGS.complete_contexts():
174 log('shared-db relation incomplete. Peer not ready?')215 log('shared-db relation incomplete. Peer not ready?')
175 else:216 else:
176 CONFIGS.write(KEYSTONE_CONF)217 CONFIGS.write(KEYSTONE_CONF)
177 if eligible_leader(CLUSTER_RES):218 if is_elected_leader(CLUSTER_RES):
178 # Bugs 1353135 & 1187508. Dbs can appear to be ready before the219 # Bugs 1353135 & 1187508. Dbs can appear to be ready before the
179 # units acl entry has been added. So, if the db supports passing220 # units acl entry has been added. So, if the db supports passing
180 # a list of permitted units then check if we're in the list.221 # a list of permitted units then check if we're in the list.
@@ -182,38 +223,46 @@
182 if allowed_units and local_unit() not in allowed_units.split():223 if allowed_units and local_unit() not in allowed_units.split():
183 log('Allowed_units list provided and this unit not present')224 log('Allowed_units list provided and this unit not present')
184 return225 return
185 migrate_database()
186 ensure_initial_admin(config)
187 # Ensure any existing service entries are updated in the226 # Ensure any existing service entries are updated in the
188 # new database backend227 # new database backend
189 for rid in relation_ids('identity-service'):228 update_all_identity_relation_units()
190 for unit in related_units(rid):
191 identity_changed(relation_id=rid, remote_unit=unit)
192229
193230
194@hooks.hook('pgsql-db-relation-changed')231@hooks.hook('pgsql-db-relation-changed')
195@restart_on_change(restart_map())232@restart_on_change(restart_map())
233@synchronize_ca_if_changed()
196def pgsql_db_changed():234def pgsql_db_changed():
197 if 'pgsql-db' not in CONFIGS.complete_contexts():235 if 'pgsql-db' not in CONFIGS.complete_contexts():
198 log('pgsql-db relation incomplete. Peer not ready?')236 log('pgsql-db relation incomplete. Peer not ready?')
199 else:237 else:
200 CONFIGS.write(KEYSTONE_CONF)238 CONFIGS.write(KEYSTONE_CONF)
201 if eligible_leader(CLUSTER_RES):239 if is_elected_leader(CLUSTER_RES):
202 migrate_database()
203 ensure_initial_admin(config)
204 # Ensure any existing service entries are updated in the240 # Ensure any existing service entries are updated in the
205 # new database backend241 # new database backend
206 for rid in relation_ids('identity-service'):242 update_all_identity_relation_units()
207 for unit in related_units(rid):
208 identity_changed(relation_id=rid, remote_unit=unit)
209243
210244
211@hooks.hook('identity-service-relation-changed')245@hooks.hook('identity-service-relation-changed')
246@synchronize_ca_if_changed()
212def identity_changed(relation_id=None, remote_unit=None):247def identity_changed(relation_id=None, remote_unit=None):
248 CONFIGS.write_all()
249
213 notifications = {}250 notifications = {}
214 if eligible_leader(CLUSTER_RES):251 if is_elected_leader(CLUSTER_RES):
215 add_service_to_keystone(relation_id, remote_unit)252 # Catch database not configured error and defer until db ready
216 synchronize_ca()253 from keystoneclient.apiclient.exceptions import InternalServerError
254 try:
255 add_service_to_keystone(relation_id, remote_unit)
256 except InternalServerError as exc:
257 key = re.compile("'keystone\..+' doesn't exist")
258 if re.search(key, exc.message):
259 log("Keystone database not yet ready (InternalServerError "
260 "raised) - deferring until *-db relation completes.",
261 level=WARNING)
262 return
263
264 log("Unexpected exception occurred", level=ERROR)
265 raise
217266
218 settings = relation_get(rid=relation_id, unit=remote_unit)267 settings = relation_get(rid=relation_id, unit=remote_unit)
219 service = settings.get('service', None)268 service = settings.get('service', None)
@@ -241,46 +290,113 @@
241 send_notifications(notifications)290 send_notifications(notifications)
242291
243292
293def append_ssl_sync_request(settings):
294 """Add request to be synced to relation settings.
295
296 This will be consumed by cluster-relation-changed ssl master.
297 """
298 if (is_str_true(config('use-https')) or
299 is_str_true(config('https-service-endpoints'))):
300 unit = local_unit().replace('/', '-')
301 settings['ssl-sync-required-%s' % (unit)] = '1'
302
303
244@hooks.hook('cluster-relation-joined')304@hooks.hook('cluster-relation-joined')
245def cluster_joined(relation_id=None):305def cluster_joined():
246 unison.ssh_authorized_peers(user=SSH_USER,306 unison.ssh_authorized_peers(user=SSH_USER,
247 group='juju_keystone',307 group='juju_keystone',
248 peer_interface='cluster',308 peer_interface='cluster',
249 ensure_local_user=True)309 ensure_local_user=True)
310
311 settings = {}
312
250 for addr_type in ADDRESS_TYPES:313 for addr_type in ADDRESS_TYPES:
251 address = get_address_in_network(314 address = get_address_in_network(
252 config('os-{}-network'.format(addr_type))315 config('os-{}-network'.format(addr_type))
253 )316 )
254 if address:317 if address:
255 relation_set(318 settings['{}-address'.format(addr_type)] = address
256 relation_id=relation_id,
257 relation_settings={'{}-address'.format(addr_type): address}
258 )
259319
260 if config('prefer-ipv6'):320 if config('prefer-ipv6'):
261 private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]321 private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
262 relation_set(relation_id=relation_id,322 settings['private-address'] = private_addr
263 relation_settings={'private-address': private_addr})323
324 append_ssl_sync_request(settings)
325
326 relation_set(relation_settings=settings)
327
328
329def apply_echo_filters(settings, echo_whitelist):
330 """Filter settings to be peer_echo'ed.
331
332 We may have received some data that we don't want to re-echo so filter
333 out unwanted keys and provide overrides.
334
335 Returns:
336 tuple(filtered list of keys to be echoed, overrides for keys omitted)
337 """
338 filtered = []
339 overrides = {}
340 for key in settings.iterkeys():
341 for ekey in echo_whitelist:
342 if ekey in key:
343 if ekey == 'identity-service:':
344 auth_host = resolve_address(ADMIN)
345 service_host = resolve_address(PUBLIC)
346 if (key.endswith('auth_host') and
347 settings[key] != auth_host):
348 overrides[key] = auth_host
349 continue
350 elif (key.endswith('service_host') and
351 settings[key] != service_host):
352 overrides[key] = service_host
353 continue
354
355 filtered.append(key)
356
357 return filtered, overrides
264358
265359
266@hooks.hook('cluster-relation-changed',360@hooks.hook('cluster-relation-changed',
267 'cluster-relation-departed')361 'cluster-relation-departed')
268@restart_on_change(restart_map(), stopstart=True)362@restart_on_change(restart_map(), stopstart=True)
269def cluster_changed():363def cluster_changed():
364 settings = relation_get()
270 # NOTE(jamespage) re-echo passwords for peer storage365 # NOTE(jamespage) re-echo passwords for peer storage
271 peer_echo(includes=['_passwd', 'identity-service:'])366 echo_whitelist, overrides = \
367 apply_echo_filters(settings, ['_passwd', 'identity-service:',
368 'ssl-cert-master'])
369 log("Peer echo overrides: %s" % (overrides), level=DEBUG)
370 relation_set(**overrides)
371 if echo_whitelist:
372 log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG)
373 peer_echo(includes=echo_whitelist)
374
375 check_peer_actions()
272 unison.ssh_authorized_peers(user=SSH_USER,376 unison.ssh_authorized_peers(user=SSH_USER,
273 group='keystone',377 group='keystone',
274 peer_interface='cluster',378 peer_interface='cluster',
275 ensure_local_user=True)379 ensure_local_user=True)
276 synchronize_ca()380
277 CONFIGS.write_all()381 if is_elected_leader(CLUSTER_RES) or is_ssl_cert_master():
278 for r_id in relation_ids('identity-service'):382 units = get_ssl_sync_request_units()
279 for unit in relation_list(r_id):383 synced_units = relation_get(attribute='ssl-synced-units',
280 identity_changed(relation_id=r_id,384 unit=local_unit())
281 remote_unit=unit)385 if synced_units:
282 for rid in relation_ids('identity-admin'):386 synced_units = json.loads(synced_units)
283 admin_relation_changed(rid)387 diff = set(units).symmetric_difference(set(synced_units))
388
389 if units and (not synced_units or diff):
390 log("New peers joined and need syncing - %s" %
391 (', '.join(units)), level=DEBUG)
392 update_all_identity_relation_units_force_sync()
393 else:
394 update_all_identity_relation_units()
395
396 for rid in relation_ids('identity-admin'):
397 admin_relation_changed(rid)
398 else:
399 CONFIGS.write_all()
284400
285401
286@hooks.hook('ha-relation-joined')402@hooks.hook('ha-relation-joined')
@@ -320,7 +436,7 @@
320 vip_group.append(vip_key)436 vip_group.append(vip_key)
321437
322 if len(vip_group) >= 1:438 if len(vip_group) >= 1:
323 relation_set(groups={'grp_ks_vips': ' '.join(vip_group)})439 relation_set(groups={CLUSTER_RES: ' '.join(vip_group)})
324440
325 init_services = {441 init_services = {
326 'res_ks_haproxy': 'haproxy'442 'res_ks_haproxy': 'haproxy'
@@ -338,17 +454,17 @@
338454
339@hooks.hook('ha-relation-changed')455@hooks.hook('ha-relation-changed')
340@restart_on_change(restart_map())456@restart_on_change(restart_map())
457@synchronize_ca_if_changed()
341def ha_changed():458def ha_changed():
459 CONFIGS.write_all()
460
342 clustered = relation_get('clustered')461 clustered = relation_get('clustered')
343 CONFIGS.write_all()462 if clustered and is_elected_leader(CLUSTER_RES):
344 if (clustered is not None and
345 is_leader(CLUSTER_RES)):
346 ensure_initial_admin(config)463 ensure_initial_admin(config)
347 log('Cluster configured, notifying other services and updating '464 log('Cluster configured, notifying other services and updating '
348 'keystone endpoint configuration')465 'keystone endpoint configuration')
349 for rid in relation_ids('identity-service'):466
350 for unit in related_units(rid):467 update_all_identity_relation_units()
351 identity_changed(relation_id=rid, remote_unit=unit)
352468
353469
354@hooks.hook('identity-admin-relation-changed')470@hooks.hook('identity-admin-relation-changed')
@@ -365,6 +481,7 @@
365 relation_set(relation_id=relation_id, **relation_data)481 relation_set(relation_id=relation_id, **relation_data)
366482
367483
484@synchronize_ca_if_changed(fatal=True)
368def configure_https():485def configure_https():
369 '''486 '''
370 Enables SSL API Apache config if appropriate and kicks identity-service487 Enables SSL API Apache config if appropriate and kicks identity-service
@@ -383,25 +500,22 @@
383500
384@hooks.hook('upgrade-charm')501@hooks.hook('upgrade-charm')
385@restart_on_change(restart_map(), stopstart=True)502@restart_on_change(restart_map(), stopstart=True)
503@synchronize_ca_if_changed()
386def upgrade_charm():504def upgrade_charm():
387 apt_install(filter_installed_packages(determine_packages()))505 apt_install(filter_installed_packages(determine_packages()))
388 unison.ssh_authorized_peers(user=SSH_USER,506 unison.ssh_authorized_peers(user=SSH_USER,
389 group='keystone',507 group='keystone',
390 peer_interface='cluster',508 peer_interface='cluster',
391 ensure_local_user=True)509 ensure_local_user=True)
510
511 CONFIGS.write_all()
392 update_nrpe_config()512 update_nrpe_config()
393 synchronize_ca()513
394 if eligible_leader(CLUSTER_RES):514 if is_elected_leader(CLUSTER_RES):
395 log('Cluster leader - ensuring endpoint configuration'515 log('Cluster leader - ensuring endpoint configuration is up to '
396 ' is up to date')516 'date', level=DEBUG)
397 time.sleep(10)517 time.sleep(10)
398 ensure_initial_admin(config)518 update_all_identity_relation_units()
399 # Deal with interface changes for icehouse
400 for r_id in relation_ids('identity-service'):
401 for unit in relation_list(r_id):
402 identity_changed(relation_id=r_id,
403 remote_unit=unit)
404 CONFIGS.write_all()
405519
406520
407@hooks.hook('nrpe-external-master-relation-joined',521@hooks.hook('nrpe-external-master-relation-joined',
408522
=== modified file 'hooks/keystone_ssl.py'
--- hooks/keystone_ssl.py 2014-07-02 07:55:44 +0000
+++ hooks/keystone_ssl.py 2015-01-21 16:51:08 +0000
@@ -5,6 +5,13 @@
5import subprocess5import subprocess
6import tarfile6import tarfile
7import tempfile7import tempfile
8import time
9
10from charmhelpers.core.hookenv import (
11 log,
12 DEBUG,
13 WARNING,
14)
815
9CA_EXPIRY = '365'16CA_EXPIRY = '365'
10ORG_NAME = 'Ubuntu'17ORG_NAME = 'Ubuntu'
@@ -101,6 +108,9 @@
101extendedKeyUsage = serverAuth, clientAuth108extendedKeyUsage = serverAuth, clientAuth
102"""109"""
103110
111# Instance can be appended to this list to represent a singleton
112CA_SINGLETON = []
113
104114
105def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):115def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
106 print 'Ensuring certificate authority exists at %s.' % ca_dir116 print 'Ensuring certificate authority exists at %s.' % ca_dir
@@ -275,23 +285,42 @@
275 crt = self._sign_csr(csr, service, common_name)285 crt = self._sign_csr(csr, service, common_name)
276 cmd = ['chown', '-R', '%s.%s' % (self.user, self.group), self.ca_dir]286 cmd = ['chown', '-R', '%s.%s' % (self.user, self.group), self.ca_dir]
277 subprocess.check_call(cmd)287 subprocess.check_call(cmd)
278 print 'Signed new CSR, crt @ %s' % crt288 log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
279 return crt, key289 return crt, key
280290
281 def get_cert_and_key(self, common_name):291 def get_cert_and_key(self, common_name):
282 print 'Getting certificate and key for %s.' % common_name292 log('Getting certificate and key for %s.' % common_name, level=DEBUG)
283 key = os.path.join(self.ca_dir, 'certs', '%s.key' % common_name)293 keypath = os.path.join(self.ca_dir, 'certs', '%s.key' % common_name)
284 crt = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)294 crtpath = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
285 if os.path.isfile(crt):295 if os.path.isfile(crtpath):
286 print 'Found existing certificate for %s.' % common_name296 log('Found existing certificate for %s.' % common_name,
287 crt = open(crt, 'r').read()297 level=DEBUG)
288 try:298 max_retries = 3
289 key = open(key, 'r').read()299 while True:
290 except:300 mtime = os.path.getmtime(crtpath)
291 print 'Could not load ssl private key for %s from %s' %\301
292 (common_name, key)302 crt = open(crtpath, 'r').read()
293 exit(1)303 try:
294 return crt, key304 key = open(keypath, 'r').read()
305 except:
306 msg = ('Could not load ssl private key for %s from %s' %
307 (common_name, keypath))
308 raise Exception(msg)
309
310 # Ensure we are not reading a file that is being written to
311 if mtime != os.path.getmtime(crtpath):
312 max_retries -= 1
313 if max_retries == 0:
314 msg = ("crt contents changed during read - retry "
315 "failed")
316 raise Exception(msg)
317
318 log("crt contents changed during read - re-reading",
319 level=WARNING)
320 time.sleep(1)
321 else:
322 return crt, key
323
295 crt, key = self._create_certificate(common_name, common_name)324 crt, key = self._create_certificate(common_name, common_name)
296 return open(crt, 'r').read(), open(key, 'r').read()325 return open(crt, 'r').read(), open(key, 'r').read()
297326
298327
=== modified file 'hooks/keystone_utils.py'
--- hooks/keystone_utils.py 2015-01-19 10:45:41 +0000
+++ hooks/keystone_utils.py 2015-01-21 16:51:08 +0000
@@ -1,20 +1,27 @@
1#!/usr/bin/python1#!/usr/bin/python
2import glob
3import grp
4import hashlib
5import json
6import os
7import pwd
8import re
2import subprocess9import subprocess
3import os10import threading
11import time
12import urlparse
4import uuid13import uuid
5import urlparse
6import time
714
8from base64 import b64encode15from base64 import b64encode
9from collections import OrderedDict16from collections import OrderedDict
10from copy import deepcopy17from copy import deepcopy
1118
12from charmhelpers.contrib.hahelpers.cluster import(19from charmhelpers.contrib.hahelpers.cluster import(
13 eligible_leader,20 is_elected_leader,
14 determine_api_port,21 determine_api_port,
15 https,22 https,
16 is_clustered,23 peer_units,
17 is_elected_leader,24 oldest_peer,
18)25)
1926
20from charmhelpers.contrib.openstack import context, templating27from charmhelpers.contrib.openstack import context, templating
@@ -37,8 +44,17 @@
37 os_release,44 os_release,
38 save_script_rc as _save_script_rc)45 save_script_rc as _save_script_rc)
3946
47from charmhelpers.core.host import (
48 mkdir,
49 write_file,
50)
51
40import charmhelpers.contrib.unison as unison52import charmhelpers.contrib.unison as unison
4153
54from charmhelpers.core.decorators import (
55 retry_on_exception,
56)
57
42from charmhelpers.core.hookenv import (58from charmhelpers.core.hookenv import (
43 config,59 config,
44 is_relation_made,60 is_relation_made,
@@ -47,8 +63,11 @@
47 relation_get,63 relation_get,
48 relation_set,64 relation_set,
49 relation_ids,65 relation_ids,
66 related_units,
50 DEBUG,67 DEBUG,
51 INFO,68 INFO,
69 WARNING,
70 ERROR,
52)71)
5372
54from charmhelpers.fetch import (73from charmhelpers.fetch import (
@@ -61,6 +80,7 @@
61from charmhelpers.core.host import (80from charmhelpers.core.host import (
62 service_stop,81 service_stop,
63 service_start,82 service_start,
83 service_restart,
64 pwgen,84 pwgen,
65 lsb_release85 lsb_release
66)86)
@@ -110,10 +130,13 @@
110APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'130APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
111APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'131APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
112132
133APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
134SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
113SSL_DIR = '/var/lib/keystone/juju_ssl/'135SSL_DIR = '/var/lib/keystone/juju_ssl/'
114SSL_CA_NAME = 'Ubuntu Cloud'136SSL_CA_NAME = 'Ubuntu Cloud'
115CLUSTER_RES = 'grp_ks_vips'137CLUSTER_RES = 'grp_ks_vips'
116SSH_USER = 'juju_keystone'138SSH_USER = 'juju_keystone'
139SSL_SYNC_SEMAPHORE = threading.Semaphore()
117140
118BASE_RESOURCE_MAP = OrderedDict([141BASE_RESOURCE_MAP = OrderedDict([
119 (KEYSTONE_CONF, {142 (KEYSTONE_CONF, {
@@ -203,6 +226,13 @@
203}226}
204227
205228
229def is_str_true(value):
230 if value and value.lower() in ['true', 'yes']:
231 return True
232
233 return False
234
235
206def resource_map():236def resource_map():
207 '''237 '''
208 Dynamically generate a map of resources that will be managed for a single238 Dynamically generate a map of resources that will be managed for a single
@@ -287,7 +317,7 @@
287 configs.set_release(openstack_release=new_os_rel)317 configs.set_release(openstack_release=new_os_rel)
288 configs.write_all()318 configs.write_all()
289319
290 if eligible_leader(CLUSTER_RES):320 if is_elected_leader(CLUSTER_RES):
291 migrate_database()321 migrate_database()
292322
293323
@@ -389,7 +419,7 @@
389419
390 up_to_date = True420 up_to_date = True
391 for k in ['publicurl', 'adminurl', 'internalurl']:421 for k in ['publicurl', 'adminurl', 'internalurl']:
392 if ep[k] != locals()[k]:422 if ep.get(k) != locals()[k]:
393 up_to_date = False423 up_to_date = False
394424
395 if up_to_date:425 if up_to_date:
@@ -500,7 +530,7 @@
500 if passwd and passwd.lower() != "none":530 if passwd and passwd.lower() != "none":
501 return passwd531 return passwd
502532
503 if eligible_leader(CLUSTER_RES):533 if is_elected_leader(CLUSTER_RES):
504 if os.path.isfile(STORED_PASSWD):534 if os.path.isfile(STORED_PASSWD):
505 log("Loading stored passwd from %s" % STORED_PASSWD, level=INFO)535 log("Loading stored passwd from %s" % STORED_PASSWD, level=INFO)
506 with open(STORED_PASSWD, 'r') as fd:536 with open(STORED_PASSWD, 'r') as fd:
@@ -527,33 +557,47 @@
527557
528558
529def ensure_initial_admin(config):559def ensure_initial_admin(config):
530 """ Ensures the minimum admin stuff exists in whatever database we're560 # Allow retry on fail since leader may not be ready yet.
561 # NOTE(hopem): ks client may not be installed at module import time so we
562 # use this wrapped approach instead.
563 from keystoneclient.apiclient.exceptions import InternalServerError
564
565 @retry_on_exception(3, base_delay=3, exc_type=InternalServerError)
566 def _ensure_initial_admin(config):
567 """Ensures the minimum admin stuff exists in whatever database we're
531 using.568 using.
569
532 This and the helper functions it calls are meant to be idempotent and570 This and the helper functions it calls are meant to be idempotent and
533 run during install as well as during db-changed. This will maintain571 run during install as well as during db-changed. This will maintain
534 the admin tenant, user, role, service entry and endpoint across every572 the admin tenant, user, role, service entry and endpoint across every
535 datastore we might use.573 datastore we might use.
574
536 TODO: Possibly migrate data from one backend to another after it575 TODO: Possibly migrate data from one backend to another after it
537 changes?576 changes?
538 """577 """
539 create_tenant("admin")578 create_tenant("admin")
540 create_tenant(config("service-tenant"))579 create_tenant(config("service-tenant"))
541 # User is managed by ldap backend when using ldap identity580 # User is managed by ldap backend when using ldap identity
542 if not (config('identity-backend') == 'ldap' and config('ldap-readonly')):581 if not (config('identity-backend') ==
543 passwd = get_admin_passwd()582 'ldap' and config('ldap-readonly')):
544 if passwd:583 passwd = get_admin_passwd()
545 create_user(config('admin-user'), passwd, tenant='admin')584 if passwd:
546 update_user_password(config('admin-user'), passwd)585 create_user(config('admin-user'), passwd, tenant='admin')
547 create_role(config('admin-role'), config('admin-user'), 'admin')586 update_user_password(config('admin-user'), passwd)
548 create_service_entry("keystone", "identity", "Keystone Identity Service")587 create_role(config('admin-role'), config('admin-user'),
549588 'admin')
550 for region in config('region').split():589 create_service_entry("keystone", "identity",
551 create_keystone_endpoint(public_ip=resolve_address(PUBLIC),590 "Keystone Identity Service")
552 service_port=config("service-port"),591
553 internal_ip=resolve_address(INTERNAL),592 for region in config('region').split():
554 admin_ip=resolve_address(ADMIN),593 create_keystone_endpoint(public_ip=resolve_address(PUBLIC),
555 auth_port=config("admin-port"),594 service_port=config("service-port"),
556 region=region)595 internal_ip=resolve_address(INTERNAL),
596 admin_ip=resolve_address(ADMIN),
597 auth_port=config("admin-port"),
598 region=region)
599
600 return _ensure_initial_admin(config)
557601
558602
559def endpoint_url(ip, port):603def endpoint_url(ip, port):
@@ -621,20 +665,357 @@
621 return passwd665 return passwd
622666
623667
624def synchronize_ca():668def ensure_permissions(path, user=None, group=None, perms=None):
625 '''669 """Set chownand chmod for path
626 Broadcast service credentials to peers or consume those that have been670
627 broadcasted by peer, depending on hook context.671 Note that -1 for uid or gid result in no change.
628 '''672 """
629 if not eligible_leader(CLUSTER_RES):673 if user:
630 return674 uid = pwd.getpwnam(user).pw_uid
631 log('Synchronizing CA to all peers.')675 else:
632 if is_clustered():676 uid = -1
633 if config('https-service-endpoints') in ['True', 'true']:677
634 unison.sync_to_peers(peer_interface='cluster',678 if group:
635 paths=[SSL_DIR], user=SSH_USER, verbose=True)679 gid = grp.getgrnam(group).gr_gid
636680 else:
637CA = []681 gid = -1
682
683 os.chown(path, uid, gid)
684
685 if perms:
686 os.chmod(path, perms)
687
688
689def check_peer_actions():
690 """Honour service action requests from sync master.
691
692 Check for service action request flags, perform the action then delete the
693 flag.
694 """
695 restart = relation_get(attribute='restart-services-trigger')
696 if restart and os.path.isdir(SYNC_FLAGS_DIR):
697 for flagfile in glob.glob(os.path.join(SYNC_FLAGS_DIR, '*')):
698 flag = os.path.basename(flagfile)
699 key = re.compile("^(.+)?\.(.+)?\.(.+)")
700 res = re.search(key, flag)
701 if res:
702 source = res.group(1)
703 service = res.group(2)
704 action = res.group(3)
705 else:
706 key = re.compile("^(.+)?\.(.+)?")
707 res = re.search(key, flag)
708 source = res.group(1)
709 action = res.group(2)
710
711 # Don't execute actions requested by this unit.
712 if local_unit().replace('.', '-') != source:
713 if action == 'restart':
714 log("Running action='%s' on service '%s'" %
715 (action, service), level=DEBUG)
716 service_restart(service)
717 elif action == 'start':
718 log("Running action='%s' on service '%s'" %
719 (action, service), level=DEBUG)
720 service_start(service)
721 elif action == 'stop':
722 log("Running action='%s' on service '%s'" %
723 (action, service), level=DEBUG)
724 service_stop(service)
725 elif action == 'update-ca-certificates':
726 log("Running %s" % (action), level=DEBUG)
727 subprocess.check_call(['update-ca-certificates'])
728 else:
729 log("Unknown action flag=%s" % (flag), level=WARNING)
730
731 try:
732 os.remove(flagfile)
733 except:
734 pass
735
736
737def create_peer_service_actions(action, services):
738 """Mark remote services for action.
739
740 Default action is restart. These action will be picked up by peer units
741 e.g. we may need to restart services on peer units after certs have been
742 synced.
743 """
744 for service in services:
745 flagfile = os.path.join(SYNC_FLAGS_DIR, '%s.%s.%s' %
746 (local_unit().replace('/', '-'),
747 service.strip(), action))
748 log("Creating action %s" % (flagfile), level=DEBUG)
749 write_file(flagfile, content='', owner=SSH_USER, group='keystone',
750 perms=0o644)
751
752
753def create_peer_actions(actions):
754 for action in actions:
755 action = "%s.%s" % (local_unit().replace('/', '-'), action)
756 flagfile = os.path.join(SYNC_FLAGS_DIR, action)
757 log("Creating action %s" % (flagfile), level=DEBUG)
758 write_file(flagfile, content='', owner=SSH_USER, group='keystone',
759 perms=0o644)
760
761
762@retry_on_exception(3, base_delay=2, exc_type=subprocess.CalledProcessError)
763def unison_sync(paths_to_sync):
764 """Do unison sync and retry a few times if it fails since peers may not be
765 ready for sync.
766 """
767 log('Synchronizing CA (%s) to all peers.' % (', '.join(paths_to_sync)),
768 level=INFO)
769 keystone_gid = grp.getgrnam('keystone').gr_gid
770 unison.sync_to_peers(peer_interface='cluster', paths=paths_to_sync,
771 user=SSH_USER, verbose=True, gid=keystone_gid,
772 fatal=True)
773
774
775def get_ssl_sync_request_units():
776 """Get list of units that have requested to be synced.
777
778 NOTE: this must be called from cluster relation context.
779 """
780 units = []
781 for unit in related_units():
782 settings = relation_get(unit=unit) or {}
783 rkeys = settings.keys()
784 key = re.compile("^ssl-sync-required-(.+)")
785 for rkey in rkeys:
786 res = re.search(key, rkey)
787 if res:
788 units.append(res.group(1))
789
790 return units
791
792
793def is_ssl_cert_master():
794 """Return True if this unit is ssl cert master."""
795 master = None
796 for rid in relation_ids('cluster'):
797 master = relation_get(attribute='ssl-cert-master', rid=rid,
798 unit=local_unit())
799
800 return master == local_unit()
801
802
803def ensure_ssl_cert_master(use_oldest_peer=False):
804 """Ensure that an ssl cert master has been elected.
805
806 Normally the cluster leader will take control but we allow for this to be
807 ignored since this could be called before the cluster is ready.
808 """
809 # Don't do anything if we are not in ssl/https mode
810 if not (is_str_true(config('use-https')) or
811 is_str_true(config('https-service-endpoints'))):
812 log("SSL/HTTPS is NOT enabled", level=DEBUG)
813 return False
814
815 if not peer_units():
816 log("Not syncing certs since there are no peer units.", level=INFO)
817 return False
818
819 if use_oldest_peer:
820 elect = oldest_peer(peer_units())
821 else:
822 elect = is_elected_leader(CLUSTER_RES)
823
824 if elect:
825 masters = []
826 for rid in relation_ids('cluster'):
827 for unit in related_units(rid):
828 m = relation_get(rid=rid, unit=unit,
829 attribute='ssl-cert-master')
830 if m is not None:
831 masters.append(m)
832
833 # We expect all peers to echo this setting
834 if not masters or 'unknown' in masters:
835 log("Notifying peers this unit is ssl-cert-master", level=INFO)
836 for rid in relation_ids('cluster'):
837 settings = {'ssl-cert-master': local_unit()}
838 relation_set(relation_id=rid, relation_settings=settings)
839
840 # Return now and wait for cluster-relation-changed (peer_echo) for
841 # sync.
842 return False
843 elif len(set(masters)) != 1 and local_unit() not in masters:
844 log("Did not get concensus from peers on who is master (%s) - "
845 "waiting for current master to release before self-electing" %
846 (masters), level=INFO)
847 return False
848
849 if not is_ssl_cert_master():
850 log("Not ssl cert master - skipping sync", level=INFO)
851 return False
852
853 return True
854
855
856def synchronize_ca(fatal=False):
857 """Broadcast service credentials to peers.
858
859 By default a failure to sync is fatal and will result in a raised
860 exception.
861
862 This function uses a relation setting 'ssl-cert-master' to get some
863 leader stickiness while synchronisation is being carried out. This ensures
864 that the last host to create and broadcast cetificates has the option to
865 complete actions before electing the new leader as sync master.
866 """
867 paths_to_sync = [SYNC_FLAGS_DIR]
868
869 if is_str_true(config('https-service-endpoints')):
870 log("Syncing all endpoint certs since https-service-endpoints=True",
871 level=DEBUG)
872 paths_to_sync.append(SSL_DIR)
873 paths_to_sync.append(APACHE_SSL_DIR)
874 paths_to_sync.append(CA_CERT_PATH)
875 elif is_str_true(config('use-https')):
876 log("Syncing keystone-endpoint certs since use-https=True",
877 level=DEBUG)
878 paths_to_sync.append(APACHE_SSL_DIR)
879 paths_to_sync.append(CA_CERT_PATH)
880
881 if not paths_to_sync:
882 log("Nothing to sync - skipping", level=DEBUG)
883 return
884
885 if not os.path.isdir(SYNC_FLAGS_DIR):
886 mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
887
888 # We need to restart peer apache services to ensure they have picked up
889 # new ssl keys.
890 create_peer_service_actions('restart', ['apache2'])
891 create_peer_actions(['update-ca-certificates'])
892
893 # Format here needs to match that used when peers request sync
894 synced_units = [unit.replace('/', '-') for unit in peer_units()]
895
896 retries = 3
897 while True:
898 hash1 = hashlib.sha256()
899 for path in paths_to_sync:
900 update_hash_from_path(hash1, path)
901
902 try:
903 unison_sync(paths_to_sync)
904 except:
905 if fatal:
906 raise
907 else:
908 log("Sync failed but fatal=False", level=INFO)
909 return
910
911 hash2 = hashlib.sha256()
912 for path in paths_to_sync:
913 update_hash_from_path(hash2, path)
914
915 # Detect whether someone else has synced to this unit while we did our
916 # transfer.
917 if hash1.hexdigest() != hash2.hexdigest():
918 retries -= 1
919 if retries > 0:
920 log("SSL dir contents changed during sync - retrying unison "
921 "sync %s more times" % (retries), level=WARNING)
922 else:
923 log("SSL dir contents changed during sync - retries failed",
924 level=ERROR)
925 return {}
926 else:
927 break
928
929 hash = hash1.hexdigest()
930 log("Sending restart-services-trigger=%s to all peers" % (hash),
931 level=DEBUG)
932
933 log("Sync complete", level=DEBUG)
934 return {'restart-services-trigger': hash,
935 'ssl-synced-units': json.dumps(synced_units)}
936
937
938def update_hash_from_path(hash, path, recurse_depth=10):
939 """Recurse through path and update the provided hash for every file found.
940 """
941 if not recurse_depth:
942 log("Max recursion depth (%s) reached for update_hash_from_path() at "
943 "path='%s' - not going any deeper" % (recurse_depth, path),
944 level=WARNING)
945 return
946
947 for p in glob.glob("%s/*" % path):
948 if os.path.isdir(p):
949 update_hash_from_path(hash, p, recurse_depth=recurse_depth - 1)
950 else:
951 with open(p, 'r') as fd:
952 hash.update(fd.read())
953
954
955def synchronize_ca_if_changed(force=False, fatal=False):
956 """Decorator to perform ssl cert sync if decorated function modifies them
957 in any way.
958
959 If force is True a sync is done regardless.
960 """
961 def inner_synchronize_ca_if_changed1(f):
962 def inner_synchronize_ca_if_changed2(*args, **kwargs):
963 # Only sync master can do sync. Ensure (a) we are not nested and
964 # (b) a master is elected and we are it.
965 acquired = SSL_SYNC_SEMAPHORE.acquire(blocking=0)
966 try:
967 if not acquired:
968 log("Nested sync - ignoring", level=DEBUG)
969 return f(*args, **kwargs)
970
971 if not ensure_ssl_cert_master():
972 log("Not leader - ignoring sync", level=DEBUG)
973 return f(*args, **kwargs)
974
975 peer_settings = {}
976 if not force:
977 ssl_dirs = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
978
979 hash1 = hashlib.sha256()
980 for path in ssl_dirs:
981 update_hash_from_path(hash1, path)
982
983 ret = f(*args, **kwargs)
984
985 hash2 = hashlib.sha256()
986 for path in ssl_dirs:
987 update_hash_from_path(hash2, path)
988
989 if hash1.hexdigest() != hash2.hexdigest():
990 log("SSL certs have changed - syncing peers",
991 level=DEBUG)
992 peer_settings = synchronize_ca(fatal=fatal)
993 else:
994 log("SSL certs have not changed - skipping sync",
995 level=DEBUG)
996 else:
997 ret = f(*args, **kwargs)
998 log("Doing forced ssl cert sync", level=DEBUG)
999 peer_settings = synchronize_ca(fatal=fatal)
1000
1001 # If we are the sync master but not leader, ensure we have
1002 # relinquished master status.
1003 if not is_elected_leader(CLUSTER_RES):
1004 log("Re-electing ssl cert master.", level=INFO)
1005 peer_settings['ssl-cert-master'] = 'unknown'
1006
1007 if peer_settings:
1008 for rid in relation_ids('cluster'):
1009 relation_set(relation_id=rid,
1010 relation_settings=peer_settings)
1011
1012 return ret
1013 finally:
1014 SSL_SYNC_SEMAPHORE.release()
1015
1016 return inner_synchronize_ca_if_changed2
1017
1018 return inner_synchronize_ca_if_changed1
6381019
6391020
640def get_ca(user='keystone', group='keystone'):1021def get_ca(user='keystone', group='keystone'):
@@ -642,22 +1023,32 @@
642 Initialize a new CA object if one hasn't already been loaded.1023 Initialize a new CA object if one hasn't already been loaded.
643 This will create a new CA or load an existing one.1024 This will create a new CA or load an existing one.
644 """1025 """
645 if not CA:1026 if not ssl.CA_SINGLETON:
646 if not os.path.isdir(SSL_DIR):1027 if not os.path.isdir(SSL_DIR):
647 os.mkdir(SSL_DIR)1028 os.mkdir(SSL_DIR)
1029
648 d_name = '_'.join(SSL_CA_NAME.lower().split(' '))1030 d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
649 ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,1031 ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,
650 ca_dir=os.path.join(SSL_DIR,1032 ca_dir=os.path.join(SSL_DIR,
651 '%s_intermediate_ca' % d_name),1033 '%s_intermediate_ca' % d_name),
652 root_ca_dir=os.path.join(SSL_DIR,1034 root_ca_dir=os.path.join(SSL_DIR,
653 '%s_root_ca' % d_name))1035 '%s_root_ca' % d_name))
1036
654 # SSL_DIR is synchronized via all peers over unison+ssh, need1037 # SSL_DIR is synchronized via all peers over unison+ssh, need
655 # to ensure permissions.1038 # to ensure permissions.
656 subprocess.check_output(['chown', '-R', '%s.%s' % (user, group),1039 subprocess.check_output(['chown', '-R', '%s.%s' % (user, group),
657 '%s' % SSL_DIR])1040 '%s' % SSL_DIR])
658 subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])1041 subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
659 CA.append(ca)1042
660 return CA[0]1043 # Ensure a master has been elected and prefer this unit. Note that we
1044 # prefer oldest peer as predicate since this action i normally only
1045 # performed once at deploy time when the oldest peer should be the
1046 # first to be ready.
1047 ensure_ssl_cert_master(use_oldest_peer=True)
1048
1049 ssl.CA_SINGLETON.append(ca)
1050
1051 return ssl.CA_SINGLETON[0]
6611052
6621053
663def relation_list(rid):1054def relation_list(rid):
@@ -683,7 +1074,7 @@
683 https_cns = []1074 https_cns = []
684 if single.issubset(settings):1075 if single.issubset(settings):
685 # other end of relation advertised only one endpoint1076 # other end of relation advertised only one endpoint
686 if 'None' in [v for k, v in settings.iteritems()]:1077 if 'None' in settings.itervalues():
687 # Some backend services advertise no endpoint but require a1078 # Some backend services advertise no endpoint but require a
688 # hook execution to update auth strategy.1079 # hook execution to update auth strategy.
689 relation_data = {}1080 relation_data = {}
@@ -699,7 +1090,7 @@
699 relation_data["auth_port"] = config('admin-port')1090 relation_data["auth_port"] = config('admin-port')
700 relation_data["service_port"] = config('service-port')1091 relation_data["service_port"] = config('service-port')
701 relation_data["region"] = config('region')1092 relation_data["region"] = config('region')
702 if config('https-service-endpoints') in ['True', 'true']:1093 if is_str_true(config('https-service-endpoints')):
703 # Pass CA cert as client will need it to1094 # Pass CA cert as client will need it to
704 # verify https connections1095 # verify https connections
705 ca = get_ca(user=SSH_USER)1096 ca = get_ca(user=SSH_USER)
@@ -711,6 +1102,7 @@
711 for role in get_requested_roles(settings):1102 for role in get_requested_roles(settings):
712 log("Creating requested role: %s" % role)1103 log("Creating requested role: %s" % role)
713 create_role(role)1104 create_role(role)
1105
714 peer_store_and_set(relation_id=relation_id,1106 peer_store_and_set(relation_id=relation_id,
715 **relation_data)1107 **relation_data)
716 return1108 return
@@ -786,7 +1178,7 @@
786 if prefix:1178 if prefix:
787 service_username = "%s%s" % (prefix, service_username)1179 service_username = "%s%s" % (prefix, service_username)
7881180
789 if 'None' in [v for k, v in settings.iteritems()]:1181 if 'None' in settings.itervalues():
790 return1182 return
7911183
792 if not service_username:1184 if not service_username:
@@ -838,7 +1230,7 @@
838 relation_data["auth_protocol"] = "http"1230 relation_data["auth_protocol"] = "http"
839 relation_data["service_protocol"] = "http"1231 relation_data["service_protocol"] = "http"
840 # generate or get a new cert/key for service if set to manage certs.1232 # generate or get a new cert/key for service if set to manage certs.
841 if config('https-service-endpoints') in ['True', 'true']:1233 if is_str_true(config('https-service-endpoints')):
842 ca = get_ca(user=SSH_USER)1234 ca = get_ca(user=SSH_USER)
843 # NOTE(jamespage) may have multiple cns to deal with to iterate1235 # NOTE(jamespage) may have multiple cns to deal with to iterate
844 https_cns = set(https_cns)1236 https_cns = set(https_cns)
@@ -853,6 +1245,7 @@
853 ca_bundle = ca.get_ca_bundle()1245 ca_bundle = ca.get_ca_bundle()
854 relation_data['ca_cert'] = b64encode(ca_bundle)1246 relation_data['ca_cert'] = b64encode(ca_bundle)
855 relation_data['https_keystone'] = 'True'1247 relation_data['https_keystone'] = 'True'
1248
856 peer_store_and_set(relation_id=relation_id,1249 peer_store_and_set(relation_id=relation_id,
857 **relation_data)1250 **relation_data)
8581251
8591252
=== modified file 'unit_tests/test_keystone_hooks.py'
--- unit_tests/test_keystone_hooks.py 2015-01-21 16:26:50 +0000
+++ unit_tests/test_keystone_hooks.py 2015-01-21 16:51:08 +0000
@@ -1,6 +1,7 @@
1from mock import call, patch, MagicMock1from mock import call, patch, MagicMock
2import os2import os
3import json3import json
4import uuid
45
5from test_utils import CharmTestCase6from test_utils import CharmTestCase
67
@@ -30,7 +31,6 @@
30 'local_unit',31 'local_unit',
31 'filter_installed_packages',32 'filter_installed_packages',
32 'relation_ids',33 'relation_ids',
33 'relation_list',
34 'relation_set',34 'relation_set',
35 'relation_get',35 'relation_get',
36 'related_units',36 'related_units',
@@ -42,9 +42,10 @@
42 'restart_on_change',42 'restart_on_change',
43 # charmhelpers.contrib.openstack.utils43 # charmhelpers.contrib.openstack.utils
44 'configure_installation_source',44 'configure_installation_source',
45 # charmhelpers.contrib.openstack.ip
46 'resolve_address',
45 # charmhelpers.contrib.hahelpers.cluster_utils47 # charmhelpers.contrib.hahelpers.cluster_utils
46 'is_leader',48 'is_elected_leader',
47 'eligible_leader',
48 'get_hacluster_config',49 'get_hacluster_config',
49 # keystone_utils50 # keystone_utils
50 'restart_map',51 'restart_map',
@@ -55,7 +56,7 @@
55 'migrate_database',56 'migrate_database',
56 'ensure_initial_admin',57 'ensure_initial_admin',
57 'add_service_to_keystone',58 'add_service_to_keystone',
58 'synchronize_ca',59 'synchronize_ca_if_changed',
59 'update_nrpe_config',60 'update_nrpe_config',
60 # other61 # other
61 'check_call',62 'check_call',
@@ -160,8 +161,13 @@
160 'Attempting to associate a postgresql database when there '161 'Attempting to associate a postgresql database when there '
161 'is already associated a mysql one')162 'is already associated a mysql one')
162163
164 @patch('keystone_utils.log')
165 @patch('keystone_utils.ensure_ssl_cert_master')
163 @patch.object(hooks, 'CONFIGS')166 @patch.object(hooks, 'CONFIGS')
164 def test_db_changed_missing_relation_data(self, configs):167 def test_db_changed_missing_relation_data(self, configs,
168 mock_ensure_ssl_cert_master,
169 mock_log):
170 mock_ensure_ssl_cert_master.return_value = False
165 configs.complete_contexts = MagicMock()171 configs.complete_contexts = MagicMock()
166 configs.complete_contexts.return_value = []172 configs.complete_contexts.return_value = []
167 hooks.db_changed()173 hooks.db_changed()
@@ -169,8 +175,13 @@
169 'shared-db relation incomplete. Peer not ready?'175 'shared-db relation incomplete. Peer not ready?'
170 )176 )
171177
178 @patch('keystone_utils.log')
179 @patch('keystone_utils.ensure_ssl_cert_master')
172 @patch.object(hooks, 'CONFIGS')180 @patch.object(hooks, 'CONFIGS')
173 def test_postgresql_db_changed_missing_relation_data(self, configs):181 def test_postgresql_db_changed_missing_relation_data(self, configs,
182 mock_ensure_leader,
183 mock_log):
184 mock_ensure_leader.return_value = False
174 configs.complete_contexts = MagicMock()185 configs.complete_contexts = MagicMock()
175 configs.complete_contexts.return_value = []186 configs.complete_contexts.return_value = []
176 hooks.pgsql_db_changed()187 hooks.pgsql_db_changed()
@@ -192,9 +203,14 @@
192 configs.write = MagicMock()203 configs.write = MagicMock()
193 hooks.pgsql_db_changed()204 hooks.pgsql_db_changed()
194205
206 @patch('keystone_utils.log')
207 @patch('keystone_utils.ensure_ssl_cert_master')
195 @patch.object(hooks, 'CONFIGS')208 @patch.object(hooks, 'CONFIGS')
196 @patch.object(hooks, 'identity_changed')209 @patch.object(hooks, 'identity_changed')
197 def test_db_changed_allowed(self, identity_changed, configs):210 def test_db_changed_allowed(self, identity_changed, configs,
211 mock_ensure_ssl_cert_master,
212 mock_log):
213 mock_ensure_ssl_cert_master.return_value = False
198 self.relation_ids.return_value = ['identity-service:0']214 self.relation_ids.return_value = ['identity-service:0']
199 self.related_units.return_value = ['unit/0']215 self.related_units.return_value = ['unit/0']
200216
@@ -207,9 +223,13 @@
207 relation_id='identity-service:0',223 relation_id='identity-service:0',
208 remote_unit='unit/0')224 remote_unit='unit/0')
209225
226 @patch('keystone_utils.log')
227 @patch('keystone_utils.ensure_ssl_cert_master')
210 @patch.object(hooks, 'CONFIGS')228 @patch.object(hooks, 'CONFIGS')
211 @patch.object(hooks, 'identity_changed')229 @patch.object(hooks, 'identity_changed')
212 def test_db_changed_not_allowed(self, identity_changed, configs):230 def test_db_changed_not_allowed(self, identity_changed, configs,
231 mock_ensure_ssl_cert_master, mock_log):
232 mock_ensure_ssl_cert_master.return_value = False
213 self.relation_ids.return_value = ['identity-service:0']233 self.relation_ids.return_value = ['identity-service:0']
214 self.related_units.return_value = ['unit/0']234 self.related_units.return_value = ['unit/0']
215235
@@ -220,9 +240,13 @@
220 self.assertFalse(self.ensure_initial_admin.called)240 self.assertFalse(self.ensure_initial_admin.called)
221 self.assertFalse(identity_changed.called)241 self.assertFalse(identity_changed.called)
222242
243 @patch('keystone_utils.log')
244 @patch('keystone_utils.ensure_ssl_cert_master')
223 @patch.object(hooks, 'CONFIGS')245 @patch.object(hooks, 'CONFIGS')
224 @patch.object(hooks, 'identity_changed')246 @patch.object(hooks, 'identity_changed')
225 def test_postgresql_db_changed(self, identity_changed, configs):247 def test_postgresql_db_changed(self, identity_changed, configs,
248 mock_ensure_ssl_cert_master, mock_log):
249 mock_ensure_ssl_cert_master.return_value = False
226 self.relation_ids.return_value = ['identity-service:0']250 self.relation_ids.return_value = ['identity-service:0']
227 self.related_units.return_value = ['unit/0']251 self.related_units.return_value = ['unit/0']
228252
@@ -235,6 +259,10 @@
235 relation_id='identity-service:0',259 relation_id='identity-service:0',
236 remote_unit='unit/0')260 remote_unit='unit/0')
237261
262 @patch('keystone_utils.log')
263 @patch('keystone_utils.ensure_ssl_cert_master')
264 @patch.object(hooks, 'peer_units')
265 @patch.object(hooks, 'ensure_permissions')
238 @patch.object(hooks, 'admin_relation_changed')266 @patch.object(hooks, 'admin_relation_changed')
239 @patch.object(hooks, 'cluster_joined')267 @patch.object(hooks, 'cluster_joined')
240 @patch.object(unison, 'ensure_user')268 @patch.object(unison, 'ensure_user')
@@ -245,11 +273,15 @@
245 def test_config_changed_no_openstack_upgrade_leader(273 def test_config_changed_no_openstack_upgrade_leader(
246 self, configure_https, identity_changed,274 self, configure_https, identity_changed,
247 configs, get_homedir, ensure_user, cluster_joined,275 configs, get_homedir, ensure_user, cluster_joined,
248 admin_relation_changed):276 admin_relation_changed, ensure_permissions, mock_peer_units,
277 mock_ensure_ssl_cert_master, mock_log):
249 self.openstack_upgrade_available.return_value = False278 self.openstack_upgrade_available.return_value = False
250 self.eligible_leader.return_value = True279 self.is_elected_leader.return_value = True
251 self.relation_ids.return_value = ['dummyid:0']280 # avoid having to mock syncer
252 self.relation_list.return_value = ['unit/0']281 mock_ensure_ssl_cert_master.return_value = False
282 mock_peer_units.return_value = []
283 self.relation_ids.return_value = ['identity-service:0']
284 self.related_units.return_value = ['unit/0']
253285
254 hooks.config_changed()286 hooks.config_changed()
255 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')287 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
@@ -264,10 +296,13 @@
264 self.log.assert_called_with(296 self.log.assert_called_with(
265 'Firing identity_changed hook for all related services.')297 'Firing identity_changed hook for all related services.')
266 identity_changed.assert_called_with(298 identity_changed.assert_called_with(
267 relation_id='dummyid:0',299 relation_id='identity-service:0',
268 remote_unit='unit/0')300 remote_unit='unit/0')
269 admin_relation_changed.assert_called_with('dummyid:0')301 admin_relation_changed.assert_called_with('identity-service:0')
270302
303 @patch('keystone_utils.log')
304 @patch('keystone_utils.ensure_ssl_cert_master')
305 @patch.object(hooks, 'ensure_permissions')
271 @patch.object(hooks, 'cluster_joined')306 @patch.object(hooks, 'cluster_joined')
272 @patch.object(unison, 'ensure_user')307 @patch.object(unison, 'ensure_user')
273 @patch.object(unison, 'get_homedir')308 @patch.object(unison, 'get_homedir')
@@ -276,9 +311,12 @@
276 @patch.object(hooks, 'configure_https')311 @patch.object(hooks, 'configure_https')
277 def test_config_changed_no_openstack_upgrade_not_leader(312 def test_config_changed_no_openstack_upgrade_not_leader(
278 self, configure_https, identity_changed,313 self, configure_https, identity_changed,
279 configs, get_homedir, ensure_user, cluster_joined):314 configs, get_homedir, ensure_user, cluster_joined,
315 ensure_permissions, mock_ensure_ssl_cert_master,
316 mock_log):
280 self.openstack_upgrade_available.return_value = False317 self.openstack_upgrade_available.return_value = False
281 self.eligible_leader.return_value = False318 self.is_elected_leader.return_value = False
319 mock_ensure_ssl_cert_master.return_value = False
282320
283 hooks.config_changed()321 hooks.config_changed()
284 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')322 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
@@ -292,6 +330,10 @@
292 self.assertFalse(self.ensure_initial_admin.called)330 self.assertFalse(self.ensure_initial_admin.called)
293 self.assertFalse(identity_changed.called)331 self.assertFalse(identity_changed.called)
294332
333 @patch('keystone_utils.log')
334 @patch('keystone_utils.ensure_ssl_cert_master')
335 @patch.object(hooks, 'peer_units')
336 @patch.object(hooks, 'ensure_permissions')
295 @patch.object(hooks, 'admin_relation_changed')337 @patch.object(hooks, 'admin_relation_changed')
296 @patch.object(hooks, 'cluster_joined')338 @patch.object(hooks, 'cluster_joined')
297 @patch.object(unison, 'ensure_user')339 @patch.object(unison, 'ensure_user')
@@ -302,11 +344,16 @@
302 def test_config_changed_with_openstack_upgrade(344 def test_config_changed_with_openstack_upgrade(
303 self, configure_https, identity_changed,345 self, configure_https, identity_changed,
304 configs, get_homedir, ensure_user, cluster_joined,346 configs, get_homedir, ensure_user, cluster_joined,
305 admin_relation_changed):347 admin_relation_changed,
348 ensure_permissions, mock_peer_units, mock_ensure_ssl_cert_master,
349 mock_log):
306 self.openstack_upgrade_available.return_value = True350 self.openstack_upgrade_available.return_value = True
307 self.eligible_leader.return_value = True351 self.is_elected_leader.return_value = True
308 self.relation_ids.return_value = ['dummyid:0']352 # avoid having to mock syncer
309 self.relation_list.return_value = ['unit/0']353 mock_ensure_ssl_cert_master.return_value = False
354 mock_peer_units.return_value = []
355 self.relation_ids.return_value = ['identity-service:0']
356 self.related_units.return_value = ['unit/0']
310357
311 hooks.config_changed()358 hooks.config_changed()
312 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')359 ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
@@ -323,25 +370,33 @@
323 self.log.assert_called_with(370 self.log.assert_called_with(
324 'Firing identity_changed hook for all related services.')371 'Firing identity_changed hook for all related services.')
325 identity_changed.assert_called_with(372 identity_changed.assert_called_with(
326 relation_id='dummyid:0',373 relation_id='identity-service:0',
327 remote_unit='unit/0')374 remote_unit='unit/0')
328 admin_relation_changed.assert_called_with('dummyid:0')375 admin_relation_changed.assert_called_with('identity-service:0')
329376
377 @patch('keystone_utils.log')
378 @patch('keystone_utils.ensure_ssl_cert_master')
330 @patch.object(hooks, 'hashlib')379 @patch.object(hooks, 'hashlib')
331 @patch.object(hooks, 'send_notifications')380 @patch.object(hooks, 'send_notifications')
332 def test_identity_changed_leader(self, mock_send_notifications,381 def test_identity_changed_leader(self, mock_send_notifications,
333 mock_hashlib):382 mock_hashlib, mock_ensure_ssl_cert_master,
334 self.eligible_leader.return_value = True383 mock_log):
384 mock_ensure_ssl_cert_master.return_value = False
335 hooks.identity_changed(385 hooks.identity_changed(
336 relation_id='identity-service:0',386 relation_id='identity-service:0',
337 remote_unit='unit/0')387 remote_unit='unit/0')
338 self.add_service_to_keystone.assert_called_with(388 self.add_service_to_keystone.assert_called_with(
339 'identity-service:0',389 'identity-service:0',
340 'unit/0')390 'unit/0')
341 self.assertTrue(self.synchronize_ca.called)
342391
343 def test_identity_changed_no_leader(self):392 @patch.object(hooks, 'local_unit')
344 self.eligible_leader.return_value = False393 @patch('keystone_utils.log')
394 @patch('keystone_utils.ensure_ssl_cert_master')
395 def test_identity_changed_no_leader(self, mock_ensure_ssl_cert_master,
396 mock_log, mock_local_unit):
397 mock_ensure_ssl_cert_master.return_value = False
398 mock_local_unit.return_value = 'unit/0'
399 self.is_elected_leader.return_value = False
345 hooks.identity_changed(400 hooks.identity_changed(
346 relation_id='identity-service:0',401 relation_id='identity-service:0',
347 remote_unit='unit/0')402 remote_unit='unit/0')
@@ -349,23 +404,44 @@
349 self.log.assert_called_with(404 self.log.assert_called_with(
350 'Deferring identity_changed() to service leader.')405 'Deferring identity_changed() to service leader.')
351406
407 @patch.object(hooks, 'local_unit')
408 @patch.object(hooks, 'peer_units')
352 @patch.object(unison, 'ssh_authorized_peers')409 @patch.object(unison, 'ssh_authorized_peers')
353 def test_cluster_joined(self, ssh_authorized_peers):410 def test_cluster_joined(self, ssh_authorized_peers, mock_peer_units,
411 mock_local_unit):
412 mock_local_unit.return_value = 'unit/0'
413 mock_peer_units.return_value = ['unit/0']
354 hooks.cluster_joined()414 hooks.cluster_joined()
355 ssh_authorized_peers.assert_called_with(415 ssh_authorized_peers.assert_called_with(
356 user=self.ssh_user, group='juju_keystone',416 user=self.ssh_user, group='juju_keystone',
357 peer_interface='cluster', ensure_local_user=True)417 peer_interface='cluster', ensure_local_user=True)
358418
419 @patch.object(hooks, 'is_ssl_cert_master')
420 @patch.object(hooks, 'peer_units')
421 @patch('keystone_utils.log')
422 @patch('keystone_utils.ensure_ssl_cert_master')
423 @patch('keystone_utils.synchronize_ca')
424 @patch.object(hooks, 'check_peer_actions')
359 @patch.object(unison, 'ssh_authorized_peers')425 @patch.object(unison, 'ssh_authorized_peers')
360 @patch.object(hooks, 'CONFIGS')426 @patch.object(hooks, 'CONFIGS')
361 def test_cluster_changed(self, configs, ssh_authorized_peers):427 def test_cluster_changed(self, configs, ssh_authorized_peers,
428 check_peer_actions, mock_synchronize_ca,
429 mock_ensure_ssl_cert_master,
430 mock_log, mock_peer_units,
431 mock_is_ssl_cert_master):
432 mock_is_ssl_cert_master.return_value = False
433 mock_peer_units.return_value = ['unit/0']
434 mock_ensure_ssl_cert_master.return_value = False
435 self.is_elected_leader.return_value = False
436 self.relation_get.return_value = {'foo_passwd': '123',
437 'identity-service:16_foo': 'bar'}
362 hooks.cluster_changed()438 hooks.cluster_changed()
363 self.peer_echo.assert_called_with(includes=['_passwd',439 self.peer_echo.assert_called_with(includes=['foo_passwd',
364 'identity-service:'])440 'identity-service:16_foo'])
365 ssh_authorized_peers.assert_called_with(441 ssh_authorized_peers.assert_called_with(
366 user=self.ssh_user, group='keystone',442 user=self.ssh_user, group='keystone',
367 peer_interface='cluster', ensure_local_user=True)443 peer_interface='cluster', ensure_local_user=True)
368 self.assertTrue(self.synchronize_ca.called)444 self.assertFalse(mock_synchronize_ca.called)
369 self.assertTrue(configs.write_all.called)445 self.assertTrue(configs.write_all.called)
370446
371 def test_ha_joined(self):447 def test_ha_joined(self):
@@ -440,34 +516,50 @@
440 }516 }
441 self.relation_set.assert_called_with(**args)517 self.relation_set.assert_called_with(**args)
442518
519 @patch('keystone_utils.log')
520 @patch('keystone_utils.ensure_ssl_cert_master')
521 @patch('keystone_utils.synchronize_ca')
443 @patch.object(hooks, 'CONFIGS')522 @patch.object(hooks, 'CONFIGS')
444 def test_ha_relation_changed_not_clustered_not_leader(self, configs):523 def test_ha_relation_changed_not_clustered_not_leader(self, configs,
524 mock_synchronize_ca,
525 mock_is_master,
526 mock_log):
527 mock_is_master.return_value = False
445 self.relation_get.return_value = False528 self.relation_get.return_value = False
446 self.is_leader.return_value = False529 self.is_elected_leader.return_value = False
447530
448 hooks.ha_changed()531 hooks.ha_changed()
449 self.assertTrue(configs.write_all.called)532 self.assertTrue(configs.write_all.called)
533 self.assertFalse(mock_synchronize_ca.called)
450534
535 @patch('keystone_utils.log')
536 @patch('keystone_utils.ensure_ssl_cert_master')
451 @patch.object(hooks, 'identity_changed')537 @patch.object(hooks, 'identity_changed')
452 @patch.object(hooks, 'CONFIGS')538 @patch.object(hooks, 'CONFIGS')
453 def test_ha_relation_changed_clustered_leader(539 def test_ha_relation_changed_clustered_leader(self, configs,
454 self, configs, identity_changed):540 identity_changed,
541 mock_ensure_ssl_cert_master,
542 mock_log):
543 mock_ensure_ssl_cert_master.return_value = False
455 self.relation_get.return_value = True544 self.relation_get.return_value = True
456 self.is_leader.return_value = True545 self.is_elected_leader.return_value = True
457 self.relation_ids.return_value = ['identity-service:0']546 self.relation_ids.return_value = ['identity-service:0']
458 self.related_units.return_value = ['unit/0']547 self.related_units.return_value = ['unit/0']
459548
460 hooks.ha_changed()549 hooks.ha_changed()
461 self.assertTrue(configs.write_all.called)550 self.assertTrue(configs.write_all.called)
462 self.log.assert_called_with(551 self.log.assert_called_with(
463 'Cluster configured, notifying other services and updating '552 'Firing identity_changed hook for all related services.')
464 'keystone endpoint configuration')
465 identity_changed.assert_called_with(553 identity_changed.assert_called_with(
466 relation_id='identity-service:0',554 relation_id='identity-service:0',
467 remote_unit='unit/0')555 remote_unit='unit/0')
468556
557 @patch('keystone_utils.log')
558 @patch('keystone_utils.ensure_ssl_cert_master')
469 @patch.object(hooks, 'CONFIGS')559 @patch.object(hooks, 'CONFIGS')
470 def test_configure_https_enable(self, configs):560 def test_configure_https_enable(self, configs, mock_ensure_ssl_cert_master,
561 mock_log):
562 mock_ensure_ssl_cert_master.return_value = False
471 configs.complete_contexts = MagicMock()563 configs.complete_contexts = MagicMock()
472 configs.complete_contexts.return_value = ['https']564 configs.complete_contexts.return_value = ['https']
473 configs.write = MagicMock()565 configs.write = MagicMock()
@@ -477,8 +569,13 @@
477 cmd = ['a2ensite', 'openstack_https_frontend']569 cmd = ['a2ensite', 'openstack_https_frontend']
478 self.check_call.assert_called_with(cmd)570 self.check_call.assert_called_with(cmd)
479571
572 @patch('keystone_utils.log')
573 @patch('keystone_utils.ensure_ssl_cert_master')
480 @patch.object(hooks, 'CONFIGS')574 @patch.object(hooks, 'CONFIGS')
481 def test_configure_https_disable(self, configs):575 def test_configure_https_disable(self, configs,
576 mock_ensure_ssl_cert_master,
577 mock_log):
578 mock_ensure_ssl_cert_master.return_value = False
482 configs.complete_contexts = MagicMock()579 configs.complete_contexts = MagicMock()
483 configs.complete_contexts.return_value = ['']580 configs.complete_contexts.return_value = ['']
484 configs.write = MagicMock()581 configs.write = MagicMock()
@@ -488,30 +585,61 @@
488 cmd = ['a2dissite', 'openstack_https_frontend']585 cmd = ['a2dissite', 'openstack_https_frontend']
489 self.check_call.assert_called_with(cmd)586 self.check_call.assert_called_with(cmd)
490587
588 @patch('keystone_utils.log')
589 @patch('keystone_utils.relation_ids')
590 @patch('keystone_utils.is_elected_leader')
591 @patch('keystone_utils.ensure_ssl_cert_master')
592 @patch('keystone_utils.update_hash_from_path')
593 @patch('keystone_utils.synchronize_ca')
491 @patch.object(unison, 'ssh_authorized_peers')594 @patch.object(unison, 'ssh_authorized_peers')
492 def test_upgrade_charm_leader(self, ssh_authorized_peers):595 def test_upgrade_charm_leader(self, ssh_authorized_peers,
493 self.eligible_leader.return_value = True596 mock_synchronize_ca,
597 mock_update_hash_from_path,
598 mock_ensure_ssl_cert_master,
599 mock_is_elected_leader,
600 mock_relation_ids,
601 mock_log):
602 mock_is_elected_leader.return_value = False
603 mock_relation_ids.return_value = []
604 mock_ensure_ssl_cert_master.return_value = True
605 # Ensure always returns diff
606 mock_update_hash_from_path.side_effect = \
607 lambda hash, *args, **kwargs: hash.update(str(uuid.uuid4()))
608
609 self.is_elected_leader.return_value = True
494 self.filter_installed_packages.return_value = []610 self.filter_installed_packages.return_value = []
495 hooks.upgrade_charm()611 hooks.upgrade_charm()
496 self.assertTrue(self.apt_install.called)612 self.assertTrue(self.apt_install.called)
497 ssh_authorized_peers.assert_called_with(613 ssh_authorized_peers.assert_called_with(
498 user=self.ssh_user, group='keystone',614 user=self.ssh_user, group='keystone',
499 peer_interface='cluster', ensure_local_user=True)615 peer_interface='cluster', ensure_local_user=True)
500 self.assertTrue(self.synchronize_ca.called)616 self.assertTrue(mock_synchronize_ca.called)
501 self.log.assert_called_with(617 self.log.assert_called_with(
502 'Cluster leader - ensuring endpoint configuration'618 'Firing identity_changed hook for all related services.')
503 ' is up to date')
504 self.assertTrue(self.ensure_initial_admin.called)619 self.assertTrue(self.ensure_initial_admin.called)
505620
621 @patch('keystone_utils.log')
622 @patch('keystone_utils.relation_ids')
623 @patch('keystone_utils.ensure_ssl_cert_master')
624 @patch('keystone_utils.update_hash_from_path')
506 @patch.object(unison, 'ssh_authorized_peers')625 @patch.object(unison, 'ssh_authorized_peers')
507 def test_upgrade_charm_not_leader(self, ssh_authorized_peers):626 def test_upgrade_charm_not_leader(self, ssh_authorized_peers,
508 self.eligible_leader.return_value = False627 mock_update_hash_from_path,
628 mock_ensure_ssl_cert_master,
629 mock_relation_ids,
630 mock_log):
631 mock_relation_ids.return_value = []
632 mock_ensure_ssl_cert_master.return_value = False
633 # Ensure always returns diff
634 mock_update_hash_from_path.side_effect = \
635 lambda hash, *args, **kwargs: hash.update(str(uuid.uuid4()))
636
637 self.is_elected_leader.return_value = False
509 self.filter_installed_packages.return_value = []638 self.filter_installed_packages.return_value = []
510 hooks.upgrade_charm()639 hooks.upgrade_charm()
511 self.assertTrue(self.apt_install.called)640 self.assertTrue(self.apt_install.called)
512 ssh_authorized_peers.assert_called_with(641 ssh_authorized_peers.assert_called_with(
513 user=self.ssh_user, group='keystone',642 user=self.ssh_user, group='keystone',
514 peer_interface='cluster', ensure_local_user=True)643 peer_interface='cluster', ensure_local_user=True)
515 self.assertTrue(self.synchronize_ca.called)
516 self.assertFalse(self.log.called)644 self.assertFalse(self.log.called)
517 self.assertFalse(self.ensure_initial_admin.called)645 self.assertFalse(self.ensure_initial_admin.called)
518646
=== modified file 'unit_tests/test_keystone_utils.py'
--- unit_tests/test_keystone_utils.py 2015-01-14 13:17:50 +0000
+++ unit_tests/test_keystone_utils.py 2015-01-21 16:51:08 +0000
@@ -26,9 +26,8 @@
26 'get_os_codename_install_source',26 'get_os_codename_install_source',
27 'grant_role',27 'grant_role',
28 'configure_installation_source',28 'configure_installation_source',
29 'eligible_leader',29 'is_elected_leader',
30 'https',30 'https',
31 'is_clustered',
32 'peer_store_and_set',31 'peer_store_and_set',
33 'service_stop',32 'service_stop',
34 'service_start',33 'service_start',
@@ -115,7 +114,7 @@
115 self, migrate_database, determine_packages, configs):114 self, migrate_database, determine_packages, configs):
116 self.test_config.set('openstack-origin', 'precise')115 self.test_config.set('openstack-origin', 'precise')
117 determine_packages.return_value = []116 determine_packages.return_value = []
118 self.eligible_leader.return_value = True117 self.is_elected_leader.return_value = True
119118
120 utils.do_openstack_upgrade(configs)119 utils.do_openstack_upgrade(configs)
121120
@@ -202,7 +201,6 @@
202 self.resolve_address.return_value = '10.0.0.3'201 self.resolve_address.return_value = '10.0.0.3'
203 self.test_config.set('admin-port', 80)202 self.test_config.set('admin-port', 80)
204 self.test_config.set('service-port', 81)203 self.test_config.set('service-port', 81)
205 self.is_clustered.return_value = False
206 self.https.return_value = False204 self.https.return_value = False
207 self.test_config.set('https-service-endpoints', 'False')205 self.test_config.set('https-service-endpoints', 'False')
208 self.get_local_endpoint.return_value = 'http://localhost:80/v2.0/'206 self.get_local_endpoint.return_value = 'http://localhost:80/v2.0/'

Subscribers

People subscribed via source and target branches