~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to assess_container_networking.py

  • Committer: Aaron Bentley
  • Date: 2016-04-24 16:09:49 UTC
  • mto: This revision was merged to the branch mainline in revision 1372.
  • Revision ID: aaron.bentley@canonical.com-20160424160949-6x1jdnkkpkcd820m
Rename create_model to add_model.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
from copy import (
6
6
    copy,
7
7
    deepcopy,
8
 
    )
 
8
)
9
9
import logging
10
10
import re
11
11
import os
19
19
    BootstrapManager,
20
20
    get_random_string,
21
21
    update_env,
22
 
    )
23
 
from jujupy import (
24
 
    KVM_MACHINE,
25
 
    LXC_MACHINE,
26
 
    LXD_MACHINE,
27
 
    )
 
22
)
28
23
from utility import (
29
 
    JujuAssertionError,
30
24
    add_basic_testing_arguments,
31
25
    configure_logging,
32
26
    wait_for_port,
33
 
    )
34
 
 
 
27
)
 
28
 
 
29
 
 
30
KVM_MACHINE = 'kvm'
 
31
LXC_MACHINE = 'lxc'
 
32
LXD_MACHINE = 'lxd'
35
33
 
36
34
__metaclass__ = type
37
35
 
91
89
            # If the connection to the host failed, try again in a couple of
92
90
            # seconds. This is usually due to heavy load.
93
91
            if(attempt < attempts - 1 and
94
 
                re.search('ssh_exchange_identification: '
95
 
                          'Connection closed by remote host', e.stderr)):
 
92
               re.search('ssh_exchange_identification: '
 
93
                         'Connection closed by remote host', e.stderr)):
96
94
                time.sleep(back_off)
97
95
                back_off *= 2
98
96
            else:
117
115
        log.info("Could not clean existing env: %s", e)
118
116
        return False
119
117
 
120
 
    for service in status.get_applications():
121
 
        client.remove_service(service)
 
118
    for service in status.status.get('services', {}):
 
119
        client.juju('remove-service', service)
122
120
 
123
121
    if not services_only:
124
122
        # First remove all containers; we can't remove a machine that is
222
220
    :param targets: machine IDs of machines to test
223
221
    :return: None;
224
222
    """
 
223
    log.info('Waiting for the bootstrap machine agent to start.')
225
224
    status = client.wait_for_started().status
226
 
    log.info('Assessing network traffic.')
227
225
    source = targets[0]
228
226
    dests = targets[1:]
229
227
 
237
235
    address = status['machines'][host]['containers'][source]['dns-name']
238
236
 
239
237
    for dest in dests:
240
 
        log.info('Assessing network traffic for {}.'.format(dest))
241
238
        msg = get_random_string()
242
239
        ssh(client, source, 'rm nc_listen.out; bash ./listen.sh')
243
240
        ssh(client, dest,
245
242
        result = ssh(client, source, 'more nc_listen.out')
246
243
        if result.rstrip() != msg:
247
244
            raise ValueError("Wrong or missing message: %r" % result.rstrip())
248
 
        log.info('SUCCESS.')
249
245
 
250
246
 
251
247
def private_address(client, host):
252
248
    default_route = ssh(client, host, 'ip -4 -o route list 0/0')
253
 
    log.info("Default route from {}: {}".format(host, default_route))
254
 
    route_match = re.search(r'([\w-]+)\s*$', default_route)
255
 
    if route_match is None:
256
 
        raise JujuAssertionError(
257
 
            "Failed to find device in {}".format(default_route))
258
 
    device = route_match.group(1)
259
 
    log.info("Fetching the device IP of {}".format(device))
 
249
    device = re.search(r'(\w+)\s*$', default_route).group(1)
260
250
    device_ip = ssh(client, host, 'ip -4 -o addr show {}'.format(device))
261
 
    log.info("Device IP for {}: {}".format(host, device_ip))
262
 
    ip_match = re.search(r'inet\s+(\S+)/\d+\s', device_ip)
263
 
    if ip_match is None:
264
 
        raise JujuAssertionError(
265
 
            "Failed to find ip for device: {}".format(device))
266
 
    return ip_match.group(1)
 
251
    return re.search(r'inet\s+(\S+)/\d+\s', device_ip).group(1)
267
252
 
268
253
 
269
254
def assess_address_range(client, targets):
272
257
    :param targets: machine IDs of machines to test
273
258
    :return: None; raises ValueError on failure
274
259
    """
