~lazypower/charms/trusty/keystone/fix_proof

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/openstack/utils.py

  • Committer: Ante Karamatic
  • Date: 2014-02-25 11:34:13 UTC
  • Revision ID: ivoks@ubuntu.com-20140225113413-tlm02x1ibc6xb10d
Rewrite charm to get it more in line with other OpenStack charms.

Added support for contexts and templating. Makes use of charm-helpers
instead of relaying on its own tools (probably could use some additional work).

HA is currently non-functional. ETA for fixing: less than 2 days.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# Common python helper functions used for OpenStack charms.
 
4
from collections import OrderedDict
 
5
 
 
6
import apt_pkg as apt
 
7
import subprocess
 
8
import os
 
9
import socket
 
10
import sys
 
11
 
 
12
from charmhelpers.core.hookenv import (
 
13
    config,
 
14
    log as juju_log,
 
15
    charm_dir,
 
16
    ERROR,
 
17
    INFO
 
18
)
 
19
 
 
20
from charmhelpers.contrib.storage.linux.lvm import (
 
21
    deactivate_lvm_volume_group,
 
22
    is_lvm_physical_volume,
 
23
    remove_lvm_physical_volume,
 
24
)
 
25
 
 
26
from charmhelpers.core.host import lsb_release, mounts, umount
 
27
from charmhelpers.fetch import apt_install
 
28
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
 
29
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
 
30
 
 
31
CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
 
32
CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
 
33
 
 
34
DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
 
35
                   'restricted main multiverse universe')
 
36
 
 
37
 
 
38
UBUNTU_OPENSTACK_RELEASE = OrderedDict([
 
39
    ('oneiric', 'diablo'),
 
40
    ('precise', 'essex'),
 
41
    ('quantal', 'folsom'),
 
42
    ('raring', 'grizzly'),
 
43
    ('saucy', 'havana'),
 
44
    ('trusty', 'icehouse')
 
45
])
 
46
 
 
47
 
 
48
OPENSTACK_CODENAMES = OrderedDict([
 
49
    ('2011.2', 'diablo'),
 
50
    ('2012.1', 'essex'),
 
51
    ('2012.2', 'folsom'),
 
52
    ('2013.1', 'grizzly'),
 
53
    ('2013.2', 'havana'),
 
54
    ('2014.1', 'icehouse'),
 
55
])
 
56
 
 
57
# The ugly duckling
 
58
SWIFT_CODENAMES = OrderedDict([
 
59
    ('1.4.3', 'diablo'),
 
60
    ('1.4.8', 'essex'),
 
61
    ('1.7.4', 'folsom'),
 
62
    ('1.8.0', 'grizzly'),
 
63
    ('1.7.7', 'grizzly'),
 
64
    ('1.7.6', 'grizzly'),
 
65
    ('1.10.0', 'havana'),
 
66
    ('1.9.1', 'havana'),
 
67
    ('1.9.0', 'havana'),
 
68
])
 
69
 
 
70
DEFAULT_LOOPBACK_SIZE = '5G'
 
71
 
 
72
 
 
73
def error_out(msg):
 
74
    juju_log("FATAL ERROR: %s" % msg, level='ERROR')
 
75
    sys.exit(1)
 
76
 
 
77
 
 
78
def get_os_codename_install_source(src):
 
79
    '''Derive OpenStack release codename from a given installation source.'''
 
80
    ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
81
    rel = ''
 
82
    if src in ['distro', 'distro-proposed']:
 
83
        try:
 
84
            rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
 
85
        except KeyError:
 
86
            e = 'Could not derive openstack release for '\
 
87
                'this Ubuntu release: %s' % ubuntu_rel
 
88
            error_out(e)
 
89
        return rel
 
90
 
 
91
    if src.startswith('cloud:'):
 
92
        ca_rel = src.split(':')[1]
 
93
        ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
 
94
        return ca_rel
 
95
 
 
96
    # Best guess match based on deb string provided
 
97
    if src.startswith('deb') or src.startswith('ppa'):
 
98
        for k, v in OPENSTACK_CODENAMES.iteritems():
 
99
            if v in src:
 
100
                return v
 
101
 
 
102
 
 
103
def get_os_version_install_source(src):
 
104
    codename = get_os_codename_install_source(src)
 
105
    return get_os_version_codename(codename)
 
