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

« back to all changes in this revision

Viewing changes to assess_container_networking.py

  • Committer: Curtis Hovey
  • Date: 2016-09-20 01:59:47 UTC
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: curtis@canonical.com-20160920015947-ko27xkj3a4i774h6
Convert juju instance=ids to true azuzre ids.

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
 
)
 
22
    )
 
23
from jujupy import (
 
24
    KVM_MACHINE,
 
25
    LXC_MACHINE,
 
26
    LXD_MACHINE,
 
27
    )
23
28
from utility import (
 
29
    JujuAssertionError,
24
30
    add_basic_testing_arguments,
25
31
    configure_logging,
26
32
    wait_for_port,
27
 
)
28
 
 
29
 
 
30
 
KVM_MACHINE = 'kvm'
31
 
LXC_MACHINE = 'lxc'
32
 
LXD_MACHINE = 'lxd'
 
33
    )
 
34
 
33
35
 
34
36
__metaclass__ = type
35
37
 
84
86
    attempts = 4
85
87
    for attempt in range(attempts):
86
88
        try:
87
 
            return client.get_juju_output('ssh', machine, cmd)
 
89
            return client.get_juju_output('ssh', '--proxy', machine, cmd)
88
90
        except subprocess.CalledProcessError as e:
89
91
            # If the connection to the host failed, try again in a couple of
90
92
            # seconds. This is usually due to heavy load.
91
 
            if(attempt < attempts-1 and
92
 
               re.search('ssh_exchange_identification: '
93
 
                         'Connection closed by remote host', e.stderr)):
 
93
            if(attempt < attempts - 1 and
 
94
                re.search('ssh_exchange_identification: '
 
95
                          'Connection closed by remote host', e.stderr)):
94
96
                time.sleep(back_off)
95
97
                back_off *= 2
96
98
            else:
115
117
        log.info("Could not clean existing env: %s", e)
116
118
        return False
117
119
 
118
 
    for service in status.status.get('services', {}):
119
 
        client.juju('remove-service', service)
 
120
    for service in status.get_applications():
 
121
        client.remove_service(service)
120
122
 
121
123
    if not services_only:
122
124
        # First remove all containers; we can't remove a machine that is
220
222
    :param targets: machine IDs of machines to test
221
223
    :return: None;
222
224
    """
223
 
    log.info('Waiting for the bootstrap machine agent to start.')
224
225
    status = client.wait_for_started().status
 
226
    log.info('Assessing network traffic.')
225
227
    source = targets[0]
226
228
    dests = targets[1:]
227
229
 
228
230
    with tempfile.NamedTemporaryFile(delete=False) as f:
229
231
        f.write('tmux new-session -d -s test "nc -l 6778 > nc_listen.out"')
230
 
    client.juju('scp', (f.name, source + ':/home/ubuntu/listen.sh'))
 
232
    client.juju('scp', ('--proxy', f.name, source + ':/home/ubuntu/listen.sh'))
231
233
    os.remove(f.name)
232
234
 
233
235
    # Containers are named 'x/type/y' where x is the host of the container. We
235
237
    address = status['machines'][host]['containers'][source]['dns-name']
236
238
 
237
239
    for dest in dests:
 
240
        log.info('Assessing network traffic for {}.'.format(dest))
238
241
        msg = get_random_string()
239
242
        ssh(client, source, 'rm nc_listen.out; bash ./listen.sh')
240
243
        ssh(client, dest,
242
245
        result = ssh(client, source, 'more nc_listen.out')
243
246
        if result.rstrip() != msg:
244
247
            raise ValueError("Wrong or missing message: %r" % result.rstrip())
 
248
        log.info('SUCCESS.')
245
249
 
246
250
 
247
251
def private_address(client, host):
248
252
    default_route = ssh(client, host, 'ip -4 -o route list 0/0')
249
 
    device = re.search(r'(\w+)\s*$', default_route).group(1)
 
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))
250
260
    device_ip = ssh(client, host, 'ip -4 -o addr show {}'.format(device))
251
 
    return re.search(r'inet\s+(\S+)/\d+\s', device_ip).group(1)
 
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)
252
267
 
253
268
 
254
269
def assess_address_range(client, targets):
257
272
    :param targets: machine IDs of machines to test
258
273
    :return: None; raises ValueError on failure
259
274
    """
 
275
    log.info('Assessing address range.')
260
276
    status = client.wait_for_started().status
261
277
 
262
278
    host_subnet_cache = {}
263
279
 
264
280
    for target in targets:
 
281
        log.info('Assessing address range for {}.'.format(target))
265
282
        host = target.split('/')[0]
266
283
 
267
284
        if host in host_subnet_cache:
277
294
            raise ValueError(
278
295
                '{} ({}) not on the same subnet as {} ({})'.format(
279
296
                    target, subnet, host, host_subnet))
 
