231 lines
7.3 KiB
Python
231 lines
7.3 KiB
Python
# 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 urlencode
|
|
from libcloud.utils.py3 import b
|
|
|
|
from libcloud.common.base import JsonResponse, ConnectionUserAndKey
|
|
from libcloud.compute.providers import Provider
|
|
from libcloud.compute.types import NodeState, InvalidCredsError
|
|
from libcloud.compute.base import Node, NodeDriver
|
|
from libcloud.compute.base import NodeSize, NodeImage, NodeLocation
|
|
from libcloud.compute.base import NodeAuthPassword, NodeAuthSSHKey
|
|
|
|
# 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('%s:%s' % (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
|