~junaidali/charms/trusty/plumgrid-director/pg-restart

« back to all changes in this revision

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

  • Committer: bbaqar at plumgrid
  • Date: 2015-05-19 21:05:20 UTC
  • Revision ID: bbaqar@plumgrid.com-20150519210520-8nymqswwgdu7qygg
PLUMgrid director initial charm

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# Copyright 2014-2015 Canonical Limited.
 
4
#
 
5
# This file is part of charm-helpers.
 
6
#
 
7
# charm-helpers is free software: you can redistribute it and/or modify
 
8
# it under the terms of the GNU Lesser General Public License version 3 as
 
9
# published by the Free Software Foundation.
 
10
#
 
11
# charm-helpers is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU Lesser General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU Lesser General Public License
 
17
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
# Common python helper functions used for OpenStack charms.
 
20
from collections import OrderedDict
 
21
from functools import wraps
 
22
 
 
23
import subprocess
 
24
import json
 
25
import os
 
26
import sys
 
27
 
 
28
import six
 
29
import yaml
 
30
 
 
31
from charmhelpers.contrib.network import ip
 
32
 
 
33
from charmhelpers.core import (
 
34
    unitdata,
 
35
)
 
36
 
 
37
from charmhelpers.core.hookenv import (
 
38
    config,
 
39
    log as juju_log,
 
40
    charm_dir,
 
41
    INFO,
 
42
    relation_ids,
 
43
    relation_set
 
44
)
 
45
 
 
46
from charmhelpers.contrib.storage.linux.lvm import (
 
47
    deactivate_lvm_volume_group,
 
48
    is_lvm_physical_volume,
 
49
    remove_lvm_physical_volume,
 
50
)
 
51
 
 
52
from charmhelpers.contrib.network.ip import (
 
53
    get_ipv6_addr
 
54
)
 
55
 
 
56
from charmhelpers.core.host import lsb_release, mounts, umount
 
57
from charmhelpers.fetch import apt_install, apt_cache, install_remote
 
58
from charmhelpers.contrib.python.packages import pip_install
 
59
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
 
60
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
 
61
 
 
62
CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
 
63
CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
 
64
 
 
65
DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
 
66
                   'restricted main multiverse universe')
 
67
 
 
68
 
 
69
UBUNTU_OPENSTACK_RELEASE = OrderedDict([
 
70
    ('oneiric', 'diablo'),
 
71
    ('precise', 'essex'),
 
72
    ('quantal', 'folsom'),
 
73
    ('raring', 'grizzly'),
 
74
    ('saucy', 'havana'),
 
75
    ('trusty', 'icehouse'),
 
76
    ('utopic', 'juno'),
 
77
    ('vivid', 'kilo'),
 
78
])
 
79
 
 
80
 
 
81
OPENSTACK_CODENAMES = OrderedDict([
 
82
    ('2011.2', 'diablo'),
 
83
    ('2012.1', 'essex'),
 
84
    ('2012.2', 'folsom'),
 
85
    ('2013.1', 'grizzly'),
 
86
    ('2013.2', 'havana'),
 
87
    ('2014.1', 'icehouse'),
 
88
    ('2014.2', 'juno'),
 
89
    ('2015.1', 'kilo'),
 
90
])
 
91
 
 
92
# The ugly duckling
 
93
SWIFT_CODENAMES = OrderedDict([
 
94
    ('1.4.3', 'diablo'),
 
95
    ('1.4.8', 'essex'),
 
96
    ('1.7.4', 'folsom'),
 
97
    ('1.8.0', 'grizzly'),
 
98
    ('1.7.7', 'grizzly'),
 
99
    ('1.7.6', 'grizzly'),
 
100
    ('1.10.0', 'havana'),
 
101
    ('1.9.1', 'havana'),
 
102
    ('1.9.0', 'havana'),
 
103
    ('1.13.1', 'icehouse'),
 
104
    ('1.13.0', 'icehouse'),
 
105
    ('1.12.0', 'icehouse'),
 
106
    ('1.11.0', 'icehouse'),
 
107
    ('2.0.0', 'juno'),
 
108
    ('2.1.0', 'juno'),
 
109
    ('2.2.0', 'juno'),
 
110
    ('2.2.1', 'kilo'),
 
111
    ('2.2.2', 'kilo'),
 
112
])
 
