blob: 7b14e1187af39c4c12023f367ac88722972b524e [file] [log] [blame]
# 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.
"""
libcloud driver for the Blue Box Blocks API
This driver implements all libcloud functionality for the Blue Box Blocks API.
Blue Box home page http://bluebox.net
Blue Box API documentation https://boxpanel.bluebox
.net/public/the_vault/index.php/Blocks_API
"""
import copy
import base64
from libcloud.utils.py3 import b, urlencode
from libcloud.common.base import JsonResponse, ConnectionUserAndKey
from libcloud.compute.base import (
Node,
NodeSize,
NodeImage,
NodeDriver,
NodeLocation,
NodeAuthSSHKey,
NodeAuthPassword,
)
from libcloud.compute.types import NodeState, InvalidCredsError
from libcloud.compute.providers import Provider
# Current end point for Blue Box API.
BLUEBOX_API_HOST = "boxpanel.bluebox.net"
# The API doesn't currently expose all of the required values for libcloud,
# so we simply list what's available right now, along with all of the various
# attributes that are needed by libcloud.
BLUEBOX_INSTANCE_TYPES = {
"1gb": {
"id": "94fd37a7-2606-47f7-84d5-9000deda52ae",
"name": "Block 1GB Virtual Server",
"ram": 1024,
"disk": 20,
"cpu": 0.5,
},
"2gb": {
"id": "b412f354-5056-4bf0-a42f-6ddd998aa092",
"name": "Block 2GB Virtual Server",
"ram": 2048,
"disk": 25,
"cpu": 1,
},
"4gb": {
"id": "0cd183d3-0287-4b1a-8288-b3ea8302ed58",
"name": "Block 4GB Virtual Server",
"ram": 4096,
"disk": 50,
"cpu": 2,
},
"8gb": {
"id": "b9b87a5b-2885-4a2e-b434-44a163ca6251",
"name": "Block 8GB Virtual Server",
"ram": 8192,
"disk": 100,
"cpu": 4,
},
}
RAM_PER_CPU = 2048
NODE_STATE_MAP = {
"queued": NodeState.PENDING,
"building": NodeState.PENDING,
"running": NodeState.RUNNING,
"error": NodeState.TERMINATED,
"unknown": NodeState.UNKNOWN,
}
class BlueboxResponse(JsonResponse):
def parse_error(self):
if int(self.status) == 401:
if not self.body:
raise InvalidCredsError(str(self.status) + ": " + self.error)
else:
raise InvalidCredsError(self.body)
return self.body
class BlueboxNodeSize(NodeSize):
def __init__(self, id, name, cpu, ram, disk, price, driver):
self.id = id
self.name = name
self.cpu = cpu
self.ram = ram
self.disk = disk
self.price = price
self.driver = driver
def __repr__(self):
return (
"<NodeSize: id=%s, name=%s, cpu=%s, ram=%s, disk=%s, " "price=%s, driver=%s ...>"
) % (
self.id,
self.name,
self.cpu,
self.ram,
self.disk,
self.price,
self.driver.name,
)
class BlueboxConnection(ConnectionUserAndKey):
"""
Connection class for the Bluebox driver
"""
host = BLUEBOX_API_HOST
secure = True
responseCls = BlueboxResponse
allow_insecure = False
def add_default_headers(self, headers):
user_b64 = base64.b64encode(b("{}:{}".format(self.user_id, self.key)))
headers["Authorization"] = "Basic %s" % (user_b64)
return headers
class BlueboxNodeDriver(NodeDriver):
"""
Bluebox Blocks node driver
"""
connectionCls = BlueboxConnection
type = Provider.BLUEBOX
api_name = "bluebox"
name = "Bluebox Blocks"
website = "http://bluebox.net"
features = {"create_node": ["ssh_key", "password"]}
def list_nodes(self):
result = self.connection.request("/api/blocks.json")
return [self._to_node(i) for i in result.object]
def list_sizes(self, location=None):
sizes = []
for key, values in list(BLUEBOX_INSTANCE_TYPES.items()):
attributes = copy.deepcopy(values)
attributes.update({"price": self._get_size_price(size_id=key)})
sizes.append(BlueboxNodeSize(driver=self.connection.driver, **attributes))
return sizes
def list_images(self, location=None):
result = self.connection.request("/api/block_templates.json")
images = []
for image in result.object:
images.extend([self._to_image(image)])
return images
def create_node(self, name, size, image, auth=None, ex_username=None):
headers = {"Content-Type": "application/x-www-form-urlencoded"}
auth = self._get_and_check_auth(auth)
data = {"hostname": name, "product": size.id, "template": image.id}
ssh = None
password = None
if isinstance(auth, NodeAuthSSHKey):
ssh = auth.pubkey # pylint: disable=no-member
data.update(ssh_public_key=ssh)
elif isinstance(auth, NodeAuthPassword):
password = auth.password
data.update(password=password)
if ex_username:
data.update(username=ex_username)
if not ssh and not password:
raise Exception("SSH public key or password required.")
params = urlencode(data)
result = self.connection.request(
"/api/blocks.json", headers=headers, data=params, method="POST"
)
node = self._to_node(result.object)
if getattr(auth, "generated", False):
node.extra["password"] = auth.password
return node
def destroy_node(self, node):
url = "/api/blocks/%s.json" % (node.id)
result = self.connection.request(url, method="DELETE")
return result.status == 200
def list_locations(self):
return [NodeLocation(0, "Blue Box Seattle US", "US", self)]
def reboot_node(self, node):
url = "/api/blocks/%s/reboot.json" % (node.id)
result = self.connection.request(url, method="PUT")
return result.status == 200
def _to_node(self, vm):
state = NODE_STATE_MAP[vm.get("status", NodeState.UNKNOWN)]
n = Node(
id=vm["id"],
name=vm["hostname"],
state=state,
public_ips=[ip["address"] for ip in vm["ips"]],
private_ips=[],
extra={"storage": vm["storage"], "cpu": vm["cpu"]},
driver=self.connection.driver,
)
return n
def _to_image(self, image):
image = NodeImage(id=image["id"], name=image["description"], driver=self.connection.driver)
return image