297
        log.info('SUCCESS.')
280
298
 
281
299
 
282
300
def assess_internet_connection(client, targets):
285
303
    :param targets: machine IDs of machines to test
286
304
    :return: None; raises ValueError on failure
287
305
    """
288
 
 
 
306
    log.info('Assessing internet connection.')
289
307
    for target in targets:
 
308
        log.info("Assessing internet connection for {}".format(target))
290
309
        routes = ssh(client, target, 'ip route show')
291
310
 
292
311
        d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE)
293
312
        if d:
294
 
            rc = client.juju('ssh', (target, 'ping -c1 -q ' + d.group(1)),
295
 
                             check=False)
 
313
            rc = client.juju('ssh', ('--proxy', target,
 
314
                                     'ping -c1 -q ' + d.group(1)), check=False)
296
315
            if rc != 0:
297
316
                raise ValueError('%s unable to ping default route' % target)
298
317
        else:
299
318
            raise ValueError("Default route not found")
 
319
        log.info("SUCCESS")
300
320
 
301
321
 
302
322
def _assessment_iteration(client, containers):
325
345
        test_containers = [
326
346
            containers[container_type][hosts[0]][0],
327
347
            containers[container_type][hosts[1]][0],
328
 
            ]
 
348
        ]
329
349
        _assessment_iteration(client, test_containers)
330
350
 
331
351
    if KVM_MACHINE in types and LXC_MACHINE in types:
332
352
        test_containers = [
333
353
            containers[LXC_MACHINE][hosts[0]][0],
334
354
            containers[KVM_MACHINE][hosts[0]][0],
335
 
            ]
 
355
        ]
336
356
        _assessment_iteration(client, test_containers)
337
357
 
338
358
        # Test with an LXC and a KVM on different machines
339
359
        test_containers = [
340
360
            containers[LXC_MACHINE][hosts[0]][0],
341
361
            containers[KVM_MACHINE][hosts[1]][0],
342
 
            ]
 
362
        ]
343
363
        _assessment_iteration(client, test_containers)
344
364
 
345
365
 
 
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
 
346
377
def assess_container_networking(client, types):
347
378
    """Runs _assess_address_allocation, reboots hosts, repeat.
 
379
 
348
380
    :param client: Juju client
349
381
    :param types: Container types to test
350
382
    :return: None
351
383
    """
 
384
    log.info("Setting up test.")
352
385
    hosts, containers = make_machines(client, types)
353
386
    status = client.wait_for_started().status
 
387
    log.info("Setup complete.")
 
388
    log.info("Test started.")
354
389
 
355
390
    _assess_container_networking(client, types, hosts, containers)
356
391
 
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
 
 
 
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)
371
419
    client.wait_for_started()
372
420
 
373
421
    # Once Juju is up it can take a little while before ssh responds.
374
422
    for host in hosts:
375
423
        hostname = status['machines'][host]['dns-name']
376
424
        wait_for_port(hostname, 22, timeout=240)
 
425
    log.info("Reboot complete and all hosts ready for retest.")
377
426
 
378
427
    _assess_container_networking(client, types, hosts, containers)
 
428
    log.info("PASS")
379
429
 
380
430
 
381
431
class _CleanedContext:
408
458
            ctx.return_code = 1
409
459
 
410
460
 
411
 
def _get_container_types(version, machine_type):
 
461
def _get_container_types(client, machine_type):
412
462
    """
413
463
    Give list of container types to run testing against.
414
464
 
416
466
    of containers. Otherwise, test all possible containers for the given
417
467
    juju version.
418
468
    """
419
 
    use_lxd = version > "2.0"
420
469
    if machine_type:
421
 
        if not use_lxd and machine_type == LXD_MACHINE:
422
 
            raise Exception("no lxd support on juju {}".format(version))
 
470
        if machine_type not in client.supported_container_types:
 
471
            raise Exception(
 
472
                "no {} support on juju {}".format(machine_type,
 
473
                                                  client.version))
423
474
        return [machine_type]
424
475
    # TODO(gz): Only include LXC for 1.X clients
425
 
    types = [KVM_MACHINE, LXC_MACHINE]
426
 
    if use_lxd:
427
 
        types.append(LXD_MACHINE)
 
476
    types = list(client.supported_container_types)
 
477
    types.sort()
428
478
    return types
429
479
 
430
480
 
433
483
    configure_logging(args.verbose)
434
484
    bs_manager = BootstrapManager.from_args(args)
435
485
    client = bs_manager.client
436
 
    client.enable_feature('address-allocation')
437
 
    machine_types = _get_container_types(client.version, args.machine_type)
 
486
    machine_types = _get_container_types(client, args.machine_type)
438
487
    with cleaned_bootstrap_context(bs_manager, args) as ctx:
439
488
        assess_container_networking(bs_manager.client, machine_types)
440
489
    return ctx.return_code