106
 
 
107
 
 
108
def get_os_codename_version(vers):
 
109
    '''Determine OpenStack codename from version number.'''
 
110
    try:
 
111
        return OPENSTACK_CODENAMES[vers]
 
112
    except KeyError:
 
113
        e = 'Could not determine OpenStack codename for version %s' % vers
 
114
        error_out(e)
 
115
 
 
116
 
 
117
def get_os_version_codename(codename):
 
118
    '''Determine OpenStack version number from codename.'''
 
119
    for k, v in OPENSTACK_CODENAMES.iteritems():
 
120
        if v == codename:
 
121
            return k
 
122
    e = 'Could not derive OpenStack version for '\
 
123
        'codename: %s' % codename
 
124
    error_out(e)
 
125
 
 
126
 
 
127
def get_os_codename_package(package, fatal=True):
 
128
    '''Derive OpenStack release codename from an installed package.'''
 
129
    apt.init()
 
130
    cache = apt.Cache()
 
131
 
 
132
    try:
 
133
        pkg = cache[package]
 
134
    except:
 
135
        if not fatal:
 
136
            return None
 
137
        # the package is unknown to the current apt cache.
 
138
        e = 'Could not determine version of package with no installation '\
 
139
            'candidate: %s' % package
 
140
        error_out(e)
 
141
 
 
142
    if not pkg.current_ver:
 
143
        if not fatal:
 
144
            return None
 
145
        # package is known, but no version is currently installed.
 
146
        e = 'Could not determine version of uninstalled package: %s' % package
 
147
        error_out(e)
 
148
 
 
149
    vers = apt.upstream_version(pkg.current_ver.ver_str)
 
150
 
 
151
    try:
 
152
        if 'swift' in pkg.name:
 
153
            swift_vers = vers[:5]
 
154
            if swift_vers not in SWIFT_CODENAMES:
 
155
                # Deal with 1.10.0 upward
 
156
                swift_vers = vers[:6]
 
157
            return SWIFT_CODENAMES[swift_vers]
 
158
        else:
 
159
            vers = vers[:6]
 
160
            return OPENSTACK_CODENAMES[vers]
 
161
    except KeyError:
 
162
        e = 'Could not determine OpenStack codename for version %s' % vers
 
163
        error_out(e)
 
164
 
 
165
 
 
166
def get_os_version_package(pkg, fatal=True):
 
167
    '''Derive OpenStack version number from an installed package.'''
 
168
    codename = get_os_codename_package(pkg, fatal=fatal)
 
169
 
 
170
    if not codename:
 
171
        return None
 
172
 
 
173
    if 'swift' in pkg:
 
174
        vers_map = SWIFT_CODENAMES
 
175
    else:
 
176
        vers_map = OPENSTACK_CODENAMES
 
177
 
 
178
    for version, cname in vers_map.iteritems():
 
179
        if cname == codename:
 
180
            return version
 
181
    #e = "Could not determine OpenStack version for package: %s" % pkg
 
182
    #error_out(e)
 
183
 
 
184
 
 
185
os_rel = None
 
186
 
 
187
 
 
188
def os_release(package, base='essex'):
 
189
    '''
 
190
    Returns OpenStack release codename from a cached global.
 
191
    If the codename can not be determined from either an installed package or
 
192
    the installation source, the earliest release supported by the charm should
 
193
    be returned.
 
194
    '''
 
195
    global os_rel
 
196
    if os_rel:
 
197
        return os_rel
 
198
    os_rel = (get_os_codename_package(package, fatal=False) or
 
199
              get_os_codename_install_source(config('openstack-origin')) or
 
200
              base)
 
201
    return os_rel
 
202
 
 
203
 
 
204
def import_key(keyid):
 
205
    cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 " \
 
206
          "--recv-keys %s" % keyid
 
207
    try:
 
208
        subprocess.check_call(cmd.split(' '))
 
209
    except subprocess.CalledProcessError:
 
210
        error_out("Error importing repo key %s" % keyid)
 
211
 
 
212
 
 
213
def configure_installation_source(rel):
 
214
    '''Configure apt installation source.'''
 
215
    if rel == 'distro':
 
216
        return
 
217
    elif rel == 'distro-proposed':
 
218
        ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
219
        with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
 
220
            f.write(DISTRO_PROPOSED % ubuntu_rel)
 
