~1chb1n/charms/trusty/keystone/kilo-support

« back to all changes in this revision

Viewing changes to hooks/keystone_hooks.py

  • Committer: Edward Hope-Morley
  • Date: 2015-01-22 14:35:41 UTC
  • mfrom: (96.1.17 keystone.fix-cert-sync)
  • Revision ID: edward.hope-morley@canonical.com-20150122143541-yyhs3f39w7066wdv
[hopem,r=gnuoy]
 
Fixes ssl cert synchronisation across peers

Closes-Bug: 1317782

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
 
 
3
2
import hashlib
 
3
import json
4
4
import os
 
5
import re
 
6
import stat
5
7
import sys
6
8
import time
7
9
 
16
18
    is_relation_made,
17
19
    log,
18
20
    local_unit,
 
21
    DEBUG,
 
22
    WARNING,
19
23
    ERROR,
20
24
    relation_get,
21
25
    relation_ids,
48
52
    get_admin_passwd,
49
53
    migrate_database,
50
54
    save_script_rc,
51
 
    synchronize_ca,
 
55
    synchronize_ca_if_changed,
52
56
    register_configs,
53
 
    relation_list,
54
57
    restart_map,
55
58
    services,
56
59
    CLUSTER_RES,
58
61
    SSH_USER,
59
62
    setup_ipv6,
60
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
)
62
71
 
63
72
from charmhelpers.contrib.hahelpers.cluster import (
64
 
    eligible_leader,
65
 
    is_leader,
 
73
    is_elected_leader,
66
74
    get_hacluster_config,
 
75
    peer_units,
67
76
)
68
77
 
69
78
from charmhelpers.payload.execd import execd_preinstall
73
82
)
74
83
from charmhelpers.contrib.openstack.ip import (
75
84
    ADMIN,
 
85
    PUBLIC,
76
86
    resolve_address,
77
87
)
78
88
from charmhelpers.contrib.network.ip import (
100
110
 
101
111
@hooks.hook('config-changed')
102
112
@restart_on_change(restart_map())
 
113
@synchronize_ca_if_changed()
103
114
def config_changed():
104
115
    if config('prefer-ipv6'):
105
116
        setup_ipv6()
106
117
        sync_db_with_multi_ipv6_addresses(config('database'),
107
118
                                          config('database-user'))
108
119
 
 
120
    unison.ensure_user(user=SSH_USER, group='juju_keystone')
109
121
    unison.ensure_user(user=SSH_USER, group='keystone')
110
122
    homedir = unison.get_homedir(SSH_USER)
111
123
    if not os.path.isdir(homedir):
116
128
 
117
129
    check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
118
130
 
 
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
139
    save_script_rc()
120
140
    configure_https()
121
141
    update_nrpe_config()
122
142
    CONFIGS.write_all()
123
 
    if eligible_leader(CLUSTER_RES):
124
 
        migrate_database()
125
 
        ensure_initial_admin(config)
126
 
        log('Firing identity_changed hook for all related services.')
127
 
        # HTTPS may have been set - so fire all identity relations
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)
 
143
 
 
144
    # Update relations since SSL may have been configured. If we have peer
 
145
    # units we can rely on the sync to do this in cluster relation.
 
146
    if is_elected_leader(CLUSTER_RES) and not peer_units():
 
147
        update_all_identity_relation_units()
133
148
 
134
149
    for rid in relation_ids('identity-admin'):
135
150
        admin_relation_changed(rid)
136
 
    for rid in relation_ids('cluster'):
137
 
        cluster_joined(rid)
 
151
 
 
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)
138
158
 
139
159
 
140
160
@hooks.hook('shared-db-relation-joined')
167
187
    relation_set(database=config('database'))
168
188
 
169
189
 
 
190
def 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)
 
206
def update_all_identity_relation_units_force_sync():
 
207
    update_all_identity_relation_units()
 
208
 
 
209
 
170
210
@hooks.hook('shared-db-relation-changed')
171
211
@restart_on_change(restart_map())
 
