Source code for pycloudlib.ibm_classic.instance

# This file is part of pycloudlib. See LICENSE file for license information.
# pylint: disable=too-many-public-methods
"""IBM Classic instance class."""

import logging
from typing import List, Optional

import SoftLayer  # type: ignore

from pycloudlib.ibm._util import wait_until as _wait_until
from pycloudlib.ibm_classic.errors import IBMClassicException
from pycloudlib.instance import BaseInstance

logger = logging.getLogger(__name__)


[docs] class IBMClassicInstance(BaseInstance): """IBM Classic instance class.""" _type = "ibm_classic"
[docs] def __init__( self, key_pair, *, softlayer_client: SoftLayer.BaseClient, vs_manager: SoftLayer.VSManager, instance: dict, username: Optional[str] = None, ): """Set up instance.""" super().__init__(key_pair, username=username) self._softlayer_client = softlayer_client self._vs_manager = vs_manager self._instance = instance self._deleted = False if username: self._log.error( "Specifiying username is not supported for IBM Classic " "instances. The default 'ubuntu' user will be used." )
@property def id(self) -> str: """Return instance id.""" return self._instance["id"] @property def name(self): """Return instance name.""" return self._instance["hostname"] @property def ip(self): """Return IP address of instance.""" # update instance info if IP address was not previously available if "primaryIpAddress" not in self._instance: self._instance = self._vs_manager.get_instance(self.id) # if IP address is still not available, raise exception if "primaryIpAddress" not in self._instance: raise IBMClassicException(f"Failed to get IP address for instance {self.id}") return self._instance["primaryIpAddress"]
[docs] @staticmethod def create_raw_instance( vs_manager: SoftLayer.VSManager, target_image_global_identifier: str, hostname: str, flavor: str, datacenter: str, public_security_group_ids: List[int], private_security_group_ids: List[int], ssh_key_ids: List[int], domain_name: str, **kwargs, ): """ Verify instance configuration and create instance. Args: vs_manager: Softlayer VSManager (Virtual Server Manager) instance target_image_global_identifier: image global identifier hostname: instance hostname flavor: instance flavor datacenter: datacenter region public_security_group_ids: list of public security group ids private_security_group_ids: list of private security group ids ssh_key_ids: list of ssh key ids domain_name: domain name """ logger.debug("Creating raw instance") constant_args = { "private": False, "dedicated": False, # default "hourly": True, # default "local_disk": False, # default } instance_specific_args = { "domain": domain_name, "hostname": hostname, "ssh_keys": ssh_key_ids, "image_id": target_image_global_identifier, "flavor": flavor, "datacenter": datacenter, "public_security_groups": public_security_group_ids, "private_security_groups": private_security_group_ids, } # check if instance configuration is valid try: logger.info("Verifying configuration for instance before creating it.") vs_manager.verify_create_instance( **constant_args, **instance_specific_args, **kwargs, ) except SoftLayer.SoftLayerAPIError as e: logger.error("configuration for instance is invalid: %s", e) raise IBMClassicException(f"Failed to verify instance configuration: {e}") from e except Exception as e: logger.error( "Unexpected error while verifying instance configuration: %s", e, ) raise IBMClassicException( f"Unexpected error while verifying instance configuration: {e}" ) from e logger.info("Configuration for instance is valid. Creating instance now.") raw_instance = vs_manager.create_instance( **constant_args, **instance_specific_args, **kwargs, ) logger.info("Created instance %s", raw_instance["hostname"]) full_raw_instance_info = vs_manager.get_instance(raw_instance["id"]) logger.debug("New instance details: %s", full_raw_instance_info) return full_raw_instance_info
[docs] def console_log(self): """Return the instance console log. Raises NotImplementedError if the cloud does not support fetching the console log for this instance. """ raise NotImplementedError("Console log not supported for IBM Classic")
[docs] def delete(self, wait=True) -> List[Exception]: """Delete the instance. Args: wait: wait for instance to be deleted """ def has_no_active_transaction(): """Check if instance has no active transaction.""" instance = self._vs_manager.get_instance(self.id) return "activeTransaction" not in instance if self._deleted: logger.debug("Instance %s already deleted", self.name) self._deleted = True return [] try: if wait: logger.info( "Deleting instance %s and waiting for it to delete.", self.name, ) instance = self._vs_manager.get_instance(self.id) if "activeTransaction" in instance: at = instance["activeTransaction"] logger.info( "Instance %s has an active transaction. " "Must wait for it to complete " "or else instance cancellation will fail. ", self.name, ) t = at["transactionStatus"]["friendlyName"] msg = f"Instance {self.name} stuck in active transaction: {t}." _wait_until( has_no_active_transaction, timeout_seconds=60 * 60, timeout_msg_fn=lambda: msg, check_interval=5, ) self._wait_for_execute() self._vs_manager.cancel_instance(self.id) self.wait_for_delete() else: logger.info("Deleting instance %s without waiting.") self._vs_manager.cancel_instance(self.id) except Exception as e: # pylint: disable=broad-except return [e] self._deleted = True return []
def _do_restart(self, **kwargs): self._softlayer_client.call("Virtual_Guest", "rebootSoft", id=self.id)
[docs] def shutdown(self, wait=True, **kwargs): """Shutdown the instance. Args: wait: wait for the instance to shutdown """ if wait: logger.info( "Shutting down instance %s and waiting for it to stop.", self.name, ) self._softlayer_client.call("Virtual_Guest", "powerOff", id=self.id) self.wait_for_stop() else: logger.info( "Shutting down instance %s without waiting.", self.name, ) self._softlayer_client.call("Virtual_Guest", "powerOff", id=self.id)
[docs] def start(self, wait=True): """Start the instance. Args: wait: wait for the instance to start. """ if wait: logger.info( "Starting instance %s and waiting for it to start.", self.name, ) self._softlayer_client.call("Virtual_Guest", "powerOn", id=self.id) self._wait_for_instance_start() else: logger.info( "Starting instance %s without waiting.", self.name, ) self._softlayer_client.call("Virtual_Guest", "powerOn", id=self.id)
def _wait_for_instance_start(self, **kwargs): """Wait for the cloud instance to be up.""" def is_started(): instance = self._vs_manager.get_instance(self.id) power_state = instance.get("powerState", {}).get("keyName") last_transaction = ( instance.get("lastTransaction", {}).get("transactionStatus", {}).get("name") ) is_active_transaction = "activeTransaction" in instance logger.debug( "Instance %s powerState: %s", self.name, power_state, ) logger.debug( "Instance %s lastTransaction: %s", self.name, last_transaction, ) logger.debug( "Instance %s activeTransaction: %s", self.name, is_active_transaction, ) return ( power_state == "RUNNING" and last_transaction == "COMPLETE" and not is_active_transaction ) # wait for 3 hours for the instance to start timeout = 60 * 60 * 3 msg = f"Instance {self.name} did not start after {timeout} seconds" _wait_until( is_started, timeout_seconds=timeout, timeout_msg_fn=lambda: msg, check_interval=10, ) logger.info("Instance %s started", self.name) self._instance = self._vs_manager.get_instance(self.id)
[docs] def wait_for_restart(self, old_boot_id): """Wait for instance to be restarted and cloud-init to be complete. old_boot_id is the boot id prior to restart """ logger.info("Waiting for instance %s to restart", self.name) self._wait_for_instance_start() self._wait_for_execute(old_boot_id=old_boot_id, timeout=15) self._wait_for_cloudinit()
[docs] def wait_for_delete(self, **kwargs): """Wait for instance to be deleted.""" # we need to wait until the instance is deleted def is_deleted(): existing_instances = self._vs_manager.list_instances() return self.id not in [instance["id"] for instance in existing_instances] # wait for 10 minutes for the instance to delete timeout = 60 * 10 msg = f"Instance {self.name} failed to delete after {timeout} seconds" _wait_until( is_deleted, timeout_seconds=timeout, timeout_msg_fn=lambda: msg, check_interval=5, ) logger.info("Instance %s deleted", self.name)
[docs] def wait_for_stop(self, **kwargs): """Wait for instance stop.""" def is_stopped(): instance = self._vs_manager.get_instance(self.id) power_state = instance.get("powerState", {}).get("keyName") last_transaction = ( instance.get("lastTransaction", {}).get("transactionStatus", {}).get("name") ) is_active_transaction = "activeTransaction" in instance logger.debug( "Instance %s powerState: %s", self.name, power_state, ) logger.debug( "Instance %s lastTransaction: %s", self.name, last_transaction, ) logger.debug( "Instance %s activeTransaction: %s", self.name, is_active_transaction, ) return ( power_state == "HALTED" and last_transaction == "COMPLETE" and not is_active_transaction ) # wait for 10 minutes for the instance to stop timeout = 60 * 10 msg = f"Instance {self.name} failed to stop after {timeout} seconds" _wait_until( is_stopped, timeout_seconds=timeout, timeout_msg_fn=lambda: msg, check_interval=5, ) logger.info("Instance %s stopped", self.name)