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/06/16 14:58:10 UTC
svn commit: r1136418 - in /libcloud/trunk:
libcloud/compute/drivers/__init__.py libcloud/compute/drivers/elastichosts.py
libcloud/compute/drivers/elasticstack.py test/compute/test_elastichosts.py
Author: tomaz
Date: Thu Jun 16 12:58:09 2011
New Revision: 1136418
URL: http://svn.apache.org/viewvc?rev=1136418&view=rev
Log:
Refactor functionality from ElasticHost driver into a separate base
ElasticStack class.
There are multiple providers which are using ElasticStack platform and this
should allow us to easily add new providers based on the ElasticStack platform.
Added:
libcloud/trunk/libcloud/compute/drivers/elasticstack.py
Modified:
libcloud/trunk/libcloud/compute/drivers/__init__.py
libcloud/trunk/libcloud/compute/drivers/elastichosts.py
libcloud/trunk/test/compute/test_elastichosts.py
Modified: libcloud/trunk/libcloud/compute/drivers/__init__.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/__init__.py?rev=1136418&r1=1136417&r2=1136418&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/__init__.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/__init__.py Thu Jun 16 12:58:09 2011
@@ -23,6 +23,7 @@ __all__ = [
'dummy',
'ec2',
'ecp',
+ 'elasticstack',
'elastichosts',
'cloudsigma',
'gogrid',
Modified: libcloud/trunk/libcloud/compute/drivers/elastichosts.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/elastichosts.py?rev=1136418&r1=1136417&r2=1136418&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/elastichosts.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/elastichosts.py Thu Jun 16 12:58:09 2011
@@ -12,25 +12,15 @@
# 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.
+
"""
ElasticHosts Driver
"""
-import re
-import time
-import base64
-import httplib
-
-try:
- import json
-except:
- import simplejson as json
-
-from libcloud.common.base import ConnectionUserAndKey, Response
-from libcloud.common.types import InvalidCredsError, MalformedResponseError
-from libcloud.compute.types import Provider, NodeState
-from libcloud.compute.base import NodeDriver, NodeSize, Node
-from libcloud.compute.base import NodeImage
-from libcloud.compute.deployment import ScriptDeployment, SSHKeyDeployment, MultiStepDeployment
+
+from libcloud.compute.types import Provider
+from libcloud.compute.drivers.elasticstack import ElasticStackBaseNodeDriver
+from libcloud.compute.drivers.elasticstack import ElasticStackBaseConnection
+
# API end-points
API_ENDPOINTS = {
@@ -51,67 +41,9 @@ API_ENDPOINTS = {
},
}
-# Default API end-point for the base connection clase.
+# Default API end-point for the base connection class.
DEFAULT_ENDPOINT = 'us-1'
-# ElasticHosts doesn't specify special instance types, so I just specified
-# some plans based on the pricing page
-# (http://www.elastichosts.com/cloud-hosting/pricing)
-# and other provides.
-#
-# Basically for CPU any value between 500Mhz and 20000Mhz should work,
-# 256MB to 8192MB for ram and 1GB to 2TB for disk.
-INSTANCE_TYPES = {
- 'small': {
- 'id': 'small',
- 'name': 'Small instance',
- 'cpu': 2000,
- 'memory': 1700,
- 'disk': 160,
- 'bandwidth': None,
- },
- 'medium': {
- 'id': 'medium',
- 'name': 'Medium instance',
- 'cpu': 3000,
- 'memory': 4096,
- 'disk': 500,
- 'bandwidth': None,
- },
- 'large': {
- 'id': 'large',
- 'name': 'Large instance',
- 'cpu': 4000,
- 'memory': 7680,
- 'disk': 850,
- 'bandwidth': None,
- },
- 'extra-large': {
- 'id': 'extra-large',
- 'name': 'Extra Large instance',
- 'cpu': 8000,
- 'memory': 8192,
- 'disk': 1690,
- 'bandwidth': None,
- },
- 'high-cpu-medium': {
- 'id': 'high-cpu-medium',
- 'name': 'High-CPU Medium instance',
- 'cpu': 5000,
- 'memory': 1700,
- 'disk': 350,
- 'bandwidth': None,
- },
- 'high-cpu-extra-large': {
- 'id': 'high-cpu-extra-large',
- 'name': 'High-CPU Extra Large instance',
- 'cpu': 20000,
- 'memory': 7168,
- 'disk': 1690,
- 'bandwidth': None,
- },
-}
-
# Retrieved from http://www.elastichosts.com/cloud-hosting/api
STANDARD_DRIVES = {
'38df0986-4d85-4b76-b502-3878ffc80161': {
@@ -164,381 +96,21 @@ STANDARD_DRIVES = {
},
}
-NODE_STATE_MAP = {
- 'active': NodeState.RUNNING,
- 'dead': NodeState.TERMINATED,
- 'dumped': NodeState.TERMINATED,
-}
-
-# Default timeout (in seconds) for the drive imaging process
-IMAGING_TIMEOUT = 10 * 60
-
-class ElasticHostsException(Exception):
- """
- Exception class for ElasticHosts driver
- """
-
- def __str__(self):
- return self.args[0]
-
- def __repr__(self):
- return "<ElasticHostsException '%s'>" % (self.args[0])
-
-class ElasticHostsResponse(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
-
- try:
- data = json.loads(self.body)
- except:
- raise MalformedResponseError("Failed to parse JSON",
- body=self.body,
- driver=ElasticHostsBaseNodeDriver)
-
- return data
-
- def parse_error(self):
- error_header = self.headers.get('x-elastic-error', '')
- return 'X-Elastic-Error: %s (%s)' % (error_header, self.body.strip())
-
-class ElasticHostsNodeSize(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 ElasticHostsBaseConnection(ConnectionUserAndKey):
- """
- Base connection class for the ElasticHosts driver
- """
+class ElasticHostsBaseConnection(ElasticStackBaseConnection):
host = API_ENDPOINTS[DEFAULT_ENDPOINT]['host']
- responseCls = ElasticHostsResponse
-
- 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 ElasticHostsBaseNodeDriver(NodeDriver):
- """
- Base ElasticHosts node driver
- """
+class ElasticHostsBaseNodeDriver(ElasticStackBaseNodeDriver):
type = Provider.ELASTICHOSTS
api_name = 'elastichosts'
name = 'ElasticHosts'
connectionCls = ElasticHostsBaseConnection
features = {"create_node": ["generates_password"]}
+ _standard_drives = STANDARD_DRIVES
- def reboot_node(self, node):
- # Reboots the node
- response = self.connection.request(
- action='/servers/%s/reset' % (node.id),
- method='POST'
- )
- return response.status == 204
-
- def destroy_node(self, node):
- # Kills the server immediately
- response = self.connection.request(
- action='/servers/%s/destroy' % (node.id),
- method='POST'
- )
- return response.status == 204
-
- def list_images(self, location=None):
- # Returns a list of available pre-installed system drive images
- images = []
- for key, value in STANDARD_DRIVES.iteritems():
- image = NodeImage(
- id=value['uuid'],
- name=value['description'],
- driver=self.connection.driver,
- extra={
- 'size_gunzipped': value['size_gunzipped']
- }
- )
- images.append(image)
-
- return images
-
- def list_sizes(self, location=None):
- sizes = []
- for key, value in INSTANCE_TYPES.iteritems():
- size = ElasticHostsNodeSize(
- id=value['id'],
- name=value['name'], cpu=value['cpu'], ram=value['memory'],
- disk=value['disk'], bandwidth=value['bandwidth'],
- price=self._get_size_price(size_id=value['id']),
- driver=self.connection.driver
- )
- sizes.append(size)
-
- return sizes
-
- def list_nodes(self):
- # Returns a list of active (running) nodes
- response = self.connection.request(action='/servers/info').object
-
- nodes = []
- for data in response:
- node = self._to_node(data)
- nodes.append(node)
-
- return nodes
-
- def create_node(self, **kwargs):
- """Creates a ElasticHosts 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
- (if not specified, e1000 is used)
- @type nic_model: C{string}
-
- @keyword vnc_password: If set, the same password is also used for
- SSH access with user toor,
- otherwise VNC access is disabled and
- no SSH login is possible.
- @type vnc_password: C{string}
- """
- size = kwargs['size']
- image = kwargs['image']
- smp = kwargs.get('smp', 'auto')
- nic_model = kwargs.get('nic_model', 'e1000')
- vnc_password = ssh_password = kwargs.get('vnc_password', None)
-
- if nic_model not in ('e1000', 'rtl8139', 'virtio'):
- raise ElasticHostsException('Invalid NIC model specified')
-
- # check that drive size is not smaller then pre installed image size
-
- # First we create a drive with the specified size
- drive_data = {}
- drive_data.update({'name': kwargs['name'],
- 'size': '%sG' % (kwargs['size'].disk)})
-
- response = self.connection.request(action='/drives/create',
- data=json.dumps(drive_data),
- method='POST').object
-
- if not response:
- raise ElasticHostsException('Drive creation failed')
-
- drive_uuid = response['drive']
-
- # Then we image the selected pre-installed system drive onto it
- response = self.connection.request(
- action='/drives/%s/image/%s/gunzip' % (drive_uuid, image.id),
- method='POST'
- )
-
- if response.status != 204:
- raise ElasticHostsException('Drive imaging failed')
-
- # We wait until the drive is imaged and then boot up the node
- # (in most cases, the imaging process shouldn't take longer
- # than a few minutes)
- response = self.connection.request(
- action='/drives/%s/info' % (drive_uuid)
- ).object
- imaging_start = time.time()
- while response.has_key('imaging'):
- response = self.connection.request(
- action='/drives/%s/info' % (drive_uuid)
- ).object
- elapsed_time = time.time() - imaging_start
- if (response.has_key('imaging')
- and elapsed_time >= IMAGING_TIMEOUT):
- raise ElasticHostsException('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=json.dumps(node_data),
- method='POST'
- ).object
-
- if isinstance(response, list):
- nodes = [self._to_node(node, ssh_password) for node in response]
- else:
- nodes = self._to_node(response, ssh_password)
-
- return nodes
-
- # Extension methods
- def ex_set_node_configuration(self, node, **kwargs):
- # Changes the configuration of the running server
- 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 ElasticHostsException(
- 'Invalid configuration key specified: %s'
- % (',' .join(invalid_keys))
- )
-
- response = self.connection.request(
- action='/servers/%s/set' % (node.id), data=json.dumps(kwargs),
- method='POST'
- )
-
- return (response.status == httplib.OK and response.body != '')
-
- def deploy_node(self, **kwargs):
- """
- Create a new node, and start deployment.
-
- @keyword enable_root: If true, root password will be set to
- vnc_password (this will enable SSH access)
- and default 'toor' account will be deleted.
- @type enable_root: C{bool}
-
- For detailed description and keywords args, see
- L{NodeDriver.deploy_node}.
- """
- image = kwargs['image']
- vnc_password = kwargs.get('vnc_password', None)
- enable_root = kwargs.get('enable_root', False)
-
- if not vnc_password:
- raise ValueError('You need to provide vnc_password argument '
- 'if you want to use deployment')
-
- if (image in STANDARD_DRIVES
- and STANDARD_DRIVES[image]['supports_deployment']):
- raise ValueError('Image %s does not support deployment'
- % (image.id))
-
- if enable_root:
- script = ("unset HISTFILE;"
- "echo root:%s | chpasswd;"
- "sed -i '/^toor.*$/d' /etc/passwd /etc/shadow;"
- "history -c") % vnc_password
- root_enable_script = ScriptDeployment(script=script,
- delete=True)
- deploy = kwargs.get('deploy', None)
- if deploy:
- if (isinstance(deploy, ScriptDeployment)
- or isinstance(deploy, SSHKeyDeployment)):
- deployment = MultiStepDeployment([deploy,
- root_enable_script])
- elif isinstance(deploy, MultiStepDeployment):
- deployment = deploy
- deployment.add(root_enable_script)
- else:
- deployment = root_enable_script
-
- kwargs['deploy'] = deployment
-
- if not kwargs.get('ssh_username', None):
- kwargs['ssh_username'] = 'toor'
-
- return super(ElasticHostsBaseNodeDriver, self).deploy_node(**kwargs)
-
- def ex_shutdown_node(self, node):
- # Sends the ACPI power-down event
- response = self.connection.request(
- action='/servers/%s/shutdown' % (node.id),
- method='POST'
- )
- return response.status == 204
-
- def ex_destroy_drive(self, drive_uuid):
- # Deletes a drive
- response = self.connection.request(
- action='/drives/%s/destroy' % (drive_uuid),
- method='POST'
- )
- return response.status == 204
-
- # Helper methods
- def _to_node(self, data, ssh_password=None):
- try:
- state = NODE_STATE_MAP[data['status']]
- except KeyError:
- state = NodeState.UNKNOWN
-
- if isinstance(data['nic:0:dhcp'], list):
- public_ip = data['nic:0:dhcp']
- else:
- public_ip = [data['nic:0:dhcp']]
-
- extra = {'cpu': data['cpu'],
- 'smp': data['smp'],
- 'mem': data['mem'],
- 'started': data['started']}
-
- if data.has_key('vnc:ip') and data.has_key('vnc:password'):
- extra.update({'vnc_ip': data['vnc:ip'],
- 'vnc_password': data['vnc:password']})
-
- if ssh_password:
- extra.update({'password': ssh_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
-
-class ElasticHostsUK1Connection(ElasticHostsBaseConnection):
+class ElasticHostsUK1Connection(ElasticStackBaseConnection):
"""
Connection class for the ElasticHosts driver for
the London Peer 1 end-point
@@ -546,32 +118,37 @@ class ElasticHostsUK1Connection(ElasticH
host = API_ENDPOINTS['uk-1']['host']
+
class ElasticHostsUK1NodeDriver(ElasticHostsBaseNodeDriver):
"""
ElasticHosts node driver for the London Peer 1 end-point
"""
connectionCls = ElasticHostsUK1Connection
-class ElasticHostsUK2Connection(ElasticHostsBaseConnection):
+
+class ElasticHostsUK2Connection(ElasticStackBaseConnection):
"""
Connection class for the ElasticHosts driver for
the London Bluesquare end-point
"""
host = API_ENDPOINTS['uk-2']['host']
+
class ElasticHostsUK2NodeDriver(ElasticHostsBaseNodeDriver):
"""
ElasticHosts node driver for the London Bluesquare end-point
"""
connectionCls = ElasticHostsUK2Connection
-class ElasticHostsUS1Connection(ElasticHostsBaseConnection):
+
+class ElasticHostsUS1Connection(ElasticStackBaseConnection):
"""
Connection class for the ElasticHosts driver for
the San Antonio Peer 1 end-point
"""
host = API_ENDPOINTS['us-1']['host']
+
class ElasticHostsUS1NodeDriver(ElasticHostsBaseNodeDriver):
"""
ElasticHosts node driver for the San Antonio Peer 1 end-point
Added: libcloud/trunk/libcloud/compute/drivers/elasticstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/elasticstack.py?rev=1136418&view=auto
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/elasticstack.py (added)
+++ libcloud/trunk/libcloud/compute/drivers/elasticstack.py Thu Jun 16 12:58:09 2011
@@ -0,0 +1,465 @@
+# 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.
+
+"""
+Base driver for the providers based on the ElasticStack platform -
+http://www.elasticstack.com.
+"""
+
+import re
+import time
+import base64
+import httplib
+
+try:
+ import json
+except:
+ import simplejson as json
+
+from libcloud.common.base import ConnectionUserAndKey, Response
+from libcloud.common.types import InvalidCredsError, MalformedResponseError
+from libcloud.compute.types import NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage
+from libcloud.compute.deployment import ScriptDeployment, SSHKeyDeployment
+from libcloud.compute.deployment import MultiStepDeployment
+
+
+NODE_STATE_MAP = {
+ 'active': NodeState.RUNNING,
+ 'dead': NodeState.TERMINATED,
+ 'dumped': NodeState.TERMINATED,
+}
+
+# Default timeout (in seconds) for the drive imaging process
+IMAGING_TIMEOUT = 10 * 60
+
+# ElasticStack doesn't specify special instance types, so I just specified
+# some plans based on the other provider offerings.
+#
+# Basically for CPU any value between 500Mhz and 20000Mhz should work,
+# 256MB to 8192MB for ram and 1GB to 2TB for disk.
+INSTANCE_TYPES = {
+ 'small': {
+ 'id': 'small',
+ 'name': 'Small instance',
+ 'cpu': 2000,
+ 'memory': 1700,
+ 'disk': 160,
+ 'bandwidth': None,
+ },
+ 'medium': {
+ 'id': 'medium',
+ 'name': 'Medium instance',
+ 'cpu': 3000,
+ 'memory': 4096,
+ 'disk': 500,
+ 'bandwidth': None,
+ },
+ 'large': {
+ 'id': 'large',
+ 'name': 'Large instance',
+ 'cpu': 4000,
+ 'memory': 7680,
+ 'disk': 850,
+ 'bandwidth': None,
+ },
+ 'extra-large': {
+ 'id': 'extra-large',
+ 'name': 'Extra Large instance',
+ 'cpu': 8000,
+ 'memory': 8192,
+ 'disk': 1690,
+ 'bandwidth': None,
+ },
+ 'high-cpu-medium': {
+ 'id': 'high-cpu-medium',
+ 'name': 'High-CPU Medium instance',
+ 'cpu': 5000,
+ 'memory': 1700,
+ 'disk': 350,
+ 'bandwidth': None,
+ },
+ 'high-cpu-extra-large': {
+ 'id': 'high-cpu-extra-large',
+ 'name': 'High-CPU Extra Large instance',
+ 'cpu': 20000,
+ 'memory': 7168,
+ 'disk': 1690,
+ 'bandwidth': None,
+ },
+}
+
+
+class ElasticStackException(Exception):
+ def __str__(self):
+ return self.args[0]
+
+ def __repr__(self):
+ return "<ElasticStackException '%s'>" % (self.args[0])
+
+
+class ElasticStackResponse(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
+
+ try:
+ data = json.loads(self.body)
+ except:
+ raise MalformedResponseError('Failed to parse JSON',
+ body=self.body,
+ driver=ElasticStackBaseNodeDriver)
+
+ return data
+
+ def parse_error(self):
+ error_header = self.headers.get('x-elastic-error', '')
+ return 'X-Elastic-Error: %s (%s)' % (error_header, self.body.strip())
+
+
+class ElasticStackNodeSize(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 ElasticStackBaseConnection(ConnectionUserAndKey):
+ """
+ Base connection class for the ElasticStack driver
+ """
+
+ host = None
+ responseCls = ElasticStackResponse
+
+ 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 ElasticStackBaseNodeDriver(NodeDriver):
+ connectionCls = ElasticStackBaseConnection
+ features = {"create_node": ["generates_password"]}
+
+ def reboot_node(self, node):
+ # Reboots the node
+ response = self.connection.request(
+ action='/servers/%s/reset' % (node.id),
+ method='POST'
+ )
+ return response.status == 204
+
+ def destroy_node(self, node):
+ # Kills the server immediately
+ response = self.connection.request(
+ action='/servers/%s/destroy' % (node.id),
+ method='POST'
+ )
+ return response.status == 204
+
+ def list_images(self, location=None):
+ # Returns a list of available pre-installed system drive images
+ images = []
+ for key, value in self._standard_drives.iteritems():
+ image = NodeImage(
+ id=value['uuid'],
+ name=value['description'],
+ driver=self.connection.driver,
+ extra={
+ 'size_gunzipped': value['size_gunzipped']
+ }
+ )
+ images.append(image)
+
+ return images
+
+ def list_sizes(self, location=None):
+ sizes = []
+ for key, value in INSTANCE_TYPES.iteritems():
+ size = ElasticStackNodeSize(
+ id=value['id'],
+ name=value['name'], cpu=value['cpu'], ram=value['memory'],
+ disk=value['disk'], bandwidth=value['bandwidth'],
+ price=self._get_size_price(size_id=value['id']),
+ driver=self.connection.driver
+ )
+ sizes.append(size)
+
+ return sizes
+
+ def list_nodes(self):
+ # Returns a list of active (running) nodes
+ response = self.connection.request(action='/servers/info').object
+
+ nodes = []
+ for data in response:
+ node = self._to_node(data)
+ nodes.append(node)
+
+ return nodes
+
+ def create_node(self, **kwargs):
+ """Creates a ElasticStack 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
+ (if not specified, e1000 is used)
+ @type nic_model: C{string}
+
+ @keyword vnc_password: If set, the same password is also used for
+ SSH access with user toor,
+ otherwise VNC access is disabled and
+ no SSH login is possible.
+ @type vnc_password: C{string}
+ """
+ size = kwargs['size']
+ image = kwargs['image']
+ smp = kwargs.get('smp', 'auto')
+ nic_model = kwargs.get('nic_model', 'e1000')
+ vnc_password = ssh_password = kwargs.get('vnc_password', None)
+
+ if nic_model not in ('e1000', 'rtl8139', 'virtio'):
+ raise ElasticStackException('Invalid NIC model specified')
+
+ # check that drive size is not smaller then pre installed image size
+
+ # First we create a drive with the specified size
+ drive_data = {}
+ drive_data.update({'name': kwargs['name'],
+ 'size': '%sG' % (kwargs['size'].disk)})
+
+ response = self.connection.request(action='/drives/create',
+ data=json.dumps(drive_data),
+ method='POST').object
+
+ if not response:
+ raise ElasticStackException('Drive creation failed')
+
+ drive_uuid = response['drive']
+
+ # Then we image the selected pre-installed system drive onto it
+ response = self.connection.request(
+ action='/drives/%s/image/%s/gunzip' % (drive_uuid, image.id),
+ method='POST'
+ )
+
+ if response.status != 204:
+ raise ElasticStackException('Drive imaging failed')
+
+ # We wait until the drive is imaged and then boot up the node
+ # (in most cases, the imaging process shouldn't take longer
+ # than a few minutes)
+ response = self.connection.request(
+ action='/drives/%s/info' % (drive_uuid)
+ ).object
+
+ imaging_start = time.time()
+ while 'imaging' in response:
+ response = self.connection.request(
+ action='/drives/%s/info' % (drive_uuid)
+ ).object
+
+ elapsed_time = time.time() - imaging_start
+ if ('imaging' in response
+ and elapsed_time >= IMAGING_TIMEOUT):
+ raise ElasticStackException('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=json.dumps(node_data),
+ method='POST'
+ ).object
+
+ if isinstance(response, list):
+ nodes = [self._to_node(node, ssh_password) for node in response]
+ else:
+ nodes = self._to_node(response, ssh_password)
+
+ return nodes
+
+ # Extension methods
+ def ex_set_node_configuration(self, node, **kwargs):
+ # Changes the configuration of the running server
+ 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 ElasticStackException(
+ 'Invalid configuration key specified: %s'
+ % (',' .join(invalid_keys))
+ )
+
+ response = self.connection.request(
+ action='/servers/%s/set' % (node.id), data=json.dumps(kwargs),
+ method='POST'
+ )
+
+ return (response.status == httplib.OK and response.body != '')
+
+ def deploy_node(self, **kwargs):
+ """
+ Create a new node, and start deployment.
+
+ @keyword enable_root: If true, root password will be set to
+ vnc_password (this will enable SSH access)
+ and default 'toor' account will be deleted.
+ @type enable_root: C{bool}
+
+ For detailed description and keywords args, see
+ L{NodeDriver.deploy_node}.
+ """
+ image = kwargs['image']
+ vnc_password = kwargs.get('vnc_password', None)
+ enable_root = kwargs.get('enable_root', False)
+
+ if not vnc_password:
+ raise ValueError('You need to provide vnc_password argument '
+ 'if you want to use deployment')
+
+ if (image in self._standard_drives
+ and not self._standard_drives[image]['supports_deployment']):
+ raise ValueError('Image %s does not support deployment'
+ % (image.id))
+
+ if enable_root:
+ script = ("unset HISTFILE;"
+ "echo root:%s | chpasswd;"
+ "sed -i '/^toor.*$/d' /etc/passwd /etc/shadow;"
+ "history -c") % vnc_password
+ root_enable_script = ScriptDeployment(script=script,
+ delete=True)
+ deploy = kwargs.get('deploy', None)
+ if deploy:
+ if (isinstance(deploy, ScriptDeployment)
+ or isinstance(deploy, SSHKeyDeployment)):
+ deployment = MultiStepDeployment([deploy,
+ root_enable_script])
+ elif isinstance(deploy, MultiStepDeployment):
+ deployment = deploy
+ deployment.add(root_enable_script)
+ else:
+ deployment = root_enable_script
+
+ kwargs['deploy'] = deployment
+
+ if not kwargs.get('ssh_username', None):
+ kwargs['ssh_username'] = 'toor'
+
+ return super(ElasticStackBaseNodeDriver, self).deploy_node(**kwargs)
+
+ def ex_shutdown_node(self, node):
+ # Sends the ACPI power-down event
+ response = self.connection.request(
+ action='/servers/%s/shutdown' % (node.id),
+ method='POST'
+ )
+ return response.status == 204
+
+ def ex_destroy_drive(self, drive_uuid):
+ # Deletes a drive
+ response = self.connection.request(
+ action='/drives/%s/destroy' % (drive_uuid),
+ method='POST'
+ )
+ return response.status == 204
+
+ # Helper methods
+ def _to_node(self, data, ssh_password=None):
+ try:
+ state = NODE_STATE_MAP[data['status']]
+ except KeyError:
+ state = NodeState.UNKNOWN
+
+ if isinstance(data['nic:0:dhcp'], list):
+ public_ip = data['nic:0:dhcp']
+ else:
+ public_ip = [data['nic:0:dhcp']]
+
+ extra = {'cpu': data['cpu'],
+ 'smp': data['smp'],
+ 'mem': data['mem'],
+ 'started': data['started']}
+
+ if 'vnc:ip' in data and 'vnc:password' in data:
+ extra.update({'vnc_ip': data['vnc:ip'],
+ 'vnc_password': data['vnc:password']})
+
+ if ssh_password:
+ extra.update({'password': ssh_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
Modified: libcloud/trunk/test/compute/test_elastichosts.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/test_elastichosts.py?rev=1136418&r1=1136417&r2=1136418&view=diff
==============================================================================
--- libcloud/trunk/test/compute/test_elastichosts.py (original)
+++ libcloud/trunk/test/compute/test_elastichosts.py Thu Jun 16 12:58:09 2011
@@ -12,33 +12,43 @@
# 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.
-# Copyright 2009 RedRata Ltd
import sys
import unittest
import httplib
from libcloud.compute.base import Node
+from libcloud.compute.types import Provider
+from libcloud.compute.drivers.elasticstack import (ElasticStackException,
+ ElasticStackBaseConnection,
+ ElasticStackBaseNodeDriver as ElasticStack)
from libcloud.compute.drivers.elastichosts import \
- (ElasticHostsBaseNodeDriver as ElasticHosts,
- ElasticHostsException)
+ (ElasticHostsBaseNodeDriver as ElasticHosts)
from libcloud.common.types import InvalidCredsError, MalformedResponseError
from test import MockHttp
from test.file_fixtures import ComputeFileFixtures
-class ElasticHostsTestCase(unittest.TestCase):
+class ElasticStackTestCase(unittest.TestCase):
def setUp(self):
- ElasticHosts.connectionCls.conn_classes = (None,
- ElasticHostsHttp)
- ElasticHostsHttp.type = None
- self.driver = ElasticHosts('foo', 'bar')
+ # Re-use ElasticHosts fixtures for the base ElasticStack platform tests
+ ElasticStack.type = Provider.ELASTICHOSTS
+ ElasticStack.api_name = 'elastichosts'
+
+ ElasticStackBaseConnection.host = 'test.com'
+ ElasticStack.connectionCls.conn_classes = (None,
+ ElasticStackMockHttp)
+ ElasticStack._standard_drives = ElasticHosts._standard_drives
+
+ self.mockHttp = ElasticStackMockHttp
+ self.mockHttp.type = None
+ self.driver = ElasticStack('foo', 'bar')
self.node = Node(id=72258, name=None, state=None, public_ip=None,
private_ip=None, driver=self.driver)
def test_invalid_creds(self):
- ElasticHostsHttp.type = 'UNAUTHORIZED'
+ self.mockHttp.type = 'UNAUTHORIZED'
try:
self.driver.list_nodes()
except InvalidCredsError, e:
@@ -47,7 +57,7 @@ class ElasticHostsTestCase(unittest.Test
self.fail('test should have thrown')
def test_malformed_response(self):
- ElasticHostsHttp.type = 'MALFORMED'
+ self.mockHttp.type = 'MALFORMED'
try:
self.driver.list_nodes()
except MalformedResponseError:
@@ -56,7 +66,7 @@ class ElasticHostsTestCase(unittest.Test
self.fail('test should have thrown')
def test_parse_error(self):
- ElasticHostsHttp.type = 'PARSE_ERROR'
+ self.mockHttp.type = 'PARSE_ERROR'
try:
self.driver.list_nodes()
except Exception, e:
@@ -68,11 +78,12 @@ class ElasticHostsTestCase(unittest.Test
success = self.driver.ex_set_node_configuration(node=self.node,
name='name',
cpu='2')
+ self.assertTrue(success)
def test_ex_set_node_configuration_invalid_keys(self):
try:
self.driver.ex_set_node_configuration(node=self.node, foo='bar')
- except ElasticHostsException:
+ except ElasticStackException:
pass
else:
self.fail('Invalid option specified, but an exception was not thrown')
@@ -125,7 +136,17 @@ class ElasticHostsTestCase(unittest.Test
self.assertTrue(self.driver.create_node(name="api.ivan.net.nz",
image=image, size=size))
-class ElasticHostsHttp(MockHttp):
+
+class ElasticHostsTestCase(ElasticStackTestCase):
+
+ def setUp(self):
+ ElasticHosts.connectionCls.conn_classes = (None,
+ ElasticStackMockHttp)
+ self.driver = ElasticHosts('foo', 'bar')
+ super(ElasticHostsTestCase, self).setUp()
+
+
+class ElasticStackMockHttp(MockHttp):
fixtures = ComputeFileFixtures('elastichosts')