Source code for libcloud.compute.drivers.softlayer

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Softlayer driver
"""

import time

from libcloud.compute.base import Node, KeyPair, NodeSize, NodeImage, NodeDriver, NodeLocation
from libcloud.compute.types import Provider, NodeState, KeyPairDoesNotExistError
from libcloud.common.softlayer import SoftLayerException, SoftLayerConnection

try:
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import rsa

    crypto = True
except ImportError:
    crypto = False


DEFAULT_DOMAIN = "example.com"
DEFAULT_CPU_SIZE = 1
DEFAULT_RAM_SIZE = 2048
DEFAULT_DISK_SIZE = 100

DATACENTERS = {
    "hou02": {"country": "US"},
    "sea01": {"country": "US", "name": "Seattle - West Coast U.S."},
    "wdc01": {"country": "US", "name": "Washington, DC - East Coast U.S."},
    "dal01": {"country": "US"},
    "dal02": {"country": "US"},
    "dal04": {"country": "US"},
    "dal05": {"country": "US", "name": "Dallas - Central U.S."},
    "dal06": {"country": "US"},
    "dal07": {"country": "US"},
    "sjc01": {"country": "US", "name": "San Jose - West Coast U.S."},
    "sng01": {"country": "SG", "name": "Singapore - Southeast Asia"},
    "ams01": {"country": "NL", "name": "Amsterdam - Western Europe"},
    "tok02": {"country": "JP", "name": "Tokyo - Japan"},
}

NODE_STATE_MAP = {
    "RUNNING": NodeState.RUNNING,
    "HALTED": NodeState.UNKNOWN,
    "PAUSED": NodeState.UNKNOWN,
    "INITIATING": NodeState.PENDING,
}

SL_BASE_TEMPLATES = [
    {"name": "1 CPU, 1GB ram, 25GB", "ram": 1024, "disk": 25, "cpus": 1},
    {"name": "1 CPU, 1GB ram, 100GB", "ram": 1024, "disk": 100, "cpus": 1},
    {"name": "1 CPU, 2GB ram, 100GB", "ram": 2 * 1024, "disk": 100, "cpus": 1},
    {"name": "1 CPU, 4GB ram, 100GB", "ram": 4 * 1024, "disk": 100, "cpus": 1},
    {"name": "2 CPU, 2GB ram, 100GB", "ram": 2 * 1024, "disk": 100, "cpus": 2},
    {"name": "2 CPU, 4GB ram, 100GB", "ram": 4 * 1024, "disk": 100, "cpus": 2},
    {"name": "2 CPU, 8GB ram, 100GB", "ram": 8 * 1024, "disk": 100, "cpus": 2},
    {"name": "4 CPU, 4GB ram, 100GB", "ram": 4 * 1024, "disk": 100, "cpus": 4},
    {"name": "4 CPU, 8GB ram, 100GB", "ram": 8 * 1024, "disk": 100, "cpus": 4},
    {"name": "6 CPU, 4GB ram, 100GB", "ram": 4 * 1024, "disk": 100, "cpus": 6},
    {"name": "6 CPU, 8GB ram, 100GB", "ram": 8 * 1024, "disk": 100, "cpus": 6},
    {"name": "8 CPU, 8GB ram, 100GB", "ram": 8 * 1024, "disk": 100, "cpus": 8},
    {"name": "8 CPU, 16GB ram, 100GB", "ram": 16 * 1024, "disk": 100, "cpus": 8},
]

SL_TEMPLATES = {}
for i, template in enumerate(SL_BASE_TEMPLATES):
    # Add local disk templates
    local = template.copy()
    local["local_disk"] = True
    SL_TEMPLATES[i] = local


[docs]class SoftLayerNodeDriver(NodeDriver): """ SoftLayer node driver Extra node attributes: - password: root password - hourlyRecurringFee: hourly price (if applicable) - recurringFee : flat rate (if applicable) - recurringMonths : The number of months in which the recurringFee will be incurred. """ connectionCls = SoftLayerConnection name = "SoftLayer" website = "http://www.softlayer.com/" type = Provider.SOFTLAYER features = {"create_node": ["generates_password", "ssh_key"]} api_name = "softlayer" def _to_node(self, host): try: password = host["operatingSystem"]["passwords"][0]["password"] except (IndexError, KeyError): password = None hourlyRecurringFee = host.get("billingItem", {}).get("hourlyRecurringFee", 0) recurringFee = host.get("billingItem", {}).get("recurringFee", 0) recurringMonths = host.get("billingItem", {}).get("recurringMonths", 0) createDate = host.get("createDate", None) # When machine is launching it gets state halted # we change this to pending state = NODE_STATE_MAP.get(host["powerState"]["keyName"], NodeState.UNKNOWN) if not password and state == NodeState.UNKNOWN: state = NODE_STATE_MAP["INITIATING"] public_ips = [] private_ips = [] if "primaryIpAddress" in host: public_ips.append(host["primaryIpAddress"]) if "primaryBackendIpAddress" in host: private_ips.append(host["primaryBackendIpAddress"]) image = ( host.get("operatingSystem", {}) .get("softwareLicense", {}) .get("softwareDescription", {}) .get("longDescription", None) ) return Node( id=host["id"], name=host["fullyQualifiedDomainName"], state=state, public_ips=public_ips, private_ips=private_ips, driver=self, extra={ "hostname": host["hostname"], "fullyQualifiedDomainName": host["fullyQualifiedDomainName"], "password": password, "maxCpu": host.get("maxCpu", None), "datacenter": host.get("datacenter", {}).get("longName", None), "maxMemory": host.get("maxMemory", None), "image": image, "hourlyRecurringFee": hourlyRecurringFee, "recurringFee": recurringFee, "recurringMonths": recurringMonths, "created": createDate, }, )
[docs] def destroy_node(self, node): self.connection.request("SoftLayer_Virtual_Guest", "deleteObject", id=node.id) return True
[docs] def reboot_node(self, node): self.connection.request("SoftLayer_Virtual_Guest", "rebootSoft", id=node.id) return True
[docs] def start_node(self, node): self.connection.request("SoftLayer_Virtual_Guest", "powerOn", id=node.id) return True
[docs] def stop_node(self, node): self.connection.request("SoftLayer_Virtual_Guest", "powerOff", id=node.id) return True
[docs] def ex_start_node(self, node): # NOTE: This method is here for backward compatibility reasons after # this method was promoted to be part of the standard compute API in # Libcloud v2.7.0 return self.start_node(node=node)
[docs] def ex_stop_node(self, node): # NOTE: This method is here for backward compatibility reasons after # this method was promoted to be part of the standard compute API in # Libcloud v2.7.0 return self.stop_node(node=node)
def _get_order_information(self, node_id, timeout=1200, check_interval=5): mask = { "billingItem": "", "powerState": "", "operatingSystem": {"passwords": ""}, "provisionDate": "", } for i in range(0, timeout, check_interval): res = self.connection.request( "SoftLayer_Virtual_Guest", "getObject", id=node_id, object_mask=mask ).object if res.get("provisionDate", None): return res time.sleep(check_interval) raise SoftLayerException("Timeout on getting node details")
[docs] def create_node( self, name, size=None, image=None, location=None, ex_domain=None, ex_cpus=None, ex_disk=None, ex_ram=None, ex_bandwidth=None, ex_local_disk=None, ex_datacenter=None, ex_os=None, ex_keyname=None, ex_hourly=True, ): """Create a new SoftLayer node @inherits: :class:`NodeDriver.create_node` :keyword ex_domain: e.g. libcloud.org :type ex_domain: ``str`` :keyword ex_cpus: e.g. 2 :type ex_cpus: ``int`` :keyword ex_disk: e.g. 100 :type ex_disk: ``int`` :keyword ex_ram: e.g. 2048 :type ex_ram: ``int`` :keyword ex_bandwidth: e.g. 100 :type ex_bandwidth: ``int`` :keyword ex_local_disk: e.g. True :type ex_local_disk: ``bool`` :keyword ex_datacenter: e.g. Dal05 :type ex_datacenter: ``str`` :keyword ex_os: e.g. UBUNTU_LATEST :type ex_os: ``str`` :keyword ex_keyname: The name of the key pair :type ex_keyname: ``str`` """ os = "DEBIAN_LATEST" if ex_os: os = ex_os elif image: os = image.id size = size or NodeSize( id=123, name="Custom", ram=None, disk=None, bandwidth=None, price=None, driver=self.connection.driver, ) ex_size_data = SL_TEMPLATES.get(int(size.id)) or {} # plan keys are ints cpu_count = ex_cpus or ex_size_data.get("cpus") or DEFAULT_CPU_SIZE ram = ex_ram or ex_size_data.get("ram") or DEFAULT_RAM_SIZE bandwidth = ex_bandwidth or size.bandwidth or 10 hourly = ex_hourly local_disk = "true" if ex_size_data.get("local_disk") is False: local_disk = "false" if ex_local_disk is False: local_disk = "false" disk_size = DEFAULT_DISK_SIZE if size.disk: disk_size = size.disk if ex_disk: disk_size = ex_disk datacenter = "" if ex_datacenter: datacenter = ex_datacenter elif location: datacenter = location.id domain = ex_domain if domain is None: if name.find(".") != -1: domain = name[name.find(".") + 1 :] if domain is None: # TODO: domain is a required argument for the Sofylayer API, but it # it shouldn't be. domain = DEFAULT_DOMAIN newCCI = { "hostname": name, "domain": domain, "startCpus": cpu_count, "maxMemory": ram, "networkComponents": [{"maxSpeed": bandwidth}], "hourlyBillingFlag": hourly, "operatingSystemReferenceCode": os, "localDiskFlag": local_disk, "blockDevices": [{"device": "0", "diskImage": {"capacity": disk_size}}], } if datacenter: newCCI["datacenter"] = {"name": datacenter} if ex_keyname: newCCI["sshKeys"] = [{"id": self._key_name_to_id(ex_keyname)}] res = self.connection.request("SoftLayer_Virtual_Guest", "createObject", newCCI).object node_id = res["id"] raw_node = self._get_order_information(node_id) return self._to_node(raw_node)
[docs] def list_key_pairs(self): result = self.connection.request("SoftLayer_Account", "getSshKeys").object elems = [x for x in result] key_pairs = self._to_key_pairs(elems=elems) return key_pairs
[docs] def get_key_pair(self, name): key_id = self._key_name_to_id(name=name) result = self.connection.request( "SoftLayer_Security_Ssh_Key", "getObject", id=key_id ).object return self._to_key_pair(result)
# TODO: Check this with the libcloud guys, # can we create new dependencies?
[docs] def create_key_pair(self, name, ex_size=4096): if crypto is False: raise NotImplementedError("create_key_pair needs" "the cryptography library") key = rsa.generate_private_key( public_exponent=65537, key_size=4096, backend=default_backend() ) public_key = key.public_key().public_bytes( encoding=serialization.Encoding.OpenSSH, format=serialization.PublicFormat.OpenSSH, ) new_key = { "key": public_key, "label": name, "notes": "", } result = self.connection.request( "SoftLayer_Security_Ssh_Key", "createObject", new_key ).object result["private"] = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) return self._to_key_pair(result)
[docs] def import_key_pair_from_string(self, name, key_material): new_key = { "key": key_material, "label": name, "notes": "", } result = self.connection.request( "SoftLayer_Security_Ssh_Key", "createObject", new_key ).object key_pair = self._to_key_pair(result) return key_pair
[docs] def delete_key_pair(self, key_pair): key = self._key_name_to_id(key_pair) result = self.connection.request( "SoftLayer_Security_Ssh_Key", "deleteObject", id=key ).object return result
def _to_image(self, img): return NodeImage( id=img["template"]["operatingSystemReferenceCode"], name=img["itemPrice"]["item"]["description"], driver=self.connection.driver, )
[docs] def list_images(self, location=None): result = self.connection.request("SoftLayer_Virtual_Guest", "getCreateObjectOptions").object return [self._to_image(i) for i in result["operatingSystems"]]
[docs] def get_image(self, image_id): """ Gets an image based on an image_id. :param image_id: Image identifier :type image_id: ``str`` :return: A NodeImage object :rtype: :class:`NodeImage` """ images = self.list_images() images = [image for image in images if image.id == image_id] if len(images) < 1: raise SoftLayerException("could not find the image with id %s" % image_id) image = images[0] return image
def _to_size(self, id, size): return NodeSize( id=id, name=size["name"], ram=size["ram"], disk=size["disk"], bandwidth=size.get("bandwidth"), price=self._get_size_price(str(id)), driver=self.connection.driver, )
[docs] def list_sizes(self, location=None): return [self._to_size(id, s) for id, s in SL_TEMPLATES.items()]
def _to_loc(self, loc): country = "UNKNOWN" loc_id = loc["template"]["datacenter"]["name"] name = loc_id if loc_id in DATACENTERS: country = DATACENTERS[loc_id]["country"] name = DATACENTERS[loc_id].get("name", loc_id) return NodeLocation(id=loc_id, name=name, country=country, driver=self)
[docs] def list_locations(self): res = self.connection.request("SoftLayer_Virtual_Guest", "getCreateObjectOptions").object return [self._to_loc(loc) for loc in res["datacenters"]]
[docs] def list_nodes(self): mask = { "virtualGuests": { "powerState": "", "hostname": "", "maxMemory": "", "datacenter": "", "operatingSystem": {"passwords": ""}, "billingItem": "", }, } res = self.connection.request( "SoftLayer_Account", "getVirtualGuests", object_mask=mask ).object return [self._to_node(h) for h in res]
def _to_key_pairs(self, elems): key_pairs = [self._to_key_pair(elem=elem) for elem in elems] return key_pairs def _to_key_pair(self, elem): key_pair = KeyPair( name=elem["label"], public_key=elem["key"], fingerprint=elem["fingerprint"], private_key=elem.get("private", None), driver=self, extra={"id": elem["id"]}, ) return key_pair def _key_name_to_id(self, name): result = self.connection.request("SoftLayer_Account", "getSshKeys").object key_id = [x for x in result if x["label"] == name] if len(key_id) == 0: raise KeyPairDoesNotExistError(name, self) else: return int(key_id[0]["id"])