113
 
 
114
DEFAULT_LOOPBACK_SIZE = '5G'
 
115
 
 
116
 
 
117
def error_out(msg):
 
118
    juju_log("FATAL ERROR: %s" % msg, level='ERROR')
 
119
    sys.exit(1)
 
120
 
 
121
 
 
122
def get_os_codename_install_source(src):
 
123
    '''Derive OpenStack release codename from a given installation source.'''
 
124
    ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
125
    rel = ''
 
126
    if src is None:
 
127
        return rel
 
128
    if src in ['distro', 'distro-proposed']:
 
129
        try:
 
130
            rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
 
131
        except KeyError:
 
132
            e = 'Could not derive openstack release for '\
 
133
                'this Ubuntu release: %s' % ubuntu_rel
 
134
            error_out(e)
 
135
        return rel
 
136
 
 
137
    if src.startswith('cloud:'):
 
138
        ca_rel = src.split(':')[1]
 
139
        ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
 
140
        return ca_rel
 
141
 
 
142
    # Best guess match based on deb string provided
 
143
    if src.startswith('deb') or src.startswith('ppa'):
 
144
        for k, v in six.iteritems(OPENSTACK_CODENAMES):
 
145
            if v in src:
 
146
                return v
 
147
 
 
148
 
 
149
def get_os_version_install_source(src):
 
150
    codename = get_os_codename_install_source(src)
 
151
    return get_os_version_codename(codename)
 
152
 
 
153
 
 
154
def get_os_codename_version(vers):
 
155
    '''Determine OpenStack codename from version number.'''
 
156
    try:
 
157
        return OPENSTACK_CODENAMES[vers]
 
158
    except KeyError:
 
159
        e = 'Could not determine OpenStack codename for version %s' % vers
 
160
        error_out(e)
 
161
 
 
162
 
 
163
def get_os_version_codename(codename):
 
164
    '''Determine OpenStack version number from codename.'''
 
165
    for k, v in six.iteritems(OPENSTACK_CODENAMES):
 
166
        if v == codename:
 
167
            return k
 
168
    e = 'Could not derive OpenStack version for '\
 
169
        'codename: %s' % codename
 
170
    error_out(e)
 
171
 
 
172
 
 
173
def get_os_codename_package(package, fatal=True):
 
174
    '''Derive OpenStack release codename from an installed package.'''
 
175
    import apt_pkg as apt
 
176
 
 
177
    cache = apt_cache()
 
178
 
 
179
    try:
 
180
        pkg = cache[package]
 
181
    except:
 
182
        if not fatal:
 
183
            return None
 
184
        # the package is unknown to the current apt cache.
 
185
        e = 'Could not determine version of package with no installation '\
 
186
            'candidate: %s' % package
 
187
        error_out(e)
 
188
 
 
189
    if not pkg.current_ver:
 
190
        if not fatal:
 
191
            return None
 
192
        # package is known, but no version is currently installed.
 
193
        e = 'Could not determine version of uninstalled package: %s' % package
 
194
        error_out(e)
 
195
 
 
196
    vers = apt.upstream_version(pkg.current_ver.ver_str)
 
197
 
 
198
    try:
 
199
        if 'swift' in pkg.name:
 
200
            swift_vers = vers[:5]
 
201
            if swift_vers not in SWIFT_CODENAMES:
 
202
                # Deal with 1.10.0 upward
 
203
                swift_vers = vers[:6]
 
204
            return SWIFT_CODENAMES[swift_vers]
 
205
        else:
 
206
            vers = vers[:6]
 
207
            return OPENSTACK_CODENAMES[vers]
 
208
    except KeyError:
 
209
        e = 'Could not determine OpenStack codename for version %s' % vers
 
210
        error_out(e)
 
211
 
 
212
 
 
213
def get_os_version_package(pkg, fatal=True):
 
214
    '''Derive OpenStack version number from an installed package.'''
 
215
    codename = get_os_codename_package(pkg, fatal=fatal)
 
216
 
 
217
    if not codename:
 
218
        return None
 
