Issue LIBCLOUD-469: Add Reserved Instance support to EC2 driver. Signed-off-by: Tomaz Muraus <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/c1d86602 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/c1d86602 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/c1d86602 Branch: refs/heads/trunk Commit: c1d8660217794fc2dd545d83bf2b46f7300a3368 Parents: 7c72c2f Author: Chris DeRamus <[email protected]> Authored: Sun Dec 22 09:57:48 2013 -0500 Committer: Tomaz Muraus <[email protected]> Committed: Mon Dec 23 19:04:06 2013 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/ec2.py | 116 +++++++++++++++++++ .../ec2/describe_reserved_instances.xml | 21 ++++ libcloud/test/compute/test_ec2.py | 20 ++++ 3 files changed, 157 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/c1d86602/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index a1235d4..e4387fe 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -559,6 +559,28 @@ class ExEC2AvailabilityZone(object): % (self.name, self.zone_state, self.region_name)) +class EC2ReservedNode(Node): + """ + Class which stores information about EC2 reserved instances/nodes + Inherits from Node and passes in None for name and private/public IPs + + Note: This class is EC2 specific. + """ + + def __init__(self, id, name, state, public_ips, private_ips, + driver, size=None, image=None, extra=None): + self.id = str(id) if id else None + self.name = name + self.state = state + self.public_ips = public_ips + self.private_ips = private_ips + self.driver = driver + self.extra = extra or {} + + def __repr__(self): + return (('<EC2ReservedNode: id=%s>') % (self.id)) + + class BaseEC2NodeDriver(NodeDriver): """ Base Amazon EC2 node driver. @@ -608,6 +630,84 @@ class BaseEC2NodeDriver(NodeDriver): for term_status in ('shutting-down', 'terminated')]) + def _to_reserved_nodes(self, object, xpath): + return [self._to_reserved_node(el) + for el in object.findall(fixxpath(xpath=xpath, + namespace=NAMESPACE))] + + def _to_reserved_node(self, element): + """ + Build an EC2ReservedNode object using the reserved instance properties. + Information on these properties can be found at http://goo.gl/ulXCC7. + """ + # Build our extra attributes map + extra_attributes_map = { + 'instance_type': { + 'xpath': 'instanceType', + 'type': str + }, + 'availability': { + 'xpath': 'availabilityZone', + 'type': str + }, + 'start': { + 'xpath': 'start', + 'type': str + }, + 'duration': { + 'xpath': 'duration', + 'type': int + }, + 'usage_price': { + 'xpath': 'usagePrice', + 'type': float + }, + 'fixed_price': { + 'xpath': 'fixedPrice', + 'type': float + }, + 'instance_count': { + 'xpath': 'instanceCount', + 'type': int + }, + 'description': { + 'xpath': 'productDescription', + 'type': str + }, + 'instance_tenancy': { + 'xpath': 'instanceTenancy', + 'type': str + }, + 'currency_code': { + 'xpath': 'currencyCode', + 'type': str + }, + 'offering_type': { + 'xpath': 'offeringType', + 'type': str + } + } + + # Define and build our extra dictionary + extra = {} + for attribute, values in extra_attributes_map.items(): + type_func = values['type'] + value = findattr(element=element, xpath=values['xpath'], + namespace=NAMESPACE) + extra[attribute] = type_func(value) + + return EC2ReservedNode(id=findtext(element=element, + xpath='reservedInstancesId', + namespace=NAMESPACE), + name=None, + state=findattr(element=element, + xpath='state', + namespace=NAMESPACE), + public_ips=None, + private_ips=None, + driver=self, + extra=extra) + def _to_nodes(self, object, xpath, groups=None): return [self._to_node(el, groups=groups) for el in object.findall(fixxpath(xpath=xpath, @@ -776,6 +876,22 @@ class BaseEC2NodeDriver(NodeDriver): 'description': description, 'state': state}) + def list_reserved_nodes(self): + """ + List all reserved instances/nodes which can be purchased from Amazon + for one or three year terms. Reservations are made at a region level + and reduce the hourly charge for instances. + + More information can be found at http://goo.gl/ulXCC7. + + :rtype: ``list`` of :class:`ExEC2ReservedNode` + """ + params = {'Action': 'DescribeReservedInstances'} + + response = self.connection.request(self.path, params=params).object + + return self._to_reserved_nodes(response, 'reservedInstancesSet/item') + def list_nodes(self, ex_node_ids=None): """ List all nodes http://git-wip-us.apache.org/repos/asf/libcloud/blob/c1d86602/libcloud/test/compute/fixtures/ec2/describe_reserved_instances.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/describe_reserved_instances.xml b/libcloud/test/compute/fixtures/ec2/describe_reserved_instances.xml new file mode 100644 index 0000000..15e14e3 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/describe_reserved_instances.xml @@ -0,0 +1,21 @@ +<DescribeReservedInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>56d0fffa-8819-4658-bdd7-548f143a86d2</requestId> + <reservedInstancesSet> + <item> + <reservedInstancesId>93bbbca2-c500-49d0-9ede-9d8737400498</reservedInstancesId> + <instanceType>t1.micro</instanceType> + <availabilityZone>us-east-1b</availabilityZone> + <start>2013-06-18T12:07:53.161Z</start> + <duration>31536000</duration> + <fixedPrice>23.0</fixedPrice> + <usagePrice>0.012</usagePrice> + <instanceCount>1</instanceCount> + <productDescription>Linux/UNIX</productDescription> + <state>active</state> + <instanceTenancy>default</instanceTenancy> + <currencyCode>USD</currencyCode> + <offeringType>Light Utilization</offeringType> + <recurringCharges/> + </item> + </reservedInstancesSet> +</DescribeReservedInstancesResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/c1d86602/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index bdd7a61..0222d08 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -843,6 +843,10 @@ class EC2MockHttp(MockHttpTestCase): body = self.fixtures.load('describe_instances_with_tags.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _DescribeReservedInstances(self, method, url, body, headers): + body = self.fixtures.load('describe_reserved_instances.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _DescribeAvailabilityZones(self, method, url, body, headers): body = self.fixtures.load('describe_availability_zones.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) @@ -1105,6 +1109,22 @@ class NimbusTests(EC2Tests): self.assertTrue('m1.large' in ids) self.assertTrue('m1.xlarge' in ids) + def test_list_reserved_nodes(self): + node = self.driver.list_reserved_nodes()[0] + self.assertEqual(node.id, '93bbbca2-c500-49d0-9ede-9d8737400498') + self.assertEqual(node.state, 'active') + self.assertEqual(node.extra['instance_type'], 't1.micro') + self.assertEqual(node.extra['availability'], 'us-east-1b') + self.assertEqual(node.extra['start'], '2013-06-18T12:07:53.161Z') + self.assertEqual(node.extra['duration'], 31536000) + self.assertEqual(node.extra['usage_price'], 0.012) + self.assertEqual(node.extra['fixed_price'], 23.0) + self.assertEqual(node.extra['instance_count'], 1) + self.assertEqual(node.extra['description'], 'Linux/UNIX') + self.assertEqual(node.extra['instance_tenancy'], 'default') + self.assertEqual(node.extra['currency_code'], 'USD') + self.assertEqual(node.extra['offering_type'], 'Light Utilization') + def test_list_nodes(self): # overridden from EC2Tests -- Nimbus doesn't support elastic IPs. node = self.driver.list_nodes()[0]
