~corey.bryant/charms/trusty/keystone/python-six

« back to all changes in this revision

Viewing changes to hooks/keystone_utils.py

  • Committer: James Page
  • Date: 2014-03-27 10:54:38 UTC
  • mfrom: (55.1.22 keystone)
  • mto: (52.4.7 keystone)
  • mto: This revision was merged to the branch mainline in revision 60.
  • Revision ID: james.page@canonical.com-20140327105438-oid8czi9ud51iut1
Merge ssl-everywhere branch (may break stuff)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
 
import ConfigParser
3
 
import sys
4
 
import json
5
 
import time
6
2
import subprocess
7
3
import os
8
 
 
9
 
from lib.openstack_common import(
 
4
import urlparse
 
5
import time
 
6
 
 
7
from base64 import b64encode
 
8
from collections import OrderedDict
 
9
from copy import deepcopy
 
10
 
 
11
from charmhelpers.contrib.hahelpers.cluster import(
 
12
    eligible_leader,
 
13
    determine_api_port,
 
14
    https,
 
15
    is_clustered
 
16
)
 
17
 
 
18
from charmhelpers.contrib.openstack import context, templating
 
19
 
 
20
from charmhelpers.contrib.openstack.utils import (
 
21
    configure_installation_source,
 
22
    error_out,
10
23
    get_os_codename_install_source,
11
 
    get_os_codename_package,
12
 
    error_out,
13
 
    configure_installation_source
14
 
    )
15
 
 
 
24
    os_release,
 
25
    save_script_rc as _save_script_rc)
 
26
 
 
27
import charmhelpers.contrib.unison as unison
 
28
 
 
29
from charmhelpers.core.hookenv import (
 
30
    config,
 
31
    log,
 
32
    relation_get,
 
33
    relation_set,
 
34
    unit_private_ip,
 
35
    INFO,
 
36
)
 
37
 
 
38
from charmhelpers.fetch import (
 
39
    apt_install,
 
40
    apt_update,
 
41
)
 
42
 
 
43
from charmhelpers.core.host import (
 
44
    service_stop,
 
45
    service_start,
 
46
)
 
47
 
 
48
import keystone_context
16
49
import keystone_ssl as ssl
17
 
import lib.unison as unison
18
 
import lib.utils as utils
19
 
import lib.cluster_utils as cluster
20
 
 
21
 
 
22
 
keystone_conf = "/etc/keystone/keystone.conf"
23
 
stored_passwd = "/var/lib/keystone/keystone.passwd"
24
 
stored_token = "/var/lib/keystone/keystone.token"
 
50
 
 
51
TEMPLATES = 'templates/'
 
52
 
 
53
# removed from original: charm-helper-sh
 
54
BASE_PACKAGES = [
 
55
    'apache2',
 
56
    'haproxy',
 
57
    'openssl',
 
58
    'python-keystoneclient',
 
59
    'python-mysqldb',
 
60
    'pwgen',
 
61
    'unison',
 
62
    'uuid',
 
63
]
 
64
 
 
65
BASE_SERVICES = [
 
66
    'keystone',
 
67
]
 
68
 
 
69
API_PORTS = {
 
70
    'keystone-admin': config('admin-port'),
 
71
    'keystone-public': config('service-port')
 
72
}
 
73
 
 
74
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
 
75
KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
 
76
STORED_PASSWD = "/var/lib/keystone/keystone.passwd"
 
77
STORED_TOKEN = "/var/lib/keystone/keystone.token"
25
78
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
26
79
 
 
80
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
 
81
APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
 
82
APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
 
83
 
27
84
SSL_DIR = '/var/lib/keystone/juju_ssl/'
28
85
SSL_CA_NAME = 'Ubuntu Cloud'
29
86
CLUSTER_RES = 'res_ks_vip'
30
87
SSH_USER = 'juju_keystone'
31
88
 
32
 
 
33
 
def execute(cmd, die=False, echo=False):
34
 
    """ Executes a command
35
 
 
36
 
    if die=True, script will exit(1) if command does not return 0
37
 
    if echo=True, output of command will be printed to stdout
38
 
 
39
 
    returns a tuple: (stdout, stderr, return code)
40
 
    """
41
 
    p = subprocess.Popen(cmd.split(" "),
42
 
                         stdout=subprocess.PIPE,
43
 
                         stdin=subprocess.PIPE,
44
 
                         stderr=subprocess.PIPE)
45
 
    stdout = ""
46
 
    stderr = ""
47
 
 
48
 
    def print_line(l):
49
 
        if echo:
50
 
            print l.strip('\n')
51
 
            sys.stdout.flush()
52
 
 
53
 
    for l in iter(p.stdout.readline, ''):
54
 
        print_line(l)
55
 
        stdout += l
56
 
    for l in iter(p.stderr.readline, ''):
57
 
        print_line(l)
58
 
        stderr += l
59
 
 
60
 
    p.communicate()
61
 
    rc = p.returncode
62
 
 
63
 
    if die and rc != 0:
64
 
        error_out("ERROR: command %s return non-zero.\n" % cmd)
65
 
    return (stdout, stderr, rc)
66
 
 
67
 
 
68
 
def config_get():
69
 
    """ Obtain the units config via 'config-get'
70
 
    Returns a dict representing current config.
71
 
    private-address and IP of the unit is also tacked on for
72
 
    convienence
73
 
    """
