~chris.macnaughton/openstack-mojo-specs/bzr-pike

« back to all changes in this revision

Viewing changes to helper/utils/mojo_utils.py

  • Committer: David Ames
  • Date: 2017-03-06 15:47:38 UTC
  • mfrom: (300.2.7 mojo-openstack-specs)
  • Revision ID: david.ames@canonical.com-20170306154738-k3yxjja7j7gluajy
[thedac, r=tinwood] Kiki juju 1.x and juju 2.x translator

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
import utils.juju_wait as juju_wait
10
10
from collections import Counter
11
11
 
 
12
import kiki
 
13
 
 
14
 
12
15
JUJU_STATUSES = {
13
16
    'good': ['ACTIVE', 'started'],
14
17
    'bad': ['error'],
23
26
}
24
27
 
25
28
 
 
29
class ConfigFileNotFound(Exception):
 
30
    pass
 
31
 
 
32
 
26
33
def get_juju_status(service=None, unit=None):
27
 
    cmd = ['juju', 'status', '--format=yaml']
 
34
    cmd = [kiki.cmd(), 'status', '--format=yaml']
28
35
    if service:
29
36
        cmd.append(service)
30
37
    if unit:
46
53
    if service:
47
54
        services = [service]
48
55
    else:
49
 
        services = [svc for svc in juju_status['services']]
 
56
        services = [svc for svc in juju_status[kiki.applications()]]
50
57
    for svc in services:
51
 
        if 'units' in juju_status['services'][svc]:
52
 
            for unit in juju_status['services'][svc]['units']:
 
58
        if 'units' in juju_status[kiki.applications()][svc]:
 
59
            for unit in juju_status[kiki.applications()][svc]['units']:
53
60
                units.append(unit)
54
61
    return units
55
62
 
58
65
    if not juju_status:
59
66
        juju_status = get_juju_status()
60
67
    p_services = []
61
 
    for svc in juju_status['services']:
62
 
        if 'subordinate-to' not in juju_status['services'][svc]:
 
68
    for svc in juju_status[kiki.applications()]:
 
69
        if 'subordinate-to' not in juju_status[kiki.applications()][svc]:
63
70
            p_services.append(svc)
64
71
    return p_services
65
72
 
72
79
def convert_unit_to_machinename(unit):
73
80
    juju_status = get_juju_status(unit)
74
81
    service = unit.split('/')[0]
75
 
    return int(juju_status['services'][service]['units'][unit]['machine'])
 
82
    return int(
 
83
        juju_status[kiki.applications()][service]['units'][unit]['machine'])
76
84
 
77
85
 
78
86
def convert_machineno_to_unit(machineno, juju_status=None):
79
87
    if not juju_status:
80
88
        juju_status = get_juju_status()
81
 
    services = [service for service in juju_status['services']]
 
89
    services = [service for service in juju_status[kiki.applications()]]
82
90
    for svc in services:
83
 
        if 'units' in juju_status['services'][svc]:
84
 
            for unit in juju_status['services'][svc]['units']:
85
 
                unit_info = juju_status['services'][svc]['units'][unit]
 
91
        if 'units' in juju_status[kiki.applications()][svc]:
 
92
            for unit in juju_status[kiki.applications()][svc]['units']:
 
93
                unit_info = juju_status[
 
94
                    kiki.applications()][svc]['units'][unit]
86
95
                if unit_info['machine'] == machineno:
87
96
                    return unit
88
97
 
89
98
 
90
99
def remote_shell_check(unit, timeout=None):
91
 
    cmd = ['juju', 'run']
 
100
    cmd = [kiki.cmd(), 'run']
92
101
    if timeout:
93
102
        cmd.extend(['--timeout', str(timeout)])
94
103
    cmd.extend(['--unit', unit, 'uname -a'])
99
108
def remote_run(unit, remote_cmd=None, timeout=None, fatal=None):
100
109
    if fatal is None:
101
110
        fatal = True
102
 
    cmd = ['juju', 'run', '--unit', unit]
 
111
    cmd = [kiki.cmd(), 'run', '--unit', unit]
103
112
    if timeout:
104
113
        cmd.extend(['--timeout', str(timeout)])
105
114
    if remote_cmd:
118
127
        dst = unit + ':' + remote_dir
119
128
    else:
120
129
        dst = unit + ':/tmp/'
121
 
    cmd = ['juju', 'scp', script, dst]
 
130
    cmd = [kiki.cmd(), 'scp', script, dst]
122
131
    return subprocess.check_call(cmd)
123
132
 
124
133
 
126
135
    service = unit.split('/')[0]
127
136
    unit_count = len(get_juju_units(service=service))
128
137
    logging.info('Removing unit ' + unit)
