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

« back to all changes in this revision

Viewing changes to substrate.py

  • Committer: Curtis Hovey
  • Date: 2016-05-10 17:56:24 UTC
  • mto: This revision was merged to the branch mainline in revision 1404.
  • Revision ID: curtis@canonical.com-20160510175624-2hz1dg8a1zwedgda
Use tests.TestCase, and allow tests to define a sane environ.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
    translate_to_env,
18
18
)
19
19
from utility import (
20
 
    print_now,
21
20
    temp_dir,
22
21
    until_timeout,
23
22
)
26
25
__metaclass__ = type
27
26
 
28
27
 
 
28
log = logging.getLogger("substrate")
 
29
 
 
30
 
29
31
LIBVIRT_DOMAIN_RUNNING = 'running'
30
32
LIBVIRT_DOMAIN_SHUT_OFF = 'shut off'
31
33
 
41
43
 
42
44
def terminate_instances(env, instance_ids):
43
45
    if len(instance_ids) == 0:
44
 
        print_now("No instances to delete.")
 
46
        log.info("No instances to delete.")
45
47
        return
46
48
    provider_type = env.config.get('type')
47
49
    environ = dict(os.environ)
52
54
        environ.update(translate_to_env(env.config))
53
55
        command_args = ['nova', 'delete'] + instance_ids
54
56
    elif provider_type == 'maas':
55
 
        with MAASAccount.manager_from_config(env.config) as substrate:
 
57
        with maas_account_from_config(env.config) as substrate:
56
58
            substrate.terminate_instances(instance_ids)
57
59
        return
58
60
    elif provider_type == 'lxd':
66
68
                    "This test does not support the %s provider"
67
69
                    % provider_type)
68
70
            return substrate.terminate_instances(instance_ids)
69
 
    print_now("Deleting %s." % ', '.join(instance_ids))
 
71
    log.info("Deleting %s." % ', '.join(instance_ids))
70
72
    subprocess.check_call(command_args, env=environ)
71
73
 
72
74
 
107
109
            the specified instances.
108
110
        :return: an iterator of (group-id, group-name) tuples.