212
@synchronize_ca_if_changed()
172
213
def db_changed():
173
214
    if 'shared-db' not in CONFIGS.complete_contexts():
174
215
        log('shared-db relation incomplete. Peer not ready?')
175
216
    else:
176
217
        CONFIGS.write(KEYSTONE_CONF)
177
 
        if eligible_leader(CLUSTER_RES):
 
218
        if is_elected_leader(CLUSTER_RES):
178
219
            # Bugs 1353135 & 1187508. Dbs can appear to be ready before the
179
220
            # units acl entry has been added. So, if the db supports passing
180
221
            # a list of permitted units then check if we're in the list.
182
223
            if allowed_units and local_unit() not in allowed_units.split():
183
224
                log('Allowed_units list provided and this unit not present')
184
225
                return
185
 
            migrate_database()
186
 
            ensure_initial_admin(config)
187
226
            # Ensure any existing service entries are updated in the
188
227
            # new database backend
189
 
            for rid in relation_ids('identity-service'):
190
 
                for unit in related_units(rid):
191
 
                    identity_changed(relation_id=rid, remote_unit=unit)
 
228
            update_all_identity_relation_units()
192
229
 
193
230
 
194
231
@hooks.hook('pgsql-db-relation-changed')
195
232
@restart_on_change(restart_map())
 
233
@synchronize_ca_if_changed()
196
234
def pgsql_db_changed():
197
235
    if 'pgsql-db' not in CONFIGS.complete_contexts():
198
236
        log('pgsql-db relation incomplete. Peer not ready?')
199
237
    else:
200
238
        CONFIGS.write(KEYSTONE_CONF)
201
 
        if eligible_leader(CLUSTER_RES):
202
 
            migrate_database()
203
 
            ensure_initial_admin(config)
 
239
        if is_elected_leader(CLUSTER_RES):
204
240
            # Ensure any existing service entries are updated in the
205
241
            # new database backend
206
 
            for rid in relation_ids('identity-service'):
207
 
                for unit in related_units(rid):
208
 
                    identity_changed(relation_id=rid, remote_unit=unit)
 
242
            update_all_identity_relation_units()
209
243
 
210
244
 
211
245
@hooks.hook('identity-service-relation-changed')
 
246
@synchronize_ca_if_changed()
212
247
def identity_changed(relation_id=None, remote_unit=None):
 
248
    CONFIGS.write_all()
 
249
 
213
250
    notifications = {}
214
 
    if eligible_leader(CLUSTER_RES):
215
 
        add_service_to_keystone(relation_id, remote_unit)
216
 
        synchronize_ca()
 
251
    if is_elected_leader(CLUSTER_RES):
 
252
        # Catch database not configured error and defer until db ready
 
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
217
266
 
218
267
        settings = relation_get(rid=relation_id, unit=remote_unit)
219
268
        service = settings.get('service', None)
241
290
        send_notifications(notifications)
242
291
 
243
292
 
 
293
def 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
304
@hooks.hook('cluster-relation-joined')
245
 
def cluster_joined(relation_id=None):
 
305
def cluster_joined():
246
306
    unison.ssh_authorized_peers(user=SSH_USER,
247
307
                                group='juju_keystone',
248
308
                                peer_interface='cluster',
249
309
                                ensure_local_user=True)
 
310
 
 
311
    settings = {}
 
312
 
250
313
    for addr_type in ADDRESS_TYPES:
251
314
        address = get_address_in_network(
252
315
            config('os-{}-network'.format(addr_type))
253
316
        )
254
317
        if address:
255
 
            relation_set(
256
 
                relation_id=relation_id,
257
 
                relation_settings={'{}-address'.format(addr_type): address}
258
 
            )
 
318
            settings['{}-address'.format(addr_type)] = address
259
319
 
260
320
    if config('prefer-ipv6'):
261
321
        private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
262
 
        relation_set(relation_id=relation_id,
263
 
                     relation_settings={'private-address': private_addr})
 
322
        settings['private-address'] = private_addr
 
323
 
 
324
    append_ssl_sync_request(settings)
 
