Author: tomaz
Date: Sat Jun 1 07:58:30 2013
New Revision: 1488483
URL: http://svn.apache.org/r1488483
Log:
Update Gandi compute driver to handle new billing model.
Contributed by Aymeric Barantal, part of LIBCLOUD-317.
Added:
libcloud/trunk/libcloud/test/compute/fixtures/gandi/account_info_rating.xml
Modified:
libcloud/trunk/CHANGES
libcloud/trunk/libcloud/compute/drivers/gandi.py
libcloud/trunk/libcloud/data/pricing.json
libcloud/trunk/libcloud/test/compute/test_gandi.py
Modified: libcloud/trunk/CHANGES
URL:
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1488483&r1=1488482&r2=1488483&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Sat Jun 1 07:58:30 2013
@@ -63,6 +63,9 @@ Changes with Apache Libcloud in deveplom
create_node method. (LIBCLOUD-330)
[Sebastien Goasguen, Tomaz Muraus]
+ - Update Gandi driver to handle new billing model. (LIBCLOUD-317)
+ [Aymeric Barantal]
+
*) Storage
- Fix an issue with double encoding the container name in the CloudFiles
Modified: libcloud/trunk/libcloud/compute/drivers/gandi.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/gandi.py?rev=1488483&r1=1488482&r2=1488483&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/gandi.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/gandi.py Sat Jun 1 07:58:30 2013
@@ -39,6 +39,41 @@ NODE_STATE_MAP = {
NODE_PRICE_HOURLY_USD = 0.02
+INSTANCE_TYPES = {
+ 'small': {
+ 'id': 'small',
+ 'name': 'Small instance',
+ 'cpu': 1,
+ 'memory': 256,
+ 'disk': 3,
+ 'bandwidth': 100,
+ },
+ 'medium': {
+ 'id': 'medium',
+ 'name': 'Medium instance',
+ 'cpu': 1,
+ 'memory': 1024,
+ 'disk': 20,
+ 'bandwidth': 100,
+ },
+ 'large': {
+ 'id': 'large',
+ 'name': 'Large instance',
+ 'cpu': 2,
+ 'memory': 2048,
+ 'disk': 50,
+ 'bandwidth': 100,
+ },
+ 'extra-large': {
+ 'id': 'x-large',
+ 'name': 'Extra Large instance',
+ 'cpu': 4,
+ 'memory': 4096,
+ 'disk': 100,
+ 'bandwidth': 100,
+ },
+}
+
class GandiNodeDriver(BaseGandiDriver, NodeDriver):
"""
@@ -61,7 +96,7 @@ class GandiNodeDriver(BaseGandiDriver, N
def _resource_info(self, type, id):
try:
- obj = self.connection.request('%s.info' % type, int(id))
+ obj = self.connection.request('hosting.%s.info' % type, int(id))
return obj.object
except Exception:
e = sys.exc_info()[1]
@@ -109,8 +144,8 @@ class GandiNodeDriver(BaseGandiDriver, N
return [self._to_volume(d) for d in disks]
def list_nodes(self):
- vms = self.connection.request('vm.list').object
- ips = self.connection.request('ip.list').object
+ vms = self.connection.request('hosting.vm.list').object
+ ips = self.connection.request('hosting.ip.list').object
for vm in vms:
vm['ips'] = []
for ip in ips:
@@ -123,7 +158,7 @@ class GandiNodeDriver(BaseGandiDriver, N
return nodes
def reboot_node(self, node):
- op = self.connection.request('vm.reboot', int(node.id))
+ op = self.connection.request('hosting.vm.reboot', int(node.id))
self._wait_operation(op.object['id'])
vm = self._node_info(int(node.id))
if vm['state'] == 'running':
@@ -134,11 +169,11 @@ class GandiNodeDriver(BaseGandiDriver, N
vm = self._node_info(node.id)
if vm['state'] == 'running':
# Send vm_stop and wait for accomplish
- op_stop = self.connection.request('vm.stop', int(node.id))
+ op_stop = self.connection.request('hosting.vm.stop', int(node.id))
if not self._wait_operation(op_stop.object['id']):
raise GandiException(1010, 'vm.stop failed')
# Delete
- op = self.connection.request('vm.delete', int(node.id))
+ op = self.connection.request('hosting.vm.delete', int(node.id))
if self._wait_operation(op.object['id']):
return True
return False
@@ -198,6 +233,10 @@ class GandiNodeDriver(BaseGandiDriver, N
raise GandiException(
1022, 'size must be a subclass of NodeSize')
+ # If size name is in INSTANCE_TYPE we use new rating model
+ instance = INSTANCE_TYPES.get(size.id)
+ cores = instance['cpu'] if instance else int(size.id)
+
src_disk_id = int(kwargs['image'].id)
disk_spec = {
@@ -211,7 +250,7 @@ class GandiNodeDriver(BaseGandiDriver, N
'login': kwargs['login'],
'password': kwargs['password'], # TODO : use NodeAuthPassword
'memory': int(size.ram),
- 'cores': int(size.id),
+ 'cores': cores,
'bandwidth': int(size.bandwidth),
'ip_version': kwargs.get('inet_family', 4),
}
@@ -219,7 +258,7 @@ class GandiNodeDriver(BaseGandiDriver, N
# Call create_from helper api. Return 3 operations : disk_create,
# iface_create,vm_create
(op_disk, op_iface, op_vm) = self.connection.request(
- 'vm.create_from',
+ 'hosting.vm.create_from',
vm_spec, disk_spec, src_disk_id
).object
@@ -250,7 +289,7 @@ class GandiNodeDriver(BaseGandiDriver, N
filtering = {'datacenter_id': int(location.id)}
else:
filtering = {}
- images = self.connection.request('image.list', filtering)
+ images = self.connection.request('hosting.image.list', filtering)
return [self._to_image(i) for i in images.object]
except Exception:
e = sys.exc_info()[1]
@@ -267,8 +306,26 @@ class GandiNodeDriver(BaseGandiDriver, N
driver=self.connection.driver,
)
+ def _instance_type_to_size(self, instance):
+ return NodeSize(
+ id=instance['id'],
+ name=instance['name'],
+ ram=instance['memory'],
+ disk=instance['disk'],
+ bandwidth=instance['bandwidth'],
+ price=self._get_size_price(size_id=instance['id']),
+ driver=self.connection.driver,
+ )
+
+ def list_instance_type(self, location=None):
+ return [self._instance_type_to_size(instance)
+ for name, instance in INSTANCE_TYPES.items()]
+
def list_sizes(self, location=None):
- account = self.connection.request('account.info').object
+ account = self.connection.request('hosting.account.info').object
+ if account.get('rating_enabled'):
+ # This account use new rating model
+ return self.list_instance_type(location)
# Look for available shares, and return a list of share_definition
available_res = account['resources']['available']
@@ -306,7 +363,7 @@ class GandiNodeDriver(BaseGandiDriver, N
)
def list_locations(self):
- res = self.connection.request('datacenter.list')
+ res = self.connection.request('hosting.datacenter.list')
return [self._to_loc(l) for l in res.object]
def list_volumes(self):
@@ -314,7 +371,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{list} of L{StorageVolume}
"""
- res = self.connection.request('disk.list', {})
+ res = self.connection.request('hosting.disk.list', {})
return self._to_volumes(res.object)
def create_volume(self, size, name, location=None, snapshot=None):
@@ -324,17 +381,17 @@ class GandiNodeDriver(BaseGandiDriver, N
'datacenter_id': int(location.id)
}
if snapshot:
- op = self.connection.request('disk.create_from',
+ op = self.connection.request('hosting.disk.create_from',
disk_param, int(snapshot.id))
else:
- op = self.connection.request('disk.create', disk_param)
+ op = self.connection.request('hosting.disk.create', disk_param)
if self._wait_operation(op.object['id']):
disk = self._volume_info(op.object['disk_id'])
return self._to_volume(disk)
return None
def attach_volume(self, node, volume, device=None):
- op = self.connection.request('vm.disk_attach',
+ op = self.connection.request('hosting.vm.disk_attach',
int(node.id), int(volume.id))
if self._wait_operation(op.object['id']):
return True
@@ -352,14 +409,14 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{bool}
"""
- op = self.connection.request('vm.disk_detach',
+ op = self.connection.request('hosting.vm.disk_detach',
int(node.id), int(volume.id))
if self._wait_operation(op.object['id']):
return True
return False
def destroy_volume(self, volume):
- op = self.connection.request('disk.delete', int(volume.id))
+ op = self.connection.request('hosting.disk.delete', int(volume.id))
if self._wait_operation(op.object['id']):
return True
return False
@@ -401,8 +458,8 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{list} of L{GandiNetworkInterface}
"""
- ifaces = self.connection.request('iface.list').object
- ips = self.connection.request('ip.list').object
+ ifaces = self.connection.request('hosting.iface.list').object
+ ips = self.connection.request('hosting.ip.list').object
for iface in ifaces:
iface['ips'] = list(
filter(lambda i: i['iface_id'] == iface['id'], ips))
@@ -431,7 +488,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{list} of L{GandiDisk}
"""
- res = self.connection.request('disk.list', {})
+ res = self.connection.request('hosting.disk.list', {})
return self._to_disks(res.object)
def ex_node_attach_disk(self, node, disk):
@@ -446,7 +503,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{bool}
"""
- op = self.connection.request('vm.disk_attach',
+ op = self.connection.request('hosting.vm.disk_attach',
int(node.id), int(disk.id))
if self._wait_operation(op.object['id']):
return True
@@ -464,7 +521,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{bool}
"""
- op = self.connection.request('vm.disk_detach',
+ op = self.connection.request('hosting.vm.disk_detach',
int(node.id), int(disk.id))
if self._wait_operation(op.object['id']):
return True
@@ -483,7 +540,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{bool}
"""
- op = self.connection.request('vm.iface_attach',
+ op = self.connection.request('hosting.vm.iface_attach',
int(node.id), int(iface.id))
if self._wait_operation(op.object['id']):
return True
@@ -502,7 +559,7 @@ class GandiNodeDriver(BaseGandiDriver, N
@rtype: C{bool}
"""
- op = self.connection.request('vm.iface_detach',
+ op = self.connection.request('hosting.vm.iface_detach',
int(node.id), int(iface.id))
if self._wait_operation(op.object['id']):
return True
@@ -526,7 +583,7 @@ class GandiNodeDriver(BaseGandiDriver, N
suffix = datetime.today().strftime('%Y%m%d')
name = 'snap_%s' % (suffix)
op = self.connection.request(
- 'disk.create_from',
+ 'hosting.disk.create_from',
{'name': name, 'type': 'snapshot', },
int(disk.id),
)
@@ -554,7 +611,7 @@ class GandiNodeDriver(BaseGandiDriver, N
params.update({'size': new_size})
if new_name:
params.update({'name': new_name})
- op = self.connection.request('disk.update',
+ op = self.connection.request('hosting.disk.update',
int(disk.id),
params)
if self._wait_operation(op.object['id']):
Modified: libcloud/trunk/libcloud/data/pricing.json
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/data/pricing.json?rev=1488483&r1=1488482&r2=1488483&view=diff
==============================================================================
--- libcloud/trunk/libcloud/data/pricing.json (original)
+++ libcloud/trunk/libcloud/data/pricing.json Sat Jun 1 07:58:30 2013
@@ -239,7 +239,11 @@
},
"gandi": {
- "1": 0.02
+ "1": 0.02,
+ "small": 0.02,
+ "medium": 0.03,
+ "large": 0.06,
+ "x-large": 0.12
},
"vps_net": {
Added:
libcloud/trunk/libcloud/test/compute/fixtures/gandi/account_info_rating.xml
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/compute/fixtures/gandi/account_info_rating.xml?rev=1488483&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/compute/fixtures/gandi/account_info_rating.xml
(added)
+++ libcloud/trunk/libcloud/test/compute/fixtures/gandi/account_info_rating.xml
Sat Jun 1 07:58:30 2013
@@ -0,0 +1,58 @@
+<?xml version='1.0'?>
+<methodResponse>
+<params>
+<param>
+<value><struct>
+<member>
+<name>handle</name>
+<value><string>AB9090-GANDI</string></value>
+</member>
+<member>
+<name>rating_enabled</name>
+<value><boolean>1</boolean></value>
+</member>
+<member>
+<name>date_credits_expiration</name>
+<value><nil/></value></member>
+<member>
+<name>credits</name>
+<value><int>0</int></value>
+</member>
+<member>
+<name>products</name>
+<value><nil/></value></member>
+<member>
+<name>average_credit_cost</name>
+<value><nil/></value></member>
+<member>
+<name>share_definition</name>
+<value><nil/></value></member>
+<member>
+<name>fullname</name>
+<value><string>Aymeric BARANTAL</string></value>
+</member>
+<member>
+<name>id</name>
+<value><int>24</int></value>
+</member>
+<member>
+<name>resources</name>
+<value><struct>
+<member>
+<name>available</name>
+<value><nil/></value></member>
+<member>
+<name>granted</name>
+<value><nil/></value></member>
+<member>
+<name>used</name>
+<value><nil/></value></member>
+<member>
+<name>expired</name>
+<value><nil/></value></member>
+</struct></value>
+</member>
+</struct></value>
+</param>
+</params>
+</methodResponse>
Modified: libcloud/trunk/libcloud/test/compute/test_gandi.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/compute/test_gandi.py?rev=1488483&r1=1488482&r2=1488483&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/compute/test_gandi.py (original)
+++ libcloud/trunk/libcloud/test/compute/test_gandi.py Sat Jun 1 07:58:30 2013
@@ -155,35 +155,73 @@ class GandiTests(unittest.TestCase):
self.assertTrue(self.driver.ex_update_disk(disks[0], new_size=4096))
+class GandiRatingTests(unittest.TestCase):
+ """Tests where rating model is involved"""
+
+ node_name = 'test2'
+
+ def setUp(self):
+ GandiNodeDriver.connectionCls.conn_classes = (
+ GandiMockRatingHttp, GandiMockRatingHttp)
+ GandiMockRatingHttp.type = None
+ self.driver = GandiNodeDriver(*GANDI_PARAMS)
+
+ def test_list_sizes(self):
+ sizes = self.driver.list_sizes()
+ self.assertEqual(len(sizes), 4)
+
+ def test_create_node(self):
+ login = 'libcloud'
+ passwd = ''.join(random.choice(string.ascii_letters)
+ for i in range(10))
+
+ # Get france datacenter
+ loc = list(filter(lambda x: 'france' in x.country.lower(),
+ self.driver.list_locations()))[0]
+
+ # Get a debian image
+ images = self.driver.list_images(loc)
+ images = [x for x in images if x.name.lower().startswith('debian')]
+ img = list(filter(lambda x: '5' in x.name, images))[0]
+
+ # Get a configuration size
+ size = self.driver.list_sizes()[0]
+ node = self.driver.create_node(name=self.node_name, login=login,
+ password=passwd, image=img,
+ location=loc, size=size)
+ self.assertEqual(node.name, self.node_name)
+
+
+
class GandiMockHttp(BaseGandiMockHttp):
fixtures = ComputeFileFixtures('gandi')
- def _xmlrpc__datacenter_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_datacenter_list(self, method, url, body, headers):
body = self.fixtures.load('datacenter_list.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__image_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_image_list(self, method, url, body, headers):
body = self.fixtures.load('image_list_dc0.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_list(self, method, url, body, headers):
body = self.fixtures.load('vm_list.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__ip_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_ip_list(self, method, url, body, headers):
body = self.fixtures.load('ip_list.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__account_info(self, method, url, body, headers):
+ def _xmlrpc__hosting_account_info(self, method, url, body, headers):
body = self.fixtures.load('account_info.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_info(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_info(self, method, url, body, headers):
body = self.fixtures.load('vm_info.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_delete(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_delete(self, method, url, body, headers):
body = self.fixtures.load('vm_delete.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
@@ -191,62 +229,93 @@ class GandiMockHttp(BaseGandiMockHttp):
body = self.fixtures.load('operation_info.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_create_from(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_create_from(self, method, url, body, headers):
body = self.fixtures.load('vm_create_from.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_reboot(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_reboot(self, method, url, body, headers):
body = self.fixtures.load('vm_reboot.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_stop(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_stop(self, method, url, body, headers):
body = self.fixtures.load('vm_stop.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__iface_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_iface_list(self, method, url, body, headers):
body = self.fixtures.load('iface_list.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_list(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_list(self, method, url, body, headers):
body = self.fixtures.load('disk_list.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_iface_attach(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_iface_attach(self, method, url, body, headers):
body = self.fixtures.load('iface_attach.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_iface_detach(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_iface_detach(self, method, url, body, headers):
body = self.fixtures.load('iface_detach.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_disk_attach(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_disk_attach(self, method, url, body, headers):
body = self.fixtures.load('disk_attach.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__vm_disk_detach(self, method, url, body, headers):
+ def _xmlrpc__hosting_vm_disk_detach(self, method, url, body, headers):
body = self.fixtures.load('disk_detach.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_create(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_create(self, method, url, body, headers):
body = self.fixtures.load('disk_create.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_create_from(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_create_from(self, method, url, body, headers):
body = self.fixtures.load('disk_create_from.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_info(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_info(self, method, url, body, headers):
body = self.fixtures.load('disk_info.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_update(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_update(self, method, url, body, headers):
body = self.fixtures.load('disk_update.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
- def _xmlrpc__disk_delete(self, method, url, body, headers):
+ def _xmlrpc__hosting_disk_delete(self, method, url, body, headers):
body = self.fixtures.load('disk_delete.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+class GandiMockRatingHttp(BaseGandiMockHttp):
+ """Fixtures needed for tests related to rating model"""
+
+ fixtures = ComputeFileFixtures('gandi')
+
+ def _xmlrpc__hosting_datacenter_list(self, method, url, body, headers):
+ body = self.fixtures.load('datacenter_list.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _xmlrpc__hosting_image_list(self, method, url, body, headers):
+ body = self.fixtures.load('image_list_dc0.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _xmlrpc__hosting_vm_create_from(self, method, url, body, headers):
+ body = self.fixtures.load('vm_create_from.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _xmlrpc__operation_info(self, method, url, body, headers):
+ body = self.fixtures.load('operation_info.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _xmlrpc__hosting_vm_info(self, method, url, body, headers):
+ body = self.fixtures.load('vm_info.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ # Specific to rating tests
+ def _xmlrpc__hosting_account_info(self, method, url, body, headers):
+ body = self.fixtures.load('account_info_rating.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
if __name__ == '__main__':
sys.exit(unittest.main())