221
    elif rel[:4] == "ppa:":
 
222
        src = rel
 
223
        subprocess.check_call(["add-apt-repository", "-y", src])
 
224
    elif rel[:3] == "deb":
 
225
        l = len(rel.split('|'))
 
226
        if l == 2:
 
227
            src, key = rel.split('|')
 
228
            juju_log("Importing PPA key from keyserver for %s" % src)
 
229
            import_key(key)
 
230
        elif l == 1:
 
231
            src = rel
 
232
        with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
 
233
            f.write(src)
 
234
    elif rel[:6] == 'cloud:':
 
235
        ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
236
        rel = rel.split(':')[1]
 
237
        u_rel = rel.split('-')[0]
 
238
        ca_rel = rel.split('-')[1]
 
239
 
 
240
        if u_rel != ubuntu_rel:
 
241
            e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
 
242
                'version (%s)' % (ca_rel, ubuntu_rel)
 
243
            error_out(e)
 
244
 
 
245
        if 'staging' in ca_rel:
 
246
            # staging is just a regular PPA.
 
247
            os_rel = ca_rel.split('/')[0]
 
248
            ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel
 
249
            cmd = 'add-apt-repository -y %s' % ppa
 
250
            subprocess.check_call(cmd.split(' '))
 
251
            return
 
252
 
 
253
        # map charm config options to actual archive pockets.
 
254
        pockets = {
 
255
            'folsom': 'precise-updates/folsom',
 
256
            'folsom/updates': 'precise-updates/folsom',
 
257
            'folsom/proposed': 'precise-proposed/folsom',
 
258
            'grizzly': 'precise-updates/grizzly',
 
259
            'grizzly/updates': 'precise-updates/grizzly',
 
260
            'grizzly/proposed': 'precise-proposed/grizzly',
 
261
            'havana': 'precise-updates/havana',
 
262
            'havana/updates': 'precise-updates/havana',
 
263
            'havana/proposed': 'precise-proposed/havana',
 
264
            'icehouse': 'precise-updates/icehouse',
 
265
            'icehouse/updates': 'precise-updates/icehouse',
 
266
            'icehouse/proposed': 'precise-proposed/icehouse',
 
267
        }
 
268
 
 
269
        try:
 
270
            pocket = pockets[ca_rel]
 
271
        except KeyError:
 
272
            e = 'Invalid Cloud Archive release specified: %s' % rel
 
273
            error_out(e)
 
274
 
 
275
        src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
 
276
        apt_install('ubuntu-cloud-keyring', fatal=True)
 
277
 
 
278
        with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
 
279
            f.write(src)
 
280
    else:
 
281
        error_out("Invalid openstack-release specified: %s" % rel)
 
282
 
 
283
 
 
284
def save_script_rc(script_path="scripts/scriptrc", **env_vars):
 
285
    """
 
286
    Write an rc file in the charm-delivered directory containing
 
287
    exported environment variables provided by env_vars. Any charm scripts run
 
288
    outside the juju hook environment can source this scriptrc to obtain
 
289
    updated config information necessary to perform health checks or
 
290
    service changes.
 
291
    """
 
292
    juju_rc_path = "%s/%s" % (charm_dir(), script_path)
 
293
    if not os.path.exists(os.path.dirname(juju_rc_path)):
 
294
        os.mkdir(os.path.dirname(juju_rc_path))
 
295
    with open(juju_rc_path, 'wb') as rc_script:
 
296
        rc_script.write(
 
297
            "#!/bin/bash\n")
 
298
        [rc_script.write('export %s=%s\n' % (u, p))
 
299
         for u, p in env_vars.iteritems() if u != "script_path"]
 
300
 
 
301
 
 
302
def openstack_upgrade_available(package):
 
303
    """
 
304
    Determines if an OpenStack upgrade is available from installation
 
305
    source, based on version of installed package.
 
306
 
 
307
    :param package: str: Name of installed package.
 
308
 
 
309
    :returns: bool:    : Returns True if configured installation source offers
 
310
                         a newer version of package.
 
311
 
 
312
    """
 
313
 
 
314
    src = config('openstack-origin')
 
315
    cur_vers = get_os_version_package(package)
 
316
    available_vers = get_os_version_install_source(src)
 
317
    apt.init()
 
