Source code for libcloud.compute.drivers.cloudframes

# 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


[docs]class CloudFramesException(ProviderError): pass
[docs]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)
[docs] 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
[docs]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
[docs]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)
[docs]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)
[docs]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, retry_delay=None, backoff=None, proxy_url=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, retry_delay=retry_delay, backoff=backoff, proxy_url=proxy_url) self._auth = base64.b64encode( b('%s:%s' % (key, secret))).decode('utf-8') self.endpoint = url def __getattr__(self, key): return CloudFramesComponent(self, key)
[docs] def add_default_headers(self, headers): headers['Authorization'] = 'Basic %s' % self._auth return headers
[docs]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
[docs] 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
[docs] def list_sizes(self, location=None): sizes = [] for id in range(len(SIZES)): sizes.append(self._to_size(id, *SIZES[id])) return sizes
[docs] 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
[docs] 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
[docs] 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))
[docs] def destroy_node(self, node): return self.connection.machine.delete(node.id, False)
[docs] def reboot_node(self, node, ex_clean=True): return self.connection.machine.reboot(node.id, ex_clean)
[docs] 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)
[docs] 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
[docs] def ex_list_snapshots(self, node): return [self._to_snapshot(snapshot_dict) for snapshot_dict in self.connection.machine.listSnapshots(node.id, False, '', '')]
[docs] def ex_destroy_snapshot(self, node, snapshot): return self.connection.machine.delete(snapshot.id, False)
if __name__ == "__main__": import doctest doctest.testmod()