74
 
    output = execute("config-get --format json")[0]
75
 
    config = json.loads(output)
76
 
    # make sure no config element is blank after config-get
77
 
    for c in config.keys():
78
 
        if config[c] is None:
79
 
            error_out("ERROR: Config option has no paramter: %s" % c)
80
 
    # tack on our private address and ip
81
 
    config["hostname"] = utils.unit_get('private-address')
82
 
    return config
83
 
 
84
 
 
85
 
@utils.cached
 
89
BASE_RESOURCE_MAP = OrderedDict([
 
90
    (KEYSTONE_CONF, {
 
91
        'services': BASE_SERVICES,
 
92
        'contexts': [keystone_context.KeystoneContext(),
 
93
                     context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR),
 
94
                     context.SyslogContext(),
 
95
                     keystone_context.HAProxyContext()],
 
96
    }),
 
97
    (HAPROXY_CONF, {
 
98
        'contexts': [context.HAProxyContext(),
 
99
                     keystone_context.HAProxyContext()],
 
100
        'services': ['haproxy'],
 
101
    }),
 
102
    (APACHE_CONF, {
 
103
        'contexts': [keystone_context.ApacheSSLContext()],
 
104
        'services': ['apache2'],
 
105
    }),
 
106
    (APACHE_24_CONF, {
 
107
        'contexts': [keystone_context.ApacheSSLContext()],
 
108
        'services': ['apache2'],
 
109
    }),
 
110
])
 
111
 
 
112
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
113
 
 
114
valid_services = {
 
115
    "nova": {
 
116
        "type": "compute",
 
117
        "desc": "Nova Compute Service"
 
118
    },
 
119
    "nova-volume": {
 
120
        "type": "volume",
 
121
        "desc": "Nova Volume Service"
 
122
    },
 
123
    "cinder": {
 
124
        "type": "volume",
 
125
        "desc": "Cinder Volume Service"
 
126
    },
 
127
    "ec2": {
 
128
        "type": "ec2",
 
129
        "desc": "EC2 Compatibility Layer"
 
130
    },
 
131
    "glance": {
 
132
        "type": "image",
 
133
        "desc": "Glance Image Service"
 
134
    },
 
135
    "s3": {
 
136
        "type": "s3",
 
137
        "desc": "S3 Compatible object-store"
 
138
    },
 
139
    "swift": {
 
140
        "type": "object-store",
 
141
        "desc": "Swift Object Storage Service"
 
142
    },
 
143
    "quantum": {
 
144
        "type": "network",
 
145
        "desc": "Quantum Networking Service"
 
146
    },
 
147
    "oxygen": {
 
148
        "type": "oxygen",
 
149
        "desc": "Oxygen Cloud Image Service"
 
150
    },
 
151
    "ceilometer": {
 
152
        "type": "metering",
 
153
        "desc": "Ceilometer Metering Service"
 
154
    },
 
155
    "heat": {
 
156
        "type": "orchestration",
 
157
        "desc": "Heat Orchestration API"
 
158
    },
 
159
    "heat-cfn": {
 
160
        "type": "cloudformation",
 
161
        "desc": "Heat CloudFormation API"
 
162
    }
 
163
}
 
164
 
 
165
 
 
166
def resource_map():
 
167
    '''
 
168
    Dynamically generate a map of resources that will be managed for a single
 
169
    hook execution.
 
170
    '''
 
171
    resource_map = deepcopy(BASE_RESOURCE_MAP)
 
172
 
 
173
    if os.path.exists('/etc/apache2/conf-available'):
 
174
        resource_map.pop(APACHE_CONF)
 
175
    else:
 
176
        resource_map.pop(APACHE_24_CONF)
 
177
    return resource_map
 
178
 
 
179
 
 
180
def register_configs():
 
181
    release = os_release('keystone')
 
182
    configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
 
183
                                          openstack_release=release)
 
184
    for cfg, rscs in resource_map().iteritems():
 
185
        configs.register(cfg, rscs['contexts'])
 
186
    return configs
 
187
 
 
188
 
 
189
def restart_map():
 
190
    return OrderedDict([(cfg, v['services'])
 
191
                        for cfg, v in resource_map().iteritems()
 
192
                        if v['services']])
 
193
 
 
194
 
 
195
def determine_ports():
 
196
    '''Assemble a list of API ports for services we are managing'''
 
197
    ports = [config('admin-port'), config('service-port')]
 
198
    return list(set(ports))
 
199
 
 
200
 
 
201
def api_port(service):
 
202
    return API_PORTS[service]
 
203
 
 
204
 
 
205
def determine_packages():
 
206
    # currently all packages match service names
 
207
    packages = [] + BASE_PACKAGES
 
208
    for k, v in resource_map().iteritems():
 
209
        packages.extend(v['services'])
 
210
    return list(set(packages))
 
211
 
 
212
 
 
213
def save_script_rc():
 
214
    env_vars = {'OPENSTACK_SERVICE_KEYSTONE': 'keystone',
 
215
                'OPENSTACK_PORT_ADMIN': determine_api_port(
 
216
                    api_port('keystone-admin')),
 
217
                'OPENSTACK_PORT_PUBLIC': determine_api_port(
 
218
                    api_port('keystone-public'))}
 
219
    _save_script_rc(**env_vars)
 
220
 
 
221
 
 
222
def do_openstack_upgrade(configs):
 