219
 
 
220
    if 'swift' in pkg:
 
221
        vers_map = SWIFT_CODENAMES
 
222
    else:
 
223
        vers_map = OPENSTACK_CODENAMES
 
224
 
 
225
    for version, cname in six.iteritems(vers_map):
 
226
        if cname == codename:
 
227
            return version
 
228
    # e = "Could not determine OpenStack version for package: %s" % pkg
 
229
    # error_out(e)
 
230
 
 
231
 
 
232
os_rel = None
 
233
 
 
234
 
 
235
def os_release(package, base='essex'):
 
236
    '''
 
237
    Returns OpenStack release codename from a cached global.
 
238
    If the codename can not be determined from either an installed package or
 
239
    the installation source, the earliest release supported by the charm should
 
240
    be returned.
 
241
    '''
 
242
    global os_rel
 
243
    if os_rel:
 
244
        return os_rel
 
245
    os_rel = (get_os_codename_package(package, fatal=False) or
 
246
              get_os_codename_install_source(config('openstack-origin')) or
 
247
              base)
 
248
    return os_rel
 
249
 
 
250
 
 
251
def import_key(keyid):
 
252
    cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 " \
 
253
          "--recv-keys %s" % keyid
 
254
    try:
 
255
        subprocess.check_call(cmd.split(' '))
 
256
    except subprocess.CalledProcessError:
 
257
        error_out("Error importing repo key %s" % keyid)
 
258
 
 
259
 
 
260
def configure_installation_source(rel):
 
261
    '''Configure apt installation source.'''
 
262
    if rel == 'distro':
 
263
        return
 
264
    elif rel == 'distro-proposed':
 
265
        ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
266
        with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
 
267
            f.write(DISTRO_PROPOSED % ubuntu_rel)
 
268
    elif rel[:4] == "ppa:":
 
269
        src = rel
 
270
        subprocess.check_call(["add-apt-repository", "-y", src])
 
271
    elif rel[:3] == "deb":
 
272
        l = len(rel.split('|'))
 
273
        if l == 2:
 
274
            src, key = rel.split('|')
 
275
            juju_log("Importing PPA key from keyserver for %s" % src)
 
276
            import_key(key)
 
277
        elif l == 1:
 
278
            src = rel
 
279
        with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
 
280
            f.write(src)
 
281
    elif rel[:6] == 'cloud:':
 
282
        ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
 
283
        rel = rel.split(':')[1]
 
284
        u_rel = rel.split('-')[0]
 
285
        ca_rel = rel.split('-')[1]
 
286
 
 
287
        if u_rel != ubuntu_rel:
 
288
            e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
 
289
                'version (%s)' % (ca_rel, ubuntu_rel)
 
290
            error_out(e)
 
291
 
 
292
        if 'staging' in ca_rel:
 
293
            # staging is just a regular PPA.
 
294
            os_rel = ca_rel.split('/')[0]
 
295
            ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel
 
296
            cmd = 'add-apt-repository -y %s' % ppa
 
297
            subprocess.check_call(cmd.split(' '))
 
298
            return
 
299
 
 
300
        # map charm config options to actual archive pockets.
 
301
        pockets = {
 
302
            'folsom': 'precise-updates/folsom',
 
303
            'folsom/updates': 'precise-updates/folsom',
 
304
            'folsom/proposed': 'precise-proposed/folsom',
 
305
            'grizzly': 'precise-updates/grizzly',
 
306
            'grizzly/updates': 'precise-updates/grizzly',
 
307
            'grizzly/proposed': 'precise-proposed/grizzly',
 
308
            'havana': 'precise-updates/havana',
 
309
            'havana/updates': 'precise-updates/havana',
 
310
            'havana/proposed': 'precise-proposed/havana',
 
311
            'icehouse': 'precise-updates/icehouse',
 
312
            'icehouse/updates': 'precise-updates/icehouse',
 
313
            'icehouse/proposed': 'precise-proposed/icehouse',
 
314
            'juno': 'trusty-updates/juno',
 
315
            'juno/updates': 'trusty-updates/juno',
 
316
            'juno/proposed': 'trusty-proposed/juno',
 
317
            'kilo': 'trusty-updates/kilo',
 
318
            'kilo/updates': 'trusty-updates/kilo',
 
319
            'kilo/proposed': 'trusty-proposed/kilo',
 
320
        }
 
321
 
 
322
        try:
 
323
            pocket = pockets[ca_rel]
 
324
        except KeyError:
 
325
            e = 'Invalid Cloud Archive release specified: %s' % rel
 
326
            error_out(e)
 
327
 
 
328
        src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
 
329
        apt_install('ubuntu-cloud-keyring', fatal=True)
 
330
 
 
331
        with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
 
332
            f.write(src)
 
333
    else:
 
334
        error_out("Invalid openstack-release specified: %s" % rel)
 
335
 
 
336
 
 
337
def config_value_changed(option):
 
338
    """
 
339
    Determine if config value changed since last call to this function.
 
340
    """
 
341
    hook_data = unitdata.HookData()
 
342
    with hook_data():
 
343
        db = unitdata.kv()
 
344
        current = config(option)
 
345
        saved = db.get(option)
 
346
        db.set(option, current)
 
347
        if saved is None:
 
348
            return False
 
349
        return current != saved
 
350
 
 
351
 
 
352
def save_script_rc(script_path="scripts/scriptrc", **env_vars):
 
353
    """
 
354
    Write an rc file in the charm-delivered directory containing
 
355
    exported environment variables provided by env_vars. Any charm scripts run
 
356
    outside the juju hook environment can source this scriptrc to obtain
 
357
    updated config information necessary to perform health checks or
 
358
    service changes.
 
359
    """
 
360
    juju_rc_path = "%s/%s" % (charm_dir(), script_path)
 
361
    if not os.path.exists(os.path.dirname(juju_rc_path)):
 
362
        os.mkdir(os.path.dirname(juju_rc_path))
 
363
    with open(juju_rc_path, 'wb') as rc_script:
 
364
        rc_script.write(
 
365
            "#!/bin/bash\n")
 
366
        [rc_script.write('export %s=%s\n' % (u, p))
 
367
         for u, p in six.iteritems(env_vars) if u != "script_path"]
 
368
 
 
369
 
 
370
def openstack_upgrade_available(package):
 
371
    """
 
372
    Determines if an OpenStack upgrade is available from installation
 
373
    source, based on version of installed package.
 
374
 
 
375
    :param package: str: Name of installed package.
 
376
 
 
377
    :returns: bool:    : Returns True if configured installation source offers
 
378
                         a newer version of package.
 
379
 
 
380
    """
 
381
 
 
382
    import apt_pkg as apt
 
383
    src = config('openstack-origin')
 
384
    cur_vers = get_os_version_package(package)
 
385
    available_vers = get_os_version_install_source(src)
 
386
    apt.init()
 
387
    return apt.version_compare(available_vers, cur_vers) == 1
 
388
 
 
389
 
 
390
def ensure_block_device(block_device):
 
391
    '''
 
392
    Confirm block_device, create as loopback if necessary.
 
393
 
 
394
    :param block_device: str: Full path of block device to ensure.
 
395
 
 
396
    :returns: str: Full path of ensured block device.
 
397
    '''
 
398
    _none = ['None', 'none', None]
 
399
    if (block_device in _none):
 
400
        error_out('prepare_storage(): Missing required input: block_device=%s.'
 
401
                  % block_device)
 
402
 
 
403
    if block_device.startswith('/dev/'):
 
404
        bdev = block_device
 
405
    elif block_device.startswith('/'):
 
406
        _bd = block_device.split('|')
 
407
        if len(_bd) == 2:
 
408
            bdev, size = _bd
 
409
        else:
 
410
            bdev = block_device
 
411
            size = DEFAULT_LOOPBACK_SIZE
 
412
        bdev = ensure_loopback_device(bdev, size)
 
413
    else:
 
414
        bdev = '/dev/%s' % block_device
 
415
 
 
416
    if not is_block_device(bdev):
 
417
        error_out('Failed to locate valid block device at %s' % bdev)
 
418
 
 
419
    return bdev
 
420
 
 
421
 
 
422
def clean_storage(block_device):
 
