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.")
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)
58
60
elif provider_type == 'lxd':
66
68
"This test does not support the %s provider"
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)
107
109
the specified instances.
108
110
:return: an iterator of (group-id, group-name) tuples.
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'):
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)
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)
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)
362
364
class MAASAccount:
363
"""Represent a Mass account."""
365
"""Represent a MAAS 2.0 account."""
367
_API_PATH = 'api/2.0/'
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
372
def manager_from_config(cls, config):
373
"""Create a ContextManager for a MaasAccount."""
375
config['name'], config['maas-server'], config['maas-oauth'])
381
375
"""Login with the maas cli."""
382
376
subprocess.check_call(
387
381
subprocess.check_call(
388
382
['maas', 'logout', self.profile])
384
def _machine_release_args(self, machine_id):
385
return ['maas', self.profile, 'machine', 'release', machine_id]
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))
394
def _list_allocated_args(self):
395
return ['maas', self.profile, 'machines', 'list-allocated']
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}
416
class MAAS1Account(MAASAccount):
417
"""Represent a MAAS 1.X account."""
419
_API_PATH = 'api/1.0/'
421
def _list_allocated_args(self):
422
return ['maas', self.profile, 'nodes', 'list-allocated']
424
def _machine_release_args(self, machine_id):
425
return ['maas', self.profile, 'node', 'release', machine_id]
429
def maas_account_from_config(config):
430
"""Create a ContextManager for either a MAASAccount or a MAAS1Account.
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.
435
args = (config['name'], config['maas-server'], config['maas-oauth'])
436
manager = MAASAccount(*args)
439
except subprocess.CalledProcessError:
440
log.info("Could not login with MAAS 2.0 API, trying 1.0")
441
manager = MAAS1Account(*args)
418
447
class LXDAccount:
419
448
"""Represent a LXD account."""
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))
600
629
# Only MAAS requires special handling at prsent.
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: