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

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

    crypto = True
except ImportError:
    crypto = False

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

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"])