325
 
 
326
    relation_set(relation_settings=settings)
 
327
 
 
328
 
 
329
def 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
264
358
 
265
359
 
266
360
@hooks.hook('cluster-relation-changed',
267
361
            'cluster-relation-departed')
268
362
@restart_on_change(restart_map(), stopstart=True)
269
363
def cluster_changed():
 
364
    settings = relation_get()
270
365
    # 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
376
    unison.ssh_authorized_peers(user=SSH_USER,
273
377
                                group='keystone',
274
378
                                peer_interface='cluster',
275
379
                                ensure_local_user=True)
276
 
    synchronize_ca()
277
 
    CONFIGS.write_all()
278
 
    for r_id in relation_ids('identity-service'):
279
 
        for unit in relation_list(r_id):
280
 
            identity_changed(relation_id=r_id,
281
 
                             remote_unit=unit)
282
 
    for rid in relation_ids('identity-admin'):
283
 
        admin_relation_changed(rid)
 
380
 
 
381
    if is_elected_leader(CLUSTER_RES) or is_ssl_cert_master():
 
382
        units = get_ssl_sync_request_units()
 
383
        synced_units = relation_get(attribute='ssl-synced-units',
 
384
                                    unit=local_unit())
 
385
        if synced_units:
 
386
            synced_units = json.loads(synced_units)
 
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()
284
400
 
285
401
 
286
402
@hooks.hook('ha-relation-joined')
320
436
            vip_group.append(vip_key)
321
437
 
322
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)})
324
440
 
325
441
    init_services = {
326
442
        'res_ks_haproxy': 'haproxy'
338
454
 
339
455
@hooks.hook('ha-relation-changed')
340
456
@restart_on_change(restart_map())
 
457
@synchronize_ca_if_changed()
341
458
def ha_changed():
 
459
    CONFIGS.write_all()
 
460
 
342
461
    clustered = relation_get('clustered')
343
 
    CONFIGS.write_all()
344
 
    if (clustered is not None and
345
 
            is_leader(CLUSTER_RES)):
 
462
    if clustered and is_elected_leader(CLUSTER_RES):
346
463
        ensure_initial_admin(config)
347
464
        log('Cluster configured, notifying other services and updating '
348
465
            'keystone endpoint configuration')
349
 
    for rid in relation_ids('identity-service'):
350
 
        for unit in related_units(rid):
351
 
            identity_changed(relation_id=rid, remote_unit=unit)
 
466
 
 
467
        update_all_identity_relation_units()
352
468
 
353
469
 
354
470
@hooks.hook('identity-admin-relation-changed')
365
481
    relation_set(relation_id=relation_id, **relation_data)
366
482
 
367
483
 
 
484
@synchronize_ca_if_changed(fatal=True)
368
485
def configure_https():
369
486
    '''
370
487
    Enables SSL API Apache config if appropriate and kicks identity-service
383
500
 
384
501
@hooks.hook('upgrade-charm')
385
502
@restart_on_change(restart_map(), stopstart=True)
 
503
@synchronize_ca_if_changed()
386
504
def upgrade_charm():
387
505
    apt_install(filter_installed_packages(determine_packages()))
388
506
    unison.ssh_authorized_peers(user=SSH_USER,
389
507
                                group='keystone',
390
508
                                peer_interface='cluster',
391
509
                                ensure_local_user=True)
 
510
 
 
511
    CONFIGS.write_all()
392
512
    update_nrpe_config()
393
 
    synchronize_ca()
394
 
    if eligible_leader(CLUSTER_RES):
395
 
        log('Cluster leader - ensuring endpoint configuration'
396
 
            ' is up to date')
 
513
 
 
514
    if is_elected_leader(CLUSTER_RES):
 
515
        log('Cluster leader - ensuring endpoint configuration is up to '
 
516
            'date', level=DEBUG)
397
517
        time.sleep(10)
398
 
        ensure_initial_admin(config)
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()
 
518
        update_all_identity_relation_units()
405
519
 
406
520
 
407
521
@hooks.hook('nrpe-external-master-relation-joined',