| # 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. |
| """ProfitBricks Compute driver |
| """ |
| import copy |
| import json |
| import time |
| import base64 |
| from collections import defaultdict |
| |
| from libcloud.utils.py3 import b, urlencode |
| from libcloud.common.base import JsonResponse, ConnectionUserAndKey |
| from libcloud.common.types import LibcloudError, MalformedResponseError |
| from libcloud.compute.base import ( |
| Node, |
| NodeSize, |
| NodeImage, |
| UuidMixin, |
| NodeDriver, |
| NodeLocation, |
| StorageVolume, |
| NodeAuthSSHKey, |
| VolumeSnapshot, |
| NodeAuthPassword, |
| ) |
| from libcloud.compute.types import NodeState |
| from libcloud.common.exceptions import BaseHTTPError |
| from libcloud.compute.providers import Provider |
| |
| __all__ = [ |
| "API_VERSION", |
| "API_HOST", |
| "ProfitBricksNodeDriver", |
| "Datacenter", |
| "ProfitBricksNetworkInterface", |
| "ProfitBricksFirewallRule", |
| "ProfitBricksLan", |
| "ProfitBricksIPFailover", |
| "ProfitBricksLoadBalancer", |
| "ProfitBricksAvailabilityZone", |
| "ProfitBricksIPBlock", |
| ] |
| |
| API_HOST = "api.profitbricks.com" |
| API_VERSION = "/cloudapi/v4/" |
| |
| |
| class ProfitBricksResponse(JsonResponse): |
| """ |
| ProfitBricks response parsing. |
| """ |
| |
| def parse_error(self): |
| http_code = None |
| fault_code = None |
| message = None |
| try: |
| body = json.loads(self.body) |
| if "httpStatus" in body: |
| http_code = body["httpStatus"] |
| else: |
| http_code = "unknown" |
| |
| if "messages" in body: |
| message = ", ".join(list(map(lambda item: item["message"], body["messages"]))) |
| fault_code = ", ".join(list(map(lambda item: item["errorCode"], body["messages"]))) |
| else: |
| message = "No messages returned." |
| fault_code = "unknown" |
| except Exception: |
| raise MalformedResponseError( |
| "Failed to parse Json", body=self.body, driver=ProfitBricksNodeDriver |
| ) |
| |
| return LibcloudError( |
| """ |
| HTTP Code: %s, |
| Fault Code(s): %s, |
| Message(s): %s |
| """ |
| % (http_code, fault_code, message), |
| driver=self, |
| ) |
| |
| |
| class ProfitBricksConnection(ConnectionUserAndKey): |
| """ |
| Represents a single connection to the ProfitBricks endpoint. |
| """ |
| |
| host = API_HOST |
| api_prefix = API_VERSION |
| responseCls = ProfitBricksResponse |
| |
| def add_default_headers(self, headers): |
| headers["Authorization"] = "Basic %s" % ( |
| base64.b64encode(b("{}:{}".format(self.user_id, self.key))).decode("utf-8") |
| ) |
| |
| return headers |
| |
| def encode_data(self, data): |
| """ |
| If a string is passed in, just return it |
| or else if a dict is passed in, encode it |
| as a json string. |
| """ |
| if type(data) is str: |
| return data |
| |
| elif type(data) is dict: |
| return json.dumps(data) |
| |
| else: |
| return "" |
| |
| def request( |
| self, |
| action, |
| params=None, |
| data=None, |
| headers=None, |
| method="GET", |
| raw=False, |
| with_full_url=False, |
| ): |
| |
| """ |
| Some requests will use the href attribute directly. |
| If this is not the case, then we should formulate the |
| url based on the action specified. |
| If we are using a full url, we need to remove the |
| host and protocol components. |
| """ |
| if not with_full_url or with_full_url is False: |
| action = self.api_prefix + action.lstrip("/") |
| else: |
| action = action.replace("https://{host}".format(host=self.host), "") |
| |
| return super().request( |
| action=action, |
| params=params, |
| data=data, |
| headers=headers, |
| method=method, |
| raw=raw, |
| ) |
| |
| |
| class Datacenter(UuidMixin): |
| """ |
| Class which stores information about ProfitBricks datacenter |
| instances. |
| |
| :param id: The datacenter ID. |
| :type id: ``str`` |
| |
| :param href: The datacenter href. |
| :type href: ``str`` |
| |
| :param name: The datacenter name. |
| :type name: ``str`` |
| |
| :param version: Datacenter version. |
| :type version: ``str`` |
| |
| :param driver: ProfitBricks Node Driver. |
| :type driver: :class:`ProfitBricksNodeDriver` |
| |
| :param extra: Extra properties for the Datacenter. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific. |
| """ |
| |
| def __init__(self, id, href, name, version, driver, extra=None): |
| self.id = str(id) |
| self.href = href |
| self.name = name |
| self.version = version |
| self.driver = driver |
| self.extra = extra or {} |
| UuidMixin.__init__(self) |
| |
| def __repr__(self): |
| return ("<Datacenter: id=%s, href=%s name=%s, version=%s, driver=%s> ...>") % ( |
| self.id, |
| self.href, |
| self.name, |
| self.version, |
| self.driver.name, |
| ) |
| |
| |
| class ProfitBricksNetworkInterface: |
| """ |
| Class which stores information about ProfitBricks network |
| interfaces. |
| |
| :param id: The network interface ID. |
| :type id: ``str`` |
| |
| :param name: The network interface name. |
| :type name: ``str`` |
| |
| :param href: The network interface href. |
| :type href: ``str`` |
| |
| :param state: The network interface name. |
| :type state: ``int`` |
| |
| :param extra: Extra properties for the network interface. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific. |
| """ |
| |
| def __init__(self, id, name, href, state, extra=None): |
| self.id = id |
| self.name = name |
| self.href = href |
| self.state = state |
| self.extra = extra or {} |
| |
| def __repr__(self): |
| return ("<ProfitBricksNetworkInterface: id=%s, name=%s, href=%s>") % ( |
| self.id, |
| self.name, |
| self.href, |
| ) |
| |
| |
| class ProfitBricksFirewallRule: |
| """ |
| Extension class which stores information about a ProfitBricks |
| firewall rule. |
| |
| :param id: The firewall rule ID. |
| :type id: ``str`` |
| |
| :param name: The firewall rule name. |
| :type name: ``str`` |
| |
| :param href: The firewall rule href. |
| :type href: ``str`` |
| |
| :param state: The current state of the firewall rule. |
| :type state: ``int`` |
| |
| :param extra: Extra properties for the firewall rule. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific. |
| |
| """ |
| |
| def __init__(self, id, name, href, state, extra=None): |
| self.id = id |
| self.name = name |
| self.href = href |
| self.state = state |
| self.extra = extra or {} |
| |
| def __repr__(self): |
| return ("<ProfitBricksFirewallRule: id=%s, name=%s, href=%s>") % ( |
| self.id, |
| self.name, |
| self.href, |
| ) |
| |
| |
| class ProfitBricksLan: |
| """ |
| Extension class which stores information about a |
| ProfitBricks LAN |
| |
| :param id: The ID of the lan. |
| :param id: ``str`` |
| |
| :param name: The name of the lan. |
| :type name: ``str`` |
| |
| :param href: The lan href. |
| :type href: ``str`` |
| |
| :param is_public: If public, the lan faces the public internet. |
| :type is_public: ``bool`` |
| |
| :param state: The current state of the lan. |
| :type state: ``int`` |
| |
| :param extra: Extra properties for the lan. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific. |
| |
| """ |
| |
| def __init__(self, id, name, href, is_public, state, driver, extra=None): |
| self.id = id |
| self.name = name |
| self.href = href |
| self.is_public = is_public |
| self.state = state |
| self.driver = driver |
| self.extra = extra or {} |
| |
| def __repr__(self): |
| return ("<ProfitBricksLan: id=%s, name=%s, href=%s>") % ( |
| self.id, |
| self.name, |
| self.href, |
| ) |
| |
| |
| class ProfitBricksIPFailover: |
| """ |
| Extension class which stores information about a |
| ProfitBricks LAN's failover |
| |
| :param ip: The IP address to fail over. |
| :type ip: ``str`` |
| |
| :param nic_uuid: The ID of the NIC to fail over. |
| :param nic_uuid: ``str`` |
| |
| Note: This class is ProfitBricks specific. |
| |
| """ |
| |
| def __init__(self, ip, nic_uuid): |
| self.ip = ip |
| self.nic_uuid = nic_uuid |
| |
| def __repr__(self): |
| return ("<ProfitBricksIPFailover: ip=%s, nic_uuid=%s>") % ( |
| self.ip, |
| self.nic_uuid, |
| ) |
| |
| |
| class ProfitBricksLoadBalancer: |
| """ |
| Extention class which stores information about a |
| ProfitBricks load balancer |
| |
| :param id: The ID of the load balancer. |
| :param id: ``str`` |
| |
| :param name: The name of the load balancer. |
| :type name: ``str`` |
| |
| :param href: The load balancer href. |
| :type href: ``str`` |
| |
| :param state: The current state of the load balancer. |
| :type state: ``int`` |
| |
| :param extra: Extra properties for the load balancer. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific |
| |
| """ |
| |
| def __init__(self, id, name, href, state, driver, extra=None): |
| self.id = id |
| self.name = name |
| self.href = href |
| self.state = state |
| self.driver = driver |
| self.extra = extra or {} |
| |
| def __repr__(self): |
| return ("ProfitBricksLoadbalancer: id=%s, name=%s, href=%s>") % ( |
| self.id, |
| self.name, |
| self.href, |
| ) |
| |
| |
| class ProfitBricksAvailabilityZone: |
| """ |
| Extension class which stores information about a ProfitBricks |
| availability zone. |
| |
| :param name: The availability zone name. |
| :type name: ``str`` |
| |
| Note: This class is ProfitBricks specific. |
| """ |
| |
| def __init__(self, name): |
| self.name = name |
| |
| def __repr__(self): |
| return ("<ProfitBricksAvailabilityZone: name=%s>") % (self.name) |
| |
| |
| class ProfitBricksIPBlock: |
| """ |
| Extension class which stores information about a ProfitBricks |
| IP block. |
| |
| :param id: The ID of the IP block. |
| :param id: ``str`` |
| |
| :param name: The name of the IP block. |
| :type name: ``str`` |
| |
| :param href: The IP block href. |
| :type href: ``str`` |
| |
| :param location: The location of the IP block. |
| :type location: ``str`` |
| |
| :param size: Number of IP addresses in the block. |
| :type size: ``int`` |
| |
| :param ips: A collection of IPs associated with the block. |
| :type ips: ``list`` |
| |
| :param state: The current state of the IP block. |
| :type state: ``int`` |
| |
| :param extra: Extra properties for the IP block. |
| :type extra: ``dict`` |
| |
| Note: This class is ProfitBricks specific |
| """ |
| |
| def __init__(self, id, name, href, location, size, ips, state, driver, extra=None): |
| |
| self.id = id |
| self.name = name |
| self.href = href |
| self.location = location |
| self.size = size |
| self.ips = ips |
| self.state = state |
| self.driver = driver |
| self.extra = extra or {} |
| |
| def __repr__(self): |
| return ("<ProfitBricksIPBlock: id=%s," "name=%s, href=%s,location=%s, size=%s>") % ( |
| self.id, |
| self.name, |
| self.href, |
| self.location, |
| self.size, |
| ) |
| |
| |
| class ProfitBricksNodeDriver(NodeDriver): |
| """ |
| Base ProfitBricks node driver. |
| """ |
| |
| connectionCls = ProfitBricksConnection |
| name = "ProfitBricks" |
| website = "http://www.profitbricks.com" |
| type = Provider.PROFIT_BRICKS |
| |
| PROVISIONING_STATE = { |
| "AVAILABLE": NodeState.RUNNING, |
| "BUSY": NodeState.PENDING, |
| "INACTIVE": NodeState.PENDING, |
| } |
| |
| NODE_STATE_MAP = { |
| "NOSTATE": NodeState.UNKNOWN, |
| "RUNNING": NodeState.RUNNING, |
| "BLOCKED": NodeState.STOPPED, |
| "PAUSE": NodeState.PAUSED, |
| "SHUTDOWN": NodeState.STOPPING, |
| "SHUTOFF": NodeState.STOPPED, |
| "CRASHED": NodeState.ERROR, |
| "AVAILABLE": NodeState.RUNNING, |
| "BUSY": NodeState.PENDING, |
| } |
| |
| AVAILABILITY_ZONE = { |
| "1": {"name": "AUTO"}, |
| "2": {"name": "ZONE_1"}, |
| "3": {"name": "ZONE_2"}, |
| } |
| |
| """ |
| ProfitBricks is unique in that they allow the user to define all aspects |
| of the instance size, i.e. disk size, core size, and memory size. |
| |
| These are instance types that match up with what other providers support. |
| |
| You can configure disk size, core size, and memory size using the ``ex_`` |
| parameters on the create_node method. |
| """ |
| |
| PROFIT_BRICKS_GENERIC_SIZES = { |
| "1": {"id": "1", "name": "Micro", "ram": 1024, "disk": 50, "cores": 1}, |
| "2": {"id": "2", "name": "Small Instance", "ram": 2048, "disk": 50, "cores": 1}, |
| "3": { |
| "id": "3", |
| "name": "Medium Instance", |
| "ram": 4096, |
| "disk": 50, |
| "cores": 2, |
| }, |
| "4": {"id": "4", "name": "Large Instance", "ram": 7168, "disk": 50, "cores": 4}, |
| "5": { |
| "id": "5", |
| "name": "ExtraLarge Instance", |
| "ram": 14336, |
| "disk": 50, |
| "cores": 8, |
| }, |
| "6": { |
| "id": "6", |
| "name": "Memory Intensive Instance Medium", |
| "ram": 28672, |
| "disk": 50, |
| "cores": 4, |
| }, |
| "7": { |
| "id": "7", |
| "name": "Memory Intensive Instance Large", |
| "ram": 57344, |
| "disk": 50, |
| "cores": 8, |
| }, |
| } |
| |
| """ |
| Core Functions |
| """ |
| |
| def list_sizes(self): |
| """ |
| Lists all sizes |
| |
| :return: A list of all configurable node sizes. |
| :rtype: ``list`` of :class:`NodeSize` |
| """ |
| sizes = [] |
| |
| for key, values in self.PROFIT_BRICKS_GENERIC_SIZES.items(): |
| node_size = self._to_node_size(values) |
| sizes.append(node_size) |
| |
| return sizes |
| |
| def list_images(self, image_type=None, is_public=True): |
| """ |
| List all images with an optional filter. |
| |
| :param image_type: The image type (HDD, CDROM) |
| :type image_type: ``str`` |
| |
| :param is_public: Image is public |
| :type is_public: ``bool`` |
| |
| :return: ``list`` of :class:`NodeImage` |
| :rtype: ``list`` |
| """ |
| response = self.connection.request(action="images", params={"depth": 1}, method="GET") |
| |
| return self._to_images(response.object, image_type, is_public) |
| |
| def list_locations(self): |
| """ |
| List all locations. |
| |
| :return: ``list`` of :class:`NodeLocation` |
| :rtype: ``list`` |
| """ |
| return self._to_locations( |
| self.connection.request(action="locations", params={"depth": 1}, method="GET").object |
| ) |
| |
| """ |
| Node functions |
| """ |
| |
| def list_nodes(self): |
| """ |
| List all nodes. |
| |
| :return: ``list`` of :class:`Node` |
| :rtype: ``list`` |
| """ |
| datacenters = self.ex_list_datacenters() |
| nodes = list() |
| |
| for datacenter in datacenters: |
| servers_href = datacenter.extra["entities"]["servers"]["href"] |
| response = self.connection.request( |
| action=servers_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=True, |
| ) |
| |
| mapped_nodes = self._to_nodes(response.object) |
| nodes += mapped_nodes |
| |
| return nodes |
| |
| def reboot_node(self, node): |
| """ |
| Reboots the node. |
| |
| :rtype: ``bool`` |
| """ |
| action = node.extra["href"] + "/reboot" |
| |
| self.connection.request(action=action, method="POST", with_full_url=True) |
| |
| return True |
| |
| def create_node( |
| self, |
| name, |
| image=None, |
| size=None, |
| location=None, |
| ex_cpu_family=None, |
| volume=None, |
| ex_datacenter=None, |
| ex_network_interface=True, |
| ex_internet_access=True, |
| ex_exposed_public_ports=[], |
| ex_exposed_private_ports=[22], |
| ex_availability_zone=None, |
| ex_ram=None, |
| ex_cores=None, |
| ex_disk=None, |
| ex_password=None, |
| ex_ssh_keys=None, |
| ex_bus_type=None, |
| ex_disk_type=None, |
| **kwargs, |
| ): |
| """ |
| Creates a node. |
| |
| image is optional as long as you pass ram, cores, and disk |
| to the method. ProfitBricks allows you to adjust compute |
| resources at a much more granular level. |
| |
| :param name: The name for the new node. |
| :param type: ``str`` |
| |
| :param image: The image to create the node with. |
| :type image: :class:`NodeImage` |
| |
| :param size: Standard configured size offered by |
| ProfitBricks - containing configuration for the |
| number of cpu cores, amount of ram and disk size. |
| :param size: :class:`NodeSize` |
| |
| :param location: The location of the new data center |
| if one is not supplied. |
| :type location: :class:`NodeLocation` |
| |
| :param ex_cpu_family: The CPU family to use (AMD_OPTERON, INTEL_XEON) |
| :type ex_cpu_family: ``str`` |
| |
| :param volume: If the volume already exists then pass this in. |
| :type volume: :class:`StorageVolume` |
| |
| :param ex_datacenter: If you've already created the DC then pass |
| it in. |
| :type ex_datacenter: :class:`Datacenter` |
| |
| :param ex_network_interface: Create with a network interface. |
| :type ex_network_interface: : ``bool`` |
| |
| :param ex_internet_access: Configure public Internet access. |
| :type ex_internet_access: : ``bool`` |
| |
| :param ex_exposed_public_ports: Ports to be opened |
| for the public nic. |
| :param ex_exposed_public_ports: ``list`` of ``int`` |
| |
| :param ex_exposed_private_ports: Ports to be opened |
| for the private nic. |
| :param ex_exposed_private_ports: ``list`` of ``int`` |
| |
| :param ex_availability_zone: The availability zone. |
| :type ex_availability_zone: class: `ProfitBricksAvailabilityZone` |
| |
| :param ex_ram: The amount of ram required. |
| :type ex_ram: : ``int`` |
| |
| :param ex_cores: The number of cores required. |
| :type ex_cores: ``int`` |
| |
| :param ex_disk: The amount of disk required. |
| :type ex_disk: ``int`` |
| |
| :param ex_password: The password for the volume. |
| :type ex_password: :class:`NodeAuthPassword` or ``str`` |
| |
| :param ex_ssh_keys: Optional SSH keys for the volume. |
| :type ex_ssh_keys: ``list`` of :class:`NodeAuthSSHKey` or |
| ``list`` of ``str`` |
| |
| :param ex_bus_type: Volume bus type (VIRTIO, IDE). |
| :type ex_bus_type: ``str`` |
| |
| :param ex_disk_type: Volume disk type (SSD, HDD). |
| :type ex_disk_type: ``str`` |
| |
| :return: Instance of class ``Node`` |
| :rtype: :class: `Node` |
| """ |
| |
| """ |
| If we have a volume we can determine the DC |
| that it belongs to and set accordingly. |
| """ |
| if volume is not None: |
| dc_url_pruned = volume.extra["href"].split("/")[:-2] |
| dc_url = "/".join(item for item in dc_url_pruned) |
| ex_datacenter = self.ex_describe_datacenter(ex_href=dc_url) |
| |
| if not ex_datacenter: |
| """ |
| Determine location for new DC by |
| getting the location of the image. |
| """ |
| if not location: |
| if image is not None: |
| location = self.ex_describe_location(ex_location_id=image.extra["location"]) |
| |
| """ |
| Creating a Datacenter for the node |
| since one was not provided. |
| """ |
| new_datacenter = self._create_new_datacenter_for_node(name=name, location=location) |
| |
| """ |
| Then wait for the operation to finish, |
| assigning the full data center on completion. |
| """ |
| ex_datacenter = self._wait_for_datacenter_state(datacenter=new_datacenter) |
| |
| if not size: |
| if not ex_ram: |
| raise ValueError( |
| "You need to either pass a " |
| "NodeSize or specify ex_ram as " |
| "an extra parameter." |
| ) |
| if not ex_cores: |
| raise ValueError( |
| "You need to either pass a " |
| "NodeSize or specify ex_cores as " |
| "an extra parameter." |
| ) |
| |
| """ |
| If passing in an image we need |
| to enforce a password or ssh keys. |
| """ |
| if not volume and image is not None: |
| if ex_password is None and ex_ssh_keys is None: |
| raise ValueError( |
| "When creating a server without a " |
| "volume, you need to specify either an " |
| "array of SSH keys or a volume password." |
| ) |
| |
| if not size: |
| if not ex_disk: |
| raise ValueError( |
| "You need to either pass a " |
| "StorageVolume, a NodeSize, or specify " |
| "ex_disk as an extra parameter." |
| ) |
| |
| """ |
| You can override the suggested sizes by passing in unique |
| values for ram, cores, and disk allowing you to size it |
| for your specific use. |
| """ |
| |
| if image is not None: |
| if not ex_disk: |
| ex_disk = size.disk |
| |
| if not ex_disk_type: |
| ex_disk_type = "HDD" |
| |
| if not ex_bus_type: |
| ex_bus_type = "VIRTIO" |
| |
| if not ex_ram: |
| ex_ram = size.ram |
| |
| if not ex_cores: |
| ex_cores = size.extra["cores"] |
| |
| action = ex_datacenter.href + "/servers" |
| body = { |
| "properties": {"name": name, "ram": ex_ram, "cores": ex_cores}, |
| "entities": {"volumes": {"items": []}}, |
| } |
| |
| """ |
| If we are using a pre-existing storage volume. |
| """ |
| if volume is not None: |
| body["entities"]["volumes"]["items"].append({"id": volume.id}) |
| elif image is not None: |
| new_volume = { |
| "properties": { |
| "size": ex_disk, |
| "name": name + " - volume", |
| "image": image.id, |
| "type": ex_disk_type, |
| "bus": ex_bus_type, |
| } |
| } |
| |
| if ex_password is not None: |
| if isinstance(ex_password, NodeAuthPassword): |
| new_volume["properties"]["imagePassword"] = ex_password.password |
| else: |
| new_volume["properties"]["imagePassword"] = ex_password |
| |
| if ex_ssh_keys is not None: |
| if isinstance(ex_ssh_keys[0], NodeAuthSSHKey): |
| new_volume["properties"]["sshKeys"] = [ |
| ssh_key.pubkey for ssh_key in ex_ssh_keys |
| ] |
| else: |
| new_volume["properties"]["sshKeys"] = ex_ssh_keys |
| |
| body["entities"]["volumes"]["items"].append(new_volume) |
| |
| if ex_network_interface is True: |
| body["entities"]["nics"] = {} |
| body["entities"]["nics"]["items"] = list() |
| |
| """ |
| Get the LANs for the data center this node |
| will be provisioned at. |
| """ |
| dc_lans = self.ex_list_lans(datacenter=ex_datacenter) |
| |
| private_lans = [lan for lan in dc_lans if lan.is_public is False] |
| private_lan = None |
| |
| if private_lans: |
| private_lan = private_lans[0] |
| |
| if private_lan is not None: |
| private_nic = { |
| "properties": { |
| "name": name + " - private nic", |
| "lan": private_lan.id, |
| }, |
| "entities": {"firewallrules": {"items": []}}, |
| } |
| |
| for port in ex_exposed_private_ports: |
| private_nic["entities"]["firewallrules"]["items"].append( |
| { |
| "properties": { |
| "name": ( |
| "{name} - firewall rule:{port}".format(name=name, port=port) |
| ), |
| "protocol": "TCP", |
| "portRangeStart": port, |
| "portRangeEnd": port, |
| } |
| } |
| ) |
| |
| body["entities"]["nics"]["items"].append(private_nic) |
| |
| if ex_internet_access is not None and ex_internet_access is True: |
| public_lans = [lan for lan in dc_lans if lan.is_public] |
| public_lan = None |
| |
| if public_lans: |
| public_lan = public_lans[0] |
| |
| if public_lan is not None: |
| pub_nic = { |
| "properties": { |
| "name": name + " - public nic", |
| "lan": public_lan.id, |
| }, |
| "entities": {"firewallrules": {"items": []}}, |
| } |
| |
| for port in ex_exposed_public_ports: |
| pub_nic["entities"]["firewallrules"]["items"].append( |
| { |
| "properties": { |
| "name": ( |
| "{name} - firewall rule:{port}".format(name=name, port=port) |
| ), |
| "protocol": "TCP", |
| "portRangeStart": port, |
| "portRangeEnd": port, |
| } |
| } |
| ) |
| |
| body["entities"]["nics"]["items"].append(pub_nic) |
| |
| if ex_cpu_family is not None: |
| body["properties"]["cpuFamily"] = ex_cpu_family |
| |
| if ex_availability_zone is not None: |
| body["properties"]["availabilityZone"] = ex_availability_zone.name |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_node(response.object, response.headers) |
| |
| def destroy_node(self, node, ex_remove_attached_disks=False): |
| """ |
| Destroys a node. |
| |
| :param node: The node you wish to destroy. |
| :type volume: :class:`Node` |
| |
| :param ex_remove_attached_disks: True to destroy all attached volumes. |
| :type ex_remove_attached_disks: : ``bool`` |
| |
| :rtype: : ``bool`` |
| """ |
| |
| if ex_remove_attached_disks is True: |
| for volume in self.ex_list_attached_volumes(node): |
| self.destroy_volume(volume) |
| |
| action = node.extra["href"] |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def start_node(self, node): |
| """ |
| Starts a node. |
| |
| :param node: The node you wish to start. |
| :type node: :class:`Node` |
| |
| :rtype: ``bool`` |
| """ |
| action = node.extra["href"] + "/start" |
| |
| self.connection.request(action=action, method="POST", with_full_url=True) |
| return True |
| |
| def stop_node(self, node): |
| """ |
| Stops a node. |
| |
| This also deallocates the public IP space. |
| |
| :param node: The node you wish to halt. |
| :type node: :class:`Node` |
| |
| :rtype: : ``bool`` |
| """ |
| action = node.extra["href"] + "/stop" |
| |
| self.connection.request(action=action, method="POST", with_full_url=True) |
| |
| return True |
| |
| """ |
| Volume Functions |
| """ |
| |
| def list_volumes(self): |
| """ |
| List all volumes attached to a data center. |
| |
| :return: ``list`` of :class:`StorageVolume` |
| :rtype: ``list`` |
| """ |
| datacenters = self.ex_list_datacenters() |
| volumes = list() |
| |
| for datacenter in datacenters: |
| volumes_href = datacenter.extra["entities"]["volumes"]["href"] |
| |
| response = self.connection.request( |
| action=volumes_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=True, |
| ) |
| |
| mapped_volumes = self._to_volumes(response.object) |
| volumes += mapped_volumes |
| |
| return volumes |
| |
| def attach_volume(self, node, volume): |
| """ |
| Attaches a volume. |
| |
| :param node: The node to which you're attaching the volume. |
| :type node: :class:`Node` |
| |
| :param volume: The volume you're attaching. |
| :type volume: :class:`StorageVolume` |
| |
| :return: Instance of class ``StorageVolume`` |
| :rtype: :class:`StorageVolume` |
| """ |
| action = node.extra["href"] + "/volumes" |
| body = {"id": volume.id} |
| |
| data = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_volume(data.object, data.headers) |
| |
| def create_volume( |
| self, |
| size, |
| ex_datacenter, |
| name=None, |
| image=None, |
| ex_image_alias=None, |
| ex_type=None, |
| ex_bus_type=None, |
| ex_ssh_keys=None, |
| ex_password=None, |
| ex_availability_zone=None, |
| ): |
| """ |
| Creates a volume. |
| |
| :param size: The size of the volume in GB. |
| :type size: ``int`` |
| |
| :param ex_datacenter: The datacenter you're placing |
| the storage in. (req) |
| :type ex_datacenter: :class:`Datacenter` |
| |
| :param name: The name to be given to the volume. |
| :param name: ``str`` |
| |
| :param image: The OS image for the volume. |
| :type image: :class:`NodeImage` |
| |
| :param ex_image_alias: An alias to a ProfitBricks public image. |
| Use instead of 'image'. |
| :type ex_image_alias: ``str`` |
| |
| :param ex_type: The type to be given to the volume (SSD or HDD). |
| :param ex_type: ``str`` |
| |
| :param ex_bus_type: Bus type. Either IDE or VIRTIO (default). |
| :type ex_bus_type: ``str`` |
| |
| :param ex_ssh_keys: Optional SSH keys. |
| :type ex_ssh_keys: ``list`` of :class:`NodeAuthSSHKey` or |
| ``list`` of ``str`` |
| |
| :param ex_password: Optional password for root. |
| :type ex_password: :class:`NodeAuthPassword` or ``str`` |
| |
| :param ex_availability_zone: Volume Availability Zone. |
| :type ex_availability_zone: ``str`` |
| |
| :return: Instance of class ``StorageVolume`` |
| :rtype: :class:`StorageVolume` |
| """ |
| |
| if not ex_datacenter: |
| raise ValueError("You need to specify a data center" " to attach this volume to.") |
| |
| if image is not None: |
| if image.extra["image_type"] != "HDD": |
| raise ValueError( |
| "Invalid type of {image_type} specified for " |
| "{image_name}, which needs to be of type HDD".format( |
| image_type=image.extra["image_type"], image_name=image.name |
| ) |
| ) |
| |
| if ex_datacenter.extra["location"] != image.extra["location"]: |
| raise ValueError( |
| "The image {image_name} " |
| "(location: {image_location}) you specified " |
| "is not available at the data center " |
| "{datacenter_name} " |
| "(location: {datacenter_location}).".format( |
| image_name=image.extra["name"], |
| datacenter_name=ex_datacenter.extra["name"], |
| image_location=image.extra["location"], |
| datacenter_location=ex_datacenter.extra["location"], |
| ) |
| ) |
| else: |
| if not ex_image_alias: |
| raise ValueError( |
| "You need to specify an image or image alias" " to create this volume from." |
| ) |
| |
| action = ex_datacenter.href + "/volumes" |
| body = {"properties": {"size": size}} |
| |
| if image is not None: |
| body["properties"]["image"] = image.id |
| else: |
| body["properties"]["imageAlias"] = ex_image_alias |
| if name is not None: |
| body["properties"]["name"] = name |
| if ex_type is not None: |
| body["properties"]["type"] = ex_type |
| if ex_bus_type is not None: |
| body["properties"]["bus"] = ex_bus_type |
| if ex_ssh_keys is not None: |
| if isinstance(ex_ssh_keys[0], NodeAuthSSHKey): |
| body["properties"]["sshKeys"] = [ssh_key.pubkey for ssh_key in ex_ssh_keys] |
| else: |
| body["properties"]["sshKeys"] = ex_ssh_keys |
| if ex_password is not None: |
| if isinstance(ex_password, NodeAuthPassword): |
| body["properties"]["imagePassword"] = ex_password.password |
| else: |
| body["properties"]["imagePassword"] = ex_password |
| if ex_availability_zone is not None: |
| body["properties"]["availabilityZone"] = ex_availability_zone |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_volume(response.object, response.headers) |
| |
| def detach_volume(self, node, volume): |
| """ |
| Detaches a volume. |
| |
| :param node: The node to which you're detaching the volume. |
| :type node: :class:`Node` |
| |
| :param volume: The volume you're detaching. |
| :type volume: :class:`StorageVolume` |
| |
| :rtype: :``bool`` |
| """ |
| |
| action = node.extra["href"] + "/volumes/{volume_id}".format(volume_id=volume.id) |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def destroy_volume(self, volume): |
| """ |
| Destroys a volume. |
| |
| :param volume: The volume you're destroying. |
| :type volume: :class:`StorageVolume` |
| |
| :rtype: : ``bool`` |
| """ |
| action = volume.extra["href"] |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| Volume snapshot functions |
| """ |
| |
| def list_snapshots(self): |
| """ |
| Fetches as a list of all snapshots |
| |
| :return: ``list`` of class ``VolumeSnapshot`` |
| :rtype: `list` |
| """ |
| |
| response = self.connection.request(action="snapshots", params={"depth": 3}, method="GET") |
| |
| return self._to_snapshots(response.object) |
| |
| def create_volume_snapshot(self, volume): |
| """ |
| Creates a snapshot for a volume |
| |
| :param volume: The volume you're creating a snapshot for. |
| :type volume: :class:`StorageVolume` |
| |
| :return: Instance of class ``VolumeSnapshot`` |
| :rtype: :class:`VolumeSnapshot` |
| """ |
| |
| action = volume.extra["href"] + "/create-snapshot" |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_snapshot(response.object, response.headers) |
| |
| def destroy_volume_snapshot(self, snapshot): |
| """ |
| Delete a snapshot |
| |
| :param snapshot: The snapshot you wish to delete. |
| :type: snapshot: :class:`VolumeSnapshot` |
| |
| :rtype ``bool`` |
| """ |
| |
| action = snapshot.extra["href"] |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| Extension Functions |
| """ |
| |
| """ |
| Server Extension Functions |
| """ |
| |
| 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) |
| |
| 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 ex_list_availability_zones(self): |
| """ |
| Returns a list of availability zones. |
| |
| :return: ``list`` of :class:`ProfitBricksAvailabilityZone` |
| :rtype: ``list`` |
| """ |
| |
| availability_zones = [] |
| |
| for key, values in self.AVAILABILITY_ZONE.items(): |
| name = copy.deepcopy(values)["name"] |
| |
| availability_zone = ProfitBricksAvailabilityZone(name=name) |
| availability_zones.append(availability_zone) |
| |
| return availability_zones |
| |
| def ex_list_attached_volumes(self, node): |
| """ |
| Returns a list of attached volumes for a server |
| |
| :param node: The node with the attached volumes. |
| :type node: :class:`Node` |
| |
| :return: ``list`` of :class:`StorageVolume` |
| :rtype: ``list`` |
| """ |
| action = node.extra["entities"]["volumes"]["href"] |
| response = self.connection.request( |
| action=action, params={"depth": 3}, method="GET", with_full_url=True |
| ) |
| |
| return self._to_volumes(response.object) |
| |
| def ex_describe_node(self, ex_href=None, ex_datacenter_id=None, ex_node_id=None): |
| """ |
| Fetches a node directly by href or |
| by a combination of the datacenter |
| ID and the server ID. |
| |
| :param ex_href: The href (url) of the node you wish to describe. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID for the data center. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_node_id: The ID for the node (server). |
| :type ex_node_id: ``str`` |
| |
| :return: Instance of class ``Node`` |
| :rtype: :class:`Node` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None or ex_node_id is None: |
| raise ValueError("IDs for the data center and node are required.") |
| else: |
| use_full_url = False |
| ex_href = ("datacenters/{datacenter_id}/" "servers/{server_id}").format( |
| datacenter_id=ex_datacenter_id, server_id=ex_node_id |
| ) |
| |
| response = self.connection.request( |
| action=ex_href, |
| method="GET", |
| params={"depth": 3}, |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_node(response.object) |
| |
| def ex_update_node( |
| self, |
| node, |
| name=None, |
| cores=None, |
| ram=None, |
| availability_zone=None, |
| ex_licence_type=None, |
| ex_boot_volume=None, |
| ex_boot_cdrom=None, |
| ex_cpu_family=None, |
| ): |
| """ |
| Updates a node. |
| |
| :param node: The node you wish to update. |
| :type node: :class:`Node` |
| |
| :param name: The new name for the node. |
| :type name: ``str`` |
| |
| :param cores: The number of CPUs the node should have. |
| :type cores: : ``int`` |
| |
| :param ram: The amount of ram the node should have. |
| :type ram: : ``int`` |
| |
| :param availability_zone: Update the availability zone. |
| :type availability_zone: :class:`ProfitBricksAvailabilityZone` |
| |
| :param ex_licence_type: Licence type (WINDOWS, WINDOWS2016, LINUX, |
| OTHER, UNKNOWN). |
| :type ex_licence_type: ``str`` |
| |
| :param ex_boot_volume: Setting the new boot (HDD) volume. |
| :type ex_boot_volume: :class:`StorageVolume` |
| |
| :param ex_boot_cdrom: Setting the new boot (CDROM) volume. |
| :type ex_boot_cdrom: :class:`StorageVolume` |
| |
| :param ex_cpu_family: CPU family (INTEL_XEON, AMD_OPTERON). |
| :type ex_cpu_family: ``str`` |
| |
| :return: Instance of class ``Node`` |
| :rtype: :class: `Node` |
| """ |
| action = node.extra["href"] |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if cores is not None: |
| body["cores"] = cores |
| |
| if ram is not None: |
| body["ram"] = ram |
| |
| if availability_zone is not None: |
| body["availabilityZone"] = availability_zone.name |
| |
| if ex_licence_type is not None: |
| body["licencetype"] = ex_licence_type |
| |
| if ex_boot_volume is not None: |
| body["bootVolume"] = ex_boot_volume.id |
| |
| if ex_boot_cdrom is not None: |
| body["bootCdrom"] = ex_boot_cdrom.id |
| |
| if ex_cpu_family is not None: |
| body["allowReboot"] = True |
| body["cpuFamily"] = ex_cpu_family |
| |
| response = self.connection.request( |
| action=action, |
| data=body, |
| headers={"Content-Type": "application/json"}, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_node(response.object, response.headers) |
| |
| """ |
| Data center Extension Functions |
| """ |
| |
| def ex_create_datacenter(self, name, location, description=None): |
| """ |
| Creates a datacenter. |
| |
| ProfitBricks has a concept of datacenters. |
| These represent buckets into which you |
| can place various compute resources. |
| |
| :param name: The datacenter name. |
| :type name: : ``str`` |
| |
| :param location: instance of class ``NodeLocation``. |
| :type location: : ``NodeLocation`` |
| |
| :param description: The datacenter description. |
| :type description: : ``str`` |
| |
| :return: Instance of class ``Datacenter`` |
| :rtype: :class:`Datacenter` |
| """ |
| body = {"properties": {"name": name, "location": location.id}} |
| |
| if description is not None: |
| body["properties"]["description"] = description |
| |
| body["entities"] = defaultdict(dict) |
| body["entities"]["lans"]["items"] = [ |
| {"properties": {"name": name + " - public lan", "public": True}}, |
| {"properties": {"name": name + " - private lan", "public": False}}, |
| ] |
| |
| response = self.connection.request( |
| action="datacenters", |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| ) |
| |
| return self._to_datacenter(response.object, response.headers) |
| |
| def ex_destroy_datacenter(self, datacenter): |
| """ |
| Destroys a datacenter. |
| |
| :param datacenter: The DC you're destroying. |
| :type datacenter: :class:`Datacenter` |
| |
| :rtype: : ``bool`` |
| """ |
| action = datacenter.href |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def ex_describe_datacenter(self, ex_href=None, ex_datacenter_id=None): |
| """ |
| Fetches the details for a data center. |
| |
| :param ex_href: The href for the data center |
| you are describing. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID for the data center |
| you are describing. |
| :type ex_datacenter_id: ``str`` |
| |
| :return: Instance of class ``Datacenter`` |
| :rtype: :class:`Datacenter` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None: |
| raise ValueError("The data center ID is required.") |
| else: |
| use_full_url = False |
| ex_href = ("datacenters/{datacenter_id}").format(datacenter_id=ex_datacenter_id) |
| |
| response = self.connection.request( |
| action=ex_href, |
| method="GET", |
| params={"depth": 3}, |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_datacenter(response.object) |
| |
| def ex_list_datacenters(self): |
| """ |
| Lists all datacenters. |
| |
| :return: ``list`` of :class:`DataCenter` |
| :rtype: ``list`` |
| """ |
| response = self.connection.request(action="datacenters", params={"depth": 2}, method="GET") |
| |
| return self._to_datacenters(response.object) |
| |
| def ex_rename_datacenter(self, datacenter, name): |
| """ |
| Update a datacenter. |
| |
| :param datacenter: The DC you are renaming. |
| :type datacenter: :class:`Datacenter` |
| |
| :param name: The DC name. |
| :type name: : ``str`` |
| |
| :return: Instance of class ``Datacenter`` |
| :rtype: :class:`Datacenter` |
| """ |
| action = datacenter.href |
| body = {"name": name} |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_datacenter(response.object, response.headers) |
| |
| """ |
| Image Extension Functions |
| """ |
| |
| def ex_describe_image(self, ex_href=None, ex_image_id=None): |
| """ |
| Describe a ProfitBricks image |
| |
| :param ex_href: The href for the image you are describing |
| :type ex_href: ``str`` |
| |
| :param ex_image_id: The ID for the image you are describing |
| :type ex_image_id: ``str`` |
| |
| :return: Instance of class ``Image`` |
| :rtype: :class:`Image` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_image_id is None: |
| raise ValueError("The image ID is required.") |
| else: |
| use_full_url = False |
| ex_href = ("images/{image_id}").format(image_id=ex_image_id) |
| |
| response = self.connection.request(action=ex_href, method="GET", with_full_url=use_full_url) |
| |
| return self._to_image(response.object) |
| |
| def ex_delete_image(self, image): |
| """ |
| Delete a private image |
| |
| :param image: The private image you are deleting. |
| :type image: :class:`NodeImage` |
| |
| :rtype: : ``bool`` |
| """ |
| |
| self.connection.request(action=image.extra["href"], method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def ex_update_image( |
| self, |
| image, |
| name=None, |
| description=None, |
| licence_type=None, |
| cpu_hot_plug=None, |
| cpu_hot_unplug=None, |
| ram_hot_plug=None, |
| ram_hot_unplug=None, |
| nic_hot_plug=None, |
| nic_hot_unplug=None, |
| disc_virtio_hot_plug=None, |
| disc_virtio_hot_unplug=None, |
| disc_scsi_hot_plug=None, |
| disc_scsi_hot_unplug=None, |
| ): |
| """ |
| Update a private image |
| |
| :param image: The private image you are deleting. |
| :type image: :class:`NodeImage` |
| |
| :return: Instance of class ``Image`` |
| :rtype: :class:`Image` |
| """ |
| action = image.extra["href"] |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if description is not None: |
| body["description"] = description |
| |
| if licence_type is not None: |
| body["licenceType"] = licence_type |
| |
| if cpu_hot_plug is not None: |
| body["cpuHotPlug"] = cpu_hot_plug |
| |
| if cpu_hot_unplug is not None: |
| body["cpuHotUnplug"] = cpu_hot_unplug |
| |
| if ram_hot_plug is not None: |
| body["ramHotPlug"] = ram_hot_plug |
| |
| if ram_hot_unplug is not None: |
| body["ramHotUnplug"] = ram_hot_unplug |
| |
| if nic_hot_plug is not None: |
| body["nicHotPlug"] = nic_hot_plug |
| |
| if nic_hot_unplug is not None: |
| body["nicHotUnplug"] = nic_hot_unplug |
| |
| if disc_virtio_hot_plug is not None: |
| body["discVirtioHotPlug"] = disc_virtio_hot_plug |
| |
| if disc_virtio_hot_unplug is not None: |
| body["discVirtioHotUnplug"] = disc_virtio_hot_unplug |
| |
| if disc_scsi_hot_plug is not None: |
| body["discScsiHotPlug"] = disc_scsi_hot_plug |
| |
| if disc_scsi_hot_unplug is not None: |
| body["discScsiHotUnplug"] = disc_scsi_hot_unplug |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_image(response.object, response.headers) |
| |
| """ |
| Location Extension Functions |
| """ |
| |
| def ex_describe_location(self, ex_href=None, ex_location_id=None): |
| """ |
| Fetch details for a ProfitBricks location. |
| |
| :param ex_href: The href for the location |
| you are describing. |
| :type ex_href: ``str`` |
| |
| :param ex_location_id: The id for the location you are |
| describing ('de/fra', 'de/fkb', 'us/las', 'us/ewr') |
| :type ex_location_id: ``str`` |
| |
| :return: Instance of class ``NodeLocation`` |
| :rtype: :class:`NodeLocation` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_location_id is None: |
| raise ValueError("The location ID is required.") |
| else: |
| use_full_url = False |
| ex_href = ("locations/{location_id}").format(location_id=ex_location_id) |
| |
| response = self.connection.request(action=ex_href, method="GET", with_full_url=use_full_url) |
| |
| return self._to_location(response.object) |
| |
| """ |
| Network Interface Extension Functions |
| """ |
| |
| def ex_list_network_interfaces(self): |
| """ |
| Fetch a list of all network interfaces from all data centers. |
| |
| :return: ``list`` of class ``ProfitBricksNetworkInterface`` |
| :rtype: `list` |
| """ |
| nodes = self.list_nodes() |
| nics = list() |
| |
| for node in nodes: |
| action = node.extra["entities"]["nics"]["href"] |
| nics += self._to_interfaces( |
| self.connection.request( |
| action=action, params={"depth": 1}, method="GET", with_full_url=True |
| ).object |
| ) |
| |
| return nics |
| |
| def ex_describe_network_interface( |
| self, ex_href=None, ex_datacenter_id=None, ex_server_id=None, ex_nic_id=None |
| ): |
| """ |
| Fetch information on a network interface. |
| |
| :param ex_href: The href of the NIC you wish to describe. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID of parent data center |
| of the NIC you wish to describe. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_server_id: The server the NIC is connected to. |
| :type ex_server_id: ``str`` |
| |
| :param ex_nic_id: The ID of the NIC |
| :type ex_nic_id: ``str`` |
| |
| :return: Instance of class ``ProfitBricksNetworkInterface`` |
| :rtype: :class:`ProfitBricksNetworkInterface` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None or ex_server_id is None or ex_nic_id is None: |
| raise ValueError( |
| ( |
| "IDs are required for the data center", |
| "server and network interface.", |
| ) |
| ) |
| else: |
| use_full_url = False |
| ex_href = ( |
| "datacenters/{datacenter_id}" "/servers/{server_id}" "/nics/{nic_id}" |
| ).format( |
| datacenter_id=ex_datacenter_id, |
| server_id=ex_server_id, |
| nic_id=ex_nic_id, |
| ) |
| |
| response = self.connection.request(action=ex_href, method="GET", with_full_url=use_full_url) |
| |
| return self._to_interface(response.object) |
| |
| def ex_create_network_interface( |
| self, node, lan_id=None, ips=None, nic_name=None, dhcp_active=True |
| ): |
| """ |
| Creates a network interface. |
| |
| :param lan_id: The ID for the LAN. |
| :type lan_id: : ``int`` |
| |
| :param ips: The IP addresses for the NIC. |
| :type ips: ``list`` |
| |
| :param nic_name: The name of the NIC, e.g. PUBLIC. |
| :type nic_name: ``str`` |
| |
| :param dhcp_active: Set to false to disable. |
| :type dhcp_active: ``bool`` |
| |
| :return: Instance of class ``ProfitBricksNetworkInterface`` |
| :rtype: :class:`ProfitBricksNetworkInterface` |
| """ |
| |
| if lan_id is not None: |
| lan_id = str(lan_id) |
| |
| else: |
| lan_id = str(1) |
| |
| action = node.extra["href"] + "/nics" |
| body = {"properties": {"lan": lan_id, "dhcp": dhcp_active}} |
| |
| if ips is not None: |
| body["properties"]["ips"] = ips |
| |
| if nic_name is not None: |
| body["properties"]["name"] = nic_name |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_interface(response.object, response.headers) |
| |
| def ex_update_network_interface( |
| self, network_interface, name=None, lan_id=None, ips=None, dhcp_active=None |
| ): |
| """ |
| Updates a network interface. |
| |
| :param network_interface: The network interface being updated. |
| :type network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :param name: The name of the NIC, e.g. PUBLIC. |
| :type name: ``str`` |
| |
| :param lan_id: The ID for the LAN. |
| :type lan_id: : ``int`` |
| |
| :param ips: The IP addresses for the NIC as a list. |
| :type ips: ``list`` |
| |
| :param dhcp_active: Set to false to disable. |
| :type dhcp_active: ``bool`` |
| |
| :return: Instance of class ``ProfitBricksNetworkInterface`` |
| :rtype: :class:`ProfitBricksNetworkInterface` |
| """ |
| |
| if lan_id: |
| lan_id = str(lan_id) |
| |
| action = network_interface.href |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if lan_id is not None: |
| body["lan"] = str(lan_id) |
| |
| if ips is not None: |
| body["ips"] = ips |
| |
| if dhcp_active is not None: |
| body["dhcp"] = dhcp_active |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_interface(response.object, response.headers) |
| |
| def ex_destroy_network_interface(self, network_interface): |
| """ |
| Destroy a network interface. |
| |
| :param network_interface: The NIC you wish to describe. |
| :type network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :rtype: : ``bool`` |
| """ |
| |
| action = network_interface.href |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def ex_set_inet_access(self, network_interface, internet_access=True): |
| """ |
| Add/remove public internet access to an interface. |
| |
| :param network_interface: The NIC you wish to update. |
| :type network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :return: Instance of class ``ProfitBricksNetworkInterface`` |
| :rtype: :class:`ProfitBricksNetworkInterface` |
| """ |
| |
| action = network_interface.href |
| body = {"nat": internet_access} |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_interface(response.object, response.headers) |
| |
| """ |
| Firewall Rule Extension Functions |
| """ |
| |
| def ex_list_firewall_rules(self, network_interface): |
| """ |
| Fetch firewall rules for a network interface. |
| |
| :param network_interface: The network interface. |
| :type network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :return: ``list`` of class ``ProfitBricksFirewallRule`` |
| :rtype: `list` |
| """ |
| action = network_interface.href + "/firewallrules" |
| response = self.connection.request( |
| action=action, method="GET", params={"depth": 3}, with_full_url=True |
| ) |
| |
| return self._to_firewall_rules(response.object) |
| |
| def ex_describe_firewall_rule( |
| self, |
| ex_href=None, |
| ex_datacenter_id=None, |
| ex_server_id=None, |
| ex_nic_id=None, |
| ex_firewall_rule_id=None, |
| ): |
| """ |
| Fetch data for a firewall rule. |
| |
| :param href: The href of the firewall rule you wish to describe. |
| :type href: ``str`` |
| |
| :param ex_datacenter_id: The ID of parent data center |
| of the NIC you wish to describe. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_server_id: The server the NIC is connected to. |
| :type ex_server_id: ``str`` |
| |
| :param ex_nic_id: The ID of the NIC. |
| :type ex_nic_id: ``str`` |
| |
| :param ex_firewall_rule_id: The ID of the firewall rule. |
| :type ex_firewall_rule_id: ``str`` |
| |
| :return: Instance class ``ProfitBricksFirewallRule`` |
| :rtype: :class:`ProfitBricksFirewallRule` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ( |
| ex_datacenter_id is None |
| or ex_server_id is None |
| or ex_nic_id is None |
| or ex_firewall_rule_id is None |
| ): |
| raise ValueError( |
| ( |
| "IDs are required for the data " "center, server, network interface", |
| "and firewall rule.", |
| ) |
| ) |
| else: |
| use_full_url = False |
| ex_href = ( |
| "datacenters/{datacenter_id}" |
| "/servers/{server_id}" |
| "/nics/{nic_id}" |
| "/firewallrules/{firewall_rule_id}" |
| ).format( |
| datacenter_id=ex_datacenter_id, |
| server_id=ex_server_id, |
| nic_id=ex_nic_id, |
| firewall_rule_id=ex_firewall_rule_id, |
| ) |
| |
| response = self.connection.request(action=ex_href, method="GET", with_full_url=use_full_url) |
| |
| return self._to_firewall_rule(response.object) |
| |
| def ex_create_firewall_rule( |
| self, |
| network_interface, |
| protocol, |
| name=None, |
| source_mac=None, |
| source_ip=None, |
| target_ip=None, |
| port_range_start=None, |
| port_range_end=None, |
| icmp_type=None, |
| icmp_code=None, |
| ): |
| """ |
| Create a firewall rule for a network interface. |
| |
| :param network_interface: The network interface to |
| attach the firewall rule to. |
| :type: network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :param protocol: The protocol for the rule (TCP, UDP, ICMP, ANY) |
| :type protocol: ``str`` |
| |
| :param name: The name for the firewall rule |
| :type name: ``str`` |
| |
| :param source_mac: Only traffic originating from the respective |
| MAC address is allowed. |
| Valid format: aa:bb:cc:dd:ee:ff. |
| Value null allows all source MAC address. |
| :type source_mac: ``str`` |
| |
| :param source_ip: Only traffic originating from the respective IPv4 |
| address is allowed. Value null allows all source IPs. |
| :type source_ip: ``str`` |
| |
| :param target_ip: In case the target NIC has multiple IP addresses, |
| only traffic directed to the respective IP address |
| of the NIC is allowed. |
| Value null allows all target IPs. |
| :type target_ip: ``str`` |
| |
| :param port_range_start: Defines the start range of the allowed port |
| (from 1 to 65534) if protocol TCP or UDP is chosen. |
| Leave portRangeStart and portRangeEnd value null |
| to allow all ports. |
| type: port_range_start: ``int`` |
| |
| :param port_range_end: Defines the end range of the allowed port |
| (from 1 to 65534) if protocol TCP or UDP is chosen. |
| Leave portRangeStart and portRangeEnd value null |
| to allow all ports. |
| type: port_range_end: ``int`` |
| |
| :param icmp_type: Defines the allowed type (from 0 to 254) if the |
| protocol ICMP is chosen. Value null allows all types. |
| :type icmp_type: ``int`` |
| |
| :param icmp_code: Defines the allowed code (from 0 to 254) if |
| protocol ICMP is chosen. Value null allows all codes. |
| :type icmp_code: ``int`` |
| |
| :return: Instance class ``ProfitBricksFirewallRule`` |
| :rtype: :class:`ProfitBricksFirewallRule` |
| """ |
| |
| action = network_interface.href + "/firewallrules" |
| body = {"properties": {"protocol": protocol}} |
| |
| if name is not None: |
| body["properties"]["name"] = name |
| |
| if source_mac is not None: |
| body["properties"]["sourceMac"] = source_mac |
| |
| if source_ip is not None: |
| body["properties"]["sourceIp"] = source_ip |
| |
| if target_ip is not None: |
| body["properties"]["targetIp"] = target_ip |
| |
| if port_range_start is not None: |
| body["properties"]["portRangeStart"] = str(port_range_start) |
| |
| if port_range_end is not None: |
| body["properties"]["portRangeEnd"] = str(port_range_end) |
| |
| if icmp_type is not None: |
| body["properties"]["icmpType"] = str(icmp_type) |
| |
| if icmp_code is not None: |
| body["properties"]["icmpType"] = str(icmp_code) |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_firewall_rule(response.object, response.headers) |
| |
| def ex_update_firewall_rule( |
| self, |
| firewall_rule, |
| name=None, |
| source_mac=None, |
| source_ip=None, |
| target_ip=None, |
| port_range_start=None, |
| port_range_end=None, |
| icmp_type=None, |
| icmp_code=None, |
| ): |
| """ |
| Update a firewall rule |
| |
| :param firewall_rule: The firewall rule to update |
| :type: firewall_rule: :class:`ProfitBricksFirewallRule` |
| |
| :param name: The name for the firewall rule |
| :type name: ``str`` |
| |
| :param source_mac: Only traffic originating from the respective |
| MAC address is allowed. |
| Valid format: aa:bb:cc:dd:ee:ff. |
| Value null allows all source MAC address. |
| :type source_mac: ``str`` |
| |
| :param source_ip: Only traffic originating from the respective IPv4 |
| address is allowed. Value null allows all source IPs. |
| :type source_ip: ``str`` |
| |
| :param target_ip: In case the target NIC has multiple IP addresses, |
| only traffic directed to the respective IP address |
| of the NIC is allowed. |
| Value null allows all target IPs. |
| :type target_ip: ``str`` |
| |
| :param port_range_start: Defines the start range of the allowed port |
| (from 1 to 65534) if protocol TCP or UDP is chosen. |
| Leave portRangeStart and portRangeEnd value null |
| to allow all ports. |
| type: port_range_start: ``int`` |
| |
| :param port_range_end: Defines the end range of the allowed port |
| (from 1 to 65534) if protocol TCP or UDP is chosen. |
| Leave portRangeStart and portRangeEnd value null |
| to allow all ports. |
| type: port_range_end: ``int`` |
| |
| :param icmp_type: Defines the allowed type (from 0 to 254) if the |
| protocol ICMP is chosen. Value null allows all types. |
| :type icmp_type: ``int`` |
| |
| :param icmp_code: Defines the allowed code (from 0 to 254) if |
| protocol ICMP is chosen. Value null allows all codes. |
| :type icmp_code: ``int`` |
| |
| :return: Instance class ``ProfitBricksFirewallRule`` |
| :rtype: :class:`ProfitBricksFirewallRule` |
| """ |
| |
| action = firewall_rule.href |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if source_mac is not None: |
| body["sourceMac"] = source_mac |
| |
| if source_ip is not None: |
| body["sourceIp"] = source_ip |
| |
| if target_ip is not None: |
| body["targetIp"] = target_ip |
| |
| if port_range_start is not None: |
| body["portRangeStart"] = str(port_range_start) |
| |
| if port_range_end is not None: |
| body["portRangeEnd"] = str(port_range_end) |
| |
| if icmp_type is not None: |
| body["icmpType"] = str(icmp_type) |
| |
| if icmp_code is not None: |
| body["icmpType"] = str(icmp_code) |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_firewall_rule(response.object, response.headers) |
| |
| def ex_delete_firewall_rule(self, firewall_rule): |
| """ |
| Delete a firewall rule |
| |
| :param firewall_rule: The firewall rule to delete. |
| :type: firewall_rule: :class:`ProfitBricksFirewallRule` |
| |
| :rtype ``bool`` |
| """ |
| action = firewall_rule.href |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| LAN extension functions |
| """ |
| |
| def ex_list_lans(self, datacenter=None): |
| """ |
| List local area network on: |
| - a datacenter if one is specified |
| - all datacenters if none specified |
| |
| :param datacenter: The parent DC for the LAN. |
| :type datacenter: :class:`Datacenter` |
| |
| :return: ``list`` of class ``ProfitBricksLan`` |
| :rtype: `list` |
| """ |
| if datacenter is not None: |
| action = datacenter.extra["entities"]["lans"]["href"] |
| request = self.connection.request( |
| action=action, params={"depth": 3}, method="GET", with_full_url=True |
| ) |
| lans = self._to_lans(request.object) |
| |
| else: |
| datacenters = self.ex_list_datacenters() |
| lans = [] |
| for datacenter in datacenters: |
| action = datacenter.extra["entities"]["lans"]["href"] |
| request = self.connection.request( |
| action=action, params={"depth": 3}, method="GET", with_full_url=True |
| ) |
| lans += self._to_lans(request.object) |
| |
| return lans |
| |
| def ex_create_lan(self, datacenter, name=None, is_public=False, nics=None): |
| """ |
| Create and attach a Lan to a data center. |
| |
| :param datacenter: The parent DC for the LAN.. |
| :type datacenter: :class:`Datacenter` |
| |
| :param name: LAN name. |
| :type name: ``str`` |
| |
| :param is_public: True if the Lan is to have internet access. |
| :type is_public: ``bool`` |
| |
| :param nics: Optional network interfaces to attach to the lan. |
| :param nics: ``list`` of class ``ProfitBricksNetworkInterface`` |
| |
| :return: Instance class ``ProfitBricksLan`` |
| :rtype: :class:`ProfitBricksLan` |
| """ |
| |
| action = datacenter.extra["entities"]["lans"]["href"] |
| body = { |
| "properties": { |
| "name": name or "LAN - {datacenter_name}".format(datacenter_name=datacenter.name), |
| "public": is_public, |
| } |
| } |
| |
| if nics is not None: |
| body["entities"] = defaultdict(dict) |
| body["entities"]["nics"]["items"] = [{"id": nic.id} for nic in nics] |
| |
| request = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_lan(request.object, request.headers) |
| |
| def ex_describe_lan(self, ex_href=None, ex_datacenter_id=None, ex_lan_id=None): |
| """ |
| Fetch data on a local area network |
| |
| :param ex_href: The href of the lan you wish to describe. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID of the parent |
| datacenter for the LAN. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_lan_id: The ID of LAN. |
| :type ex_lan_id: ``str`` |
| |
| :return: Instance class ``ProfitBricksLan`` |
| :rtype: :class:`ProfitBricksLan` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None or ex_lan_id is None: |
| raise ValueError("IDs for the data center and LAN are required.") |
| else: |
| use_full_url = False |
| ex_href = ("datacenters/{datacenter_id}/" "lans/{lan_id}").format( |
| datacenter_id=ex_datacenter_id, lan_id=ex_lan_id |
| ) |
| |
| response = self.connection.request( |
| action=ex_href, |
| method="GET", |
| params={"depth": 1}, |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_lan(response.object) |
| |
| def ex_update_lan(self, lan, is_public, name=None, ip_failover=None): |
| """ |
| Update a local area network |
| |
| :param lan: The lan you wish to update. |
| :type: lan: :class:`ProfitBricksLan` |
| |
| :param is_public: Boolean indicating if |
| the lan faces the public internet. |
| :type is_public: ``bool`` |
| |
| :param name: The name of the lan. |
| :type name: ``str`` |
| |
| :param ip_failover: The IP to fail over. |
| :type ip_failover: ``list`` of :class: ``ProfitBricksIPFailover`` |
| |
| :return: Instance class ``ProfitBricksLan`` |
| :rtype: :class:`ProfitBricksLan` |
| """ |
| action = lan.href |
| body = {"public": is_public} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if ip_failover is not None: |
| body["ipFailover"] = [{"ip": item.ip, "nicUuid": item.nic_uuid} for item in ip_failover] |
| |
| request = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_lan(request.object, request.headers) |
| |
| def ex_delete_lan(self, lan): |
| """ |
| Delete a local area network |
| |
| :param lan: The lan you wish to delete. |
| :type: lan: :class:`ProfitBrickLan` |
| |
| :rtype ``bool`` |
| """ |
| |
| action = lan.href |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| Volume extension functions |
| """ |
| |
| def ex_update_volume(self, volume, ex_storage_name=None, size=None, ex_bus_type=None): |
| """ |
| Updates a volume. |
| |
| :param volume: The volume you're updating. |
| :type volume: :class:`StorageVolume` |
| |
| :param ex_storage_name: The name of the volume. |
| :type ex_storage_name: ``str`` |
| |
| :param size: The desired size. |
| :type size: ``int`` |
| |
| :param ex_bus_type: Volume bus type (VIRTIO, IDE). |
| :type ex_bus_type: ``str`` |
| |
| :return: Instance of class ``StorageVolume`` |
| :rtype: :class:`StorageVolume` |
| """ |
| |
| if not ex_storage_name: |
| ex_storage_name = volume.name |
| if not size: |
| size = str(volume.size) |
| |
| action = volume.extra["href"] |
| body = {"name": ex_storage_name, "size": size} |
| |
| if ex_bus_type is not None: |
| body["bus"] = ex_bus_type |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_volume(response.object, response.headers) |
| |
| def ex_describe_volume(self, ex_href=None, ex_datacenter_id=None, ex_volume_id=None): |
| """ |
| Fetches and returns a volume |
| |
| :param ex_href: The full href (url) of the volume. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID of the parent |
| datacenter for the volume. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_volume_id: The ID of the volume. |
| :type ex_volume_id: ``str`` |
| |
| :return: Instance of class ``StorageVolume`` |
| :rtype: :class:`StorageVolume` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None or ex_volume_id is None: |
| raise ValueError("IDs for the data center and volume are required.") |
| else: |
| use_full_url = False |
| ex_href = ("datacenters/{datacenter_id}/" "volumes/{volume_id}").format( |
| datacenter_id=ex_datacenter_id, volume_id=ex_volume_id |
| ) |
| |
| response = self.connection.request( |
| action=ex_href, |
| method="GET", |
| params={"depth": 3}, |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_volume(response.object) |
| |
| def ex_restore_volume_snapshot(self, volume, snapshot): |
| """ |
| Restores a snapshot for a volume |
| |
| :param volume: The volume you're restoring the snapshot to. |
| :type volume: :class:`StorageVolume` |
| |
| :param snapshot: The snapshot you're restoring to the volume. |
| :type snapshot: :class:`ProfitBricksSnapshot` |
| |
| :rtype ``bool`` |
| """ |
| |
| action = volume.extra["href"] + "/restore-snapshot" |
| data = {"snapshotId": snapshot.id} |
| body = urlencode(data) |
| |
| self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return True |
| |
| """ |
| Volume snapshot extension functions |
| """ |
| |
| def ex_describe_snapshot(self, ex_href=None, ex_snapshot_id=None): |
| """ |
| Fetches and returns a volume snapshot |
| |
| :param ex_href: The full href (url) of the snapshot. |
| :type ex_href: ``str`` |
| |
| :param ex_snapshot_id: The ID of the snapshot. |
| :type ex_snapshot_id: ``str`` |
| |
| :return: Instance of class ``ProfitBricksSnapshot`` |
| :rtype: :class:`ProfitBricksSnapshot` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_snapshot_id is None: |
| raise ValueError("The snapshot ID is required.") |
| else: |
| use_full_url = False |
| ex_href = ("snapshots/{snapshot_id}").format(snapshot_id=ex_snapshot_id) |
| |
| response = self.connection.request( |
| action=ex_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_snapshot(response.object) |
| |
| def ex_update_snapshot( |
| self, |
| snapshot, |
| name=None, |
| description=None, |
| cpu_hot_plug=None, |
| cpu_hot_unplug=None, |
| ram_hot_plug=None, |
| ram_hot_unplug=None, |
| nic_hot_plug=None, |
| nic_hot_unplug=None, |
| disc_virtio_hot_plug=None, |
| disc_virtio_hot_unplug=None, |
| disc_scsi_hot_plug=None, |
| disc_scsi_hot_unplug=None, |
| licence_type=None, |
| ): |
| """ |
| Updates a snapshot |
| |
| :param snapshot: The snapshot you're restoring to the volume. |
| :type snapshot: :class:`VolumeSnapshot` |
| |
| :param name: The snapshot name |
| :type name: `str` |
| |
| :param description: The snapshot description |
| :type description: `str` |
| |
| :param cpu_hot_plug: Snapshot CPU is hot pluggalbe |
| :type cpu_hot_plug: `str` |
| |
| :param cpu_hot_unplug: Snapshot CPU is hot unpluggalbe |
| :type cpu_hot_unplug: `str` |
| |
| :param ram_hot_plug: Snapshot RAM is hot pluggalbe |
| :type ram_hot_plug: `str` |
| |
| :param ram_hot_unplug: Snapshot RAM is hot unpluggalbe |
| :type ram_hot_unplug: `str` |
| |
| :param nic_hot_plug: Snapshot Network Interface is hot pluggalbe |
| :type nic_hot_plug: `str` |
| |
| :param nic_hot_unplug: Snapshot Network Interface is hot unpluggalbe |
| :type nic_hot_unplug: `str` |
| |
| :param disc_virtio_hot_plug: Snapshot VIRTIO disk is hot pluggalbe |
| :type disc_virtio_hot_plug: `str` |
| |
| :param disc_virtio_hot_unplug: Snapshot VIRTIO disk is hot unpluggalbe |
| :type disc_virtio_hot_unplug: `str` |
| |
| :param disc_scsi_hot_plug: Snapshot SCSI disk is hot pluggalbe |
| :type disc_scsi_hot_plug: `str` |
| |
| :param disc_scsi_hot_unplug: Snapshot SCSI disk is hot unpluggalbe |
| :type disc_scsi_hot_unplug: `str` |
| |
| :param licence_type: The snapshot licence_type |
| :type licence_type: `str` |
| |
| :return: Instance of class ``VolumeSnapshot`` |
| :rtype: :class:`VolumeSnapshot` |
| """ |
| |
| action = snapshot.extra["href"] |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if description is not None: |
| body["description"] = description |
| |
| if cpu_hot_plug is not None: |
| body["cpuHotPlug"] = cpu_hot_plug |
| |
| if cpu_hot_unplug is not None: |
| body["cpuHotUnplug"] = cpu_hot_unplug |
| |
| if ram_hot_plug is not None: |
| body["ramHotPlug"] = ram_hot_plug |
| |
| if ram_hot_unplug is not None: |
| body["ramHotUnplug"] = ram_hot_unplug |
| |
| if nic_hot_plug is not None: |
| body["nicHotPlug"] = nic_hot_plug |
| |
| if nic_hot_unplug is not None: |
| body["nicHotUnplug"] = nic_hot_unplug |
| |
| if disc_virtio_hot_plug is not None: |
| body["discVirtioHotPlug"] = disc_virtio_hot_plug |
| |
| if disc_virtio_hot_unplug is not None: |
| body["discVirtioHotUnplug"] = disc_virtio_hot_unplug |
| |
| if disc_scsi_hot_plug is not None: |
| body["discScsiHotPlug"] = disc_scsi_hot_plug |
| |
| if disc_scsi_hot_unplug is not None: |
| body["discScsiHotUnplug"] = disc_scsi_hot_unplug |
| |
| if licence_type is not None: |
| body["licenceType"] = licence_type |
| |
| response = self.connection.request( |
| action=action, |
| params={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_snapshot(response.object, response.headers) |
| |
| """ |
| Load balancer extension functions |
| """ |
| |
| def ex_list_load_balancers(self): |
| """ |
| Fetches as a list of load balancers |
| |
| :return: ``list`` of class ``ProfitBricksLoadBalancer`` |
| :rtype: `list` |
| """ |
| |
| datacenters = self.ex_list_datacenters() |
| load_balancers = list() |
| |
| for datacenter in datacenters: |
| extra = datacenter.extra |
| load_balancers_href = extra["entities"]["loadbalancers"]["href"] |
| |
| response = self.connection.request( |
| action=load_balancers_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=True, |
| ) |
| |
| mapped_load_balancers = self._to_load_balancers(response.object) |
| load_balancers += mapped_load_balancers |
| |
| return load_balancers |
| |
| def ex_describe_load_balancer( |
| self, ex_href=None, ex_datacenter_id=None, ex_load_balancer_id=None |
| ): |
| """ |
| Fetches and returns a load balancer |
| |
| :param href: The full href (url) of the load balancer. |
| :type href: ``str`` |
| |
| :param ex_datacenter_id: The ID of the parent data center |
| for the load balancer. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_load_balancer_id: The load balancer ID. |
| :type ex_load_balancer_id: ``str`` |
| |
| :return: Instance of class ``ProfitBricksLoadBalancer`` |
| :rtype: :class:`ProfitBricksLoadBalancer` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_datacenter_id is None or ex_load_balancer_id is None: |
| raise ValueError("IDs for the data center and " "load balancer are required.") |
| else: |
| use_full_url = False |
| ex_href = ( |
| "datacenters/{datacenter_id}/" "loadbalancers/{load_balancer_id}" |
| ).format(datacenter_id=ex_datacenter_id, load_balancer_id=ex_load_balancer_id) |
| |
| response = self.connection.request( |
| action=ex_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_load_balancer(response.object) |
| |
| def ex_create_load_balancer(self, datacenter, name=None, ip=None, dhcp=None, nics=None): |
| """ |
| Create and attach a load balancer to a data center. |
| |
| :param datacenter: The parent DC for the load balancer. |
| :type datacenter: :class:`Datacenter` |
| |
| :param name: Load balancer name. |
| :type name: ``str`` |
| |
| :param ip: Load balancer IPV4 address. |
| :type ip: ``str`` |
| |
| :param dhcp: If true, the load balancer |
| will reserve an IP address using DHCP. |
| :type dhcp: ``bool`` |
| |
| :param nics: Optional network interfaces |
| taking part in load balancing. |
| :param nics: ``list`` of class ``ProfitBricksNetworkInterface`` |
| |
| :return: Instance class ``ProfitBricksLoadBalancer`` |
| :rtype: :class:`ProfitBricksLoadBalancer` |
| """ |
| |
| action = datacenter.extra["entities"]["loadbalancers"]["href"] |
| body = { |
| "properties": { |
| "name": name |
| or "Load Balancer - {datacenter_name}".format(datacenter_name=datacenter.name) |
| } |
| } |
| |
| if ip is not None: |
| body["properties"]["ip"] = ip |
| |
| if dhcp is not None: |
| body["properties"]["dhcp"] = dhcp |
| |
| if nics is not None: |
| body["entities"] = defaultdict(dict) |
| body["entities"]["balancednics"]["items"] = [{"id": nic.id} for nic in nics] |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return self._to_load_balancer(response.object, response.headers) |
| |
| def ex_update_load_balancer(self, load_balancer, name=None, ip=None, dhcp=None): |
| """ |
| Update a load balancer |
| |
| :param load_balancer: The load balancer you wish to update. |
| :type: load_balancer: :class:`ProfitBricksLoadBalancer` |
| |
| :param name: The name of the load balancer. |
| :type name: ``str`` |
| |
| :param ip: The IPV4 address of the load balancer. |
| :type ip: ``str`` |
| |
| :param dhcp: If true, the load balancer |
| will reserve an IP address using DHCP. |
| :type dhcp: ``bool`` |
| |
| :return: Instance class ``ProfitBricksLoadBalancer`` |
| :rtype: :class:`ProfitBricksLoadBalancer` |
| """ |
| action = load_balancer.href |
| body = {} |
| |
| if name is not None: |
| body["name"] = name |
| |
| if ip is not None: |
| body["ip"] = ip |
| |
| if dhcp is not None: |
| body["dhcp"] = dhcp |
| |
| response = self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="PATCH", |
| with_full_url=True, |
| ) |
| |
| return self._to_load_balancer(response.object, response.headers) |
| |
| def ex_list_load_balanced_nics(self, load_balancer): |
| """ |
| List balanced network interfaces for a load balancer. |
| |
| :param load_balancer: The load balancer you wish to update. |
| :type: load_balancer: :class:`ProfitBricksLoadBalancer` |
| |
| :return: ``list`` of class ``ProfitBricksNetorkInterface`` |
| :rtype: `list` |
| """ |
| action = load_balancer.extra["entities"]["balancednics"]["href"] |
| |
| response = self.connection.request( |
| action=action, params={"depth": 3}, method="GET", with_full_url=True |
| ) |
| |
| return self._to_interfaces(response.object) |
| |
| def ex_describe_load_balanced_nic( |
| self, ex_href=None, ex_datacenter_id=None, ex_server_id=None, ex_nic_id=None |
| ): |
| """ |
| Fetch information on a load balanced network interface. |
| |
| :param ex_href: The href of the load balanced |
| NIC you wish to describe. |
| :type ex_href: ``str`` |
| |
| :param ex_datacenter_id: The ID of parent data center |
| of the NIC you wish to describe. |
| :type ex_datacenter_id: ``str`` |
| |
| :param ex_server_id: The server the NIC is connected to. |
| :type ex_server_id: ``str`` |
| |
| :param ex_nic_id: The ID of the NIC |
| :type ex_nic_id: ``str`` |
| |
| :return: Instance of class ``ProfitBricksNetworkInterface`` |
| :rtype: :class:`ProfitBricksNetworkInterface` |
| """ |
| return self.ex_describe_network_interface( |
| ex_href=ex_href, |
| ex_datacenter_id=ex_datacenter_id, |
| ex_server_id=ex_server_id, |
| ex_nic_id=ex_nic_id, |
| ) |
| |
| def ex_attach_nic_to_load_balancer(self, load_balancer, network_interface): |
| """ |
| Attaches a network interface to a load balancer |
| |
| :param load_balancer: The load balancer you wish |
| to attach the network interface to. |
| :type: load_balancer: :class:`ProfitBricksLoadBalancer` |
| |
| :param network_interface: The network interface |
| being attached. |
| :type: network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :rtype ``bool`` |
| """ |
| action = load_balancer.extra["entities"]["balancednics"]["href"] |
| body = {"id": network_interface.id} |
| |
| self.connection.request( |
| action=action, |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| with_full_url=True, |
| ) |
| |
| return True |
| |
| def ex_remove_nic_from_load_balancer(self, load_balancer, network_interface): |
| """ |
| Removed a network interface from a load balancer |
| |
| :param load_balancer: The load balancer you |
| wish to remove the network interface from. |
| :type: load_balancer: :class:`ProfitBricksLoadBalancer` |
| |
| :param network_interface: The network interface |
| being removed. |
| :type: network_interface: :class:`ProfitBricksNetworkInterface` |
| |
| :rtype ``bool`` |
| """ |
| action = load_balancer.href + "/balancednics/" + network_interface.id |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| def ex_delete_load_balancer(self, load_balancer): |
| """ |
| Delete a load balancer |
| |
| :param load_balancer: The load balancer you wish to delete. |
| :type: load_balancer: :class:`ProfitBricksLoadBalancer` |
| |
| :rtype ``bool`` |
| """ |
| |
| action = load_balancer.href |
| |
| self.connection.request(action=action, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| IP Block extension functions |
| """ |
| |
| def ex_list_ip_blocks(self): |
| """ |
| List all IP blocks |
| |
| :return: ``list`` of class ``ProfitBricksIPBlock`` |
| :rtype: `list` |
| """ |
| |
| response = self.connection.request(action="ipblocks", params={"depth": 3}, method="GET") |
| |
| return self._to_ip_blocks(response.object) |
| |
| def ex_create_ip_block(self, location, size, name=None): |
| """ |
| Create an IP block |
| |
| :param location: The location of the IP block. |
| :type location: :class:`NodeLocation` |
| |
| :param size: The size of the IP block. |
| :type size: ``int`` |
| |
| :param name: The name of the IP block. |
| :type name: ``str`` |
| |
| :return: Instance class ``ProfitBricksIPBlock`` |
| :rtype: :class:`ProfitBricksIPBlock` |
| """ |
| |
| body = {"properties": {"location": location.id, "size": size}} |
| |
| if name is not None: |
| body["properties"]["name"] = name |
| |
| response = self.connection.request( |
| action="ipblocks", |
| headers={"Content-Type": "application/json"}, |
| data=body, |
| method="POST", |
| ) |
| |
| return self._to_ip_block(response.object, response.headers) |
| |
| def ex_describe_ip_block(self, ex_href=None, ex_ip_block_id=None): |
| """ |
| Fetch an IP block |
| |
| :param ex_href: The href of the IP block. |
| :type ex_href: ``str`` |
| |
| :param ex_ip_block_id: The ID of the IP block. |
| :type ex_ip_block_id: ``str`` |
| |
| :return: Instance class ``ProfitBricksIPBlock`` |
| :rtype: :class:`ProfitBricksIPBlock` |
| """ |
| |
| use_full_url = True |
| |
| if ex_href is None: |
| if ex_ip_block_id is None: |
| raise ValueError("The IP block ID is required.") |
| else: |
| use_full_url = False |
| ex_href = ("ipblocks/{ip_block_id}").format(ip_block_id=ex_ip_block_id) |
| |
| response = self.connection.request( |
| action=ex_href, |
| params={"depth": 3}, |
| method="GET", |
| with_full_url=use_full_url, |
| ) |
| |
| return self._to_ip_block(response.object) |
| |
| def ex_delete_ip_block(self, ip_block): |
| """ |
| Delete an IP block |
| |
| :param ip_block: The IP block you wish to delete. |
| :type: ip_block: :class:`ProfitBricksIPBlock` |
| |
| :rtype ``bool`` |
| """ |
| |
| self.connection.request(action=ip_block.href, method="DELETE", with_full_url=True) |
| |
| return True |
| |
| """ |
| Private Functions |
| """ |
| |
| def _to_ip_blocks(self, object): |
| return [self._to_ip_block(ip_block) for ip_block in object["items"]] |
| |
| def _to_ip_block(self, ip_block, headers=None): |
| nested = {"metadata": ip_block["metadata"]} |
| |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| } |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| state = self.NODE_STATE_MAP.get(ip_block["metadata"]["state"], NodeState.UNKNOWN) |
| |
| # self, id, name, href, location, size, ips, state, driver, extra=None |
| |
| return ProfitBricksIPBlock( |
| id=ip_block["id"], |
| name=ip_block["properties"]["name"], |
| href=ip_block["href"], |
| location=ip_block["properties"]["location"], |
| size=ip_block["properties"]["size"], |
| ips=ip_block["properties"]["ips"] or [], |
| state=state, |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_load_balancers(self, object): |
| return [self._to_load_balancer(load_balancer) for load_balancer in object["items"]] |
| |
| def _to_load_balancer(self, load_balancer, headers=None): |
| nested = { |
| "props": load_balancer["properties"], |
| "metadata": load_balancer["metadata"], |
| } |
| |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| "props": {"name": "name", "ip": "ip", "dhcp": "dhcp"}, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| if "entities" in load_balancer: |
| extra["entities"] = load_balancer["entities"] |
| |
| state = self.NODE_STATE_MAP.get(load_balancer["metadata"]["state"], NodeState.UNKNOWN) |
| |
| return ProfitBricksLoadBalancer( |
| id=load_balancer["id"], |
| name=load_balancer["properties"]["name"], |
| href=load_balancer["href"], |
| state=state, |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_snapshots(self, object): |
| return [self._to_snapshot(snapshot) for snapshot in object["items"]] |
| |
| def _to_snapshot(self, snapshot, headers=None): |
| nested = {"props": snapshot["properties"], "metadata": snapshot["metadata"]} |
| |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| "props": { |
| "name": "name", |
| "description": "description", |
| "location": "location", |
| "size": "size", |
| "cpuHotPlug": "cpu_hot_plug", |
| "cpuHotUnplug": "cpu_hot_unplug", |
| "ramHotPlug": "ram_hot_plug", |
| "ramHotUnplug": "ram_hot_unplug", |
| "nicHotPlug": "nic_hot_plug", |
| "nicHotUnplug": "nic_hot_unplug", |
| "discVirtioHotPlug": "disc_virtio_hot_plug", |
| "discVirtioHotUnplug": "disc_virtio_hot_unplug", |
| "discScsiHotPlug": "disc_scsi_hot_plug", |
| "discScsiHotUnplug": "disc_scsi_hot_unplug", |
| "licenceType": "licence_type", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| state = self.NODE_STATE_MAP.get(snapshot["metadata"]["state"], NodeState.UNKNOWN) |
| |
| extra["href"] = snapshot["href"] |
| |
| return VolumeSnapshot( |
| id=snapshot["id"], |
| driver=self.connection.driver, |
| size=extra["size"], |
| extra=extra, |
| created=extra["created_date"], |
| state=state, |
| name=extra["name"], |
| ) |
| |
| def _to_lans(self, object): |
| return [self._to_lan(lan) for lan in object["items"]] |
| |
| def _to_lan(self, lan, headers=None): |
| nested = {"props": lan["properties"], "metadata": lan["metadata"]} |
| |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| "props": {"name": "name", "public": "is_public"}, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if "entities" in lan: |
| extra["entities"] = lan["entities"] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| extra["provisioning_state"] = self.PROVISIONING_STATE.get( |
| lan["metadata"]["state"], NodeState.UNKNOWN |
| ) |
| |
| state = self.NODE_STATE_MAP.get(lan["metadata"]["state"], NodeState.UNKNOWN) |
| |
| return ProfitBricksLan( |
| id=lan["id"], |
| name=lan["properties"]["name"], |
| href=lan["href"], |
| is_public=lan["properties"]["public"], |
| state=state, |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_datacenters(self, object): |
| return [self._to_datacenter(datacenter) for datacenter in object["items"]] |
| |
| def _to_datacenter(self, datacenter, headers=None): |
| nested = {"props": datacenter["properties"], "metadata": datacenter["metadata"]} |
| |
| if "entities" in datacenter: |
| nested["entities"] = datacenter["entities"] |
| |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| "props": { |
| "description": "description", |
| "features": "features", |
| "location": "location", |
| "name": "name", |
| "version": "version", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if "entities" in datacenter: |
| extra["entities"] = datacenter["entities"] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| extra["provisioning_state"] = self.PROVISIONING_STATE.get( |
| datacenter["metadata"]["state"], NodeState.UNKNOWN |
| ) |
| |
| return Datacenter( |
| id=datacenter["id"], |
| href=datacenter["href"], |
| name=datacenter["properties"]["name"], |
| version=datacenter["properties"]["version"], |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_images(self, object, image_type=None, is_public=True): |
| |
| if image_type is not None: |
| images = [ |
| image |
| for image in object["items"] |
| if image["properties"]["imageType"] == image_type |
| and image["properties"]["public"] == is_public |
| ] |
| else: |
| images = [ |
| image for image in object["items"] if image["properties"]["public"] == is_public |
| ] |
| |
| return [self._to_image(image) for image in images] |
| |
| def _to_image(self, image, headers=None): |
| nested = {"props": image["properties"], "metadata": image["metadata"]} |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| "props": { |
| "name": "name", |
| "description": "description", |
| "location": "location", |
| "size": "size", |
| "cpuHotPlug": "cpu_hot_plug", |
| "cpuHotUnplug": "cpu_hot_unplug", |
| "ramHotPlug": "ram_hot_plug", |
| "ramHotUnplug": "ram_hot_unplug", |
| "nicHotPlug": "nic_hot_plug", |
| "nicHotUnplug": "nic_hot_unplug", |
| "discVirtioHotPlug": "disc_virtio_hot_plug", |
| "discVirtioHotUnplug": "disc_virtio_hot_unplug", |
| "discScsiHotPlug": "disc_scsi_hot_plug", |
| "discScsiHotUnplug": "disc_scsi_hot_unplug", |
| "licenceType": "licence_type", |
| "imageType": "image_type", |
| "imageAliases": "image_aliases", |
| "public": "public", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| """ |
| Put the href inside extra |
| because we cannot assign |
| it to the NodeImage type. |
| """ |
| extra["href"] = image["href"] |
| |
| return NodeImage( |
| id=image["id"], |
| name=image["properties"]["name"], |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_nodes(self, object): |
| return [self._to_node(n) for n in object["items"]] |
| |
| def _to_node(self, node, headers=None): |
| """ |
| Convert the request into a node Node |
| """ |
| nested = { |
| "props": node["properties"], |
| "metadata": node["metadata"], |
| "entities": node["entities"], |
| } |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "props": { |
| "name": "name", |
| "availabilityZone": "availability_zone", |
| "bootCdrom": "boot_cdrom", |
| "bootVolume": "boot_volume", |
| "cores": "cores", |
| "cpuFamily": "cpu_family", |
| "ram": "ram", |
| "vmState": "vm_state", |
| }, |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedBy": "last_modified_by", |
| "lastModifiedDate": "last_modified_date", |
| "state": "state", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| state = self.NODE_STATE_MAP.get(node["properties"]["vmState"], NodeState.UNKNOWN) |
| |
| extra["entities"] = nested["entities"] |
| extra["href"] = node["href"] |
| |
| public_ips = [] |
| private_ips = [] |
| |
| if "nics" in nested["entities"]: |
| if "items" in nested["entities"]["nics"]: |
| for nic in nested["entities"]["nics"]["items"]: |
| if nic["properties"]["nat"] is True: |
| public_ips += nic["properties"]["ips"] |
| elif nic["properties"]["nat"] is False: |
| private_ips += nic["properties"]["ips"] |
| |
| return Node( |
| id=node["id"], |
| name=nested["props"]["name"], |
| state=state, |
| public_ips=public_ips, |
| private_ips=private_ips, |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_volumes(self, object): |
| return [self._to_volume(volume) for volume in object["items"]] |
| |
| def _to_volume(self, volume, headers=None): |
| |
| nested = {"props": volume["properties"], "metadata": volume["metadata"]} |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "props": { |
| "bus": "bus", |
| "size": "size", |
| "cpuHotPlug": "cpu_hot_plug", |
| "cpuHotUnplug": "cpu_hot_unplug", |
| "discScsiHotPlug": "disc_scsi_hot_plug", |
| "discScsiHotUnplug": "disc_scsi_hot_unplug", |
| "discVirtioHotPlug": "disc_virtio_hot_plug", |
| "discVirtioHotUnplug": "disc_virtio_hot_unplug", |
| "image": "image", |
| "imagePassword": "image_password", |
| "licenceType": "licence_type", |
| "name": "name", |
| "nicHotPlug": "nic_hot_plug", |
| "nicHotUnplug": "nic_hot_unplug", |
| "ramHotPlug": "ram_hot_plug", |
| "ramHotUnplug": "ram_hot_unplug", |
| "sshKeys": "ssh_keys", |
| "type": "type", |
| "deviceNumber": "device_number", |
| }, |
| "metadata": { |
| "createdBy": "created_by", |
| "createdDate": "created_date", |
| "etag": "etag", |
| "lastModifiedBy": "last_modified_by", |
| "lastModifiedDate": "last_modified_date", |
| "state": "state", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| extra["provisioning_state"] = self.PROVISIONING_STATE.get( |
| volume["metadata"]["state"], NodeState.UNKNOWN |
| ) |
| |
| extra["href"] = volume["href"] |
| |
| if "availabilityZone" in volume["properties"]: |
| properties = volume["properties"] |
| extra["availability_zone"] = properties["availabilityZone"] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| return StorageVolume( |
| id=volume["id"], |
| name=volume["properties"]["name"], |
| size=volume["properties"]["size"], |
| driver=self.connection.driver, |
| extra=extra, |
| ) |
| |
| def _to_interfaces(self, object): |
| return [self._to_interface(interface) for interface in object["items"]] |
| |
| def _to_interface(self, interface, headers=None): |
| nested = {"props": interface["properties"], "metadata": interface["metadata"]} |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "props": { |
| "dhcp": "dhcp", |
| "firewallActive": "firewall_active", |
| "ips": "ips", |
| "lan": "lan", |
| "mac": "mac", |
| "name": "name", |
| "nat": "nat", |
| }, |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedBy": "last_modified_by", |
| "lastModifiedDate": "last_modified_date", |
| "state": "state", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if "entities" in interface: |
| extra["entities"] = interface["entities"] |
| |
| state = self.NODE_STATE_MAP.get(interface["metadata"]["state"], NodeState.UNKNOWN) |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| return ProfitBricksNetworkInterface( |
| id=interface["id"], |
| name=interface["properties"]["name"], |
| href=interface["href"], |
| state=state, |
| extra=extra, |
| ) |
| |
| def _to_firewall_rules(self, object): |
| return [self._to_firewall_rule(firewall_rule) for firewall_rule in object["items"]] |
| |
| def _to_firewall_rule(self, firewallrule, headers=None): |
| nested = { |
| "props": firewallrule["properties"], |
| "metadata": firewallrule["metadata"], |
| } |
| extra = {} |
| |
| MAPPED_ATTRS = { |
| "props": { |
| "name": "name", |
| "protocol": "protocol", |
| "sourceMac": "source_mac", |
| "sourceIp": "source_ip", |
| "targetIp": "target_ip", |
| "icmpCode": "icmp_code", |
| "icmpType": "icmp_type", |
| "portRangeStart": "port_range_start", |
| "portRangeEnd": "port_range_end", |
| }, |
| "metadata": { |
| "createdDate": "created_date", |
| "createdBy": "created_by", |
| "etag": "etag", |
| "lastModifiedDate": "last_modified_date", |
| "lastModifiedBy": "last_modified_by", |
| "state": "state", |
| }, |
| } |
| |
| for k, v in MAPPED_ATTRS.items(): |
| for original_name, altered_name in v.items(): |
| extra[altered_name] = nested[k][original_name] |
| |
| if headers is not None: |
| if "location" in headers: |
| extra["status_url"] = headers["location"] |
| |
| state = self.NODE_STATE_MAP.get(firewallrule["metadata"]["state"], NodeState.UNKNOWN) |
| |
| return ProfitBricksFirewallRule( |
| id=firewallrule["id"], |
| name=firewallrule["properties"]["name"], |
| href=firewallrule["href"], |
| state=state, |
| extra=extra, |
| ) |
| |
| def _to_locations(self, object): |
| return [self._to_location(location) for location in object["items"]] |
| |
| def _to_location(self, location): |
| return NodeLocation( |
| id=location["id"], |
| name=location["properties"]["name"], |
| country=location["id"].split("/")[0], |
| driver=self.connection.driver, |
| ) |
| |
| def _to_node_size(self, data): |
| """ |
| Convert the PROFIT_BRICKS_GENERIC_SIZES into NodeSize |
| """ |
| return NodeSize( |
| id=data["id"], |
| name=data["name"], |
| ram=data["ram"], |
| disk=data["disk"], |
| bandwidth=None, |
| price=None, |
| driver=self.connection.driver, |
| extra={"cores": data["cores"]}, |
| ) |
| |
| def _wait_for_datacenter_state( |
| self, datacenter, state=NodeState.RUNNING, timeout=300, interval=5 |
| ): |
| """ |
| Private function that waits the datacenter |
| to transition into the specified state. |
| |
| :return: Datacenter object on success. |
| :rtype: :class:`.Datacenter` |
| """ |
| |
| wait_time = 0 |
| attempts = 0 |
| |
| while attempts < 5: |
| attempts += 1 |
| try: |
| datacenter = self.ex_describe_datacenter(ex_datacenter_id=datacenter.id) |
| break |
| |
| except BaseHTTPError: |
| time.sleep(interval) |
| |
| if datacenter is None: |
| raise Exception("Data center was not ready in time to " "complete this operation.") |
| |
| while datacenter.extra["provisioning_state"] != state: |
| datacenter = self.ex_describe_datacenter(ex_href=datacenter.href) |
| |
| if datacenter.extra["provisioning_state"] == state: |
| break |
| |
| if wait_time >= timeout: |
| raise Exception( |
| "Datacenter didn't transition to %s state " "in %s seconds" % (state, timeout) |
| ) |
| |
| wait_time += interval |
| time.sleep(interval) |
| |
| return datacenter |
| |
| def _create_new_datacenter_for_node(self, name, location=None): |
| """ |
| Creates a Datacenter for a node. |
| """ |
| dc_name = name + "-DC" |
| |
| if location is None: |
| location = self.ex_describe_location(ex_location_id="us/las") |
| |
| return self.ex_create_datacenter(name=dc_name, location=location) |