129
 
    cmd = ['juju', 'destroy-unit', unit]
 
138
    cmd = [kiki.cmd(), kiki.remove_unit(), unit]
130
139
    subprocess.check_call(cmd)
131
140
    target_num = unit_count - 1
132
141
    # Wait for the unit to disappear from juju status
185
194
 
186
195
def is_crm_clustered(service):
187
196
    juju_status = get_juju_status(service)
188
 
    return 'ha' in juju_status['services'][service]['relations']
 
197
    return 'ha' in juju_status[kiki.applications()][service]['relations']
189
198
 
190
199
 
191
200
def unit_sorted(units):
201
210
    else:
202
211
        additional_units = 1
203
212
    logging.info('Adding %i unit(s) to %s' % (additional_units, service))
204
 
    cmd = ['juju', 'add-unit', service, '-n', str(additional_units)]
 
213
    cmd = [kiki.cmd(), 'add-unit', service, '-n', str(additional_units)]
205
214
    subprocess.check_call(cmd)
206
215
    target_num = unit_count + additional_units
207
216
    # Wait for the new unit to appear in juju status
214
223
    if wait is None:
215
224
        wait = True
216
225
    logging.info('Setting %s to %s' % (service, option))
217
 
    subprocess.check_call(['juju', 'set', service, option])
 
226
    subprocess.check_call([kiki.cmd(), kiki.set_config(),
 
227
                           service, option])
218
228
    if wait:
219
229
        juju_wait_finished()
220
230
 
221
231
 
222
232
def juju_get_config_keys(service):
223
 
    cmd = ['juju', 'get', service]
 
233
    cmd = [kiki.cmd(), kiki.get_config(), service]
224
234
    juju_get_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
225
235
    service_config = yaml.load(juju_get_output)
226
236
    return service_config['settings'].keys()
227
237
 
228
238
 
229
239
def juju_get(service, option):
230
 
    cmd = ['juju', 'get', service]
 
240
    cmd = [kiki.cmd(), kiki.get_config(), service]
231
241
    juju_get_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
232
242
    service_config = yaml.load(juju_get_output)
233
243
 
240
250
 
241
251
 
242
252
def get_juju_environments_yaml():
 
253
    """ Get the environments.yaml data from a Juju 1 environment
 
254
 
 
255
    @returns Dictionary of the data from the environments.yaml file
 
256
    """
243
257
    juju_env_file = open(os.environ['HOME'] + "/.juju/environments.yaml", 'r')
244
258
    return yaml.load(juju_env_file)
245
259
 
246
260
 
 
261
def get_cloud_from_controller():
 
262
    """ Get the cloud name from the Juju 2.x controller
 
263
 
 
264
    @returns String name of the cloud for the current Juju 2.x controller
 
265
    """
 
266
    cmd = [kiki.cmd(), 'show-controller', '--format=yaml']
 
267
    cloud_config = yaml.load(subprocess.check_output(cmd))
 
268
    # There will only be one top level controller from show-controller,
 
269
    # but we do not know its name.
 
270
    assert len(cloud_config) == 1
 
271
    try:
 
272
        return cloud_config.values()[0]['details']['cloud']
 
273
    except KeyError:
 
274
        raise KeyError("Failed to get cloud information from the controller")
 
275
 
 
276
 
247
277
def get_provider_type():
248
 
    juju_env = subprocess.check_output(['juju', 'switch']).strip('\n')
249
 
    juju_env_contents = get_juju_environments_yaml()
250
 
    return juju_env_contents['environments'][juju_env]['type']
 
278
    """ Get the type of the undercloud
 
279
 
 
280
    @returns String name of the undercloud type
 
281
    """
 
282
    juju_env = subprocess.check_output([kiki.cmd(), 'switch']).strip('\n')
 
283
    if kiki.version() < 2:
 
284
        juju_env_contents = get_juju_environments_yaml()
 
285
        return juju_env_contents['environments'][juju_env]['type']
 
286
    else:
 
287
        cloud = get_cloud_from_controller()
 
288
        if cloud:
 
289
            # If the controller was deployed from this system with
 
290
            # the cloud configured in ~/.local/share/juju/clouds.yaml
 
291
            # Determine the cloud type directly
 
292
            cmd = [kiki.cmd(), 'show-cloud', cloud, '--format=yaml']
 
293
            return yaml.load(subprocess.check_output(cmd))['type']
 
294
        else:
 
295
            # If the controller was deployed elsewhere
 
296
            # show-controllers unhelpfully returns an empty string for cloud
 
297
            # For now assume openstack
 
298
            return 'openstack'
 
299
 
 
300
 
 
301
class MissingOSAthenticationException(Exception):
 
