~openstack-charmers/charms/precise/ceilometer-agent/old-1410

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2013-10-14 16:10:30 UTC
  • Revision ID: james.page@canonical.com-20131014161030-x89xxf581s07ayzy
Rebase on charmhelpers for havana

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