"""Openstack instance type."""
import time
from itertools import chain
import openstack
from openstack.exceptions import (
BadRequestException,
ConflictException,
ResourceNotFound,
)
from pycloudlib.instance import BaseInstance
[docs]class OpenstackInstance(BaseInstance):
"""Openstack instance object."""
_type = 'openstack'
[docs] def __init__(self, key_pair, instance_id, network_id, connection=None):
"""Set up the instance.
Args:
key_pair: A KeyPair for SSH interactions
instance_id: The instance id representing the cloud instance
network_id: if of the network this instance was created on
connection: The connection used to create this instance.
If None, connection will be created.
"""
super().__init__(key_pair)
if not connection:
connection = openstack.connect()
self.network_id = network_id
self.conn = connection
self.server = self.conn.compute.get_server(instance_id)
self.delete_floating_ip = False
self.floating_ip = self._get_existing_floating_ip()
if self.floating_ip is None:
self.floating_ip = self._create_and_attach_floating_id()
self.delete_floating_ip = True
self.added_local_ports = []
def _get_existing_floating_ip(self):
server_addresses = chain(*self.server.addresses.values())
server_ips = [addr['addr'] for addr in server_addresses]
for floating_ip in self.conn.network.ips():
if floating_ip['floating_ip_address'] in server_ips:
return floating_ip
return None
def _create_and_attach_floating_id(self):
floating_ip = self.conn.create_floating_ip(wait=True)
tries = 30
for _ in range(tries):
try:
self.conn.compute.add_floating_ip_to_server(
self.server,
floating_ip.floating_ip_address
)
break
except BadRequestException as e:
if 'Instance network is not ready yet' in str(e):
time.sleep(1)
continue
raise e
return floating_ip
def __repr__(self):
"""Create string representation of class."""
return '{}(instance_id={})'.format(
self.__class__.__name__,
self.server.id
)
@property
def name(self):
"""Return instance name."""
return self.server.name
@property
def ip(self):
"""Return IP address of instance."""
return self.floating_ip.floating_ip_address
[docs] def console_log(self):
"""Return the instance console log."""
start = time.time()
while time.time() < start + 180:
response = self.conn.compute.get_server_console_output(self.server)
if response:
return response['output']
self._log.debug("Console output not yet available; sleeping")
time.sleep(5)
return 'No console output'
[docs] def delete(self, wait=True):
"""Delete the instance.
Args:
wait: wait for instance to be deleted
"""
try:
self.conn.compute.delete_server(self.server.id)
finally:
if self.delete_floating_ip:
self.conn.delete_floating_ip(self.floating_ip.id)
for port_id in self.added_local_ports:
self.conn.network.delete_port(
port=port_id,
ignore_missing=True
)
if wait:
self.wait_for_delete()
[docs] def restart(self, wait=True, **kwargs):
"""Restart the instance.
Args:
wait: wait for the instance to be fully started
"""
self.shutdown(wait=wait)
self.start(wait=wait)
[docs] def shutdown(self, wait=True, **kwargs):
"""Shutdown the instance.
Args:
wait: wait for the instance to shutdown
"""
self.conn.compute.stop_server(self.server)
if wait:
self.wait_for_stop()
[docs] def start(self, wait=True):
"""Start the instance.
Args:
wait: wait for the instance to start.
"""
try:
self.conn.compute.start_server(self.server)
except ConflictException as e:
# We can get an exception here if the instance is already started
if 'while it is in vm_state active' in str(e):
return
if wait:
self.wait()
def _wait_for_instance_start(self):
"""Wait for instance to be up."""
self.conn.compute.wait_for_server(self.server, status='ACTIVE')
[docs] def wait_for_delete(self):
"""Wait for instance to be deleted."""
try:
self.conn.compute.wait_for_server(self.server, status='DELETED')
except ResourceNotFound:
# We can 404 here is instance is already deleted
pass
[docs] def wait_for_stop(self):
"""Wait for instance stop."""
self.conn.compute.wait_for_server(self.server, status='SHUTOFF')
[docs] def add_network_interface(self) -> str:
"""Add nic to running instance.
Returns IP address in string form
"""
port = self.conn.network.create_port(
network_id=self.network_id,
)
self.added_local_ports.append(port.id)
interface = self.conn.compute.create_server_interface(
server=self.server.id,
port_id=port.id
)
return interface['fixed_ips'][0]['ip_address']
def _get_port_id_by_ip(self, ip_address: str):
ports = self.conn.network.ports()
for port in ports:
for ip in port['fixed_ips']:
if ip['ip_address'] == ip_address:
return port
raise Exception('Could not find port with IP: {}'.format(ip_address))
[docs] def remove_network_interface(self, ip_address: str):
"""Remove nic from running instance."""
port = self._get_port_id_by_ip(ip_address)
self.conn.network.delete_port(
port=port.id,
)
try:
self.added_local_ports.remove(port.id)
except ValueError:
self._log.warning(
'Expected port to be in added_local_ports list '
'but was not: %s', port.id)