223
    new_src = config('openstack-origin')
 
224
    new_os_rel = get_os_codename_install_source(new_src)
 
225
    log('Performing OpenStack upgrade to %s.' % (new_os_rel))
 
226
 
 
227
    configure_installation_source(new_src)
 
228
    apt_update()
 
229
 
 
230
    dpkg_opts = [
 
231
        '--option', 'Dpkg::Options::=--force-confnew',
 
232
        '--option', 'Dpkg::Options::=--force-confdef',
 
233
    ]
 
234
 
 
235
    apt_install(packages=determine_packages(), options=dpkg_opts, fatal=True)
 
236
 
 
237
    # set CONFIGS to load templates from new release and regenerate config
 
238
    configs.set_release(openstack_release=new_os_rel)
 
239
    configs.write_all()
 
240
 
 
241
    if eligible_leader(CLUSTER_RES):
 
242
        migrate_database()
 
243
 
 
244
 
 
245
def migrate_database():
 
246
    '''Runs keystone-manage to initialize a new database or migrate existing'''
 
247
    log('Migrating the keystone database.', level=INFO)
 
248
    service_stop('keystone')
 
249
    cmd = ['keystone-manage', 'db_sync']
 
250
    subprocess.check_output(cmd)
 
251
    service_start('keystone')
 
252
    time.sleep(10)
 
253
 
 
254
 
 
255
# OLD
 
256
 
86
257
def get_local_endpoint():
87
258
    """ Returns the URL for the local end-point bypassing haproxy/ssl """
88
259
    local_endpoint = 'http://localhost:{}/v2.0/'.format(
89
 
        cluster.determine_api_port(utils.config_get('admin-port'))
90
 
        )
 
260
        determine_api_port(api_port('keystone-admin'))
 
261
    )
91
262
    return local_endpoint
92
263
 
93
264
 
94
 
def set_admin_token(admin_token):
 
265
def set_admin_token(admin_token='None'):
95
266
    """Set admin token according to deployment config or use a randomly
96
267
       generated token if none is specified (default).
97
268
    """
98
269
    if admin_token != 'None':
99
 
        utils.juju_log('INFO',
100
 
                       'Configuring Keystone to use'
101
 
                       ' a pre-configured admin token.')
 
270
        log('Configuring Keystone to use a pre-configured admin token.')
102
271
        token = admin_token
103
272
    else:
104
 
        utils.juju_log('INFO',
105
 
                       'Configuring Keystone to use a random admin token.')
106
 
        if os.path.isfile(stored_token):
 
273
        log('Configuring Keystone to use a random admin token.')
 
274
        if os.path.isfile(STORED_TOKEN):
107
275
            msg = 'Loading a previously generated' \
108
 
                  ' admin token from %s' % stored_token
109
 
            utils.juju_log('INFO', msg)
110
 
            f = open(stored_token, 'r')
 
276
                  ' admin token from %s' % STORED_TOKEN
 
277
            log(msg)
 
278
            f = open(STORED_TOKEN, 'r')
111
279
            token = f.read().strip()
112
280
            f.close()
113
281
        else:
114
 
            token = execute('pwgen -c 32 1', die=True)[0].strip()
115
 
            out = open(stored_token, 'w')
 
282
            cmd = ['pwgen', '-c', '32', '1']
 
283
            token = str(subprocess.check_output(cmd)).strip()
 
284
            out = open(STORED_TOKEN, 'w')
116
285
            out.write('%s\n' % token)
117
286
            out.close()
118
 
    update_config_block('DEFAULT', admin_token=token)
 
287
    return(token)
119
288
 
120
289
 
121
290
def get_admin_token():
122
291
    """Temporary utility to grab the admin token as configured in
123
292
       keystone.conf
124
293
    """
125
 
    with open(keystone_conf, 'r') as f:
 
294
    with open(KEYSTONE_CONF, 'r') as f:
126
295
        for l in f.readlines():
127
296
            if l.split(' ')[0] == 'admin_token':
128
297
                try:
129
298
                    return l.split('=')[1].strip()
130
299
                except:
131
300
                    error_out('Could not parse admin_token line from %s' %
132
 
                              keystone_conf)
133
 
    error_out('Could not find admin_token line in %s' % keystone_conf)
134
 
 
135
 
 
136
 
# Track all updated config settings.
137
 
_config_dirty = [False]
138
 
 
139
 
def config_dirty():
140
 
    return True in _config_dirty
141
 
 
142
 
def update_config_block(section, **kwargs):
143
 
    """ Updates keystone.conf blocks given kwargs.
144
 
    Update a config setting in a specific setting of a config
145
 
    file (/etc/keystone/keystone.conf, by default)
146
 
    """
147
 
    if 'file' in kwargs:
148
 
        conf_file = kwargs['file']
149
 
        del kwargs['file']
150
 
    else:
151
 
        conf_file = keystone_conf
152
 
    config = ConfigParser.RawConfigParser()
153
 
    config.read(conf_file)
154
 
 
155
 
    if section != 'DEFAULT' and not config.has_section(section):
156
 
        config.add_section(section)
157
 
        _config_dirty[0] = True
158
 
 
159
 
    for k, v in kwargs.iteritems():
160
 
        try:
161
 
            cur = config.get(section, k)
162
 
            if cur != v:
163
 
                _config_dirty[0] = True
164
 
        except (ConfigParser.NoSectionError,
165
 
                ConfigParser.NoOptionError):
166
 
            _config_dirty[0] = True
167
 
        config.set(section, k, v)
168
 
    with open(conf_file, 'wb') as out:
169
 
        config.write(out)
 
301
                              KEYSTONE_CONF)
 
302
    error_out('Could not find admin_token line in %s' % KEYSTONE_CONF)
170
303
 
171
304
 
172
305
def create_service_entry(service_name, service_type, service_desc, owner=None):
176
309
                                      token=get_admin_token())
177
310
    for service in [s._info for s in manager.api.services.list()]:
178
311
        if service['name'] == service_name:
179
 
            utils.juju_log('INFO',
180
 
                           "Service entry for '%s' already exists." % \
181
 
                           service_name)
 
312
            log("Service entry for '%s' already exists." % service_name)
182
313
            return
183
314
    manager.api.services.create(name=service_name,
184
315
                                service_type=service_type,
185
316
                                description=service_desc)
186
 
    utils.juju_log('INFO', "Created new service entry '%s'" % service_name)
187
 
 
188
 
 
189
 
def create_endpoint_template(region, service,  publicurl, adminurl,
 
317
    log("Created new service entry '%s'" % service_name)
 
318
 
 
319
 
 
320
def create_endpoint_template(region, service, publicurl, adminurl,
190
321
                             internalurl):
191
322
    """ Create a new endpoint template for service if one does not already
192
323
        exist matching name *and* region """
196
327
    service_id = manager.resolve_service_id(service)
197
328
    for ep in [e._info for e in manager.api.endpoints.list()]:
198
329
        if ep['service_id'] == service_id and ep['region'] == region:
199
 
            utils.juju_log('INFO',
200
 
                           "Endpoint template already exists for '%s' in '%s'"
201
 
                           % (service, region))
 
330
            log("Endpoint template already exists for '%s' in '%s'"
 
331
                % (service, region))
202
332
 
203
333
            up_to_date = True
204
334
            for k in ['publicurl', 'adminurl', 'internalurl']:
209
339
                return
210
340
            else:
211
341
                # delete endpoint and recreate if endpoint urls need updating.
212
 
                utils.juju_log('INFO',
213
 
                               "Updating endpoint template with"
214
 
                               " new endpoint urls.")
 
342
                log("Updating endpoint template with new endpoint urls.")
215
343
                manager.api.endpoints.delete(ep['id'])
216
344
 
217
345
    manager.api.endpoints.create(region=region,
219
347
                                 publicurl=publicurl,
220
348
                                 adminurl=adminurl,
221
349
                                 internalurl=internalurl)
222
 
    utils.juju_log('INFO', "Created new endpoint template for '%s' in '%s'" %
223
 
                   (region, service))
 
350
    log("Created new endpoint template for '%s' in '%s'" % (region, service))
224
351
 
225
352
 
226
353
def create_tenant(name):
232
359
    if not tenants or name not in [t['name'] for t in tenants]:
233
360
        manager.api.tenants.create(tenant_name=name,
234
361
                                   description='Created by Juju')
235
 
        utils.juju_log('INFO', "Created new tenant: %s" % name)
 
362
        log("Created new tenant: %s" % name)
236
363
        return
237
 
    utils.juju_log('INFO', "Tenant '%s' already exists." % name)
 
364
    log("Tenant '%s' already exists." % name)
238
365
 
239
366
 
240
367
def create_user(name, password, tenant):
251
378
                                 password=password,
252
379
                                 email='juju@localhost',
253
380
                                 tenant_id=tenant_id)
254
 
        utils.juju_log('INFO', "Created new user '%s' tenant: %s" % \
255
 
                       (name, tenant_id))
 
381
        log("Created new user '%s' tenant: %s" % (name, tenant_id))
256
382
        return
257
 
    utils.juju_log('INFO', "A user named '%s' already exists" % name)
 
383
    log("A user named '%s' already exists" % name)
258
384
 
259
385
 
260
386
def create_role(name, user=None, tenant=None):
265
391
    roles = [r._info for r in manager.api.roles.list()]
266
392
    if not roles or name not in [r['name'] for r in roles]:
267
393
        manager.api.roles.create(name=name)
268
 
        utils.juju_log('INFO', "Created new role '%s'" % name)
 
394
        log("Created new role '%s'" % name)
269
395
    else:
270
 
        utils.juju_log('INFO', "A role named '%s' already exists" % name)
 
396
        log("A role named '%s' already exists" % name)
271
397
 
272
398
    if not user and not tenant:
273
399
        return
279
405
 
280
406
    if None in [user_id, role_id, tenant_id]:
281
407
        error_out("Could not resolve [%s, %s, %s]" %
282
 
                   (user_id, role_id, tenant_id))
 
408
                  (user_id, role_id, tenant_id))
283
409
 
284
410
    grant_role(user, name, tenant)
285
411
 
289
415
    import manager
290
416
    manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
291
417
                                      token=get_admin_token())
292
 
    utils.juju_log('INFO', "Granting user '%s' role '%s' on tenant '%s'" % \
293
 
                   (user, role, tenant))
 
418
    log("Granting user '%s' role '%s' on tenant '%s'" %
 
419
       (user, role, tenant))
294
420
    user_id = manager.resolve_user_id(user)
295
421
    role_id = manager.resolve_role_id(role)