302
    pass
251
303
 
252
304
 
253
305
def get_undercloud_auth():
254
 
    juju_env = subprocess.check_output(['juju', 'switch']).strip('\n')
255
 
    juju_env_contents = get_juju_environments_yaml()
256
 
    novarc_settings = juju_env_contents['environments'][juju_env]
257
 
    auth_settings = {
258
 
        'OS_AUTH_URL': novarc_settings['auth-url'],
259
 
        'OS_TENANT_NAME': novarc_settings['tenant-name'],
260
 
        'OS_USERNAME': novarc_settings['username'],
261
 
        'OS_PASSWORD': novarc_settings['password'],
262
 
        'OS_REGION_NAME': novarc_settings['region'],
263
 
    }
 
306
    """ Get the undercloud OpenStack authentication settings from the
 
307
    environment.
 
308
 
 
309
    @raises MissingOSAthenticationException if one or more settings are
 
310
            missing.
 
311
    @returns Dictionary of authentication settings
 
312
    """
 
313
 
 
314
    os_auth_url = os.environ.get('OS_AUTH_URL')
 
315
    if os_auth_url:
 
316
        api_version = os_auth_url.split('/')[-1].translate(None, 'v')
 
317
    else:
 
318
        logging.error('Missing OS authentication setting: OS_AUTH_URL')
 
319
        raise MissingOSAthenticationException(
 
320
            'One or more OpenStack authetication variables could '
 
321
            'be found in the environment. Please export the OS_* '
 
322
            'settings into the environment.')
 
323
 
 
324
    logging.info('AUTH_URL: {}, api_ver: {}'.format(os_auth_url, api_version))
 
325
 
 
326
    if api_version == '2.0':
 
327
        # V2
 
328
        logging.info('Using keystone API V2 for undercloud auth')
 
329
        auth_settings = {
 
330
            'OS_AUTH_URL': os.environ.get('OS_AUTH_URL'),
 
331
            'OS_TENANT_NAME': os.environ.get('OS_TENANT_NAME'),
 
332
            'OS_USERNAME': os.environ.get('OS_USERNAME'),
 
333
            'OS_PASSWORD':  os.environ.get('OS_PASSWORD'),
 
334
            'OS_REGION_NAME': os.environ.get('OS_REGION_NAME'),
 
335
            'API_VERSION': 2,
 
336
        }
 
337
    elif api_version >= '3':
 
338
        # V3 or later
 
339
        logging.info('Using keystone API V3 (or later) for undercloud auth')
 
340
        domain = os.environ.get('OS_DOMAIN_NAME')
 
341
        auth_settings = {
 
342
            'OS_AUTH_URL': os.environ.get('OS_AUTH_URL'),
 
343
            'OS_USERNAME': os.environ.get('OS_USERNAME'),
 
344
            'OS_PASSWORD': os.environ.get('OS_PASSWORD'),
 
345
            'OS_REGION_NAME': os.environ.get('OS_REGION_NAME'),
 
346
            'API_VERSION': 3,
 
347
        }
 
348
        if domain:
 
349
            auth_settings['OS_DOMAIN_NAME': 'admin_domain'] = domain
 
350
        else:
 
351
            auth_settings['OS_USER_DOMAIN_NAME'] = (
 
352
                os.environ.get('OS_USER_DOMAIN_NAME'))
 
353
            auth_settings['OS_PROJECT_NAME'] = (
 
354
                os.environ.get('OS_PROJECT_NAME'))
 
355
            auth_settings['OS_PROJECT_DOMAIN_NAME'] = (
 
356
                os.environ.get('OS_PROJECT_DOMAIN_NAME'))
 
357
 
 
358
    # Validate settings
 
359
    for key, settings in auth_settings.items():
 
360
        if settings is None:
 
361
            logging.error('Missing OS authentication setting: {}'
 
362
                          ''.format(key))
 
363
            raise MissingOSAthenticationException(
 
364
                'One or more OpenStack authetication variables could '
 
365
                'be found in the environment. Please export the OS_* '
 
366
                'settings into the environment.')
 
367
 
264
368
    return auth_settings
265
369
 
266
370
 
267
 
def get_undercloud_netid():
268
 
    juju_env = subprocess.check_output(['juju', 'switch']).strip('\n')
269
 
    juju_env_contents = get_juju_environments_yaml()
270
 
    if 'network' in juju_env_contents['environments'][juju_env]:
271
 
        return juju_env_contents['environments'][juju_env]['network']
272
 
 
273
 
 
274
371
# Openstack Client helpers
275
372
def get_auth_url(juju_status=None):
276
373
    if juju_get('keystone', 'vip'):
277
374
        return juju_get('keystone', 'vip')