423
    '''
 
424
    Ensures a block device is clean.  That is:
 
425
        - unmounted
 
426
        - any lvm volume groups are deactivated
 
427
        - any lvm physical device signatures removed
 
428
        - partition table wiped
 
429
 
 
430
    :param block_device: str: Full path to block device to clean.
 
431
    '''
 
432
    for mp, d in mounts():
 
433
        if d == block_device:
 
434
            juju_log('clean_storage(): %s is mounted @ %s, unmounting.' %
 
435
                     (d, mp), level=INFO)
 
436
            umount(mp, persist=True)
 
437
 
 
438
    if is_lvm_physical_volume(block_device):
 
439
        deactivate_lvm_volume_group(block_device)
 
440
        remove_lvm_physical_volume(block_device)
 
441
    else:
 
442
        zap_disk(block_device)
 
443
 
 
444
is_ip = ip.is_ip
 
445
ns_query = ip.ns_query
 
446
get_host_ip = ip.get_host_ip
 
447
get_hostname = ip.get_hostname
 
448
 
 
449
 
 
450
def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):
 
451
    mm_map = {}
 
452
    if os.path.isfile(mm_file):
 
453
        with open(mm_file, 'r') as f:
 
454
            mm_map = json.load(f)
 
455
    return mm_map
 
456
 
 
457
 
 
458
def sync_db_with_multi_ipv6_addresses(database, database_user,
 
459
                                      relation_prefix=None):
 
460
    hosts = get_ipv6_addr(dynamic_only=False)
 
461
 
 
462
    kwargs = {'database': database,
 
463
              'username': database_user,
 
464
              'hostname': json.dumps(hosts)}
 
465
 
 
466
    if relation_prefix:
 
467
        for key in list(kwargs.keys()):
 
468
            kwargs["%s_%s" % (relation_prefix, key)] = kwargs[key]
 
469
            del kwargs[key]
 
470
 
 
471
    for rid in relation_ids('shared-db'):
 
472
        relation_set(relation_id=rid, **kwargs)
 
473
 
 
474
 
 
475
def os_requires_version(ostack_release, pkg):
 
476
    """
 
477
    Decorator for hook to specify minimum supported release
 
478
    """
 
479
    def wrap(f):
 
480
        @wraps(f)
 
481
        def wrapped_f(*args):
 
482
            if os_release(pkg) < ostack_release:
 
483
                raise Exception("This hook is not supported on releases"
 
484
                                " before %s" % ostack_release)
 
485
            f(*args)
 
486
        return wrapped_f
 
487
    return wrap
 
488
 
 
489
 
 
490
def git_install_requested():
 
491
    """
 
492
    Returns true if openstack-origin-git is specified.
 
493
    """
 
494
    return config('openstack-origin-git') is not None
 
495
 
 
496
 
 
497
requirements_dir = None
 
498
 
 
499
 
 
500
def git_clone_and_install(projects_yaml, core_project):
 
501
    """
 
502
    Clone/install all specified OpenStack repositories.
 
503
 
 
504
    The expected format of projects_yaml is:
 
505
        repositories:
 
506
          - {name: keystone,
 
507
             repository: 'git://git.openstack.org/openstack/keystone.git',
 
508
             branch: 'stable/icehouse'}
 
509
          - {name: requirements,
 
510
             repository: 'git://git.openstack.org/openstack/requirements.git',
 
511
             branch: 'stable/icehouse'}
 
512
        directory: /mnt/openstack-git
 
513
        http_proxy: http://squid.internal:3128
 
514
        https_proxy: https://squid.internal:3128
 
515
 
 
516
        The directory, http_proxy, and https_proxy keys are optional.
 
517
    """
 
518
    global requirements_dir
 
519
    parent_dir = '/mnt/openstack-git'
 
520
 
 
521
    if not projects_yaml:
 
522
        return
 
523
 
 
524
    projects = yaml.load(projects_yaml)
 
525
    _git_validate_projects_yaml(projects, core_project)
 
526
 
 
527
    old_environ = dict(os.environ)
 
528
 
 
529
    if 'http_proxy' in projects.keys():
 
530
        os.environ['http_proxy'] = projects['http_proxy']
 
531
    if 'https_proxy' in projects.keys():
 
