Source code for SoftLayer.managers.vs

"""
    SoftLayer.vs
    ~~~~~~~~~~~~
    VS Manager/helpers

    :license: MIT, see LICENSE for more details.
"""
import datetime
import itertools
import socket
import time

from SoftLayer.managers import ordering
from SoftLayer import utils


[docs]class VSManager(utils.IdentifierMixin, object): """ Manages Virtual Servers :param SoftLayer.API.Client client: an API client instance :param SoftLayer.managers.OrderingManager ordering_manager: an optional manager to handle ordering. If none is provided, one will be auto initialized. """ def __init__(self, client, ordering_manager=None): self.client = client self.account = client['Account'] self.guest = client['Virtual_Guest'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) else: self.ordering_manager = ordering_manager
[docs] def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, memory=None, hostname=None, domain=None, local_disk=None, datacenter=None, nic_speed=None, public_ip=None, private_ip=None, **kwargs): """ Retrieve a list of all virtual servers on the account. :param boolean hourly: include hourly instances :param boolean monthly: include monthly instances :param list tags: filter based on tags :param integer cpus: filter based on number of CPUS :param integer memory: filter based on amount of memory :param string hostname: filter based on hostname :param string domain: filter based on domain :param string local_disk: filter based on local_disk :param string datacenter: filter based on datacenter :param integer nic_speed: filter based on network speed (in MBPS) :param string public_ip: filter based on public ip address :param string private_ip: filter based on private ip address :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) :returns: Returns a list of dictionaries representing the matching virtual servers :: # Print out a list of all hourly instances in the DAL05 data center. # env variables # SL_USERNAME = YOUR_USERNAME # SL_API_KEY = YOUR_API_KEY import SoftLayer client = SoftLayer.Client() mgr = SoftLayer.VSManager(client) for vsi in mgr.list_instances(hourly=True, datacenter='dal05'): print vsi['fullyQualifiedDomainName'], vs['primaryIpAddress'] """ if 'mask' not in kwargs: items = [ 'id', 'globalIdentifier', 'hostname', 'domain', 'fullyQualifiedDomainName', 'primaryBackendIpAddress', 'primaryIpAddress', 'lastKnownPowerState.name', 'powerState', 'maxCpu', 'maxMemory', 'datacenter', 'activeTransaction.transactionStatus[friendlyName,name]', 'status', 'billingItem.orderItem.order.userRecord[username]' ] kwargs['mask'] = "mask[%s]" % ','.join(items) call = 'getVirtualGuests' if not all([hourly, monthly]): if hourly: call = 'getHourlyVirtualGuests' elif monthly: call = 'getMonthlyVirtualGuests' _filter = utils.NestedDict(kwargs.get('filter') or {}) if tags: _filter['virtualGuests']['tagReferences']['tag']['name'] = { 'operation': 'in', 'options': [{'name': 'data', 'value': tags}], } if cpus: _filter['virtualGuests']['maxCpu'] = utils.query_filter(cpus) if memory: _filter['virtualGuests']['maxMemory'] = utils.query_filter(memory) if hostname: _filter['virtualGuests']['hostname'] = utils.query_filter(hostname) if domain: _filter['virtualGuests']['domain'] = utils.query_filter(domain) if local_disk is not None: _filter['virtualGuests']['localDiskFlag'] = ( utils.query_filter(bool(local_disk))) if datacenter: _filter['virtualGuests']['datacenter']['name'] = ( utils.query_filter(datacenter)) if nic_speed: _filter['virtualGuests']['networkComponents']['maxSpeed'] = ( utils.query_filter(nic_speed)) if public_ip: _filter['virtualGuests']['primaryIpAddress'] = ( utils.query_filter(public_ip)) if private_ip: _filter['virtualGuests']['primaryBackendIpAddress'] = ( utils.query_filter(private_ip)) kwargs['filter'] = _filter.to_dict() func = getattr(self.account, call) return func(**kwargs)
[docs] def get_instance(self, instance_id, **kwargs): """ Get details about a virtual server instance :param integer instance_id: the instance ID :returns: A dictionary containing a large amount of information about the specified instance. :: # Print out the FQDN and IP address for instance ID 12345. # env variables # SL_USERNAME = YOUR_USERNAME # SL_API_KEY = YOUR_API_KEY import SoftLayer client = SoftLayer.Client() mgr = SoftLayer.VSManager(client) vsi = mgr.get_instance(12345) print vsi['fullyQualifiedDomainName'], vs['primaryIpAddress'] """ if 'mask' not in kwargs: items = [ 'id', 'globalIdentifier', 'fullyQualifiedDomainName', 'hostname', 'domain', 'createDate', 'modifyDate', 'provisionDate', 'notes', 'dedicatedAccountHostOnlyFlag', 'privateNetworkOnlyFlag', 'primaryBackendIpAddress', 'primaryIpAddress', '''networkComponents[id, status, speed, maxSpeed, name, macAddress, primaryIpAddress, port, primarySubnet]''', 'lastKnownPowerState.name', 'powerState', 'status', 'maxCpu', 'maxMemory', 'datacenter', 'activeTransaction[id, transactionStatus[friendlyName,name]]', 'lastOperatingSystemReload.id', 'blockDevices', 'blockDeviceTemplateGroup[id, name, globalIdentifier]', 'postInstallScriptUri', 'userData', '''operatingSystem[passwords[username,password], softwareLicense.softwareDescription[ manufacturer,name,version, referenceCode]]''', 'hourlyBillingFlag', 'billingItem.recurringFee', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', 'billingItem.orderItem.order.userRecord[username]' ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.guest.getObject(id=instance_id, **kwargs)
[docs] def get_create_options(self): """ Retrieves the available options for creating a VS. :returns: A dictionary of creation options. """ return self.guest.getCreateObjectOptions()
[docs] def cancel_instance(self, instance_id): """ Cancel an instance immediately, deleting all its data. :param integer instance_id: the instance ID to cancel :: # Cancel for instance ID 12345. # env variables # SL_USERNAME = YOUR_USERNAME # SL_API_KEY = YOUR_API_KEY import SoftLayer client = SoftLayer.Client() mgr = SoftLayer.VSManager(client) mgr.cancel_instance(12345) """ return self.guest.deleteObject(id=instance_id)
[docs] def reload_instance(self, instance_id, post_uri=None, ssh_keys=None): """ Perform an OS reload of an instance with its current configuration. :param integer instance_id: the instance ID to reload :param string post_url: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user :: # Reload instance ID 12345 then run a custom post-provision script. # env variables # SL_USERNAME = YOUR_USERNAME # SL_API_KEY = YOUR_API_KEY import SoftLayer client = SoftLayer.Client() post_uri = 'https://somehost.com/bootstrap.sh' mgr = SoftLayer.VSManager(client) vsi = mgr.reload_instance(12345, post_uri=post_url) """ config = {} if post_uri: config['customProvisionScriptUri'] = post_uri if ssh_keys: config['sshKeyIds'] = [key_id for key_id in ssh_keys] return self.guest.reloadOperatingSystem('FORCE', config, id=instance_id)
def _generate_create_dict( self, cpus=None, memory=None, hourly=True, hostname=None, domain=None, local_disk=True, datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None): """ Returns a dict appropriate to pass into Virtual_Guest::createObject See :func:`create_instance` for a list of available options. """ required = [cpus, memory, hostname, domain] mutually_exclusive = [ {'os_code': os_code, "image_id": image_id}, ] if not all(required): raise ValueError("cpu, memory, hostname, and domain are required") for mu_ex in mutually_exclusive: if all(mu_ex.values()): raise ValueError( 'Can only specify one of: %s' % (','.join(mu_ex.keys()))) data = { "startCpus": int(cpus), "maxMemory": int(memory), "hostname": hostname, "domain": domain, "localDiskFlag": local_disk, } data["hourlyBillingFlag"] = hourly if dedicated: data["dedicatedAccountHostOnlyFlag"] = dedicated if private: data['privateNetworkOnlyFlag'] = private if image_id: data["blockDeviceTemplateGroup"] = {"globalIdentifier": image_id} elif os_code: data["operatingSystemReferenceCode"] = os_code if datacenter: data["datacenter"] = {"name": datacenter} if public_vlan: data.update({ 'primaryNetworkComponent': { "networkVlan": {"id": int(public_vlan)}}}) if private_vlan: data.update({ "primaryBackendNetworkComponent": { "networkVlan": {"id": int(private_vlan)}}}) if userdata: data['userData'] = [{'value': userdata}] if nic_speed: data['networkComponents'] = [{'maxSpeed': nic_speed}] if disks and isinstance(disks, list): data['blockDevices'] = [ {"device": "0", "diskImage": {"capacity": disks[0]}} ] for dev_id, disk in enumerate(disks[1:], start=2): data['blockDevices'].append( { "device": str(dev_id), "diskImage": {"capacity": disk} } ) if post_uri: data['postInstallScriptUri'] = post_uri if ssh_keys: data['sshKeys'] = [{'id': key_id} for key_id in ssh_keys] return data
[docs] def wait_for_transaction(self, instance_id, limit, delay=1): """ Waits on a VS transaction for the specified amount of time. is really just a wrapper for wait_for_ready(pending=True). Provided for backwards compatibility. :param int instance_id: The instance ID with the pending transaction :param int limit: The maximum amount of time to wait. :param int delay: The number of seconds to sleep before checks. Defaults to 1. """ return self.wait_for_ready(instance_id, limit, delay=delay, pending=True)
[docs] def wait_for_ready(self, instance_id, limit, delay=1, pending=False): """ Determine if a VS is ready and available. In some cases though, that can mean that no transactions are running. The default arguments imply a VS is operational and ready for use by having network connectivity and remote access is available. Setting ``pending=True`` will ensure future API calls against this instance will not error due to pending transactions such as OS Reloads and cancellations. :param int instance_id: The instance ID with the pending transaction :param int limit: The maximum amount of time to wait. :param int delay: The number of seconds to sleep before checks. Defaults to 1. :param bool pending: Wait for pending transactions not related to provisioning or reloads such as monitoring. """ for count, new_instance in enumerate(itertools.repeat(instance_id), start=1): instance = self.get_instance(new_instance) last_reload = utils.lookup(instance, 'lastOperatingSystemReload', 'id') active_transaction = utils.lookup(instance, 'activeTransaction', 'id') reloading = all(( active_transaction, last_reload, last_reload == active_transaction )) # only check for outstanding transactions if requested outstanding = False if pending: outstanding = active_transaction # return True if the instance has only if the instance has # finished provisioning and isn't currently reloading the OS. if all([instance.get('provisionDate'), not reloading, not outstanding]): return True if count >= limit: return False time.sleep(delay)
[docs] def verify_create_instance(self, **kwargs): """ Verifies an instance creation command without actually placing an order. See :func:`create_instance` for a list of available options. """ create_options = self._generate_create_dict(**kwargs) return self.guest.generateOrderTemplate(create_options)
[docs] def create_instance(self, **kwargs): """ Creates a new virtual server instance :param int cpus: The number of virtual CPUs to include in the instance. :param int memory: The amount of RAM to order. :param bool hourly: Flag to indicate if this server should be billed hourly (default) or monthly. :param string hostname: The hostname to use for the new server. :param string domain: The domain to use for the new server. :param bool local_disk: Flag to indicate if this should be a local disk (default) or a SAN disk. :param string datacenter: The short name of the data center in which the VS should reside. :param string os_code: The operating system to use. Cannot be specified if image_id is specified. :param int image_id: The ID of the image to load onto the server. Cannot be specified if os_code is specified. :param bool dedicated: Flag to indicate if this should be housed on a dedicated or shared host (default). This will incur a fee on your account. :param int public_vlan: The ID of the public VLAN on which you want this VS placed. :param int private_vlan: The ID of the public VLAN on which you want this VS placed. :param list disks: A list of disk capacities for this server. :param string post_uri: The URI of the post-install script to run after reload :param bool private: If true, the VS will be provisioned only with access to the private network. Defaults to false :param list ssh_keys: The SSH keys to add to the root user :param int nic_speed: The port speed to set :param string tag: tags to set on the VS as a comma separated list """ tags = kwargs.pop('tags', None) inst = self.guest.createObject(self._generate_create_dict(**kwargs)) if tags is not None: self.guest.setTags(tags, id=inst['id']) return inst
[docs] def create_instances(self, config_list): """ Creates multiple virtual server instances This takes a list of dictionaries using the same arguments as create_instance(). """ tags = [conf.pop('tags', None) for conf in config_list] resp = self.guest.createObjects([self._generate_create_dict(**kwargs) for kwargs in config_list]) for instance, tag in zip(resp, tags): if tag is not None: self.guest.setTags(tag, id=instance['id']) return resp
[docs] def change_port_speed(self, instance_id, public, speed): """ Allows you to change the port speed of a virtual server's NICs. :param int instance_id: The ID of the VS :param bool public: Flag to indicate which interface to change. True (default) means the public interface. False indicates the private interface. :param int speed: The port speed to set. """ if public: func = self.guest.setPublicNetworkInterfaceSpeed else: func = self.guest.setPrivateNetworkInterfaceSpeed return func(speed, id=instance_id)
def _get_ids_from_hostname(self, hostname): """ List VS ids which match the given hostname """ results = self.list_instances(hostname=hostname, mask="id") return [result['id'] for result in results] def _get_ids_from_ip(self, ip_address): """ List VS ids which match the given ip address """ try: # Does it look like an ip address? socket.inet_aton(ip_address) except socket.error: return [] # Find the VS via ip address. First try public ip, then private results = self.list_instances(public_ip=ip_address, mask="id") if results: return [result['id'] for result in results] results = self.list_instances(private_ip=ip_address, mask="id") if results: return [result['id'] for result in results]
[docs] def edit(self, instance_id, userdata=None, hostname=None, domain=None, notes=None, tag=None): """ Edit hostname, domain name, notes, and/or the user data of a VS Parameters set to None will be ignored and not attempted to be updated. :param integer instance_id: the instance ID to edit :param string userdata: user data on VS to edit. If none exist it will be created :param string hostname: valid hostname :param string domain: valid domain namem :param string notes: notes about this particular VS :param string tag: tags to set on the VS as a comma separated list. Use the empty string to remove all tags. """ obj = {} if userdata: self.guest.setUserMetadata([userdata], id=instance_id) if tag is not None: self.guest.setTags(tag, id=instance_id) if hostname: obj['hostname'] = hostname if domain: obj['domain'] = domain if notes: obj['notes'] = notes if not obj: return True return self.guest.editObject(obj, id=instance_id)
[docs] def rescue(self, instance_id): """ Reboot a VSI into the Xen recsue kernel :param integer instance_id: the instance ID to rescue """ return self.guest.executeRescueLayer(id=instance_id)
[docs] def capture(self, instance_id, name, additional_disks=False, notes=None): """ Capture one or all disks from a VS to a SoftLayer image. Parameters set to None will be ignored and not attempted to be updated. :param integer instance_id: the instance ID to edit :param string name: name assigned to the image :param string additional_disks: set to true to include all additional attached storage devices :param string notes: notes about this particular image """ vsi = self.get_instance(instance_id) disk_filter = lambda x: x['device'] == '0' # Disk 1 is swap partition. Need to skip its capture. if additional_disks: disk_filter = lambda x: x['device'] != '1' disks = [block_device for block_device in vsi['blockDevices'] if disk_filter(block_device)] return self.guest.createArchiveTransaction( name, disks, notes, id=instance_id)
[docs] def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True): """ Upgrades a VS instance :param int instance_id: Instance id of the VS to be upgraded :param int cpus: The number of virtual CPUs to upgrade to of a VS instance. :param bool public: CPU will be in Private/Public Node. :param int memory: RAM of the VS to be upgraded to. :param int nic_speed: The port speed to set :: # Upgrade instance 12345 to 4 CPUs and 4 GB of memory import SoftLayer client = SoftLayer.Client(config="~/.softlayer") mgr = SoftLayer.VSManager(client) mgr.upgrade(12345, cpus=4, memory=4) """ package_items = self._get_package_items() item_id = [] if cpus: item_id.append({'id': self._get_item_id_for_upgrade( package_items, 'cpus', cpus, public)}) if memory: item_id.append({'id': self._get_item_id_for_upgrade( package_items, 'memory', memory)}) if nic_speed: item_id.append({'id': self._get_item_id_for_upgrade( package_items, 'nic_speed', nic_speed)}) order = {} order['complexType'] = ( 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade') order['virtualGuests'] = [{'id': int(instance_id)}] order['prices'] = item_id order['properties'] = [{'name': 'MAINTENANCE_WINDOW', 'value': str(datetime.datetime.now())}] if cpus or memory or nic_speed: self.client['Product_Order'].placeOrder(order) return True return False
def _get_package_items(self): """ Following Method gets all the item ids related to VS """ mask = "mask[description,capacity,prices[id,categories[name,id]]]" package_type = "VIRTUAL_SERVER_INSTANCE" package_id = self.ordering_manager.get_package_id_by_type(package_type) package_service = self.client['Product_Package'] return package_service.getItems(id=package_id, mask=mask) def _get_item_id_for_upgrade(self, package_items, option, value, public=True): """ Find the item ids for the parameters you want to upgrade to. :param list package_items: Contains all the items related to an VS :param string option: Describes type of parameter to be upgraded :param int value: The value of the parameter to be upgraded :param bool public: CPU will be in Private/Public Node. """ vs_id = {'memory': 3, 'cpus': 80, 'nic_speed': 26} for item in package_items: categories = item['prices'][0]['categories'] for j in range(len(categories)): if not (categories[j]['id'] == vs_id[option] and item['capacity'] == str(value)): continue if option == 'cpus': if public and ('Private' not in item['description']): return item['prices'][0]['id'] elif not public and ('Private' in item['description']): return item['prices'][0]['id'] elif option == 'nic_speed': if 'Public' in item['description']: return item['prices'][0]['id'] else: return item['prices'][0]['id']