Index: trunk/libcloud/test/compute/test_ibm_sce.py
===================================================================
--- trunk/libcloud/test/compute/test_ibm_sce.py (revision 1374739)
+++ trunk/libcloud/test/compute/test_ibm_sce.py (working copy)
@@ -26,7 +26,7 @@
class IBMTests(unittest.TestCase, TestCaseMixin):
"""
- Tests the IBM Developer Cloud driver.
+ Tests the IBM SmartCloud Enterprise driver.
"""
def setUp(self):
@@ -117,7 +117,7 @@
self.fail('test should have thrown')
def test_destroy_node(self):
- # Delete existant node
+ # Delete existent node
nodes = self.driver.list_nodes() # retrieves 3 nodes
self.assertEquals(len(nodes), 3)
IBMMockHttp.type = 'DELETE'
@@ -155,6 +155,71 @@
else:
self.fail('test should have thrown')
+ def test_list_volumes(self):
+ ret = self.driver.list_volumes()
+ self.assertEqual(len(ret), 1)
+ self.assertEqual(ret[0].name, 'libcloudvol')
+ self.assertEqual(ret[0].extra['location'], '141')
+ self.assertEqual(ret[0].size, '2048')
+ self.assertEqual(ret[0].id, '39281')
+
+ def test_attach_volume(self):
+ vols = self.driver.list_volumes()
+ nodes = self.driver.list_nodes()
+ IBMMockHttp.type = 'ATTACH'
+ ret = self.driver.attach_volume(nodes[0], vols[0])
+ self.assertTrue(ret)
+
+ def test_create_volume(self):
+ IBMMockHttp.type = 'CREATE'
+ ret = self.driver.create_volume('256',
+ 'test-volume',
+ location='141',
+ format='RAW',
+ offering_id='20001208')
+ self.assertEqual(ret.id, '39293')
+ self.assertEqual(ret.size, '256')
+ self.assertEqual(ret.name, 'test-volume')
+ self.assertEqual(ret.extra['location'], '141')
+
+ def test_destroy_volume(self):
+ vols = self.driver.list_volumes()
+ IBMMockHttp.type = 'DESTROY'
+ ret = self.driver.destroy_volume(vols[0])
+ self.assertTrue(ret)
+
+ def test_detach_volume(self):
+ nodes = self.driver.list_nodes()
+ vols = self.driver.list_volumes()
+ IBMMockHttp.type = 'DETACH'
+ ret = self.driver.detach_volume(nodes[0], vols[0])
+ self.assertTrue(ret)
+
+ def test_ex_allocate_address(self):
+ IBMMockHttp.type = 'ALLOCATE'
+ ret = self.driver.ex_allocate_address('141', '20001223')
+ self.assertEqual(ret.id, '292795')
+ self.assertEqual(ret.state, '0')
+ self.assertEqual(ret.options['location'], '141')
+
+ def test_ex_delete_address(self):
+ IBMMockHttp.type = 'DELETE'
+ ret = self.driver.ex_delete_address('292795')
+ self.assertTrue(ret)
+
+ def test_ex_list_addresses(self):
+ ret = self.driver.ex_list_addresses()
+ self.assertEqual(ret[0].ip, '170.225.160.218')
+ self.assertEqual(ret[0].options['location'], '141')
+ self.assertEqual(ret[0].id, '292795')
+ self.assertEqual(ret[0].state, '2')
+
+ def test_ex_list_storage_offerings(self):
+ ret = self.driver.ex_list_storage_offerings()
+ self.assertEqual(ret[0].name, 'Small')
+ self.assertEqual(ret[0].location, '61')
+ self.assertEqual(ret[0].id, '20001208')
+
class IBMMockHttp(MockHttp):
fixtures = ComputeFileFixtures('ibm_sce')
@@ -198,6 +263,42 @@
def _computecloud_enterprise_api_rest_20100331_instances_CREATE_INVALID(self, method, url, body, headers):
return (412, 'Error 412: No DataCenter with id: 3', {}, 'Precondition Failed')
+ def _computecloud_enterprise_api_rest_20100331_storage(self, method, url, body, headers):
+ body = self.fixtures.load('list_volumes.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_instances_26557_ATTACH(self, method, url, body, headers):
+ body = self.fixtures.load('attach_volume.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_storage_CREATE(self, method, url, body, headers):
+ body = self.fixtures.load('create_volume.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_storage_39281_DESTROY(self, method, url, body, headers):
+ body = self.fixtures.load('destroy_volume.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_instances_26557_DETACH(self, method, url, body, headers):
+ body = self.fixtures.load('detach_volume.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_addresses_ALLOCATE(self, method, url, body, headers):
+ body = self.fixtures.load('allocate_address.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_addresses_292795_DELETE(self, method, url, body, headers):
+ body = self.fixtures.load('delete_address.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_addresses(self, method, url, body, headers):
+ body = self.fixtures.load('list_addresses.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _computecloud_enterprise_api_rest_20100331_offerings_storage(self, method, url, body, headers):
+ body = self.fixtures.load('list_storage_offerings.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
# This is only to accomodate the response tests built into test\__init__.py
def _computecloud_enterprise_api_rest_20100331_instances_26557(self, method, url, body, headers):
if method == 'DELETE':
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/destroy_volume.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/destroy_volume.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/destroy_volume.xml (working copy)
@@ -0,0 +1 @@
+
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/destroy_volume.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/attach_volume.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/attach_volume.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/attach_volume.xml (working copy)
@@ -0,0 +1 @@
+
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/attach_volume.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/delete_address.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/delete_address.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/delete_address.xml (working copy)
@@ -0,0 +1 @@
+
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/delete_address.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/detach_volume.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/detach_volume.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/detach_volume.xml (working copy)
@@ -0,0 +1 @@
+
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/detach_volume.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/list_volumes.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/list_volumes.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/list_volumes.xml (working copy)
@@ -0,0 +1 @@
+39281141200012100user@domain.comlibcloudvolraw204842012-08-19T13:46:50.000Z2011-08-12T00:00:00.000ZUSD0.001CNT020
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/list_volumes.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/allocate_address.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/allocate_address.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/allocate_address.xml (working copy)
@@ -0,0 +1 @@
+RESERVED14129279520001223PRIMARY0user@domain.com
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/allocate_address.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/create_volume.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/create_volume.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/create_volume.xml (working copy)
@@ -0,0 +1 @@
+3929314120001208user@domain.comtest-volumeraw25602012-08-20T12:25:02.792Z2011-08-12T00:00:00.000ZUSD0.001CNT020
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/create_volume.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/list_storage_offerings.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/list_storage_offerings.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/list_storage_offerings.xml (working copy)
@@ -0,0 +1 @@
+2000120861Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000120882Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000120961Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000120982Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000120841Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000120941Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001208121Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001209121Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000121061Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000121082Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR2000121041Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001210121Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001208101Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001209101Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001208141Small256256EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001209141Medium512512EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001210101Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR20001210141Large20482048EXT3RAW2011-08-12T00:00:00.000ZUSD0.00001UHR
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/list_storage_offerings.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/test/compute/fixtures/ibm_sce/list_addresses.xml
===================================================================
--- trunk/libcloud/test/compute/fixtures/ibm_sce/list_addresses.xml (revision 0)
+++ trunk/libcloud/test/compute/fixtures/ibm_sce/list_addresses.xml (working copy)
@@ -0,0 +1 @@
+RESERVED170.225.160.218vhost021814129279520001223PRIMARY2user@dmain.com
\ No newline at end of file
Property changes on: trunk/libcloud/test/compute/fixtures/ibm_sce/list_addresses.xml
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: trunk/libcloud/compute/drivers/ibm_sce.py
===================================================================
--- trunk/libcloud/compute/drivers/ibm_sce.py (revision 1374739)
+++ trunk/libcloud/compute/drivers/ibm_sce.py (working copy)
@@ -17,20 +17,22 @@
Driver for IBM SmartCloud Enterprise
Formerly known as:
-- IBM Developer Cloud.
+- IBM Developer Cloud
+- IBM Smart Business Development and Test on the IBM Cloud
- IBM SmartBusiness Cloud
"""
import base64
+import time
from libcloud.utils.py3 import urlencode
from libcloud.utils.py3 import b
from libcloud.common.base import XmlResponse, ConnectionUserAndKey
-from libcloud.common.types import InvalidCredsError
+from libcloud.common.types import InvalidCredsError, LibcloudError
from libcloud.compute.types import NodeState, Provider
from libcloud.compute.base import NodeDriver, Node, NodeImage, \
- NodeSize, NodeLocation, NodeAuthSSHKey
+ NodeSize, NodeLocation, NodeAuthSSHKey, StorageVolume
HOST = 'www-147.ibm.com'
REST_BASE = '/computecloud/enterprise/api/rest/20100331'
@@ -51,7 +53,7 @@
class IBMConnection(ConnectionUserAndKey):
"""
- Connection class for the IBM Developer Cloud driver
+ Connection class for the IBM SmartCloud Enterprise driver
"""
host = HOST
@@ -69,6 +71,76 @@
return urlencode(data)
+class IBMNodeLocation(NodeLocation):
+ """
+ Extends the base LibCloud NodeLocation to contain additional attributes
+ """
+ def __init__(self, id, name, country, driver, extra=None):
+ self.id = str(id)
+ self.name = name
+ self.country = country
+ self.driver = driver
+ self.extra = extra
+
+ def __repr__(self):
+ return (('').format(
+ self.id, self.name, self.country, self.driver.name, self.extra))
+
+
+class VolumeState(object):
+ """
+ The SCE specific states for a storage volume
+ """
+ NEW = '0'
+ CREATING = '1'
+ DELETING = '2'
+ DELETED = '3'
+ DETACHED = '4'
+ ATTACHED = '5'
+ FAILED = '6'
+ DELETE_PENDING = '7'
+ BEING_CLONED = '8'
+ CLONING = '9'
+ ATTACHING = '10'
+ DETACHING = '11'
+ ATTACHIED = '12'
+ IMPORTING = '13'
+ TRANSFER_RETRYING = '14'
+
+
+class VolumeOffering(object):
+ """
+ An SCE specific storage volume offering class.
+ The volume offering ID is needed to create a volume.
+ Volume offering IDs are different for each data center.
+ """
+ def __init__(self, id, name, location, extra=None):
+ self.id = id
+ self.location = location
+ self.name = name
+ self.extra = extra
+
+ def __repr__(self):
+ return (('').format(
+ self.id, self.location, self.name, self.extra))
+
+
+class Address(object):
+ """
+ A reserved IP address that can be attached to an instance.
+ Properties: id, ip, state, options(location, type, created_time, state, hostname, instance_ids, vlan, owner,
+ mode, offering_id)
+ """
+ def __init__(self, id, ip, state, options):
+ self.id = id
+ self.ip = ip
+ self.state = state
+ self.options = options
+
+ def __repr__(self):
+ return (''.format(
+ self.id, self.ip, self.state, self.options))
+
class IBMNodeDriver(NodeDriver):
"""
Node driver for IBM SmartCloud Enterprise
@@ -94,13 +166,15 @@
12: NodeState.PENDING, # Deprovision Pending
13: NodeState.PENDING, # Restart Pending
14: NodeState.PENDING, # Attaching
- 15: NodeState.PENDING, # Detaching
+ 15: NodeState.PENDING, # Detaching
}
def create_node(self, **kwargs):
"""
- Creates a node in the IBM Developer Cloud.
+ Creates a node in the IBM SmartCloud Enterprise.
+ See L{NodeDriver.create_node} for more keyword args.
+
@inherits: L{NodeDriver.create_node}
@keyword auth: Name of the pubkey to use. When constructing
@@ -131,8 +205,9 @@
data.update({'publicKey': kwargs['auth'].pubkey})
if 'ex_configurationData' in kwargs:
configurationData = kwargs['ex_configurationData']
- for key in configurationData.keys():
- data.update({key: configurationData.get(key)})
+ if configurationData:
+ for key in configurationData.keys():
+ data.update({key: configurationData.get(key)})
# Send request!
resp = self.connection.request(
@@ -142,12 +217,155 @@
data=data).object
return self._to_nodes(resp)[0]
+ def create_volume(self, size, name, location, **kwargs):
+ """
+ Create a new block storage volume (virtual disk)
+
+ @param size: Size of volume in gigabytes (required).
+ Find out the possible sizes from the offerings/storage REST interface
+ @type size: C{int}
+
+ @keyword name: Name of the volume to be created (required)
+ @type name: C{str}
+
+ @keyword location: Which data center to create a volume in. If
+ empty, it will fail for IBM SmartCloud Enterprise
+ (required)
+ @type location: L{NodeLocation}
+
+ @keyword snapshot: Not supported for IBM SmartCloud Enterprise
+ @type snapshot: C{str}
+
+ @keyword kwargs.format: Either RAW or EXT3 for IBM SmartCloud Enterprise (optional)
+ @type kwargs.format: C{str}
+
+ @keyword kwargs.offering_id: The storage offering ID for IBM SmartCloud Enterprise
+ Find this from the REST interface storage/offerings. (optional)
+ @type kwargs.offering_id: C{str}
+
+ @keyword kwargs.source_disk_id: If cloning a volume, the storage disk to make a copy from (optional)
+ @type kwargs.source_disk_id: C{str}
+
+ @keyword kwargs.storage_area_id: The id of the storage availability area to create the volume in (optional)
+ @type kwargs.storage_area_id: C{str}
+
+ @keyword kwargs.target_location_id: If cloning a volume, the storage disk to make a copy from (optional)
+ @type kwargs.target_location_id: C{str}
+
+ @return: The newly created L{StorageVolume}.
+ """
+ data = {}
+ data.update({'name': name})
+ data.update({'size': size})
+ data.update({'location': location})
+ if (('format' in kwargs) and (kwargs['format'] is not None)):
+ data.update({'format': kwargs['format']})
+ if (('offering_id' in kwargs) and (kwargs['offering_id'] is not None)):
+ data.update({'offeringID': kwargs['offering_id']})
+ if (('storage_area_id' in kwargs) and (kwargs['storage_area_id'] is not None)):
+ data.update({'storageAreaID': kwargs['storage_area_id']})
+ if 'source_disk_id' in kwargs:
+ data.update({'sourceDiskID': kwargs['source_disk_id']})
+ data.update({'type': 'clone'})
+ if 'target_location_id' in kwargs:
+ data.update({'targetLocationID': kwargs['target_location_id']})
+ resp = self.connection.request(action=REST_BASE + '/storage',
+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
+ method='POST',
+ data=data).object
+ return self._to_volumes(resp)[0]
+
+ def create_image(self, name, description=None, **kwargs):
+ """
+ Create a new node image from an existing volume or image.
+
+ @param name: Name of the image to be created (required)
+ @type name: C{str}
+
+ @param description: Description of the image to be created (optional)
+ @type description: L{str}
+
+ @keyword kwarge.image_id: The ID of the source image if cloning the image
+ @type kwargs.image_id: C{str}
+
+ @keyword kwargs.volume_id: The ID of the storage volume if importing the image
+ @type kwargs.volume_id: C{str}
+
+ @return: The newly created L{NodeImage}.
+ """
+ data = {}
+ data.update({'name': name})
+ if description is not None:
+ data.update({'description': description})
+ if (('image_id' in kwargs) and (kwargs['image_id'] is not None)):
+ data.update({'imageId': kwargs['image_id']})
+ if (('volume_id' in kwargs) and (kwargs['volume_id'] is not None)):
+ data.update({'volumeId': kwargs['volume_id']})
+ resp = self.connection.request(action=REST_BASE + '/offerings/image',
+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
+ method='POST',
+ data=data).object
+ return self._to_images(resp)[0]
+
def destroy_node(self, node):
url = REST_BASE + '/instances/%s' % (node.id)
status = int(self.connection.request(action=url,
method='DELETE').status)
return status == 200
+ def destroy_volume(self, volume):
+ """
+ Destroys a storage volume.
+
+ @param volume: Volume to be destroyed
+ @type volume: L{StorageVolume}
+
+ @return: C{bool}
+ """
+ url = REST_BASE + '/storage/%s' % (volume.id)
+ status = int(self.connection.request(action=url,
+ method='DELETE').status)
+ return status == 200
+
+ def attach_volume(self, node, volume):
+ """
+ Attaches volume to node.
+
+ @param node: Node to attach volume to
+ @type node: L{Node}
+
+ @param volume: Volume to attach
+ @type volume: L{StorageVolume}
+
+ @return: C{bool}
+ """
+ url = REST_BASE + '/instances/%s' % (node.id)
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+ data = {'storageID': volume.id, 'type': 'attach'}
+ resp = self.connection.request(action=url,
+ method='PUT',
+ headers=headers,
+ data=data)
+ return int(resp.status) == 200
+
+ def detach_volume(self, node, volume):
+ """
+ Detaches a volume from a node.
+
+ @param volume: Volume to be detached
+ @type volume: L{StorageVolume}
+
+ @returns C{bool}
+ """
+ url = REST_BASE + '/instances/%s' % (node.id)
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+ data = {'storageID': volume.id, 'type': 'detach'}
+ resp = self.connection.request(action=url,
+ method='PUT',
+ headers=headers,
+ data=data)
+ return int(resp.status) == 200
+
def reboot_node(self, node):
url = REST_BASE + '/instances/%s' % (node.id)
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
@@ -167,10 +385,21 @@
return self._to_images(
self.connection.request(REST_BASE + '/offerings/image').object)
+ def list_volumes(self):
+ """
+ List storage volumes.
+
+ @return: C{list} of L{StorageVolume} objects
+ """
+ return self._to_volumes(
+ self.connection.request(REST_BASE + '/storage').object)
+
def list_sizes(self, location=None):
"""
- Returns a generic list of sizes. See list_images() for a
- list of supported sizes for specific images.
+ Returns a generic list of sizes. See list_images() for a list of supported
+ sizes for specific images. In particular, you need to have a size that
+ matches the architecture (32-bit vs 64-bit) of the virtual machine image
+ operating system.
@inherits: L{NodeDriver.list_sizes}
"""
@@ -195,9 +424,125 @@
None, None, None, None, self.connection.driver)]
def list_locations(self):
+ """
+ List the data center locations
+ """
return self._to_locations(
self.connection.request(REST_BASE + '/locations').object)
+ def ex_list_storage_offerings(self):
+ """
+ List the storage center offerings
+ """
+ return self._to_volume_offerings(
+ self.connection.request(REST_BASE + '/offerings/storage').object)
+
+ def ex_allocate_address(self, location_id, offering_id, vlan_id=None):
+ """
+ Allocate a new reserved IP address
+
+ @param location_id: Target data center
+ @type location_id: L{str}
+ @param offering_id: Offering ID for address to create
+ @type offering_id: L{str}
+ @param vlan_id: ID of target VLAN
+ @type vlan_id: L{str}
+ @return: L{Address} object
+ """
+ url = REST_BASE + '/addresses'
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+ data = {'location': location_id, 'offeringID': offering_id}
+ if vlan_id is not None:
+ data.update({'vlanID': vlan_id})
+ resp = self.connection.request(action=url,
+ method='POST',
+ headers=headers,
+ data=data).object
+ return self._to_addresses(resp)[0]
+
+ def ex_list_addresses(self, resource_id=None):
+ """
+ List the reserved IP addresses
+
+ @param resource_id: If this is supplied only a single address will be returned (optional)
+ @type resource_id: L{str}
+ """
+ url = REST_BASE + '/addresses'
+ if resource_id:
+ url += '/' + resource_id
+ return self._to_addresses(self.connection.request(url).object)
+
+ def ex_copy_to(self, image, volume):
+ """
+ Copies a node image to a storage volume
+
+ @param image: source image to copy
+ @type image: L{NodeImage}
+
+ @param volume: Target storage volume to copy to
+ @type volume: L{StorageVolume}
+
+ @return: C{bool} The success of the operation
+ """
+ url = REST_BASE + '/storage/%s' % (volume.id)
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+ data = {'imageId': image.id}
+ resp = self.connection.request(action=url,
+ method='PUT',
+ headers=headers,
+ data=data)
+ return int(resp.status) == 200
+
+ def ex_delete_address(self, resource_id):
+ """
+ Delete a reserved IP address
+
+ @param resource_id: The address to delete (required)
+ @type resource_id: L{str}
+ """
+ url = REST_BASE + '/addresses/' + resource_id
+ status = int(self.connection.request(action=url,
+ method='DELETE').status)
+ return status == 200
+
+ def ex_wait_storage_state(self, volume, state=VolumeState.DETACHED, wait_period=60, timeout=1200):
+ """
+ Block until storage volume state changes to the given value
+
+ @param volume: Storage volume.
+ @type node: C{StorageVolume}
+
+ @param state: The target state to wait for
+ @type state: C{int}
+
+ @param wait_period: How many seconds to between each loop
+ iteration (default is 3)
+ @type wait_period: C{int}
+
+ @param timeout: How many seconds to wait before timing out
+ (default is 1200)
+ @type timeout: C{int}
+
+ @return: C{StorageVolume}
+ """
+ start = time.time()
+ end = start + timeout
+
+ while time.time() < end:
+ volumes = self.list_volumes()
+ volumes = list([v for v in volumes if v.uuid == volume.uuid])
+
+ print('ex_wait_storage_state, len(volumes): {0}, volumes[0]: {1}, extra[0]: {2}, wait_period: {3}, '\
+ 'target state: {4}'.format(len(volumes), volumes[0], volumes[0].extra, wait_period, state))
+ if (len(volumes) == 1 and volumes[0].extra['state'] == state):
+ return volumes[0]
+ else:
+ time.sleep(wait_period)
+ continue
+
+ raise LibcloudError(value='Timed out after {0} seconds'.format(timeout),
+ driver=self)
+
def _to_nodes(self, object):
return [self._to_node(instance) for instance in
object.findall('Instance')]
@@ -210,16 +555,16 @@
public_ips.append(ip)
return Node(
- id=instance.findtext('ID'),
- name=instance.findtext('Name'),
- state=self.NODE_STATE_MAP[int(instance.findtext('Status'))],
- public_ips=public_ips,
- private_ips=[],
- driver=self.connection.driver
+ id=instance.findtext('ID'),
+ name=instance.findtext('Name'),
+ state=self.NODE_STATE_MAP[int(instance.findtext('Status'))],
+ public_ips=public_ips,
+ private_ips=[],
+ driver=self.connection.driver
)
def _to_images(self, object):
- #Converts data retrieved from SCE /offerings/image REST call to a NodeImage
+ # Converts data retrieved from SCE /offerings/image REST call to a NodeImage
return [self._to_image(image) for image in object.findall('Image')]
def _to_image(self, image):
@@ -248,7 +593,7 @@
'platform': platform,
'description': description,
'documentation': documentation,
- 'supportedInstanceTypes': nodeSizes
+ 'node_sizes': nodeSizes
}
)
@@ -259,11 +604,30 @@
def _to_location(self, location):
# Converts an SCE Location object to a Libcloud NodeLocation object
name_text = location.findtext('Name')
+ description = location.findtext('Description')
+ state = location.findtext('State')
(nameVal, separator, countryVal) = name_text.partition(',')
- return NodeLocation(id=location.findtext('ID'),
- name=nameVal,
- country=countryVal.strip(),
- driver=self.connection.driver)
+ capabiltyElements = location.findall('Capabilities/Capability')
+ capabilities = {}
+ for elem in capabiltyElements:
+ capabilityID = elem.attrib['id']
+ entryElements = elem.findall('Entry')
+ entries = []
+ for entryElem in entryElements:
+ key = entryElem.attrib['key']
+ valueElements = elem.findall('Value')
+ values = []
+ for valueElem in valueElements:
+ values.append(valueElem.text)
+ entry = {'key': key, 'values': values}
+ entries.append(entry)
+ capabilities[capabilityID] = entries
+ extra = {'description': description, 'state': state, 'capabilities': capabilities}
+ return IBMNodeLocation(id=location.findtext('ID'),
+ name=nameVal,
+ country=countryVal.strip(),
+ driver=self.connection.driver,
+ extra=extra)
def _to_node_sizes(self, object):
# Converts SCE SupportedInstanceTypes object to
@@ -273,10 +637,67 @@
def _to_node_size(self, object):
# Converts to an SCE InstanceType to a Libcloud NodeSize
- return NodeSize(object.findtext('ID'),
- object.findtext('Label'),
+ return NodeSize(object.findtext('ID'),
+ object.findtext('Label'),
None,
None,
None,
object.findtext('Price/Rate'),
self.connection.driver)
+
+ def _to_volumes(self, object):
+ return [self._to_volume(iType) for iType in
+ object.findall('Volume')]
+
+ def _to_volume(self, object):
+ # Converts an SCE Volume to a Libcloud StorageVolume
+ extra = {'state': object.findtext('State'),
+ 'location': object.findtext('Location'),
+ 'instanceID': object.findtext('instanceID'),
+ 'owner': object.findtext('Owner'),
+ 'format': object.findtext('Format'),
+ 'createdTime': object.findtext('CreatedTime'),
+ 'storageAreaID': object.findtext('StorageArea/ID')}
+ return StorageVolume(object.findtext('ID'),
+ object.findtext('Name'),
+ object.findtext('Size'),
+ self.connection.driver,
+ extra)
+
+ def _to_volume_offerings(self, object):
+ return [self._to_volume_offering(iType) for iType in
+ object.findall('Offerings')]
+
+ def _to_volume_offering(self, object):
+ # Converts an SCE DescribeVolumeOfferingsResponse/Offerings XML object to an SCE VolumeOffering
+ extra = {'label': object.findtext('Label'),
+ 'supported_sizes': object.findtext('SupportedSizes'),
+ 'formats': object.findall('SupportedFormats/Format/ID'),
+ 'price': object.findall('Price')}
+ return VolumeOffering(object.findtext('ID'),
+ object.findtext('Name'),
+ object.findtext('Location'),
+ extra)
+
+ def _to_addresses(self, object):
+ # Converts an SCE DescribeAddressesResponse XML object to a list of Address objects
+ return [self._to_address(iType) for iType in
+ object.findall('Address')]
+
+ def _to_address(self, object):
+ # Converts an SCE DescribeAddressesResponse/Address XML object to an Address object
+ extra = {'location': object.findtext('Location'),
+ 'type': object.findtext('Label'),
+ 'created_time': object.findtext('SupportedSizes'),
+ 'hostname': object.findtext('Hostname'),
+ 'instance_ids': object.findtext('InstanceID'),
+ 'vlan': object.findtext('VLAN'),
+ 'owner': object.findtext('owner'),
+ 'mode': object.findtext('Mode'),
+ 'offering_id': object.findtext('OfferingID')}
+ return Address(object.findtext('ID'),
+ object.findtext('IP'),
+ object.findtext('State'),
+ extra)
+ #Properties: id, ip, state, location, options(type, created_time, hostname, instance_ids, vlan, owner,
+ #mode, price, offering_id)