532
        os.environ['https_proxy'] = projects['https_proxy']
 
533
 
 
534
    if 'directory' in projects.keys():
 
535
        parent_dir = projects['directory']
 
536
 
 
537
    for p in projects['repositories']:
 
538
        repo = p['repository']
 
539
        branch = p['branch']
 
540
        if p['name'] == 'requirements':
 
541
            repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
 
542
                                                     update_requirements=False)
 
543
            requirements_dir = repo_dir
 
544
        else:
 
545
            repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
 
546
                                                     update_requirements=True)
 
547
 
 
548
    os.environ = old_environ
 
549
 
 
550
 
 
551
def _git_validate_projects_yaml(projects, core_project):
 
552
    """
 
553
    Validate the projects yaml.
 
554
    """
 
555
    _git_ensure_key_exists('repositories', projects)
 
556
 
 
557
    for project in projects['repositories']:
 
558
        _git_ensure_key_exists('name', project.keys())
 
559
        _git_ensure_key_exists('repository', project.keys())
 
560
        _git_ensure_key_exists('branch', project.keys())
 
561
 
 
562
    if projects['repositories'][0]['name'] != 'requirements':
 
563
        error_out('{} git repo must be specified first'.format('requirements'))
 
564
 
 
565
    if projects['repositories'][-1]['name'] != core_project:
 
566
        error_out('{} git repo must be specified last'.format(core_project))
 
567
 
 
568
 
 
569
def _git_ensure_key_exists(key, keys):
 
570
    """
 
571
    Ensure that key exists in keys.
 
572
    """
 
573
    if key not in keys:
 
574
        error_out('openstack-origin-git key \'{}\' is missing'.format(key))
 
575
 
 
576
 
 
577
def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
 
578
    """
 
579
    Clone and install a single git repository.
 
580
    """
 
581
    dest_dir = os.path.join(parent_dir, os.path.basename(repo))
 
582
 
 
583
    if not os.path.exists(parent_dir):
 
584
        juju_log('Directory already exists at {}. '
 
585
                 'No need to create directory.'.format(parent_dir))
 
586
        os.mkdir(parent_dir)
 
587
 
 
588
    if not os.path.exists(dest_dir):
 
589
        juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
 
590
        repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
 
591
    else:
 
592
        repo_dir = dest_dir
 
593
 
 
594
    if update_requirements:
 
595
        if not requirements_dir:
 
596
            error_out('requirements repo must be cloned before '
 
597
                      'updating from global requirements.')
 
598
        _git_update_requirements(repo_dir, requirements_dir)
 
599
 
 
600
    juju_log('Installing git repo from dir: {}'.format(repo_dir))
 
601
    pip_install(repo_dir)
 
602
 
 
603
    return repo_dir
 
604
 
 
605
 
 
606
def _git_update_requirements(package_dir, reqs_dir):
 
607
    """
 
608
    Update from global requirements.
 
609
 
 
610
    Update an OpenStack git directory's requirements.txt and
 
611
    test-requirements.txt from global-requirements.txt.
 
612
    """
 
613
    orig_dir = os.getcwd()
 
614
    os.chdir(reqs_dir)
 
615
    cmd = ['python', 'update.py', package_dir]
 
616
    try:
 
617
        subprocess.check_call(cmd)
 
618
    except subprocess.CalledProcessError:
 
619
        package = os.path.basename(package_dir)
 
620
        error_out("Error updating {} from global-requirements.txt".format(package))
 
621
    os.chdir(orig_dir)
 
622
 
 
623
 
 
624
def git_src_dir(projects_yaml, project):
 
625
    """
 
626
    Return the directory where the specified project's source is located.
 
627
    """
 
628
    parent_dir = '/mnt/openstack-git'
 
629
 
 
630
    if not projects_yaml:
 
631
        return
 
632
 
 
633
    projects = yaml.load(projects_yaml)
 
634
 
 
635
    if 'directory' in projects.keys():
 
636
        parent_dir = projects['directory']
 
637
 
 
638
    for p in projects['repositories']:
 
639
        if p['name'] == project:
 
640
            return os.path.join(parent_dir, os.path.basename(p['repository']))
 
641
 
 
642
    return None