109
111
        """
110
 
        logging.info('Listing security groups in use.')
 
112
        log.info('Listing security groups in use.')
111
113
        reservations = self.client.get_all_instances(instance_ids=instance_ids)
112
114
        for reservation in reservations:
113
115
            for instance in reservation.instances:
146
148
                                'InvalidNetworkInterface.InUse',
147
149
                                'InvalidNetworkInterfaceID.NotFound'):
148
150
                            raise
149
 
                        logging.info(
 
151
                        log.info(
150
152
                            'Failed to delete interface {!r}. {}'.format(
151
153
                                interface.id, e.message))
152
154
                        unclean.update(g.id for g in interface.groups)
252
254
            raise StillProvisioning(provisioning)
253
255
 
254
256
    def _terminate_instance(self, machine_id):
255
 
        logging.info('Stopping instance {}'.format(machine_id))
 
257
        log.info('Stopping instance {}'.format(machine_id))
256
258
        self.client.stop_machine(machine_id)
257
259
        for ignored in until_timeout(30):
258
260
            stopping_machine = self.client._list_machines(machine_id)
261
263
            sleep(3)
262
264
        else:
263
265
            raise Exception('Instance did not stop: {}'.format(machine_id))
264
 
        logging.info('Terminating instance {}'.format(machine_id))
 
266
        log.info('Terminating instance {}'.format(machine_id))
265
267
        self.client.delete_machine(machine_id)
266
268
 
267
269
 
360
362
 
361
363
 
362
364
class MAASAccount:
363
 
    """Represent a Mass account."""
 
365
    """Represent a MAAS 2.0 account."""
 
366
 
 
367
    _API_PATH = 'api/2.0/'
364
368
 
365
369
    def __init__(self, profile, url, oauth):
366
370
        self.profile = profile
367
 
        self.url = urlparse.urljoin(url, 'api/1.0/')
 
371
        self.url = urlparse.urljoin(url, self._API_PATH)
368
372
        self.oauth = oauth
369
373
 
370
 
    @classmethod
371
 
    @contextmanager
372
 
    def manager_from_config(cls, config):
373
 
        """Create a ContextManager for a MaasAccount."""
374
 
        manager = cls(
375
 
            config['name'], config['maas-server'], config['maas-oauth'])
376
 
        manager.login()
377
 
        yield manager
378
 
        manager.logout()
379
 
 
380
374
    def login(self):
381
375
        """Login with the maas cli."""
382
376
        subprocess.check_call(
387
381
        subprocess.check_call(
388
382
            ['maas', 'logout', self.profile])
389
383
 
 
384
    def _machine_release_args(self, machine_id):
 
385
        return ['maas', self.profile, 'machine', 'release', machine_id]
 
386
 
390
387
    def terminate_instances(self, instance_ids):
391
388
        """Terminate the specified instances."""
392
389
        for instance in instance_ids:
393
390
            maas_system_id = instance.split('/')[5]
394
 
            print_now('Deleting %s.' % instance)
395
 
            subprocess.check_call(
396
 
                ['maas', self.profile, 'node', 'release', maas_system_id])
 
391
            log.info('Deleting %s.' % instance)
 
392
            subprocess.check_call(self._machine_release_args(maas_system_id))
 
393
 
 
394
    def _list_allocated_args(self):
 
395
        return ['maas', self.profile, 'machines', 'list-allocated']
397
396
 
398
397
    def get_allocated_nodes(self):
399
398
        """Return a dict of allocated nodes with the hostname as keys."""
400
 
        data = subprocess.check_output(
401
 
            ['maas', self.profile, 'nodes', 'list-allocated'])
 
399
        data = subprocess.check_output(self._list_allocated_args())
402
400
        nodes = json.loads(data)
403
401
        allocated = {node['hostname']: node for node in nodes}
404
402
        return allocated
415
413
        return ips
416
414
 
417
415
 
 
416
class MAAS1Account(MAASAccount):
 
417
    """Represent a MAAS 1.X account."""
 
418
 
 
419
    _API_PATH = 'api/1.0/'
 
420
 
 
421
    def _list_allocated_args(self):
 
422
        return ['maas', self.profile, 'nodes', 'list-allocated']
 
423
 
 
424
    def _machine_release_args(self, machine_id):
 
425
        return ['maas', self.profile, 'node', 'release', machine_id]
 
426
 
 
427
 
 
428
@contextmanager
 
429
def maas_account_from_config(config):
 
430
    """Create a ContextManager for either a MAASAccount or a MAAS1Account.
 
431
 
 
432
    As it's not possible to tell from the maas config which version of the api
 
433
    to use, try 2.0 and if that fails on login fallback to 1.0 instead.
 
434
    """
 
435
    args = (config['name'], config['maas-server'], config['maas-oauth'])
 
436
    manager = MAASAccount(*args)
 
437
    try:
 
438
        manager.login()
 
439
    except subprocess.CalledProcessError:
 
440
        log.info("Could not login with MAAS 2.0 API, trying 1.0")
 
441
        manager = MAAS1Account(*args)
 
442
        manager.login()
 
443
    yield manager
 
444
    manager.logout()
 
445
 
 
446
 
418
447
class LXDAccount:
419
448
    """Represent a LXD account."""
420
449
 
578
607
        command.extend(['--filter', 'instance-state-name=running'])
579
608
    if instances is not None:
580
609
        command.extend(instances)
581
 
    logging.info(' '.join(command))
 
610
    log.info(' '.join(command))
582
611
    return parse_euca(subprocess.check_output(command, env=env))
583
612
 
584
613
 
600
629
        # Only MAAS requires special handling at prsent.
601
630
        return
602
631
    # MAAS hostnames are not resolvable, but we can adapt them to IPs.
603
 
    with MAASAccount.manager_from_config(env.config) as account:
 
632
    with maas_account_from_config(env.config) as account:
604
633
        allocated_ips = account.get_allocated_ips()
605
634
    for remote in remote_machines:
606
635
        if remote.get_address() in allocated_ips: