Files
awx/awx/lib/site-packages/libcloud/compute/drivers/cloudframes.py
2014-07-25 11:12:13 -05:00

432 lines
15 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.
"""
CloudFrames Driver
"""
# (name, ram, disk, bandwidth, price, vcpus)
SIZES = [
('512mb_1core_10gb', 512, 10, 512, 0.025, 1),
('1024mb_1core_20gb', 1024, 20, 512, 0.05, 1),
('2048mb_2core_50gb', 2048, 50, 1024, 0.10, 2),
('4096mb_2core_100gb', 4096, 100, 2048, 0.20, 2),
('8192mb_4core_200gb', 8192, 200, 2048, 0.40, 4),
('16384mb_4core_400gb', 16384, 400, 4096, 0.80, 4),
]
import base64
import random
from libcloud.utils.py3 import urlparse, b
from libcloud.common.base import ConnectionKey
from libcloud.common.xmlrpc import XMLRPCResponse, XMLRPCConnection
from libcloud.common.types import ProviderError
from libcloud.compute.base import NodeImage, NodeSize, Node, NodeLocation
from libcloud.compute.base import NodeDriver
from libcloud.compute.types import Provider, NodeState
class CloudFramesException(ProviderError):
pass
class CloudFramesComponent(object):
"""
Represents a node in the cloudapi path.
"""
def __init__(self, cloudFramesConnection, name):
self.cloudFramesConnection = cloudFramesConnection
self.name = name
def __getattr__(self, key):
return self.method(key)
def method(self, methodname):
def foo(*args, **kwargs):
async = kwargs.get('async', False)
args = list(args)
args.append('') # jobguid
args.append({'wait': False} if async else {}) # executionparams
response = self.cloudFramesConnection.request(
'cloud_api_%s.%s' % (self.name, methodname), *args)
if not response.success():
response.parse_error()
if async:
return response.parse_body()['jobguid']
else:
return response.parse_body()['result']
return foo
class CloudFramesNodeSize(NodeSize):
def __init__(self, id, name, ram, disk, bandwidth, price, driver,
vcpus=None):
super(CloudFramesNodeSize, self).__init__(
id, name, ram, disk, bandwidth, price, driver)
self.vcpus = vcpus
class CloudFramesNode(Node):
def list_snapshots(self):
return self.driver.ex_list_snapshots(self)
def snapshot(self, label='', description=''):
return self.driver.ex_snapshot_node(self, label, description)
def rollback(self, snapshot):
return self.driver.ex_rollback_node(self, snapshot)
class CloudFramesSnapshot(object):
def __init__(self, id, timestamp, label, description, driver):
self.id = id
self.timestamp = timestamp
self.label = label
self.description = description
self.driver = driver
def destroy(self):
self.driver.ex_destroy_snapshot(self)
class CloudFramesConnection(XMLRPCConnection, ConnectionKey):
"""
Cloudapi connection class
"""
repsonseCls = XMLRPCResponse
base_url = None
def __init__(self, key=None, secret=None, secure=True,
host=None, port=None, url=None, timeout=None):
"""
:param key: The username to connect with to the cloudapi
:type key: ``str``
:param secret: The password to connect with to the cloudapi
:type secret: ``str``
:param secure: Should always be false at the moment
:type secure: ``bool``
:param host: The hostname of the cloudapi
:type host: ``str``
:param port: The port on which to connect to the cloudapi
:type port: ``int``
:param url: Url to the cloudapi (can replace all above)
:type url: ``str``
"""
super(CloudFramesConnection, self).__init__(key=key, secure=secure,
host=host, port=port,
url=url, timeout=timeout)
self._auth = base64.b64encode(
b('%s:%s' % (key, secret))).decode('utf-8')
self.endpoint = url
def __getattr__(self, key):
return CloudFramesComponent(self, key)
def add_default_headers(self, headers):
headers['Authorization'] = 'Basic %s' % self._auth
return headers
class CloudFramesNodeDriver(NodeDriver):
"""
CloudFrames node driver
"""
connectionCls = CloudFramesConnection
name = 'CloudFrames'
api_name = 'cloudframes'
website = 'http://www.cloudframes.net/'
type = Provider.CLOUDFRAMES
NODE_STATE_MAP = {
'CONFIGURED': NodeState.PENDING,
'CREATED': NodeState.PENDING,
'DELETING': NodeState.PENDING,
'HALTED': NodeState.TERMINATED,
'IMAGEONLY': NodeState.UNKNOWN,
'ISCSIEXPOSED': NodeState.PENDING,
'MOVING': NodeState.PENDING,
'OVERLOADED': NodeState.UNKNOWN,
'PAUSED': NodeState.TERMINATED,
'RUNNING': NodeState.RUNNING,
'STARTING': NodeState.PENDING,
'STOPPING': NodeState.PENDING,
'SYNCING': NodeState.PENDING,
'TODELETE': NodeState.PENDING,
}
# subclassed internal methods
def __init__(self, key=None, secret=None, secure=True,
host=None, port=None, url=None, **kwargs):
if not port:
port = 443 if secure else 80
if url:
if not url.endswith('/'):
url += '/'
scheme, netloc, _, _, _, _ = urlparse.urlparse(url)
secure = (scheme == 'https')
if '@' in netloc:
auth, hostport = netloc.rsplit('@', 1)
if ':' in auth:
key, secret = auth.split(':', 1)
else:
key = auth
else:
hostport = netloc
if ':' in hostport:
host, port = hostport.split(':')
else:
host = hostport
hostport = '%s:%s' % (host, port)
url = url.replace(netloc, hostport)
else:
url = '%s://%s:%s/appserver/xmlrpc/' % (
'https' if secure else 'http', host, port)
if secure:
raise NotImplementedError(
'The cloudapi only supports unsecure connections')
if key is None or secret is None:
raise NotImplementedError(
'Unauthenticated support to the cloudapi is not supported')
# connection url
self._url = url
# cached attributes
self.__cloudspaceguid = None
self.__languid = None
self.__locations = []
super(CloudFramesNodeDriver, self).__init__(
key, secret, secure, host, port, **kwargs)
def _ex_connection_class_kwargs(self):
return {'url': self._url}
# internal methods
@property
def _cloudspaceguid(self):
if not self.__cloudspaceguid:
self.__cloudspaceguid = self.connection.cloudspace.find(
'', '', 'cloud', '')[0]
return self.__cloudspaceguid
@property
def _languid(self):
if not self.__languid:
self.__languid = self.connection.lan.find(
'', '', 'public_virtual', '', '', '', '', '', '', '', '', '',
'', '', '', '', '')[0]
return self.__languid
def _get_machine_data(self, guid):
"""
Looks up some basic data related to the given machine guid.
"""
try:
d = self.connection.machine.list('', '', '', guid, '')[0]
except IndexError:
raise CloudFramesException('VM no longer exists', 404, self)
d['public_ips'] = []
d['private_ips'] = []
d['size'] = None
d['image'] = None
return d
def _machine_find(self, template=False, machinetype=None,
machinerole=None):
# the cloudframes xmlrpc api requires you to pass all args and kwargs
# as positional arguments, you can't use keywords arguments
if not machinetype:
guids = []
for machinetype in ['VIRTUALSERVER', 'VIRTUALDESKTOP']:
guids += self.connection.machine.find(
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', machinetype, template, '', '', '', '', '', '', '',
'', '', '', '', '', '', '')
else:
guids = self.connection.machine.find(
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', machinetype, '', '', '', '', '', '', '', '',
machinerole, '', '', '', '', '', '')
return guids
def _to_image(self, image_dict):
return NodeImage(id=image_dict['guid'],
name=image_dict['name'],
driver=self.connection.driver)
def _to_size(self, id, name, ram, disk, bandwidth, price, vcpus):
return CloudFramesNodeSize(
id, name, ram, disk, bandwidth, price, self, vcpus)
def _to_location(self, location_dict):
return NodeLocation(id=location_dict['guid'],
name=location_dict['name'],
country=None,
driver=self)
def _to_node(self, node_dict):
# only return nodes which can be worked with
# (ignore cloudframes internal autotests and deleted nodes)
if node_dict['status'] == 'CONFIGURED':
return None
return CloudFramesNode(id=node_dict['guid'],
name=node_dict['name'],
state=self.NODE_STATE_MAP.get(
node_dict['status'], NodeState.UNKNOWN),
public_ips=node_dict['public_ips'],
private_ips=node_dict['private_ips'],
driver=self.connection.driver,
size=node_dict['size'],
image=node_dict['image'],
extra={})
def _to_snapshot(self, snapshot_dict):
return CloudFramesSnapshot(id=snapshot_dict['guid'],
timestamp=snapshot_dict['timestamp'],
label=snapshot_dict['backuplabel'],
description=snapshot_dict['description'],
driver=self)
# subclassed public methods, and provider specific public methods
def list_images(self, location=None):
image_ids = self._machine_find(template=True)
image_list = []
for image_id in image_ids:
image_list.append(self._to_image(self._get_machine_data(image_id)))
return image_list
def list_sizes(self, location=None):
sizes = []
for id in range(len(SIZES)):
sizes.append(self._to_size(id, *SIZES[id]))
return sizes
def list_locations(self, ex_use_cached=True):
if not self.__locations or not ex_use_cached:
self.__locations = []
for location_id in self._machine_find(machinetype='PHYSICAL',
machinerole='COMPUTENODE'):
self.__locations.append(
self._to_location(self._get_machine_data(location_id)))
return self.__locations
def list_nodes(self):
node_ids = self._machine_find()
node_list = []
for node_id in node_ids:
node = self._to_node(self._get_machine_data(node_id))
if node:
node_list.append(node)
return node_list
def create_node(self, **kwargs):
"""
Creates a new node, by cloning the template provided.
If no location object is passed, a random location will be used.
:param image: The template to be cloned (required)
:type image: ``list`` of :class:`NodeImage`
:param name: The name for the new node (required)
:type name: ``str``
:param size: The size of the new node (required)
:type size: ``list`` of :class:`NodeSize`
:param location: The location to create the new node
:type location: ``list`` of :class:`NodeLocation`
:param default_gateway: The default gateway to be used
:type default_gateway: ``str``
:param extra: Additional requirements (extra disks fi.)
:type extra: ``dict``
:returns: ``list`` of :class:`Node` -- The newly created Node object
:raises: CloudFramesException
"""
additionalinfo = kwargs.get('extra', {})
additionalinfo.update({
'memory': kwargs['size'].ram,
'cpu': kwargs['size'].vcpus,
})
guid = self.connection.machine.createFromTemplate(
self._cloudspaceguid, kwargs['image'].id, kwargs['name'],
[{'languid': self._languid}], kwargs['name'],
kwargs.get('location', random.choice(self.list_locations())).id,
kwargs.get('default_gateway', ''), None, additionalinfo)
if not self.connection.machine.start(guid):
raise CloudFramesException(
'failed to start machine after creation', 500, self)
return self._to_node(self._get_machine_data(guid))
def destroy_node(self, node):
return self.connection.machine.delete(node.id, False)
def reboot_node(self, node, ex_clean=True):
return self.connection.machine.reboot(node.id, ex_clean)
def ex_snapshot_node(self, node, label='', description=''):
guid = self.connection.machine.snapshot(
node.id, label, description, False, False, 'PAUSED')
for snapshot in self.ex_list_snapshots(node):
if snapshot.id == guid:
return snapshot
else:
raise CloudFramesException('Snapshot creation failed', 500, self)
def ex_rollback_node(self, node, snapshot):
if not node.state == NodeState.TERMINATED:
self.connection.machine.stop(node.id, False, 930)
success = self.connection.machine.rollback(node.id, snapshot.id)
self.connection.machine.start(node.id)
return success
def ex_list_snapshots(self, node):
return [self._to_snapshot(snapshot_dict) for snapshot_dict in
self.connection.machine.listSnapshots(node.id, False, '', '')]
def ex_destroy_snapshot(self, node, snapshot):
return self.connection.machine.delete(snapshot.id, False)
if __name__ == "__main__":
import doctest
doctest.testmod()