318
    return apt.version_compare(available_vers, cur_vers) == 1
 
319
 
 
320
 
 
321
def ensure_block_device(block_device):
 
322
    '''
 
323
    Confirm block_device, create as loopback if necessary.
 
324
 
 
325
    :param block_device: str: Full path of block device to ensure.
 
326
 
 
327
    :returns: str: Full path of ensured block device.
 
328
    '''
 
329
    _none = ['None', 'none', None]
 
330
    if (block_device in _none):
 
331
        error_out('prepare_storage(): Missing required input: '
 
332
                  'block_device=%s.' % block_device, level=ERROR)
 
333
 
 
334
    if block_device.startswith('/dev/'):
 
335
        bdev = block_device
 
336
    elif block_device.startswith('/'):
 
337
        _bd = block_device.split('|')
 
338
        if len(_bd) == 2:
 
339
            bdev, size = _bd
 
340
        else:
 
341
            bdev = block_device
 
342
            size = DEFAULT_LOOPBACK_SIZE
 
343
        bdev = ensure_loopback_device(bdev, size)
 
344
    else:
 
345
        bdev = '/dev/%s' % block_device
 
346
 
 
347
    if not is_block_device(bdev):
 
348
        error_out('Failed to locate valid block device at %s' % bdev,
 
349
                  level=ERROR)
 
350
 
 
351
    return bdev
 
352
 
 
353
 
 
354
def clean_storage(block_device):
 
355
    '''
 
356
    Ensures a block device is clean.  That is:
 
357
        - unmounted
 
358
        - any lvm volume groups are deactivated
 
359
        - any lvm physical device signatures removed
 
360
        - partition table wiped
 
361
 
 
362
    :param block_device: str: Full path to block device to clean.
 
363
    '''
 
364
    for mp, d in mounts():
 
365
        if d == block_device:
 
366
            juju_log('clean_storage(): %s is mounted @ %s, unmounting.' %
 
367
                     (d, mp), level=INFO)
 
368
            umount(mp, persist=True)
 
369
 
 
370
    if is_lvm_physical_volume(block_device):
 
371
        deactivate_lvm_volume_group(block_device)
 
372
        remove_lvm_physical_volume(block_device)
 
373
    else:
 
374
        zap_disk(block_device)
 
375
 
 
376
 
 
377
def is_ip(address):
 
378
    """
 
379
    Returns True if address is a valid IP address.
 
380
    """
 
381
    try:
 
382
        # Test to see if already an IPv4 address
 
383
        socket.inet_aton(address)
 
384
        return True
 
385
    except socket.error:
 
386
        return False
 
387
 
 
388
 
 
389
def ns_query(address):
 
390
    try:
 
391
        import dns.resolver
 
392
    except ImportError:
 
393
        apt_install('python-dnspython')
 
394
        import dns.resolver
 
395
 
 
396
    if isinstance(address, dns.name.Name):
 
397
        rtype = 'PTR'
 
398
    elif isinstance(address, basestring):
 
399
        rtype = 'A'
 
400
 
 
401
    answers = dns.resolver.query(address, rtype)
 
402
    if answers:
 
403
        return str(answers[0])
 
404
    return None
 
405
 
 
406
 
 
407
def get_host_ip(hostname):
 
408
    """
 
409
    Resolves the IP for a given hostname, or returns
 
410
    the input if it is already an IP.
 
411
    """
 
412
    if is_ip(hostname):
 
413
        return hostname
 
414
 
 
415
    return ns_query(hostname)
 
416
 
 
417
 
 
418
def get_hostname(address, fqdn=True):
 
419
    """
 
420
    Resolves hostname for given IP, or returns the input
 
421
    if it is already a hostname.
 
422
    """
 
423
    if not is_ip(address):
 
424
        return address
 
425
 
 
426
    try:
 
427
        import dns.reversename
 
428
    except ImportError:
 
429
        apt_install('python-dnspython')
 
430
        import dns.reversename
 
431
 
 
432
    rev = dns.reversename.from_address(address)
 
433
    result = ns_query(rev)
 
434
    if not result:
 
435
        return None
 
436
 
 
437
    if fqdn:
 
438
        # strip trailing .
 
439
        if result.endswith('.'):
 
440
            return result[:-1]
 
441
        else:
 
442
            return result
 
443
    else:
 
444
        return result.split('.')[0]