296
422
    tenant_id = manager.resolve_tenant_id(tenant)
300
426
        manager.api.roles.add_user_role(user=user_id,
301
427
                                        role=role_id,
302
428
                                        tenant=tenant_id)
303
 
        utils.juju_log('INFO', "Granted user '%s' role '%s' on tenant '%s'" % \
304
 
                       (user, role, tenant))
305
 
    else:
306
 
        utils.juju_log('INFO',
307
 
                       "User '%s' already has role '%s' on tenant '%s'" % \
308
 
                       (user, role, tenant))
309
 
 
310
 
 
311
 
def generate_admin_token(config):
312
 
    """ generate and add an admin token """
313
 
    import manager
314
 
    manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
315
 
                                      token='ADMIN')
316
 
    if config["admin-token"] == "None":
317
 
        import random
318
 
        token = random.randrange(1000000000000, 9999999999999)
319
 
    else:
320
 
        return config["admin-token"]
321
 
    manager.api.add_token(token, config["admin-user"],
322
 
                          "admin", config["token-expiry"])
323
 
    utils.juju_log('INFO', "Generated and added new random admin token.")
324
 
    return token
 
429
        log("Granted user '%s' role '%s' on tenant '%s'" %
 
430
           (user, role, tenant))
 
431
    else:
 
432
        log("User '%s' already has role '%s' on tenant '%s'" %
 
433
           (user, role, tenant))
325
434
 
326
435
 
327
436
def ensure_initial_admin(config):
335
444
        changes?
