~zulcss/+junk/nova-compute-test

« back to all changes in this revision

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

  • Committer: Chuck Short
  • Date: 2014-05-30 12:56:35 UTC
  • Revision ID: zulcss@ubuntu.com-20140530125635-9nkb5bu3hcuj44pg
Initial commit

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