275
 
    log.info('Assessing address range.')
276
260
    status = client.wait_for_started().status
277
261
 
278
262
    host_subnet_cache = {}
279
263
 
280
264
    for target in targets:
281
 
        log.info('Assessing address range for {}.'.format(target))
282
265
        host = target.split('/')[0]
283
266
 
284
267
        if host in host_subnet_cache:
294
277
            raise ValueError(
295
278
                '{} ({}) not on the same subnet as {} ({})'.format(
296
279
                    target, subnet, host, host_subnet))
297
 
        log.info('SUCCESS.')
298
280
 
299
281
 
300
282
def assess_internet_connection(client, targets):
303
285
    :param targets: machine IDs of machines to test
304
286
    :return: None; raises ValueError on failure
305
287
    """
306
 
    log.info('Assessing internet connection.')
 
288
 
307
289
    for target in targets:
308
 
        log.info("Assessing internet connection for {}".format(target))
309
290
        routes = ssh(client, target, 'ip route show')
310
291
 
311
292
        d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE)
312
293
        if d:
313
294
            rc = client.juju('ssh', ('--proxy', target,
314
 
                                     'ping -c1 -q ' + d.group(1)), check=False)
 
295
                             'ping -c1 -q ' + d.group(1)), check=False)
315
296
            if rc != 0:
316
297
                raise ValueError('%s unable to ping default route' % target)
317
298
        else:
318
299
            raise ValueError("Default route not found")
319
 
        log.info("SUCCESS")
320
300
 
321
301
 
322
302
def _assessment_iteration(client, containers):
345
325
        test_containers = [
346
326
            containers[container_type][hosts[0]][0],
347
327
            containers[container_type][hosts[1]][0],
348
 
        ]
 
328
            ]
349
329
        _assessment_iteration(client, test_containers)
350
330
 
351
331
    if KVM_MACHINE in types and LXC_MACHINE in types:
352
332
        test_containers = [
353
333
            containers[LXC_MACHINE][hosts[0]][0],
354
334
            containers[KVM_MACHINE][hosts[0]][0],
355
 
        ]
 
335
            ]
356
336
        _assessment_iteration(client, test_containers)
357
337
 
358
338
        # Test with an LXC and a KVM on different machines
359
339
        test_containers = [
360
340
            containers[LXC_MACHINE][hosts[0]][0],
361
341
            containers[KVM_MACHINE][hosts[1]][0],
362
 
        ]
 
342
            ]
363
343
        _assessment_iteration(client, test_containers)
364
344
 
365
345
 
366
 
def get_uptime(client, host):
367
 
    uptime_pattern = re.compile(r'.*(\d+)')
368
 
    uptime_output = ssh(client, host, 'uptime -p')
369
 
    log.info('uptime -p: {}'.format(uptime_output))
370
 
    match = uptime_pattern.match(uptime_output)
371
 
    if match:
372
 
        return int(match.group(1))
373
 
    else:
374
 
        return 0
375
 
 
376
 
 
377
346
def assess_container_networking(client, types):
378
347
    """Runs _assess_address_allocation, reboots hosts, repeat.
379
 
 
380
348
    :param client: Juju client
381
349
    :param types: Container types to test
382
350
    :return: None