278
375
    if not juju_status:
279
376
        juju_status = get_juju_status()
280
 
    unit = juju_status['services']['keystone']['units'].itervalues().next()
 
377
    unit = (juju_status[kiki.applications()]['keystone']['units']
 
378
            .itervalues()
 
379
            .next())
281
380
    return unit['public-address']
282
381
 
283
382
 
321
420
 
322
421
 
323
422
def get_mojo_file(filename):
 
423
    """Search for a stage specific version,
 
424
    then the current working directory,
 
425
    then in the directory where the script was called,
 
426
    then in the directory above where the script was called.
 
427
 
 
428
    @returns string path to configuration file
 
429
    @raises ConfigFileNotFound if no file can be located
 
430
    """
 
431
    files = []
324
432
    if 'MOJO_SPEC_DIR' in os.environ and 'MOJO_STAGE' in os.environ:
325
 
        mfile = '{}/{}/{}'.format(os.environ['MOJO_SPEC_DIR'],
326
 
                                  os.environ['MOJO_STAGE'], filename)
327
 
    else:
328
 
        if os.path.isfile(filename):
329
 
            mfile = filename
330
 
    return mfile
 
433
        # Spec location
 
434
        files.append('{}/{}/{}'.format(os.environ['MOJO_SPEC_DIR'],
 
435
                                       os.environ['MOJO_STAGE'], filename))
 
436
 
 
437
    # CWD
 
438
    files.append(filename)
 
439
    # Called file directory
 
440
    files.append(os.path.join(os.path.dirname(__file__), filename))
 
441
    # Up one directory from called file
 
442
    files.append(os.path.join(
 
443
                     os.path.dirname(os.path.dirname(__file__)),
 
444
                     filename))
 
445
 
 
446
    for file_path in files:
 
447
        if os.path.isfile(file_path):
 
448
            return file_path
331
449
 
332
450
 
333
451
def get_mojo_config(filename):
387
505
def upgrade_service(svc, switch=None):
388
506
    repo_dir = os.environ['MOJO_REPO_DIR']
389
507
    logging.info('Upgrading ' + svc)
390
 
    cmd = ['juju', 'upgrade-charm']
 
508
    cmd = [kiki.cmd(), 'upgrade-charm']
391
509
    if switch and switch.get(svc):
392
510
        cmd.extend(['--switch', switch[svc]])
393
511
    cmd.extend(['--repository', repo_dir, svc])
401
519
    base_charms = ['mysql', 'percona-cluster', 'rabbitmq-server',
402
520
                   'keystone']
403
521
    for svc in base_charms:
404
 
        if svc in juju_status['services']:
 
522
        if svc in juju_status[kiki.applications()]:
405
523
            upgrade_service(svc, switch=switch)
406
524
            time.sleep(30)
407
525
    time.sleep(60)
408
526
    # Upgrade the rest
409
 
    for svc in juju_status['services']:
 
527
    for svc in juju_status[kiki.applications()]:
410
528
        if svc not in base_charms:
411
529
            upgrade_service(svc, switch=switch)
412
530
            time.sleep(30)
443
561
 
444
562
def get_service_agent_states(juju_status):
445
563
    service_state = Counter()
446
 
    for service in juju_status['services']:
447
 
        if 'units' in juju_status['services'][service]:
448
 
            for unit in juju_status['services'][service]['units']:
449
 
                unit_info = juju_status['services'][service]['units'][unit]
 
564
 
 
565
    for service in juju_status[kiki.applications()]:
 
566
        if 'units' in juju_status[kiki.applications()][service]:
 
567
            for unit in juju_status[kiki.applications()][service]['units']:
 
568
                unit_info = juju_status[
 
569
                    kiki.applications()][service]['units'][unit]
450
570
                service_state[unit_info['agent-state']] += 1
451
571
                if 'subordinates' in unit_info:
452
572
                    for sub_unit in unit_info['subordinates']:
652
772
 
653
773
 
654
774
def action_get_output(action_id):
655
 
    cmd = ['juju', 'action', 'fetch', '--format=yaml', action_id]
 
775
    cmd = kiki.show_action_output_cmd() + ['--format=yaml', action_id]
656
776
    return yaml.load(subprocess.check_output(cmd))
657
777
 
658
778
 
672
792
 
673
793
 
674
794
def action_run(unit, action_name, action_args=None, timeout=600):
675
 
    cmd = ['juju', 'action', 'do', '--format=yaml', unit, action_name]
 
795
    cmd = kiki.run_action_cmd() + ['--format=yaml', unit, action_name]
676
796
    if action_args:
677
797
        cmd.extend(action_args)
678
798
    action_out = yaml.load(subprocess.check_output(cmd))