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]

Reply via email to