383
351
    """
384
 
    log.info("Setting up test.")
385
352
    hosts, containers = make_machines(client, types)
386
353
    status = client.wait_for_started().status
387
 
    log.info("Setup complete.")
388
 
    log.info("Test started.")
389
354
 
390
355
    _assess_container_networking(client, types, hosts, containers)
391
356
 
392
 
    # Reboot all hosted modelled machines then the controller.
393
 
    log.info("Instrumenting reboot of all machines.")
394
 
    try:
395
 
        for host in hosts:
396
 
            log.info("Restarting hosted machine: {}".format(host))
397
 
            client.juju(
398
 
                'run', ('--machine', host, 'sudo shutdown -r now'))
399
 
        client.juju('show-action-status', ('--name', 'juju-run'))
400
 
 
401
 
        log.info("Restarting controller machine 0")
402
 
        controller_client = client.get_controller_client()
403
 
        controller_status = controller_client.get_status()
404
 
        controller_host = controller_status.status['machines']['0']['dns-name']
405
 
        first_uptime = get_uptime(controller_client, '0')
406
 
        ssh(controller_client, '0', 'sudo shutdown -r now')
407
 
    except subprocess.CalledProcessError as e:
408
 
        logging.info(
409
 
            "Error running shutdown:\nstdout: %s\nstderr: %s",
410
 
            e.output, getattr(e, 'stderr', None))
411
 
        raise
412
 
 
413
 
    # Wait for the controller to shut down if it has not yet restarted.
414
 
    # This ensure the call to wait_for_started happens after each host
415
 
    # has restarted.
416
 
    second_uptime = get_uptime(controller_client, '0')
417
 
    if second_uptime > first_uptime:
418
 
        wait_for_port(controller_host, 22, closed=True, timeout=300)
 
357
    # Reboot all hosts apart from machine 0 because we use machine 0 to jump
 
358
    # through for some hosts.
 
359
    for host in hosts[1:]:
 
360
        ssh(client, host, 'sudo reboot')
 
361
 
 
362
    # Finally reboot machine 0
 
363
    ssh(client, hosts[0], 'sudo reboot')
 
364
 
 
365
    # Wait for the state server to shut down. This prevents us from calling
 
366
    # wait_for_started before machine 0 has shut down, which can cause us
 
367
    # to think that we have finished rebooting before we actually have.
 
368
    hostname = status['machines'][hosts[0]]['dns-name']
 
369
    wait_for_port(hostname, 22, closed=True)
 
370
 
419
371
    client.wait_for_started()
420
372
 
421
373
    # Once Juju is up it can take a little while before ssh responds.
422
374
    for host in hosts:
423
375
        hostname = status['machines'][host]['dns-name']
424
376
        wait_for_port(hostname, 22, timeout=240)
425
 
    log.info("Reboot complete and all hosts ready for retest.")
426
377
 
427
378
    _assess_container_networking(client, types, hosts, containers)
428
 
    log.info("PASS")
429
379
 
430
380
 
431
381
class _CleanedContext:
458
408
            ctx.return_code = 1
459
409
 
460
410
 
461
 
def _get_container_types(client, machine_type):
 
411
def _get_container_types(version, machine_type):
462
412
    """
463
413
    Give list of container types to run testing against.
464
414
 
466
416
    of containers. Otherwise, test all possible containers for the given
467
417
    juju version.
468
418
    """
 
419
    use_lxd = version > "2.0"
469
420
    if machine_type:
470
 
        if machine_type not in client.supported_container_types:
471
 
            raise Exception(
472
 
                "no {} support on juju {}".format(machine_type,
473
 
                                                  client.version))
 
421
        if not use_lxd and machine_type == LXD_MACHINE:
 
422
            raise Exception("no lxd support on juju {}".format(version))
474
423
        return [machine_type]
475
424
    # TODO(gz): Only include LXC for 1.X clients
476
 
    types = list(client.supported_container_types)
477
 
    types.sort()
 
425
    types = [KVM_MACHINE, LXC_MACHINE]
 
426
    if use_lxd:
 
427
        types.append(LXD_MACHINE)
478
428
    return types
479
429
 
480
430
 
483
433
    configure_logging(args.verbose)
484
434
    bs_manager = BootstrapManager.from_args(args)
485
435
    client = bs_manager.client
486
 
    machine_types = _get_container_types(client, args.machine_type)
 
436
    client.enable_feature('address-allocation')
 
437
    machine_types = _get_container_types(client.version, args.machine_type)
487
438
    with cleaned_bootstrap_context(bs_manager, args) as ctx:
488
439
        assess_container_networking(bs_manager.client, machine_types)
489
440
    return ctx.return_code