You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2011/03/08 00:44:12 UTC
svn commit: r1079029 [2/13] - in /incubator/libcloud/trunk: ./ demos/ dist/
libcloud/ libcloud/common/ libcloud/compute/ libcloud/compute/drivers/
libcloud/drivers/ libcloud/storage/ libcloud/storage/drivers/ test/
test/compute/ test/compute/fixtures/ ...
Added: incubator/libcloud/trunk/libcloud/compute/base.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/base.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/base.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/base.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,573 @@
+# 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.
+
+"""
+Provides base classes for working with drivers
+"""
+import httplib, urllib
+import time
+import hashlib
+import StringIO
+import ssl
+import os
+import socket
+import struct
+
+from libcloud.common.base import ConnectionKey, ConnectionUserAndKey
+from libcloud.compute.types import NodeState, DeploymentError
+from libcloud.compute.ssh import SSHClient
+from libcloud.httplib_ssl import LibcloudHTTPSConnection
+from httplib import HTTPConnection as LibcloudHTTPConnection
+
+class Node(object):
+ """
+ Provide a common interface for handling nodes of all types.
+
+ The Node object provides the interface in libcloud through which
+ we can manipulate nodes in different cloud providers in the same
+ way. Node objects don't actually do much directly themselves,
+ instead the node driver handles the connection to the node.
+
+ You don't normally create a node object yourself; instead you use
+ a driver and then have that create the node for you.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node = driver.create_node()
+ >>> node.public_ip[0]
+ '127.0.0.3'
+ >>> node.name
+ 'dummy-3'
+
+ You can also get nodes from the driver's list_node function.
+
+ >>> node = driver.list_nodes()[0]
+ >>> node.name
+ 'dummy-1'
+
+ the node keeps a reference to its own driver which means that we
+ can work on nodes from different providers without having to know
+ which is which.
+
+ >>> driver = DummyNodeDriver(72)
+ >>> node2 = driver.create_node()
+ >>> node.driver.creds
+ 0
+ >>> node2.driver.creds
+ 72
+
+ Althrough Node objects can be subclassed, this isn't normally
+ done. Instead, any driver specific information is stored in the
+ "extra" proproperty of the node.
+
+ >>> node.extra
+ {'foo': 'bar'}
+
+ """
+
+ def __init__(self, id, name, state, public_ip, private_ip,
+ driver, extra=None):
+ self.id = str(id) if id else None
+ self.name = name
+ self.state = state
+ self.public_ip = public_ip
+ self.private_ip = private_ip
+ self.driver = driver
+ self.uuid = self.get_uuid()
+ if not extra:
+ self.extra = {}
+ else:
+ self.extra = extra
+
+ def get_uuid(self):
+ """Unique hash for this node
+
+ @return: C{string}
+
+ The hash is a function of an SHA1 hash of the node's ID and
+ its driver which means that it should be unique between all
+ nodes. In some subclasses (e.g. GoGrid) there is no ID
+ available so the public IP address is used. This means that,
+ unlike a properly done system UUID, the same UUID may mean a
+ different system install at a different time
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node = driver.create_node()
+ >>> node.get_uuid()
+ 'd3748461511d8b9b0e0bfa0d4d3383a619a2bb9f'
+
+ Note, for example, that this example will always produce the
+ same UUID!
+ """
+ return hashlib.sha1("%s:%d" % (self.id,self.driver.type)).hexdigest()
+
+ def reboot(self):
+ """Reboot this node
+
+ @return: C{bool}
+
+ This calls the node's driver and reboots the node
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node = driver.create_node()
+ >>> from libcloud.compute.types import NodeState
+ >>> node.state == NodeState.RUNNING
+ True
+ >>> node.state == NodeState.REBOOTING
+ False
+ >>> node.reboot()
+ True
+ >>> node.state == NodeState.REBOOTING
+ True
+ """
+ return self.driver.reboot_node(self)
+
+ def destroy(self):
+ """Destroy this node
+
+ @return: C{bool}
+
+ This calls the node's driver and destroys the node
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> from libcloud.compute.types import NodeState
+ >>> node = driver.create_node()
+ >>> node.state == NodeState.RUNNING
+ True
+ >>> node.destroy()
+ True
+ >>> node.state == NodeState.RUNNING
+ False
+
+ """
+ return self.driver.destroy_node(self)
+
+ def __repr__(self):
+ return (('<Node: uuid=%s, name=%s, state=%s, public_ip=%s, '
+ 'provider=%s ...>')
+ % (self.uuid, self.name, self.state, self.public_ip,
+ self.driver.name))
+
+
+class NodeSize(object):
+ """
+ A Base NodeSize class to derive from.
+
+ NodeSizes are objects which are typically returned a driver's
+ list_sizes function. They contain a number of different
+ parameters which define how big an image is.
+
+ The exact parameters available depends on the provider.
+
+ N.B. Where a parameter is "unlimited" (for example bandwidth in
+ Amazon) this will be given as 0.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> size = driver.list_sizes()[0]
+ >>> size.ram
+ 128
+ >>> size.bandwidth
+ 500
+ >>> size.price
+ 4
+ """
+
+ def __init__(self, id, name, ram, disk, bandwidth, price, driver):
+ self.id = str(id)
+ self.name = name
+ self.ram = ram
+ self.disk = disk
+ self.bandwidth = bandwidth
+ self.price = price
+ self.driver = driver
+ def __repr__(self):
+ return (('<NodeSize: id=%s, name=%s, ram=%s disk=%s bandwidth=%s '
+ 'price=%s driver=%s ...>')
+ % (self.id, self.name, self.ram, self.disk, self.bandwidth,
+ self.price, self.driver.name))
+
+
+class NodeImage(object):
+ """
+ An operating system image.
+
+ NodeImage objects are typically returned by the driver for the
+ cloud provider in response to the list_images function
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> image = driver.list_images()[0]
+ >>> image.name
+ 'Ubuntu 9.10'
+
+ Apart from name and id, there is no further standard information;
+ other parameters are stored in a driver specific "extra" variable
+
+ When creating a node, a node image should be given as an argument
+ to the create_node function to decide which OS image to use.
+
+ >>> node = driver.create_node(image=image)
+
+ """
+
+ def __init__(self, id, name, driver, extra=None):
+ self.id = str(id)
+ self.name = name
+ self.driver = driver
+ if not extra:
+ self.extra = {}
+ else:
+ self.extra = extra
+ def __repr__(self):
+ return (('<NodeImage: id=%s, name=%s, driver=%s ...>')
+ % (self.id, self.name, self.driver.name))
+
+class NodeLocation(object):
+ """
+ A physical location where nodes can be.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> location = driver.list_locations()[0]
+ >>> location.country
+ 'US'
+ """
+
+ def __init__(self, id, name, country, driver):
+ self.id = str(id)
+ self.name = name
+ self.country = country
+ self.driver = driver
+ def __repr__(self):
+ return (('<NodeLocation: id=%s, name=%s, country=%s, driver=%s>')
+ % (self.id, self.name, self.country, self.driver.name))
+
+class NodeAuthSSHKey(object):
+ """
+ An SSH key to be installed for authentication to a node.
+
+ This is the actual contents of the users ssh public key which will
+ normally be installed as root's public key on the node.
+
+ >>> pubkey = '...' # read from file
+ >>> from libcloud.compute.base import NodeAuthSSHKey
+ >>> k = NodeAuthSSHKey(pubkey)
+ >>> k
+ <NodeAuthSSHKey>
+
+ """
+ def __init__(self, pubkey):
+ self.pubkey = pubkey
+ def __repr__(self):
+ return '<NodeAuthSSHKey>'
+
+class NodeAuthPassword(object):
+ """
+ A password to be used for authentication to a node.
+ """
+ def __init__(self, password):
+ self.password = password
+ def __repr__(self):
+ return '<NodeAuthPassword>'
+
+class NodeDriver(object):
+ """
+ A base NodeDriver class to derive from
+
+ This class is always subclassed by a specific driver. For
+ examples of base behavior of most functions (except deploy node)
+ see the dummy driver.
+
+ """
+
+ connectionCls = ConnectionKey
+ name = None
+ type = None
+ port = None
+ features = {"create_node": []}
+ """
+ List of available features for a driver.
+ - L{create_node}
+ - ssh_key: Supports L{NodeAuthSSHKey} as an authentication method
+ for nodes.
+ - password: Supports L{NodeAuthPassword} as an authentication
+ method for nodes.
+ - generates_password: Returns a password attribute on the Node
+ object returned from creation.
+ """
+
+ NODE_STATE_MAP = {}
+
+ def __init__(self, key, secret=None, secure=True, host=None, port=None):
+ """
+ @keyword key: API key or username to used
+ @type key: str
+
+ @keyword secret: Secret password to be used
+ @type secret: str
+
+ @keyword secure: Weither to use HTTPS or HTTP. Note: Some providers
+ only support HTTPS, and it is on by default.
+ @type secure: bool
+
+ @keyword host: Override hostname used for connections.
+ @type host: str
+
+ @keyword port: Override port used for connections.
+ @type port: int
+ """
+ self.key = key
+ self.secret = secret
+ self.secure = secure
+ args = [self.key]
+
+ if self.secret != None:
+ args.append(self.secret)
+
+ args.append(secure)
+
+ if host != None:
+ args.append(host)
+
+ if port != None:
+ args.append(port)
+
+ self.connection = self.connectionCls(*args)
+
+ self.connection.driver = self
+ self.connection.connect()
+
+ def create_node(self, **kwargs):
+ """Create a new node instance.
+
+ @keyword name: String with a name for this new node (required)
+ @type name: str
+
+ @keyword size: The size of resources allocated to this node.
+ (required)
+ @type size: L{NodeSize}
+
+ @keyword image: OS Image to boot on node. (required)
+ @type image: L{NodeImage}
+
+ @keyword location: Which data center to create a node in. If empty,
+ undefined behavoir will be selected. (optional)
+ @type location: L{NodeLocation}
+
+ @keyword auth: Initial authentication information for the node
+ (optional)
+ @type auth: L{NodeAuthSSHKey} or L{NodeAuthPassword}
+
+ @return: The newly created L{Node}.
+ """
+ raise NotImplementedError, \
+ 'create_node not implemented for this driver'
+
+ def destroy_node(self, node):
+ """Destroy a node.
+
+ Depending upon the provider, this may destroy all data associated with
+ the node, including backups.
+
+ @return: C{bool} True if the destroy was successful, otherwise False
+ """
+ raise NotImplementedError, \
+ 'destroy_node not implemented for this driver'
+
+ def reboot_node(self, node):
+ """
+ Reboot a node.
+ @return: C{bool} True if the reboot was successful, otherwise False
+ """
+ raise NotImplementedError, \
+ 'reboot_node not implemented for this driver'
+
+ def list_nodes(self):
+ """
+ List all nodes
+ @return: C{list} of L{Node} objects
+ """
+ raise NotImplementedError, \
+ 'list_nodes not implemented for this driver'
+
+ def list_images(self, location=None):
+ """
+ List images on a provider
+ @return: C{list} of L{NodeImage} objects
+ """
+ raise NotImplementedError, \
+ 'list_images not implemented for this driver'
+
+ def list_sizes(self, location=None):
+ """
+ List sizes on a provider
+ @return: C{list} of L{NodeSize} objects
+ """
+ raise NotImplementedError, \
+ 'list_sizes not implemented for this driver'
+
+ def list_locations(self):
+ """
+ List data centers for a provider
+ @return: C{list} of L{NodeLocation} objects
+ """
+ raise NotImplementedError, \
+ 'list_locations not implemented for this driver'
+
+ def deploy_node(self, **kwargs):
+ """
+ Create a new node, and start deployment.
+
+ Depends on a Provider Driver supporting either using a specific password
+ or returning a generated password.
+
+ This function may raise a L{DeploymentException}, if a create_node
+ call was successful, but there is a later error (like SSH failing or
+ timing out). This exception includes a Node object which you may want
+ to destroy if incomplete deployments are not desirable.
+
+ @keyword deploy: Deployment to run once machine is online and availble to SSH.
+ @type deploy: L{Deployment}
+
+ @keyword ssh_username: Optional name of the account which is used when connecting to
+ SSH server (default is root)
+ @type ssh_username: C{str}
+
+ @keyword ssh_port: Optional SSH server port (default is 22)
+ @type ssh_port: C{int}
+
+ See L{NodeDriver.create_node} for more keyword args.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> from libcloud.deployment import ScriptDeployment, MultiStepDeployment
+ >>> from libcloud.compute.base import NodeAuthSSHKey
+ >>> driver = DummyNodeDriver(0)
+ >>> key = NodeAuthSSHKey('...') # read from file
+ >>> script = ScriptDeployment("yum -y install emacs strace tcpdump")
+ >>> msd = MultiStepDeployment([key, script])
+ >>> def d():
+ ... try:
+ ... node = driver.deploy_node(deploy=msd)
+ ... except NotImplementedError:
+ ... print "not implemented for dummy driver"
+ >>> d()
+ not implemented for dummy driver
+
+ Deploy node is typically not overridden in subclasses. The
+ existing implementation should be able to handle most such.
+ """
+ # TODO: support ssh keys
+ WAIT_PERIOD=3
+ password = None
+
+ if 'generates_password' not in self.features["create_node"]:
+ if 'password' not in self.features["create_node"]:
+ raise NotImplementedError, \
+ 'deploy_node not implemented for this driver'
+
+ if not kwargs.has_key('auth'):
+ kwargs['auth'] = NodeAuthPassword(os.urandom(16).encode('hex'))
+
+ password = kwargs['auth'].password
+ node = self.create_node(**kwargs)
+ try:
+ if 'generates_password' in self.features["create_node"]:
+ password = node.extra.get('password')
+ start = time.time()
+ end = start + (60 * 15)
+ while time.time() < end:
+ # need to wait until we get a public IP address.
+ # TODO: there must be a better way of doing this
+ time.sleep(WAIT_PERIOD)
+ nodes = self.list_nodes()
+ nodes = filter(lambda n: n.uuid == node.uuid, nodes)
+ if len(nodes) == 0:
+ raise DeploymentError(node, "Booted node[%s] is missing form list_nodes." % node)
+ if len(nodes) > 1:
+ raise DeploymentError(node, "Booted single node[%s], but multiple nodes have same UUID"% node)
+
+ node = nodes[0]
+
+ if node.public_ip is not None and node.public_ip != "" and node.state == NodeState.RUNNING:
+ break
+
+ ssh_username = kwargs.get('ssh_username', 'root')
+ ssh_port = kwargs.get('ssh_port', 22)
+
+ client = SSHClient(hostname=node.public_ip[0],
+ port=ssh_port, username=ssh_username,
+ password=password)
+ laste = None
+ while time.time() < end:
+ laste = None
+ try:
+ client.connect()
+ break
+ except (IOError, socket.gaierror, socket.error), e:
+ laste = e
+ time.sleep(WAIT_PERIOD)
+ if laste is not None:
+ raise e
+
+ tries = 3
+ while tries >= 0:
+ try:
+ n = kwargs["deploy"].run(node, client)
+ client.close()
+ break
+ except Exception, e:
+ tries -= 1
+ if tries == 0:
+ raise
+ client.connect()
+
+ except DeploymentError, e:
+ raise
+ except Exception, e:
+ raise DeploymentError(node, e)
+ return n
+
+def is_private_subnet(ip):
+ """
+ Utility function to check if an IP address is inside a private subnet.
+
+ @type ip: C{str}
+ @keyword ip: IP address to check
+
+ @return: C{bool} if the specified IP address is private.
+ """
+ priv_subnets = [ {'subnet': '10.0.0.0', 'mask': '255.0.0.0'},
+ {'subnet': '172.16.0.0', 'mask': '255.240.0.0'},
+ {'subnet': '192.168.0.0', 'mask': '255.255.0.0'} ]
+
+ ip = struct.unpack('I',socket.inet_aton(ip))[0]
+
+ for network in priv_subnets:
+ subnet = struct.unpack('I',socket.inet_aton(network['subnet']))[0]
+ mask = struct.unpack('I',socket.inet_aton(network['mask']))[0]
+
+ if (ip & mask) == (subnet & mask):
+ return True
+
+ return False
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
Added: incubator/libcloud/trunk/libcloud/compute/deployment.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/deployment.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/deployment.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/deployment.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,130 @@
+# 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.
+
+"""
+Provides generic deployment steps for machines post boot.
+"""
+import os
+
+class Deployment(object):
+ """
+ Base class for deployment tasks.
+ """
+
+ def run(self, node, client):
+ """
+ Runs this deployment task on C{node} using the C{client} provided.
+
+ @type node: L{Node}
+ @keyword node: Node to operate one
+
+ @type client: L{BaseSSHClient}
+ @keyword client: Connected SSH client to use.
+
+ @return: L{Node}
+ """
+ raise NotImplementedError, \
+ 'run not implemented for this deployment'
+
+
+class SSHKeyDeployment(Deployment):
+ """
+ Installs a public SSH Key onto a host.
+ """
+
+ def __init__(self, key):
+ """
+ @type key: C{str}
+ @keyword key: Contents of the public key write
+ """
+ self.key = key
+
+ def run(self, node, client):
+ """
+ Installs SSH key into C{.ssh/authorized_keys}
+
+ See also L{Deployment.run}
+ """
+ client.put(".ssh/authorized_keys", contents=self.key)
+ return node
+
+class ScriptDeployment(Deployment):
+ """
+ Runs an arbitrary Shell Script task.
+ """
+
+ def __init__(self, script, name=None, delete=False):
+ """
+ @type script: C{str}
+ @keyword script: Contents of the script to run
+
+ @type name: C{str}
+ @keyword name: Name of the script to upload it as, if not specified, a random name will be choosen.
+
+ @type delete: C{bool}
+ @keyword delete: Whether to delete the script on completion.
+ """
+ self.script = script
+ self.stdout = None
+ self.stderr = None
+ self.exit_status = None
+ self.delete = delete
+ self.name = name
+ if self.name is None:
+ self.name = "/root/deployment_%s.sh" % (os.urandom(4).encode('hex'))
+
+ def run(self, node, client):
+ """
+ Uploads the shell script and then executes it.
+
+ See also L{Deployment.run}
+ """
+ client.put(path=self.name, chmod=755, contents=self.script)
+ self.stdout, self.stderr, self.exit_status = client.run(self.name)
+ if self.delete:
+ client.delete(self.name)
+ return node
+
+class MultiStepDeployment(Deployment):
+ """
+ Runs a chain of Deployment steps.
+ """
+ def __init__(self, add = None):
+ """
+ @type add: C{list}
+ @keyword add: Deployment steps to add.
+ """
+ self.steps = []
+ self.add(add)
+
+ def add(self, add):
+ """Add a deployment to this chain.
+
+ @type add: Single L{Deployment} or a C{list} of L{Deployment}
+ @keyword add: Adds this deployment to the others already in this object.
+ """
+ if add is not None:
+ add = add if isinstance(add, (list, tuple)) else [add]
+ self.steps.extend(add)
+
+ def run(self, node, client):
+ """
+ Run each deployment that has been added.
+
+ See also L{Deployment.run}
+ """
+ for s in self.steps:
+ node = s.run(node, client)
+ return node
Added: incubator/libcloud/trunk/libcloud/compute/drivers/__init__.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/drivers/__init__.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/drivers/__init__.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/drivers/__init__.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,38 @@
+# 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.
+
+"""
+Drivers for working with different providers
+"""
+
+__all__ = [
+ 'brightbox',
+ 'dummy',
+ 'ec2',
+ 'ecp',
+ 'elastichosts',
+ 'cloudsigma',
+ 'gogrid',
+ 'ibm_sbc',
+ 'linode',
+ 'opennebula',
+ 'rackspace',
+ 'rimuhosting',
+ 'slicehost',
+ 'softlayer',
+ 'vcloud',
+ 'voxel',
+ 'vpsnet',
+]
Added: incubator/libcloud/trunk/libcloud/compute/drivers/brightbox.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/drivers/brightbox.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/drivers/brightbox.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/drivers/brightbox.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,221 @@
+# 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.
+"""
+Brightbox Driver
+"""
+import httplib
+import base64
+
+from libcloud.common.base import ConnectionUserAndKey, Response
+from libcloud.compute.types import Provider, NodeState, InvalidCredsError
+from libcloud.compute.base import NodeDriver
+from libcloud.compute.base import Node, NodeImage, NodeSize, NodeLocation
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+API_VERSION = '1.0'
+
+
+class BrightboxResponse(Response):
+ def success(self):
+ return self.status >= 200 and self.status < 400
+
+ def parse_body(self):
+ if self.headers['content-type'].split('; ')[0] == 'application/json' and len(self.body) > 0:
+ return json.loads(self.body)
+ else:
+ return self.body
+
+ def parse_error(self):
+ return json.loads(self.body)['error']
+
+
+class BrightboxConnection(ConnectionUserAndKey):
+ """
+ Connection class for the Brightbox driver
+ """
+
+ host = 'api.gb1.brightbox.com'
+ responseCls = BrightboxResponse
+
+ def _fetch_oauth_token(self):
+ body = json.dumps({'client_id': self.user_id, 'grant_type': 'none'})
+
+ authorization = 'Basic ' + base64.encodestring('%s:%s' % (self.user_id, self.key)).rstrip()
+
+ self.connect()
+
+ response = self.connection.request(method='POST', url='/token', body=body, headers={
+ 'Host': self.host,
+ 'User-Agent': self._user_agent(),
+ 'Authorization': authorization,
+ 'Content-Type': 'application/json',
+ 'Content-Length': str(len(body))
+ })
+
+ response = self.connection.getresponse()
+
+ if response.status == 200:
+ return json.loads(response.read())['access_token']
+ else:
+ message = '%s (%s)' % (json.loads(response.read())['error'], response.status)
+
+ raise InvalidCredsError, message
+
+ def add_default_headers(self, headers):
+ try:
+ headers['Authorization'] = 'OAuth ' + self.token
+ except AttributeError:
+ self.token = self._fetch_oauth_token()
+
+ headers['Authorization'] = 'OAuth ' + self.token
+
+ return headers
+
+ def encode_data(self, data):
+ return json.dumps(data)
+
+
+class BrightboxNodeDriver(NodeDriver):
+ """
+ Brightbox node driver
+ """
+
+ connectionCls = BrightboxConnection
+
+ type = Provider.BRIGHTBOX
+ name = 'Brightbox'
+
+ NODE_STATE_MAP = { 'creating': NodeState.PENDING,
+ 'active': NodeState.RUNNING,
+ 'inactive': NodeState.UNKNOWN,
+ 'deleting': NodeState.UNKNOWN,
+ 'deleted': NodeState.TERMINATED,
+ 'failed': NodeState.UNKNOWN }
+
+ def _to_node(self, data):
+ return Node(
+ id = data['id'],
+ name = data['name'],
+ state = self.NODE_STATE_MAP[data['status']],
+ public_ip = map(lambda cloud_ip: cloud_ip['public_ip'], data['cloud_ips']),
+ private_ip = map(lambda interface: interface['ipv4_address'], data['interfaces']),
+ driver = self.connection.driver,
+ extra = {
+ 'status': data['status'],
+ 'interfaces': data['interfaces']
+ }
+ )
+
+ def _to_image(self, data):
+ return NodeImage(
+ id = data['id'],
+ name = data['name'],
+ driver = self,
+ extra = {
+ 'description': data['description'],
+ 'arch': data['arch']
+ }
+ )
+
+ def _to_size(self, data):
+ return NodeSize(
+ id = data['id'],
+ name = data['name'],
+ ram = data['ram'],
+ disk = data['disk_size'],
+ bandwidth = 0,
+ price = '',
+ driver = self
+ )
+
+ def _to_location(self, data):
+ return NodeLocation(
+ id = data['id'],
+ name = data['handle'],
+ country = 'GB',
+ driver = self
+ )
+
+ def _post(self, path, data={}):
+ headers = {'Content-Type': 'application/json'}
+
+ return self.connection.request(path, data=data, headers=headers, method='POST')
+
+ def create_node(self, **kwargs):
+ data = {
+ 'name': kwargs['name'],
+ 'server_type': kwargs['size'].id,
+ 'image': kwargs['image'].id,
+ 'user_data': ''
+ }
+
+ if kwargs.has_key('location'):
+ data['zone'] = kwargs['location'].id
+ else:
+ data['zone'] = ''
+
+ data = self._post('/%s/servers' % API_VERSION, data).object
+
+ return self._to_node(data)
+
+ def destroy_node(self, node):
+ response = self.connection.request('/%s/servers/%s' % (API_VERSION, node.id), method='DELETE')
+
+ return response.status == httplib.ACCEPTED
+
+ def list_nodes(self):
+ data = self.connection.request('/%s/servers' % API_VERSION).object
+
+ return map(self._to_node, data)
+
+ def list_images(self):
+ data = self.connection.request('/%s/images' % API_VERSION).object
+
+ return map(self._to_image, data)
+
+ def list_sizes(self):
+ data = self.connection.request('/%s/server_types' % API_VERSION).object
+
+ return map(self._to_size, data)
+
+ def list_locations(self):
+ data = self.connection.request('/%s/zones' % API_VERSION).object
+
+ return map(self._to_location, data)
+
+ def ex_list_cloud_ips(self):
+ return self.connection.request('/%s/cloud_ips' % API_VERSION).object
+
+ def ex_create_cloud_ip(self):
+ return self._post('/%s/cloud_ips' % API_VERSION).object
+
+ def ex_map_cloud_ip(self, cloud_ip_id, interface_id):
+ response = self._post('/%s/cloud_ips/%s/map' % (API_VERSION, cloud_ip_id), {'interface': interface_id})
+
+ return response.status == httplib.ACCEPTED
+
+ def ex_unmap_cloud_ip(self, cloud_ip_id):
+ response = self._post('/%s/cloud_ips/%s/unmap' % (API_VERSION, cloud_ip_id))
+
+ return response.status == httplib.ACCEPTED
+
+ def ex_destroy_cloud_ip(self, cloud_ip_id):
+ response = self.connection.request('/%s/cloud_ips/%s' % (API_VERSION, cloud_ip_id), method='DELETE')
+
+ return response.status == httplib.OK
Added: incubator/libcloud/trunk/libcloud/compute/drivers/cloudsigma.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/drivers/cloudsigma.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/drivers/cloudsigma.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/drivers/cloudsigma.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,654 @@
+# -*- coding: utf-8 -*-
+# 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.
+"""
+CloudSigma Driver
+"""
+import re
+import time
+import base64
+
+from libcloud.common.base import ConnectionUserAndKey, Response
+from libcloud.common.types import InvalidCredsError
+from libcloud.compute.types import NodeState, Provider
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage
+
+# API end-points
+API_ENDPOINTS = {
+ 'zrh': {
+ 'name': 'Zurich',
+ 'country': 'Switzerland',
+ 'host': 'api.cloudsigma.com'
+ },
+}
+
+# Default API end-point for the base connection clase.
+DEFAULT_ENDPOINT = 'zrh'
+
+# CloudSigma doesn't specify special instance types.
+# Basically for CPU any value between 0.5 GHz and 20.0 GHz should work, 500 MB to 32000 MB for ram
+# and 1 GB to 1024 GB for hard drive size.
+# Plans in this file are based on examples listed on http://www.cloudsigma.com/en/pricing/price-schedules
+INSTANCE_TYPES = {
+ 'micro-regular': {
+ 'id': 'micro-regular',
+ 'name': 'Micro/Regular instance',
+ 'cpu': 1100,
+ 'memory': 640,
+ 'disk': 50,
+ 'price': '0.0548',
+ 'bandwidth': None,
+ },
+ 'micro-high-cpu': {
+ 'id': 'micro-high-cpu',
+ 'name': 'Micro/High CPU instance',
+ 'cpu': 2200,
+ 'memory': 640,
+ 'disk': 80,
+ 'price': '.381',
+ 'bandwidth': None,
+ },
+ 'standard-small': {
+ 'id': 'standard-small',
+ 'name': 'Standard/Small instance',
+ 'cpu': 1100,
+ 'memory': 1741,
+ 'disk': 50,
+ 'price': '0.0796',
+ 'bandwidth': None,
+ },
+ 'standard-large': {
+ 'id': 'standard-large',
+ 'name': 'Standard/Large instance',
+ 'cpu': 4400,
+ 'memory': 7680,
+ 'disk': 250,
+ 'price': '0.381',
+ 'bandwidth': None,
+ },
+ 'standard-extra-large': {
+ 'id': 'standard-extra-large',
+ 'name': 'Standard/Extra Large instance',
+ 'cpu': 8800,
+ 'memory': 15360,
+ 'disk': 500,
+ 'price': '0.762',
+ 'bandwidth': None,
+ },
+ 'high-memory-extra-large': {
+ 'id': 'high-memory-extra-large',
+ 'name': 'High Memory/Extra Large instance',
+ 'cpu': 7150,
+ 'memory': 17510,
+ 'disk': 250,
+ 'price': '0.642',
+ 'bandwidth': None,
+ },
+ 'high-memory-double-extra-large': {
+ 'id': 'high-memory-double-extra-large',
+ 'name': 'High Memory/Double Extra Large instance',
+ 'cpu': 14300,
+ 'memory': 32768,
+ 'disk': 500,
+ 'price': '1.383',
+ 'bandwidth': None,
+ },
+ 'high-cpu-medium': {
+ 'id': 'high-cpu-medium',
+ 'name': 'High CPU/Medium instance',
+ 'cpu': 5500,
+ 'memory': 1741,
+ 'disk': 150,
+ 'price': '0.211',
+ 'bandwidth': None,
+ },
+ 'high-cpu-extra-large': {
+ 'id': 'high-cpu-extra-large',
+ 'name': 'High CPU/Extra Large instance',
+ 'cpu': 20000,
+ 'memory': 7168,
+ 'disk': 500,
+ 'price': '0.780',
+ 'bandwidth': None,
+ },
+}
+
+NODE_STATE_MAP = {
+ 'active': NodeState.RUNNING,
+ 'stopped': NodeState.TERMINATED,
+ 'dead': NodeState.TERMINATED,
+ 'dumped': NodeState.TERMINATED,
+}
+
+# Default timeout (in seconds) for the drive imaging process
+IMAGING_TIMEOUT = 20 * 60
+
+class CloudSigmaException(Exception):
+ def __str__(self):
+ return self.args[0]
+
+ def __repr__(self):
+ return "<CloudSigmaException '%s'>" % (self.args[0])
+
+class CloudSigmaInsufficientFundsException(Exception):
+ def __repr__(self):
+ return "<CloudSigmaInsufficientFundsException '%s'>" % (self.args[0])
+
+class CloudSigmaResponse(Response):
+ def success(self):
+ if self.status == 401:
+ raise InvalidCredsError()
+
+ return self.status >= 200 and self.status <= 299
+
+ def parse_body(self):
+ if not self.body:
+ return self.body
+
+ return str2dicts(self.body)
+
+ def parse_error(self):
+ return 'Error: %s' % (self.body.replace('errors:', '').strip())
+
+class CloudSigmaNodeSize(NodeSize):
+ def __init__(self, id, name, cpu, ram, disk, bandwidth, price, driver):
+ self.id = id
+ self.name = name
+ self.cpu = cpu
+ self.ram = ram
+ self.disk = disk
+ self.bandwidth = bandwidth
+ self.price = price
+ self.driver = driver
+
+ def __repr__(self):
+ return (('<NodeSize: id=%s, name=%s, cpu=%s, ram=%s disk=%s bandwidth=%s '
+ 'price=%s driver=%s ...>')
+ % (self.id, self.name, self.cpu, self.ram, self.disk, self.bandwidth,
+ self.price, self.driver.name))
+
+class CloudSigmaBaseConnection(ConnectionUserAndKey):
+ host = API_ENDPOINTS[DEFAULT_ENDPOINT]['host']
+ responseCls = CloudSigmaResponse
+
+ def add_default_headers(self, headers):
+ headers['Accept'] = 'application/json'
+ headers['Content-Type'] = 'application/json'
+
+ headers['Authorization'] = 'Basic %s' % (base64.b64encode('%s:%s' % (self.user_id, self.key)))
+
+ return headers
+
+class CloudSigmaBaseNodeDriver(NodeDriver):
+ type = Provider.CLOUDSIGMA
+ name = 'CloudSigma'
+ connectionCls = CloudSigmaBaseConnection
+
+ def reboot_node(self, node):
+ """
+ Reboot a node.
+
+ Because Cloudsigma API does not provide native reboot call, it's emulated using stop and start.
+ """
+ node = self._get_node(node.id)
+ state = node.state
+
+ if state == NodeState.RUNNING:
+ stopped = self.ex_stop_node(node)
+ else:
+ stopped = True
+
+ if not stopped:
+ raise CloudSigmaException('Could not stop node with id %s' % (node.id))
+
+ success = self.ex_start_node(node)
+
+ return success
+
+ def destroy_node(self, node):
+ """
+ Destroy a node (all the drives associated with it are NOT destroyed).
+
+ If a node is still running, it's stopped before it's destroyed.
+ """
+ node = self._get_node(node.id)
+ state = node.state
+
+ # Node cannot be destroyed while running so it must be stopped first
+ if state == NodeState.RUNNING:
+ stopped = self.ex_stop_node(node)
+ else:
+ stopped = True
+
+ if not stopped:
+ raise CloudSigmaException('Could not stop node with id %s' % (node.id))
+
+ response = self.connection.request(action = '/servers/%s/destroy' % (node.id),
+ method = 'POST')
+ return response.status == 204
+
+ def list_images(self, location=None):
+ """
+ Return a list of available standard images (this call might take up to 15 seconds to return).
+ """
+ response = self.connection.request(action = '/drives/standard/info').object
+
+ images = []
+ for value in response:
+ if value.get('type'):
+ if value['type'] == 'disk':
+ image = NodeImage(id = value['drive'], name = value['name'], driver = self.connection.driver,
+ extra = {'size': value['size']})
+ images.append(image)
+
+ return images
+
+ def list_sizes(self, location = None):
+ """
+ Return a list of available node sizes.
+ """
+ sizes = []
+ for key, value in INSTANCE_TYPES.iteritems():
+ size = CloudSigmaNodeSize(id = value['id'], name = value['name'], cpu = value['cpu'], ram = value['memory'],
+ disk = value['disk'], bandwidth = value['bandwidth'], price = value['price'],
+ driver = self.connection.driver)
+ sizes.append(size)
+
+ return sizes
+
+ def list_nodes(self):
+ """
+ Return a list of nodes.
+ """
+ response = self.connection.request(action = '/servers/info').object
+
+ nodes = []
+ for data in response:
+ node = self._to_node(data)
+ if node:
+ nodes.append(node)
+ return nodes
+
+ def create_node(self, **kwargs):
+ """
+ Creates a CloudSigma instance
+
+ See L{NodeDriver.create_node} for more keyword args.
+
+ @keyword name: String with a name for this new node (required)
+ @type name: C{string}
+
+ @keyword smp: Number of virtual processors or None to calculate based on the cpu speed
+ @type smp: C{int}
+
+ @keyword nic_model: e1000, rtl8139 or virtio (is not specified, e1000 is used)
+ @type nic_model: C{string}
+
+ @keyword vnc_password: If not set, VNC access is disabled.
+ @type vnc_password: C{bool}
+ """
+ size = kwargs['size']
+ image = kwargs['image']
+ smp = kwargs.get('smp', 'auto')
+ nic_model = kwargs.get('nic_model', 'e1000')
+ vnc_password = kwargs.get('vnc_password', None)
+
+ if nic_model not in ['e1000', 'rtl8139', 'virtio']:
+ raise CloudSigmaException('Invalid NIC model specified')
+
+ drive_data = {}
+ drive_data.update({'name': kwargs['name'], 'size': '%sG' % (kwargs['size'].disk)})
+
+ response = self.connection.request(action = '/drives/%s/clone' % image.id, data = dict2str(drive_data),
+ method = 'POST').object
+
+ if not response:
+ raise CloudSigmaException('Drive creation failed')
+
+ drive_uuid = response[0]['drive']
+
+ response = self.connection.request(action = '/drives/%s/info' % (drive_uuid)).object
+ imaging_start = time.time()
+ while response[0].has_key('imaging'):
+ response = self.connection.request(action = '/drives/%s/info' % (drive_uuid)).object
+ elapsed_time = time.time() - imaging_start
+ if response[0].has_key('imaging') and elapsed_time >= IMAGING_TIMEOUT:
+ raise CloudSigmaException('Drive imaging timed out')
+ time.sleep(1)
+
+ node_data = {}
+ node_data.update({'name': kwargs['name'], 'cpu': size.cpu, 'mem': size.ram, 'ide:0:0': drive_uuid,
+ 'boot': 'ide:0:0', 'smp': smp})
+ node_data.update({'nic:0:model': nic_model, 'nic:0:dhcp': 'auto'})
+
+ if vnc_password:
+ node_data.update({'vnc:ip': 'auto', 'vnc:password': vnc_password})
+
+ response = self.connection.request(action = '/servers/create', data = dict2str(node_data),
+ method = 'POST').object
+
+ if not isinstance(response, list):
+ response = [ response ]
+
+ node = self._to_node(response[0])
+ if node is None:
+ # Insufficient funds, destroy created drive
+ self.ex_drive_destroy(drive_uuid)
+ raise CloudSigmaInsufficientFundsException('Insufficient funds, node creation failed')
+
+ # Start the node after it has been created
+ started = self.ex_start_node(node)
+
+ if started:
+ node.state = NodeState.RUNNING
+
+ return node
+
+ def ex_destroy_node_and_drives(self, node):
+ """
+ Destroy a node and all the drives associated with it.
+ """
+ node = self._get_node_info(node)
+
+ drive_uuids = []
+ for key, value in node.iteritems():
+ if (key.startswith('ide:') or key.startswith('scsi') or key.startswith('block')) and \
+ not (key.endswith(':bytes') or key.endswith(':requests') or key.endswith('media')):
+ drive_uuids.append(value)
+
+ node_destroyed = self.destroy_node(self._to_node(node))
+
+ if not node_destroyed:
+ return False
+
+ for drive_uuid in drive_uuids:
+ self.ex_drive_destroy(drive_uuid)
+
+ return True
+
+ def ex_static_ip_list(self):
+ """
+ Return a list of available static IP addresses.
+ """
+ response = self.connection.request(action = '/resources/ip/list', method = 'GET')
+
+ if response.status != 200:
+ raise CloudSigmaException('Could not retrieve IP list')
+
+ ips = str2list(response.body)
+ return ips
+
+ def ex_drives_list(self):
+ """
+ Return a list of all the available drives.
+ """
+ response = self.connection.request(action = '/drives/info', method = 'GET')
+
+ result = str2dicts(response.body)
+ return result
+
+ def ex_static_ip_create(self):
+ """
+ Create a new static IP address.
+ """
+ response = self.connection.request(action = '/resources/ip/create', method = 'GET')
+
+ result = str2dicts(response.body)
+ return result
+
+ def ex_static_ip_destroy(self, ip_address):
+ """
+ Destroy a static IP address.
+ """
+ response = self.connection.request(action = '/resources/ip/%s/destroy' % (ip_address), method = 'GET')
+
+ return response.status == 204
+
+ def ex_drive_destroy(self, drive_uuid):
+ """
+ Destroy a drive with a specified uuid.
+ If the drive is currently mounted an exception is thrown.
+ """
+ response = self.connection.request(action = '/drives/%s/destroy' % (drive_uuid), method = 'POST')
+
+ return response.status == 204
+
+
+ def ex_set_node_configuration(self, node, **kwargs):
+ """
+ Update a node configuration.
+ Changing most of the parameters requires node to be stopped.
+ """
+ valid_keys = ('^name$', '^parent$', '^cpu$', '^smp$', '^mem$', '^boot$', '^nic:0:model$', '^nic:0:dhcp',
+ '^nic:1:model$', '^nic:1:vlan$', '^nic:1:mac$', '^vnc:ip$', '^vnc:password$', '^vnc:tls',
+ '^ide:[0-1]:[0-1](:media)?$', '^scsi:0:[0-7](:media)?$', '^block:[0-7](:media)?$')
+
+ invalid_keys = []
+ for key in kwargs.keys():
+ matches = False
+ for regex in valid_keys:
+ if re.match(regex, key):
+ matches = True
+ break
+ if not matches:
+ invalid_keys.append(key)
+
+ if invalid_keys:
+ raise CloudSigmaException('Invalid configuration key specified: %s' % (',' .join(invalid_keys)))
+
+ response = self.connection.request(action = '/servers/%s/set' % (node.id), data = dict2str(kwargs),
+ method = 'POST')
+
+ return (response.status == 200 and response.body != '')
+
+ def ex_start_node(self, node):
+ """
+ Start a node.
+ """
+ response = self.connection.request(action = '/servers/%s/start' % (node.id),
+ method = 'POST')
+
+ return response.status == 200
+
+ def ex_stop_node(self, node):
+ """
+ Stop (shutdown) a node.
+ """
+ response = self.connection.request(action = '/servers/%s/stop' % (node.id),
+ method = 'POST')
+ return response.status == 204
+
+ def ex_shutdown_node(self, node):
+ """
+ Stop (shutdown) a node.
+ """
+ return self.ex_stop_node(node)
+
+ def ex_destroy_drive(self, drive_uuid):
+ """
+ Destroy a drive.
+ """
+ response = self.connection.request(action = '/drives/%s/destroy' % (drive_uuid),
+ method = 'POST')
+ return response.status == 204
+
+ def _to_node(self, data):
+ if data:
+ try:
+ state = NODE_STATE_MAP[data['status']]
+ except KeyError:
+ state = NodeState.UNKNOWN
+
+ if 'server' not in data:
+ # Response does not contain server UUID if the server
+ # creation failed because of insufficient funds.
+ return None
+
+ public_ip = []
+ if data.has_key('nic:0:dhcp'):
+ if isinstance(data['nic:0:dhcp'], list):
+ public_ip = data['nic:0:dhcp']
+ else:
+ public_ip = [data['nic:0:dhcp']]
+
+ extra = {}
+ extra_keys = [ ('cpu', 'int'), ('smp', 'auto'), ('mem', 'int'), ('status', 'str') ]
+ for key, value_type in extra_keys:
+ if data.has_key(key):
+ value = data[key]
+
+ if value_type == 'int':
+ value = int(value)
+ elif value_type == 'auto':
+ try:
+ value = int(value)
+ except ValueError:
+ pass
+
+ extra.update({key: value})
+
+ if data.has_key('vnc:ip') and data.has_key('vnc:password'):
+ extra.update({'vnc_ip': data['vnc:ip'], 'vnc_password': data['vnc:password']})
+
+ node = Node(id = data['server'], name = data['name'], state = state,
+ public_ip = public_ip, private_ip = None, driver = self.connection.driver,
+ extra = extra)
+
+ return node
+ return None
+
+ def _get_node(self, node_id):
+ nodes = self.list_nodes()
+ node = [node for node in nodes if node.id == node.id]
+
+ if not node:
+ raise CloudSigmaException('Node with id %s does not exist' % (node_id))
+
+ return node[0]
+
+ def _get_node_info(self, node):
+ response = self.connection.request(action = '/servers/%s/info' % (node.id))
+
+ result = str2dicts(response.body)
+ return result[0]
+
+class CloudSigmaZrhConnection(CloudSigmaBaseConnection):
+ """
+ Connection class for the CloudSigma driver for the Zurich end-point
+ """
+ host = API_ENDPOINTS[DEFAULT_ENDPOINT]['host']
+
+class CloudSigmaZrhNodeDriver(CloudSigmaBaseNodeDriver):
+ """
+ CloudSigma node driver for the Zurich end-point
+ """
+ connectionCls = CloudSigmaZrhConnection
+
+# Utility methods (should we place them in libcloud/utils.py ?)
+def str2dicts(data):
+ """
+ Create a list of dictionaries from a whitespace and newline delimited text.
+
+ For example, this:
+ cpu 1100
+ ram 640
+
+ cpu 2200
+ ram 1024
+
+ becomes:
+ [{'cpu': '1100', 'ram': '640'}, {'cpu': '2200', 'ram': '1024'}]
+ """
+ list_data = []
+ list_data.append({})
+ d = list_data[-1]
+
+ lines = data.split('\n')
+ for line in lines:
+ line = line.strip()
+
+ if not line:
+ d = {}
+ list_data.append(d)
+ d = list_data[-1]
+ continue
+
+ whitespace = line.find(' ')
+
+ if not whitespace:
+ continue
+
+ key = line[0:whitespace]
+ value = line[whitespace + 1:]
+ d.update({key: value})
+
+ list_data = [value for value in list_data if value != {}]
+ return list_data
+
+def str2list(data):
+ """
+ Create a list of values from a whitespace and newline delimited text (keys are ignored).
+
+ For example, this:
+ ip 1.2.3.4
+ ip 1.2.3.5
+ ip 1.2.3.6
+
+ becomes:
+ ['1.2.3.4', '1.2.3.5', '1.2.3.6']
+ """
+ list_data = []
+
+ for line in data.split('\n'):
+ line = line.strip()
+
+ if not line:
+ continue
+
+ try:
+ splitted = line.split(' ')
+ # key = splitted[0]
+ value = splitted[1]
+ except Exception:
+ continue
+
+ list_data.append(value)
+
+ return list_data
+
+def dict2str(data):
+ """
+ Create a string with a whitespace and newline delimited text from a dictionary.
+
+ For example, this:
+ {'cpu': '1100', 'ram': '640', 'smp': 'auto'}
+
+ becomes:
+ cpu 1100
+ ram 640
+ smp auto
+
+ cpu 2200
+ ram 1024
+ """
+ result = ''
+ for k in data:
+ if data[k] != None:
+ result += '%s %s\n' % (str(k), str(data[k]))
+ else:
+ result += '%s\n' % str(k)
+
+ return result
Added: incubator/libcloud/trunk/libcloud/compute/drivers/dreamhost.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/drivers/dreamhost.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/drivers/dreamhost.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/drivers/dreamhost.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,243 @@
+# 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.
+"""
+DreamHost Driver
+"""
+
+try:
+ import json
+except:
+ import simplejson as json
+
+from libcloud.common.base import ConnectionKey, Response
+from libcloud.common.types import InvalidCredsError
+from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize
+from libcloud.compute.base import NodeImage
+from libcloud.compute.types import Provider, NodeState
+
+"""
+DreamHost Private Servers can be resized on the fly, but Libcloud doesn't
+currently support extensions to its interface, so we'll put some basic sizes
+in for node creation.
+"""
+DH_PS_SIZES = {
+ 'minimum': {
+ 'id' : 'minimum',
+ 'name' : 'Minimum DH PS size',
+ 'ram' : 300,
+ 'price' : 15,
+ 'disk' : None,
+ 'bandwidth' : None
+ },
+ 'maximum': {
+ 'id' : 'maximum',
+ 'name' : 'Maximum DH PS size',
+ 'ram' : 4000,
+ 'price' : 200,
+ 'disk' : None,
+ 'bandwidth' : None
+ },
+ 'default': {
+ 'id' : 'default',
+ 'name' : 'Default DH PS size',
+ 'ram' : 2300,
+ 'price' : 115,
+ 'disk' : None,
+ 'bandwidth' : None
+ },
+ 'low': {
+ 'id' : 'low',
+ 'name' : 'DH PS with 1GB RAM',
+ 'ram' : 1000,
+ 'price' : 50,
+ 'disk' : None,
+ 'bandwidth' : None
+ },
+ 'high': {
+ 'id' : 'high',
+ 'name' : 'DH PS with 3GB RAM',
+ 'ram' : 3000,
+ 'price' : 150,
+ 'disk' : None,
+ 'bandwidth' : None
+ },
+}
+
+
+class DreamhostAPIException(Exception):
+
+ def __str__(self):
+ return self.args[0]
+
+ def __repr__(self):
+ return "<DreamhostException '%s'>" % (self.args[0])
+
+
+class DreamhostResponse(Response):
+ """
+ Response class for DreamHost PS
+ """
+
+ def parse_body(self):
+ resp = json.loads(self.body)
+ if resp['result'] != 'success':
+ raise Exception(self._api_parse_error(resp))
+ return resp['data']
+
+ def parse_error(self):
+ raise Exception
+
+ def _api_parse_error(self, response):
+ if 'data' in response:
+ if response['data'] == 'invalid_api_key':
+ raise InvalidCredsError("Oops! You've entered an invalid API key")
+ else:
+ raise DreamhostAPIException(response['data'])
+ else:
+ raise DreamhostAPIException("Unknown problem: %s" % (self.body))
+
+class DreamhostConnection(ConnectionKey):
+ """
+ Connection class to connect to DreamHost's API servers
+ """
+
+ host = 'api.dreamhost.com'
+ responseCls = DreamhostResponse
+ format = 'json'
+
+ def add_default_params(self, params):
+ """
+ Add key and format parameters to the request. Eventually should add
+ unique_id to prevent re-execution of a single request.
+ """
+ params['key'] = self.key
+ params['format'] = self.format
+ #params['unique_id'] = generate_unique_id()
+ return params
+
+
+class DreamhostNodeDriver(NodeDriver):
+ """
+ Node Driver for DreamHost PS
+ """
+ type = Provider.DREAMHOST
+ name = "Dreamhost"
+ connectionCls = DreamhostConnection
+ _sizes = DH_PS_SIZES
+
+ def create_node(self, **kwargs):
+ """Create a new Dreamhost node
+
+ See L{NodeDriver.create_node} for more keyword args.
+
+ @keyword ex_movedata: Copy all your existing users to this new PS
+ @type ex_movedata: C{str}
+ """
+ size = kwargs['size'].ram
+ params = {
+ 'cmd' : 'dreamhost_ps-add_ps',
+ 'movedata' : kwargs.get('movedata', 'no'),
+ 'type' : kwargs['image'].name,
+ 'size' : size
+ }
+ data = self.connection.request('/', params).object
+ return Node(
+ id = data['added_web'],
+ name = data['added_web'],
+ state = NodeState.PENDING,
+ public_ip = [],
+ private_ip = [],
+ driver = self.connection.driver,
+ extra = {
+ 'type' : kwargs['image'].name
+ }
+ )
+
+ def destroy_node(self, node):
+ params = {
+ 'cmd' : 'dreamhost_ps-remove_ps',
+ 'ps' : node.id
+ }
+ try:
+ return self.connection.request('/', params).success()
+ except DreamhostAPIException:
+ return False
+
+ def reboot_node(self, node):
+ params = {
+ 'cmd' : 'dreamhost_ps-reboot',
+ 'ps' : node.id
+ }
+ try:
+ return self.connection.request('/', params).success()
+ except DreamhostAPIException:
+ return False
+
+ def list_nodes(self, **kwargs):
+ data = self.connection.request('/', {'cmd': 'dreamhost_ps-list_ps'}).object
+ return [self._to_node(n) for n in data]
+
+ def list_images(self, **kwargs):
+ data = self.connection.request('/', {'cmd': 'dreamhost_ps-list_images'}).object
+ images = []
+ for img in data:
+ images.append(NodeImage(
+ id = img['image'],
+ name = img['image'],
+ driver = self.connection.driver
+ ))
+ return images
+
+ def list_sizes(self, **kwargs):
+ return [ NodeSize(driver=self.connection.driver, **i)
+ for i in self._sizes.values() ]
+
+ def list_locations(self, **kwargs):
+ raise NotImplementedError('You cannot select a location for DreamHost Private Servers at this time.')
+
+ ############################################
+ # Private Methods (helpers and extensions) #
+ ############################################
+ def _resize_node(self, node, size):
+ if (size < 300 or size > 4000):
+ return False
+
+ params = {
+ 'cmd' : 'dreamhost_ps-set_size',
+ 'ps' : node.id,
+ 'size' : size
+ }
+ try:
+ return self.connection.request('/', params).success()
+ except DreamhostAPIException:
+ return False
+
+ def _to_node(self, data):
+ """
+ Convert the data from a DreamhostResponse object into a Node
+ """
+ return Node(
+ id = data['ps'],
+ name = data['ps'],
+ state = NodeState.UNKNOWN,
+ public_ip = [data['ip']],
+ private_ip = [],
+ driver = self.connection.driver,
+ extra = {
+ 'current_size' : data['memory_mb'],
+ 'account_id' : data['account_id'],
+ 'type' : data['type']
+ }
+ )
Added: incubator/libcloud/trunk/libcloud/compute/drivers/dummy.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/compute/drivers/dummy.py?rev=1079029&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/compute/drivers/dummy.py (added)
+++ incubator/libcloud/trunk/libcloud/compute/drivers/dummy.py Mon Mar 7 23:44:06 2011
@@ -0,0 +1,297 @@
+# 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.
+"""
+Dummy Driver
+
+@note: This driver is out of date
+"""
+import uuid
+import socket
+import struct
+
+from libcloud.base import ConnectionKey, NodeDriver, NodeSize, NodeLocation
+from libcloud.compute.base import NodeImage, Node
+from libcloud.compute.types import Provider,NodeState
+
+class DummyConnection(ConnectionKey):
+ """
+ Dummy connection class
+ """
+
+ def connect(self, host=None, port=None):
+ pass
+
+class DummyNodeDriver(NodeDriver):
+ """
+ Dummy node driver
+
+ This is a fake driver which appears to always create or destroy
+ nodes successfully.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node=driver.create_node()
+ >>> node.public_ip[0]
+ '127.0.0.3'
+ >>> node.name
+ 'dummy-3'
+
+ If the credentials you give convert to an integer then the next
+ node to be created will be one higher.
+
+ Each time you create a node you will get a different IP address.
+
+ >>> driver = DummyNodeDriver(22)
+ >>> node=driver.create_node()
+ >>> node.name
+ 'dummy-23'
+
+ """
+
+ name = "Dummy Node Provider"
+ type = Provider.DUMMY
+
+ def __init__(self, creds):
+ self.creds = creds
+ try:
+ num = int(creds)
+ except ValueError:
+ num = None
+ if num:
+ self.nl = []
+ startip = _ip_to_int('127.0.0.1')
+ for i in xrange(num):
+ ip = _int_to_ip(startip + i)
+ self.nl.append(
+ Node(id=i,
+ name='dummy-%d' % (i),
+ state=NodeState.RUNNING,
+ public_ip=[ip],
+ private_ip=[],
+ driver=self,
+ extra={'foo': 'bar'})
+ )
+ else:
+ self.nl = [
+ Node(id=1,
+ name='dummy-1',
+ state=NodeState.RUNNING,
+ public_ip=['127.0.0.1'],
+ private_ip=[],
+ driver=self,
+ extra={'foo': 'bar'}),
+ Node(id=2,
+ name='dummy-2',
+ state=NodeState.RUNNING,
+ public_ip=['127.0.0.1'],
+ private_ip=[],
+ driver=self,
+ extra={'foo': 'bar'}),
+ ]
+ self.connection = DummyConnection(self.creds)
+
+ def get_uuid(self, unique_field=None):
+ return str(uuid.uuid4())
+
+ def list_nodes(self):
+ """
+ List the nodes known to a particular driver;
+ There are two default nodes created at the beginning
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node_list=driver.list_nodes()
+ >>> sorted([node.name for node in node_list ])
+ ['dummy-1', 'dummy-2']
+
+ each item in the list returned is a node object from which you
+ can carry out any node actions you wish
+
+ >>> node_list[0].reboot()
+ True
+
+ As more nodes are added, list_nodes will return them
+
+ >>> node=driver.create_node()
+ >>> sorted([node.name for node in driver.list_nodes()])
+ ['dummy-1', 'dummy-2', 'dummy-3']
+ """
+ return self.nl
+
+ def reboot_node(self, node):
+ """
+ Sets the node state to rebooting; in this dummy driver always
+ returns True as if the reboot had been successful.
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> node=driver.create_node()
+ >>> from libcloud.compute.types import NodeState
+ >>> node.state == NodeState.RUNNING
+ True
+ >>> node.state == NodeState.REBOOTING
+ False
+ >>> driver.reboot_node(node)
+ True
+ >>> node.state == NodeState.REBOOTING
+ True
+
+ Please note, dummy nodes never recover from the reboot.
+ """
+
+ node.state = NodeState.REBOOTING
+ return True
+
+ def destroy_node(self, node):
+ """
+ Sets the node state to terminated and removes it from the node list
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> from libcloud.compute.types import NodeState
+ >>> node = [node for node in driver.list_nodes() if node.name == 'dummy-1'][0]
+ >>> node.state == NodeState.RUNNING
+ True
+ >>> driver.destroy_node(node)
+ True
+ >>> node.state == NodeState.RUNNING
+ False
+ >>> [node for node in driver.list_nodes() if node.name == 'dummy-1']
+ []
+ """
+
+ node.state = NodeState.TERMINATED
+ self.nl.remove(node)
+ return True
+
+ def list_images(self, location=None):
+ """
+ Returns a list of images as a cloud provider might have
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> sorted([image.name for image in driver.list_images()])
+ ['Slackware 4', 'Ubuntu 9.04', 'Ubuntu 9.10']
+ """
+ return [
+ NodeImage(id=1, name="Ubuntu 9.10", driver=self),
+ NodeImage(id=2, name="Ubuntu 9.04", driver=self),
+ NodeImage(id=3, name="Slackware 4", driver=self),
+ ]
+
+ def list_sizes(self, location=None):
+ """
+ Returns a list of node sizes as a cloud provider might have
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> sorted([size.ram for size in driver.list_sizes()])
+ [128, 512, 4096, 8192]
+ """
+
+ return [
+ NodeSize(id=1,
+ name="Small",
+ ram=128,
+ disk=4,
+ bandwidth=500,
+ price=4,
+ driver=self),
+ NodeSize(id=2,
+ name="Medium",
+ ram=512,
+ disk=16,
+ bandwidth=1500,
+ price=8,
+ driver=self),
+ NodeSize(id=3,
+ name="Big",
+ ram=4096,
+ disk=32,
+ bandwidth=2500,
+ price=32,
+ driver=self),
+ NodeSize(id=4,
+ name="XXL Big",
+ ram=4096*2,
+ disk=32*4,
+ bandwidth=2500*3,
+ price=32*2,
+ driver=self),
+ ]
+
+ def list_locations(self):
+ """
+ Returns a list of locations of nodes
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> sorted([loc.name + " in " + loc.country for loc in driver.list_locations()])
+ ['Island Datacenter in FJ', 'London Loft in GB', "Paul's Room in US"]
+ """
+ return [
+ NodeLocation(id=1,
+ name="Paul's Room",
+ country='US',
+ driver=self),
+ NodeLocation(id=2,
+ name="London Loft",
+ country='GB',
+ driver=self),
+ NodeLocation(id=3,
+ name="Island Datacenter",
+ country='FJ',
+ driver=self),
+ ]
+
+ def create_node(self, **kwargs):
+ """
+ Creates a dummy node; the node id is equal to the number of
+ nodes in the node list
+
+ >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+ >>> driver = DummyNodeDriver(0)
+ >>> sorted([node.name for node in driver.list_nodes()])
+ ['dummy-1', 'dummy-2']
+ >>> nodeA = driver.create_node()
+ >>> sorted([node.name for node in driver.list_nodes()])
+ ['dummy-1', 'dummy-2', 'dummy-3']
+ >>> driver.create_node().name
+ 'dummy-4'
+ >>> driver.destroy_node(nodeA)
+ True
+ >>> sorted([node.name for node in driver.list_nodes()])
+ ['dummy-1', 'dummy-2', 'dummy-4']
+ """
+ l = len(self.nl) + 1
+ n = Node(id=l,
+ name='dummy-%d' % l,
+ state=NodeState.RUNNING,
+ public_ip=['127.0.0.%d' % l],
+ private_ip=[],
+ driver=self,
+ extra={'foo': 'bar'})
+ self.nl.append(n)
+ return n
+
+def _ip_to_int(ip):
+ return socket.htonl(struct.unpack('I', socket.inet_aton(ip))[0])
+
+def _int_to_ip(ip):
+ return socket.inet_ntoa(struct.pack('I', socket.ntohl(ip)))
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()