336
445
    """
337
446
    create_tenant("admin")
338
 
    create_tenant(config["service-tenant"])
 
447
    create_tenant(config("service-tenant"))
339
448
 
340
449
    passwd = ""
341
 
    if config["admin-password"] != "None":
342
 
        passwd = config["admin-password"]
343
 
    elif os.path.isfile(stored_passwd):
344
 
        utils.juju_log('INFO', "Loading stored passwd from %s" % stored_passwd)
345
 
        passwd = open(stored_passwd, 'r').readline().strip('\n')
 
450
    if config("admin-password") != "None":
 
451
        passwd = config("admin-password")
 
452
    elif os.path.isfile(STORED_PASSWD):
 
453
        log("Loading stored passwd from %s" % STORED_PASSWD)
 
454
        passwd = open(STORED_PASSWD, 'r').readline().strip('\n')
346
455
    if passwd == "":
347
 
        utils.juju_log('INFO', "Generating new passwd for user: %s" % \
348
 
                       config["admin-user"])
349
 
        passwd = execute("pwgen -c 16 1", die=True)[0]
350
 
        open(stored_passwd, 'w+').writelines("%s\n" % passwd)
 
456
        log("Generating new passwd for user: %s" %
 
457
            config("admin-user"))
 
458
        cmd = ['pwgen', '-c', '16', '1']
 
459
        passwd = str(subprocess.check_output(cmd)).strip()
 
460
        open(STORED_PASSWD, 'w+').writelines("%s\n" % passwd)
351
461
 
352
 
    create_user(config['admin-user'], passwd, tenant='admin')
353
 
    update_user_password(config['admin-user'], passwd)
354
 
    create_role(config['admin-role'], config['admin-user'], 'admin')
 
462
    create_user(config('admin-user'), passwd, tenant='admin')
 
463
    update_user_password(config('admin-user'), passwd)
 
464
    create_role(config('admin-role'), config('admin-user'), 'admin')
355
465
    # TODO(adam_g): The following roles are likely not needed since redux merge
356
 
    create_role("KeystoneAdmin", config["admin-user"], 'admin')
357
 
    create_role("KeystoneServiceAdmin", config["admin-user"], 'admin')
 
466
    create_role("KeystoneAdmin", config("admin-user"), 'admin')
 
467
    create_role("KeystoneServiceAdmin", config("admin-user"), 'admin')
358
468
    create_service_entry("keystone", "identity", "Keystone Identity Service")
359
469
 
360
 
    if cluster.is_clustered():
361
 
        utils.juju_log('INFO', "Creating endpoint for clustered configuration")
362
 
        service_host = auth_host = config["vip"]
 
470
    if is_clustered():
 
471
        log("Creating endpoint for clustered configuration")
 
472
        service_host = auth_host = config("vip")
363
473
    else:
364
 
        utils.juju_log('INFO', "Creating standard endpoint")
365
 
        service_host = auth_host = config["hostname"]
 
474
        log("Creating standard endpoint")
 
475
        service_host = auth_host = unit_private_ip()
366
476
 
367
 
    for region in config['region'].split():
 
477
    for region in config('region').split():
368
478
        create_keystone_endpoint(service_host=service_host,
369
 
                                 service_port=config["service-port"],
 
479
                                 service_port=config("service-port"),
370
480
                                 auth_host=auth_host,
371
 
                                 auth_port=config["admin-port"],
 
481
                                 auth_port=config("admin-port"),
372
482
                                 region=region)
373
483
 
374
484
 
375
485
def create_keystone_endpoint(service_host, service_port,
376
486
                             auth_host, auth_port, region):
377
 
    public_url = "http://%s:%s/v2.0" % (service_host, service_port)
378
 
    admin_url = "http://%s:%s/v2.0" % (auth_host, auth_port)
379
 
    internal_url = "http://%s:%s/v2.0" % (service_host, service_port)
 
487
    proto = 'http'
 
488
    if https():
 
489
        log("Setting https keystone endpoint")
 
490
        proto = 'https'
 
491
    public_url = "%s://%s:%s/v2.0" % (proto, service_host, service_port)
 
492
    admin_url = "%s://%s:%s/v2.0" % (proto, auth_host, auth_port)
 
493
    internal_url = "%s://%s:%s/v2.0" % (proto, service_host, service_port)
380
494
    create_endpoint_template(region, "keystone", public_url,
381
495
                             admin_url, internal_url)
382
496
 
385
499
    import manager
386
500
    manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
387
501
                                      token=get_admin_token())
388
 
    utils.juju_log('INFO', "Updating password for user '%s'" % username)
 
502
    log("Updating password for user '%s'" % username)
389
503
 
390
504
    user_id = manager.resolve_user_id(username)
391
505
    if user_id is None:
392
506
        error_out("Could not resolve user id for '%s'" % username)
393
507
 
394
508
    manager.api.users.update_password(user=user_id, password=password)
395
 
    utils.juju_log('INFO', "Successfully updated password for user '%s'" % \
396
 
                   username)
 
509
    log("Successfully updated password for user '%s'" %
 
510
        username)
397
511
 
398
512
 
399
513
def load_stored_passwords(path=SERVICE_PASSWD_PATH):
425
539
    return passwd
426
540
 
427
541
 
428
 
def configure_pki_tokens(config):
429
 
    '''Configure PKI token signing, if enabled.'''
430
 
    if config['enable-pki'] not in ['True', 'true']:
431
 
        update_config_block('signing', token_format='UUID')
432
 
    else:
433
 
        utils.juju_log('INFO', 'TODO: PKI Support, setting to UUID for now.')
434
 
        update_config_block('signing', token_format='UUID')
435
 
 
436
 
 
437
 
def do_openstack_upgrade(install_src, packages):
438
 
    '''Upgrade packages from a given install src.'''
439
 
 
440
 
    config = config_get()
441
 
    old_vers = get_os_codename_package('keystone')
442
 
    new_vers = get_os_codename_install_source(install_src)
443
 
 
444
 
    utils.juju_log('INFO',
445
 
                   "Beginning Keystone upgrade: %s -> %s" % \
446
 
                   (old_vers, new_vers))
447
 
 
448
 
    # Backup previous config.
449
 
    utils.juju_log('INFO', "Backing up contents of /etc/keystone.")
450
 
    stamp = time.strftime('%Y%m%d%H%M')
451
 
    cmd = 'tar -pcf /var/lib/juju/keystone-backup-%s.tar /etc/keystone' % stamp
452
 
    execute(cmd, die=True, echo=True)
453
 
 
454
 
    configure_installation_source(install_src)
455
 
    execute('apt-get update', die=True, echo=True)
456
 
    os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
457
 
    cmd = 'apt-get --option Dpkg::Options::=--force-confnew -y '\
458
 
          'dist-upgrade'
459
 
    execute(cmd, echo=True, die=True)
460
 
 
461
 
    # we have new, fresh config files that need updating.
462
 
    # set the admin token, which is still stored in config.
463
 
    set_admin_token(config['admin-token'])
464
 
 
465
 
    # set the sql connection string if a shared-db relation is found.
466
 
    ids = utils.relation_ids('shared-db')
467
 
 
468
 
    if ids:
469
 
        for rid in ids:
470
 
            for unit in utils.relation_list(rid):
471
 
                utils.juju_log('INFO',
472
 
                               'Configuring new keystone.conf for '
473
 
                               'database access on existing database'
474
 
                               ' relation to %s' % unit)
475
 
                relation_data = utils.relation_get_dict(relation_id=rid,
476
 
                                                        remote_unit=unit)
477
 
 
478
 
                update_config_block('sql', connection="mysql://%s:%s@%s/%s" %
479
 
                                        (config["database-user"],
480
 
                                         relation_data["password"],
481
 
                                         relation_data["private-address"],
482
 
                                         config["database"]))
483
 
 
484
 
    utils.stop('keystone')
485
 
    if (cluster.eligible_leader(CLUSTER_RES)):
486
 
        utils.juju_log('INFO',
487
 
                       'Running database migrations for %s' % new_vers)
488
 
        execute('keystone-manage db_sync', echo=True, die=True)
489
 
    else:
490
 
        utils.juju_log('INFO',
491
 
                       'Not cluster leader; snoozing whilst'
492
 
                       ' leader upgrades DB')
493
 
        time.sleep(10)
494
 
    utils.start('keystone')
495
 
    time.sleep(5)
496
 
    utils.juju_log('INFO',
497
 
                   'Completed Keystone upgrade: '
498
 
                   '%s -> %s' % (old_vers, new_vers))
499
 
 
500
 
 
501
542
def synchronize_service_credentials():
502
543
    '''
503
544
    Broadcast service credentials to peers or consume those that have been
504
545
    broadcasted by peer, depending on hook context.
505
546
    '''
506
 
    if (not cluster.eligible_leader(CLUSTER_RES) or
507
 
        not os.path.isfile(SERVICE_PASSWD_PATH)):
 
547
    if (not eligible_leader(CLUSTER_RES) or
 
548
            not os.path.isfile(SERVICE_PASSWD_PATH)):
508
549
        return
509
 
    utils.juju_log('INFO', 'Synchronizing service passwords to all peers.')
510
 
    unison.sync_to_peers(peer_interface='cluster',
511
 
                         paths=[SERVICE_PASSWD_PATH], user=SSH_USER,
512
 
                         verbose=True)
 
550
    log('Synchronizing service passwords to all peers.')
 
551
    if is_clustered():
 
552
        unison.sync_to_peers(peer_interface='cluster',
 
553
                             paths=[SERVICE_PASSWD_PATH], user=SSH_USER,
 
554
                             verbose=True)
 
555
        if config('https-service-endpoints') in ['True', 'true']:
 
556
            unison.sync_to_peers(peer_interface='cluster',
 
557
                                 paths=[SSL_DIR], user=SSH_USER, verbose=True)
513
558
 
514
559
CA = []
515
560
 
527
572
                        ca_dir=os.path.join(SSL_DIR,
528
573
                                            '%s_intermediate_ca' % d_name),
529
574
                        root_ca_dir=os.path.join(SSL_DIR,
530
 
                                            '%s_root_ca' % d_name))
 
575
                                                 '%s_root_ca' % d_name))
531
576
        # SSL_DIR is synchronized via all peers over unison+ssh, need
532
577
        # to ensure permissions.
533
 
        execute('chown -R %s.%s %s' % (user, group, SSL_DIR))
534
 
        execute('chmod -R g+rwx %s' % SSL_DIR)
 
578
        subprocess.check_output(['chown', '-R', '%s.%s' % (user, group),
 
579
                                '%s' % SSL_DIR])
 
580
        subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
535
581
        CA.append(ca)
536
582
    return CA[0]
537
583
 
538
584
 
539
 
def https():
540
 
    if (utils.config_get('https-service-endpoints') in ["yes", "true", "True"]
541
 
        or cluster.https()):
542
 
        return True
543
 
    else:
544
 
        return False
 
585
def relation_list(rid):
 
586
    cmd = [
 
587
        'relation-list',
 
588
        '-r', rid,
 
589
    ]
 
590
    result = str(subprocess.check_output(cmd)).split()
 
591
    if result == "":
 
592
        return None
 
593
    else:
 
594
        return result
 
595
 
 
596
 
 
597
def add_service_to_keystone(relation_id=None, remote_unit=None):
 
598
    settings = relation_get(rid=relation_id, unit=remote_unit)
 
599
    # the minimum settings needed per endpoint
 
600
    single = set(['service', 'region', 'public_url', 'admin_url',
 
601
                  'internal_url'])
 
602
    if single.issubset(settings):
 
603
        # other end of relation advertised only one endpoint
 
604
        if 'None' in [v for k, v in settings.iteritems()]:
 
605
            # Some backend services advertise no endpoint but require a
 
606
            # hook execution to update auth strategy.
 
607
            relation_data = {}
 
608
            # Check if clustered and use vip + haproxy ports if so
 
609
            if is_clustered():
 
610
                relation_data["auth_host"] = config('vip')
 
611
                relation_data["service_host"] = config('vip')
 
612
            else:
 
613
                relation_data["auth_host"] = unit_private_ip()
 
614
                relation_data["service_host"] = unit_private_ip()
 
615
            if https():
 
616
                relation_data["auth_protocol"] = "https"
 
617
                relation_data["service_protocol"] = "https"
 
618
            else:
 
619
                relation_data["auth_protocol"] = "http"
 
620
                relation_data["service_protocol"] = "http"
 
621
            relation_data["auth_port"] = config('admin-port')
 
622
            relation_data["service_port"] = config('service-port')
 
623
            if config('https-service-endpoints') in ['True', 'true']:
 
624
                # Pass CA cert as client will need it to
 
625
                # verify https connections
 
626
                ca = get_ca(user=SSH_USER)
 
627
                ca_bundle = ca.get_ca_bundle()
 
628
                relation_data['https_keystone'] = 'True'
 
629
                relation_data['ca_cert'] = b64encode(ca_bundle)
 
630
            # Allow the remote service to request creation of any additional
 
631
            # roles. Currently used by Horizon
 
632
            for role in get_requested_roles(settings):
 
633
                log("Creating requested role: %s" % role)
 
634
                create_role(role)
 
635
            relation_set(relation_id=relation_id,
 
636
                         **relation_data)
 
637
            return
 
638
        else:
 
639
            ensure_valid_service(settings['service'])
 
640
            add_endpoint(region=settings['region'],
 
641
                         service=settings['service'],
 
642
                         publicurl=settings['public_url'],
 
643
                         adminurl=settings['admin_url'],
 
644
                         internalurl=settings['internal_url'])
 
645
            service_username = settings['service']
 
646
            https_cn = urlparse.urlparse(settings['internal_url'])
 
647
            https_cn = https_cn.hostname
 
648
    else:
 
649
        # assemble multiple endpoints from relation data. service name
 
650
        # should be prepended to setting name, ie:
 
651
        #  realtion-set ec2_service=$foo ec2_region=$foo ec2_public_url=$foo
 
652
        #  relation-set nova_service=$foo nova_region=$foo nova_public_url=$foo
 
653
        # Results in a dict that looks like:
 
654
        # { 'ec2': {
 
655
        #       'service': $foo
 
656
        #       'region': $foo
 
657
        #       'public_url': $foo
 
658
        #   }
 
659
        #   'nova': {
 
660
        #       'service': $foo
 
661
        #       'region': $foo
 
662
        #       'public_url': $foo
 
663
        #   }
 
664
        # }
 
665
        endpoints = {}
 
666
        for k, v in settings.iteritems():
 
667
            ep = k.split('_')[0]
 
668
            x = '_'.join(k.split('_')[1:])
 
669
            if ep not in endpoints:
 
670
                endpoints[ep] = {}
 
671
            endpoints[ep][x] = v
 
672
        services = []
 
673
        https_cn = None
 
674
        for ep in endpoints:
 
675
            # weed out any unrelated relation stuff Juju might have added
 
676
            # by ensuring each possible endpiont has appropriate fields
 
677
            #  ['service', 'region', 'public_url', 'admin_url', 'internal_url']
 
678
            if single.issubset(endpoints[ep]):
 
679
                ep = endpoints[ep]
 
680
                ensure_valid_service(ep['service'])
 
681
                add_endpoint(region=ep['region'], service=ep['service'],
 
682
                             publicurl=ep['public_url'],
 
683
                             adminurl=ep['admin_url'],
 
684
                             internalurl=ep['internal_url'])
 
685
                services.append(ep['service'])
 
686
                if not https_cn:
 
687
                    https_cn = urlparse.urlparse(ep['internal_url'])
 
688
                    https_cn = https_cn.hostname
 
689
        service_username = '_'.join(services)
 
690
 
 
691
    if 'None' in [v for k, v in settings.iteritems()]:
 
692
        return
 
693
 
 
694
    if not service_username:
 
695
        return
 
696
 
 
697
    token = get_admin_token()
 
698
    log("Creating service credentials for '%s'" % service_username)
 
699
 
 
700
    service_password = get_service_password(service_username)
 
701
    create_user(service_username, service_password, config('service-tenant'))
 
702
    grant_role(service_username, config('admin-role'),
 
703
               config('service-tenant'))
 
704
 
 
705
    # Allow the remote service to request creation of any additional roles.
 
706
    # Currently used by Swift and Ceilometer.
 
707
    for role in get_requested_roles(settings):
 
708
        log("Creating requested role: %s" % role)
 
709
        create_role(role, service_username,
 
710
                    config('service-tenant'))
 
711
 
 
712
    # As of https://review.openstack.org/#change,4675, all nodes hosting
 
713
    # an endpoint(s) needs a service username and password assigned to
 
714
    # the service tenant and granted admin role.
 
715
    # note: config('service-tenant') is created in utils.ensure_initial_admin()
 
716
    # we return a token, information about our API endpoints, and the generated
 
717
    # service credentials
 
718
    relation_data = {
 
719
        "admin_token": token,
 
720
        "service_host": unit_private_ip(),
 
721
        "service_port": config("service-port"),
 
722
        "auth_host": unit_private_ip(),
 
723
        "auth_port": config("admin-port"),
 
724
        "service_username": service_username,
 
725
        "service_password": service_password,
 
726
        "service_tenant": config('service-tenant'),
 
727
        "https_keystone": "False",
 
728
        "ssl_cert": "",
 
729
        "ssl_key": "",
 
730
        "ca_cert": ""
 
731
    }
 
732
 
 
733
    # Check if clustered and use vip + haproxy ports if so
 
734
    if is_clustered():
 
735
        relation_data["auth_host"] = config('vip')
 
736
        relation_data["service_host"] = config('vip')
 
737
    if https():
 
738
        relation_data["auth_protocol"] = "https"
 
739
        relation_data["service_protocol"] = "https"
 
740
    else:
 
741
        relation_data["auth_protocol"] = "http"
 
742
        relation_data["service_protocol"] = "http"
 
743
    # generate or get a new cert/key for service if set to manage certs.
 
744
    if config('https-service-endpoints') in ['True', 'true']:
 
745
        ca = get_ca(user=SSH_USER)
 
746
        cert, key = ca.get_cert_and_key(common_name=https_cn)
 
747
        ca_bundle = ca.get_ca_bundle()
 
748
        relation_data['ssl_cert'] = b64encode(cert)
 
749
        relation_data['ssl_key'] = b64encode(key)
 
750
        relation_data['ca_cert'] = b64encode(ca_bundle)
 
751
        relation_data['https_keystone'] = 'True'
 
752
    relation_set(relation_id=relation_id,
 
753
                 **relation_data)
 
754
 
 
755
 
 
756
def ensure_valid_service(service):
 
757
    if service not in valid_services.keys():
 
758
        log("Invalid service requested: '%s'" % service)
 
759
        relation_set(admin_token=-1)
 
760
        return
 
761
 
 
762
 
 
763
def add_endpoint(region, service, publicurl, adminurl, internalurl):
 
764
    desc = valid_services[service]["desc"]
 
765
    service_type = valid_services[service]["type"]
 
766
    create_service_entry(service, service_type, desc)
 
767
    create_endpoint_template(region=region, service=service,
 
768
                             publicurl=publicurl,
 
769
                             adminurl=adminurl,
 
770
                             internalurl=internalurl)
 
771
 
 
772
 
 
773
def get_requested_roles(settings):
 
774
    ''' Retrieve any valid requested_roles from dict settings '''
 
775
    if ('requested_roles' in settings and
 
776
            settings['requested_roles'] not in ['None', None]):
 
777
        return settings['requested_roles'].split(',')
 
778
    else:
 
779
        return []