Updated Branches:
  refs/heads/trunk c27d46a7e -> 518ab8d59

New improvements in ec2 and cloudstack driver:

1. Added new functionality for ec2:

* list_volumes
* list_snapshots
* destroy_volume_snapshot
* create_volume_snapshot
* delete_keypair
* ex_destroy_image
* ex_modify_image_attributes

2. CloudStack changes

* add possibilities create a port range rules in PortForwardingRule
* fix some methods names (list keypairs)I

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/f15bc5ed
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f15bc5ed
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f15bc5ed

Branch: refs/heads/trunk
Commit: f15bc5ed2ecef63db51dbebdbc8654e09e296483
Parents: c27d46a
Author: gigimon <[email protected]>
Authored: Wed Oct 9 16:48:11 2013 +0300
Committer: Tomaz Muraus <[email protected]>
Committed: Tue Oct 15 11:44:57 2013 +0200

----------------------------------------------------------------------
 libcloud/compute/base.py                        |  20 ++-
 libcloud/compute/drivers/cloudstack.py          | 115 +++++++++----
 libcloud/compute/drivers/ec2.py                 | 164 ++++++++++++++++++-
 .../listPortForwardingRules_default.json        |   2 +-
 .../compute/fixtures/ec2/create_snapshot.xml    |  11 ++
 .../compute/fixtures/ec2/delete_snapshot.xml    |   4 +
 .../compute/fixtures/ec2/deregister_image.xml   |   4 +
 .../compute/fixtures/ec2/describe_snapshots.xml |  39 +++++
 .../compute/fixtures/ec2/describe_volumes.xml   |  23 +++
 .../fixtures/ec2/modify_image_attribute.xml     |   3 +
 libcloud/test/compute/test_cloudstack.py        |  17 +-
 libcloud/test/compute/test_ec2.py               |  98 ++++++++++-
 12 files changed, 457 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/compute/base.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py
index eb9f65d..2cb3ff2 100644
--- a/libcloud/compute/base.py
+++ b/libcloud/compute/base.py
@@ -433,8 +433,26 @@ class StorageVolume(UuidMixin):
 
 
 class VolumeSnapshot(object):
-    def __init__(self, driver):
+    """
+    A base VolumeSnapshot class to derive from.
+    """
+    def __init__(self, id, driver, size=None, extra=None):
+        """
+        Initialize VolumeSnapshot object
+
+        :param      id: Snapshot ID
+        :type       id: ``str``
+
+        :param      size: A snapshot size in Gb
+        :type       size: ``int``
+
+        :param      extra: Platform depends parameters for snapshot
+        :type       extra: ``dict``
+        """
         self.driver = driver
+        self.id = id
+        self.size = size
+        self.extra = extra or {}
 
     def destroy(self):
         """

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/compute/drivers/cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/cloudstack.py 
b/libcloud/compute/drivers/cloudstack.py
index c8bef98..462738a 100644
--- a/libcloud/compute/drivers/cloudstack.py
+++ b/libcloud/compute/drivers/cloudstack.py
@@ -97,13 +97,46 @@ class CloudStackPortForwardingRule(object):
     "A Port forwarding rule for Source NAT."
 
     def __init__(self, node, rule_id, address, protocol, public_port,
-                 private_port):
+                 private_port, public_end_port=None, private_end_port=None):
+        """
+        A Port forwarding rule for Source NAT.
+
+        @note: This is a non-standard extension API, and only works for EC2.
+
+        :param      node: Node for rule
+        :type       node: :class:`Node`
+
+        :param      rule_id: Rule ID
+        :type       rule_id: ``int``
+
+        :param      address: External IP address
+        :type       address: :class:`CloudStackAddress`
+
+        :param      protocol: TCP/IP Protocol (TCP, UDP)
+        :type       protocol: ``str``
+
+        :param      public_port: External port for rule (or started port if 
used port range)
+        :type       public_port: ``int``
+
+        :param      private_port: Internal node port for rule (or started port 
if used port range)
+        :type       private_port: ``int``
+
+        :param      public_end_port: End of external port range
+        :type       public_end_port: ``int``
+
+        :param      private_end_port: End of internal port range
+        :type       private_end_port: ``int``
+
+        :rtype: :class:`CloudStackPortForwardingRule`
+        """
         self.node = node
         self.id = rule_id
         self.address = address
         self.protocol = protocol
         self.public_port = public_port
+        self.public_end_port = public_end_port
         self.private_port = private_port
+        self.private_end_port = private_end_port
 
     def delete(self):
         self.node.ex_delete_port_forwarding_rule(self)
@@ -238,7 +271,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
         for addr in addrs.get('publicipaddress', []):
             if 'virtualmachineid' not in addr:
                 continue
-            vm_id = addr['virtualmachineid']
+            vm_id = str(addr['virtualmachineid'])
             if vm_id not in public_ips_map:
                 public_ips_map[vm_id] = {}
             public_ips_map[vm_id][addr['ipaddress']] = addr['id']
@@ -277,7 +310,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
                 driver=self,
                 extra={'zoneid': vm['zoneid'],
                        'password': password,
-                       'key_name': keypair,
+                       'keyname': keypair,
                        'securitygroup': securitygroup,
                        'created': vm['created']
                        }
@@ -291,7 +324,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
             for addr in addresses:
                 result = self._sync_request('listIpForwardingRules')
                 for r in result.get('ipforwardingrule', []):
-                    if r['virtualmachineid'] == node.id:
+                    if str(r['virtualmachineid']) == node.id:
                         rule = CloudStackIPForwardingRule(node, r['id'],
                                                           addr,
                                                           r['protocol']
@@ -302,17 +335,21 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
             node.extra['ip_forwarding_rules'] = rules
 
             rules = []
-            for addr in addrs:
-                result = self._sync_request('listPortForwardingRules')
-                for r in result.get('portforwardingrule', []):
-                    if r['virtualmachineid'] == node.id:
-                        rule = CloudStackPortForwardingRule(node, r['id'],
-                                                            addr,
-                                                            r['protocol']
-                                                            .upper(),
-                                                            r['publicport'],
-                                                            r['privateport'])
-                        rules.append(rule)
+            public_ips = self.ex_list_public_ips()
+            result = self._sync_request('listPortForwardingRules')
+            for r in result.get('portforwardingrule', []):
+                if str(r['virtualmachineid']) == node.id:
+                    addr = [a for a in public_ips if a.address == 
r['ipaddress']]
+                    rule = CloudStackPortForwardingRule(node, r['id'],
+                                                        addr[0],
+                                                        r['protocol'].upper(),
+                                                        r['publicport'],
+                                                        r['privateport'],
+                                                        r['publicendport'],
+                                                        r['privateendport'],)
+                    if not addr[0].address in node.public_ips:
+                        node.public_ips.append(addr[0].address)
+                    rules.append(rule)
             node.extra['port_forwarding_rules'] = rules
 
             nodes.append(node)
@@ -384,7 +421,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
                    'ip_forwarding_rules': [],
                    'port_forwarding_rules': [],
                    'password': password,
-                   'key_name': keypair,
+                   'keyname': keypair,
                    'securitygroup': securitygroup,
                    'created': node['created']
                    }
@@ -587,14 +624,19 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
         self._async_request('detachVolume', id=volume.id)
         return True
 
-    def list_volumes(self):
+    def list_volumes(self, node=None):
         """
         List all volumes
 
+        :type node: :class:`CloudStackNode`
+
         :rtype: ``list`` of :class:`StorageVolume`
         """
         list_volumes = []
-        volumes = self._sync_request('listVolumes')
+        if node:
+            volumes = self._sync_request('listVolumes', 
virtualmachineid=node.id)
+        else:
+            volumes = self._sync_request('listVolumes')
         for vol in volumes['volume']:
             list_volumes.append(StorageVolume(id=vol['id'],
                                 name=vol['name'],
@@ -651,27 +693,28 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
         """
         rules = []
         result = self._sync_request('listPortForwardingRules')
-        if result == {}:
-            pass
-        else:
+        if not result == {}:
+            public_ips = self.ex_list_public_ips()
             nodes = self.list_nodes()
             for rule in result['portforwardingrule']:
                 node = [n for n in nodes
-                        if n.id == rule['virtualmachineid']]
-                addr = [a for a in self.ex_list_public_ips()
-                        if a.address == rule['ipaddress']]
+                        if n.id == str(rule['virtualmachineid'])]
+                addr = [a for a in public_ips if a.address == 
rule['ipaddress']]
                 rules.append(CloudStackPortForwardingRule
                              (node[0],
                               rule['id'],
                               addr[0],
                               rule['protocol'],
                               rule['publicport'],
-                              rule['privateport']))
+                              rule['privateport'],
+                              rule['publicendport'],
+                              rule['privateendport']))
 
         return rules
 
     def ex_create_port_forwarding_rule(self, address, private_port,
-                                       public_port, protocol, node):
+                                       public_port, protocol, node,
+                                       public_end_port=None, 
private_end_port=None, openfirewall=True):
         """
         Creates a Port Forwarding Rule, used for Source NAT
 
@@ -698,8 +741,12 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
             'privateport': int(private_port),
             'publicport': int(public_port),
             'virtualmachineid': node.id,
-            'openfirewall': True
+            'openfirewall': openfirewall
         }
+        if public_end_port:
+            args['publicendport'] = int(public_end_port)
+        if private_end_port:
+            args['privateendport'] = int(private_end_port)
 
         result = self._async_request('createPortForwardingRule', **args)
         rule = CloudStackPortForwardingRule(node,
@@ -708,9 +755,11 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
                                             address,
                                             protocol,
                                             public_port,
-                                            private_port)
+                                            private_port,
+                                            public_end_port,
+                                            private_end_port)
         node.extra['port_forwarding_rules'].append(rule)
-        node.public_ips.append(address)
+        node.public_ips.append(address.address)
         return rule
 
     def ex_delete_port_forwarding_rule(self, node, rule):
@@ -874,12 +923,12 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
         res = self._sync_request('createSSHKeyPair', name=name, **extra_args)
         return res['keypair']
 
-    def ex_delete_keypair(self, name, **kwargs):
+    def ex_delete_keypair(self, keypair, **kwargs):
         """
         Deletes an existing SSH KeyPair
 
-        :param     name: Name of the keypair (required)
-        :type      name: ``str``
+        :param     keypair: Name of the keypair (required)
+        :type      keypair: ``str``
 
         :param     projectid: The project associated with keypair
         :type      projectid: ``str``
@@ -897,7 +946,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, 
NodeDriver):
 
         extra_args = kwargs.copy()
 
-        res = self._sync_request('deleteSSHKeyPair', name=name, **extra_args)
+        res = self._sync_request('deleteSSHKeyPair', name=keypair, 
**extra_args)
         return res['success']
 
     def ex_import_keypair_from_string(self, name, key_material):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index 8fcb055..2362d91 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -31,13 +31,14 @@ from libcloud.utils.py3 import b, basestring
 from libcloud.utils.xml import fixxpath, findtext, findattr, findall
 from libcloud.utils.publickey import get_pubkey_ssh2_fingerprint
 from libcloud.utils.publickey import get_pubkey_comment
+from libcloud.utils.iso8601 import parse_date
 from libcloud.common.aws import AWSBaseResponse, SignedAWSConnection
 from libcloud.common.types import (InvalidCredsError, MalformedResponseError,
                                    LibcloudError)
 from libcloud.compute.providers import Provider
 from libcloud.compute.types import NodeState
 from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize
-from libcloud.compute.base import NodeImage, StorageVolume
+from libcloud.compute.base import NodeImage, StorageVolume, VolumeSnapshot
 
 API_VERSION = '2010-08-31'
 NAMESPACE = 'http://ec2.amazonaws.com/doc/%s/' % (API_VERSION)
@@ -600,11 +601,31 @@ class BaseEC2NodeDriver(NodeDriver):
         volId = findtext(element=element, xpath='volumeId',
                          namespace=NAMESPACE)
         size = findtext(element=element, xpath='size', namespace=NAMESPACE)
-
+        state = findtext(element=element, xpath='status', namespace=NAMESPACE)
+        create_time = findtext(element=element, xpath='createTime', 
namespace=NAMESPACE)
         return StorageVolume(id=volId,
                              name=name,
                              size=int(size),
-                             driver=self)
+                             driver=self,
+                             extra={'state': state,
+                                    'device': findtext(element=element, 
xpath='attachmentSet/item/device', namespace=NAMESPACE),
+                                    'create-time': parse_date(create_time)})
+
+    def _to_snapshots(self, response):
+        return [self._to_snapshot(el) for el in response.findall(
+            fixxpath(xpath='snapshotSet/item', namespace=NAMESPACE))
+        ]
+
+    def _to_snapshot(self, element):
+        snapId = findtext(element=element, xpath='snapshotId', 
namespace=NAMESPACE)
+        volId = findtext(element=element, xpath='volumeId', 
namespace=NAMESPACE)
+        size = findtext(element=element, xpath='volumeSize', 
namespace=NAMESPACE)
+        state = findtext(element=element, xpath='status', namespace=NAMESPACE)
+        description = findtext(element=element, xpath='description', 
namespace=NAMESPACE)
+        return VolumeSnapshot(snapId, size=int(size), driver=self,
+                               extra={'volume_id': volId,
+                                        'description': description,
+                                        'state': state})
 
     def list_nodes(self, ex_node_ids=None):
         """
@@ -650,7 +671,7 @@ class BaseEC2NodeDriver(NodeDriver):
             sizes.append(NodeSize(driver=self, **attributes))
         return sizes
 
-    def list_images(self, location=None, ex_image_ids=None):
+    def list_images(self, location=None, ex_image_ids=None, ex_owner=None):
         """
         List all images
 
@@ -658,13 +679,24 @@ class BaseEC2NodeDriver(NodeDriver):
         images that should be returned. Only the images
         with the corresponding image ids will be returned.
 
+        Ex_owner parameter is used to filter the list of
+        images that should be returned. Only the images
+        with the corresponding owner will be returned.
+        Valid values: amazon|aws-marketplace|self|all|aws id
+
         :param      ex_image_ids: List of ``NodeImage.id``
         :type       ex_image_ids: ``list`` of ``str``
 
+        :param      ex_owner: Owner name
+        :type       ex_image_ids: ``str``
+
         :rtype: ``list`` of :class:`NodeImage`
         """
         params = {'Action': 'DescribeImages'}
 
+        if ex_owner:
+            params.update({'Owner.1': owner})
+
         if ex_image_ids:
             params.update(self._pathlist('ImageId', ex_image_ids))
 
@@ -683,6 +715,21 @@ class BaseEC2NodeDriver(NodeDriver):
                     )
         return locations
 
+    def list_volumes(self, node=None):
+        params = {
+            'Action': 'DescribeVolumes',
+        }
+        if node:
+            params.update({
+                'Filter.1.Name': 'attachment.instance-id',
+                'Filter.1.Value': node.id,
+            })
+        response = self.connection.request(self.path, params=params).object
+        volumes = [self._to_volume(el, '') for el in response.findall(
+            fixxpath(xpath='volumeSet/item', namespace=NAMESPACE))
+        ]
+        return volumes
+
     def create_volume(self, size, name, location=None, snapshot=None):
         params = {
             'Action': 'CreateVolume',
@@ -722,6 +769,71 @@ class BaseEC2NodeDriver(NodeDriver):
         self.connection.request(self.path, params=params)
         return True
 
+    def create_volume_snapshot(self, volume, name=None):
+        """
+        Create snapshot from volume
+
+        :param      volume: Instance of ``StorageVolume``
+        :type       volume: ``StorageVolume``
+
+        :param      name: Description for snapshot
+        :type       name: ``str``
+
+        :rtype: :class:`VolumeSnapshot`
+        """
+        params = {
+            'Action': 'CreateSnapshot',
+            'VolumeId': volume.id,
+        }
+        if name:
+            params.update({
+                'Description': name,
+            })
+        response = self.connection.request(self.path, params=params).object
+        snapshot = self._to_snapshot(response)
+        return snapshot
+
+    def list_volume_snapshots(self, snapshot):
+        return self.list_snapshots(snapshot)
+
+    def list_snapshots(self, snapshot=None, owner=None):
+        """
+        Describe all snapshots
+        @param snapshot: If this setted, describe only this snapshot id
+        @param owner: Owner for snapshot: self|amazon|ID
+        @return: C{list(VolumeSnapshots)}
+        """
+        params = {
+            'Action': 'DescribeSnapshots',
+        }
+        if snapshot:
+            params.update({
+                'SnapshotId.1': snapshot.id,
+            })
+        if owner:
+            params.update({
+                'Owner.1': owner,
+            })
+        response = self.connection.request(self.path, params=params).object
+        snapshots = self._to_snapshots(response)
+        return snapshots
+
+    def destroy_volume_snapshot(self, snapshot):
+        params = {
+            'Action': 'DeleteSnapshot',
+            'SnapshotId': snapshot.id
+        }
+        response = self.connection.request(self.path, params=params).object
+        return self._get_boolean(response)
+
+    def ex_destroy_image(self, image):
+        params = {
+            'Action': 'DeregisterImage',
+            'ImageId': image.id
+        }
+        response = self.connection.request(self.path, params=params).object
+        return self._get_boolean(response)
+
     def ex_create_keypair(self, name):
         """Creates a new keypair
 
@@ -747,6 +859,25 @@ class BaseEC2NodeDriver(NodeDriver):
             'keyFingerprint': key_fingerprint,
         }
 
+    def ex_delete_keypair(self, keypair):
+        """Destroy a keypair by name
+
+        @note: This is a non-standard extension API, and only works for EC2.
+
+        :param      keypair: The name of the keypair to Delete.
+        :type       keypair: ``str``
+
+        :rtype: ``bool``
+        """
+        params = {
+            'Action': 'DeleteKeyPair',
+            'KeyName.1': keypair
+        }
+        result = self.connection.request(self.path, params=params).object
+        element = findtext(element=result, xpath='return',
+                           namespace=NAMESPACE)
+        return element == 'true'
+
     def ex_import_keypair_from_string(self, name, key_material):
         """
         imports a new public key where the public key is passed in as a string
@@ -1292,6 +1423,31 @@ class BaseEC2NodeDriver(NodeDriver):
                            namespace=NAMESPACE)
         return element == 'true'
 
+    def ex_modify_image_attribute(self, image, attributes):
+        """
+        Modify image attributes.
+
+        :param      node: Node instance
+        :type       node: :class:`Node`
+
+        :param      attributes: Dictionary with node attributes
+        :type       attributes: ``dict``
+
+        :return: True on success, False otherwise.
+        :rtype: ``bool``
+        """
+        attributes = attributes or {}
+        attributes.update({'ImageId': image.id})
+
+        params = {'Action': 'ModifyImageAttribute'}
+        params.update(attributes)
+
+        result = self.connection.request(self.path,
+                                         params=params.copy()).object
+        element = findtext(element=result, xpath='return',
+                           namespace=NAMESPACE)
+        return element == 'true'
+
     def ex_change_node_size(self, node, new_size):
         """
         Change the node size.

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/cloudstack/listPortForwardingRules_default.json
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/cloudstack/listPortForwardingRules_default.json
 
b/libcloud/test/compute/fixtures/cloudstack/listPortForwardingRules_default.json
index a65d08d..d6aae0f 100644
--- 
a/libcloud/test/compute/fixtures/cloudstack/listPortForwardingRules_default.json
+++ 
b/libcloud/test/compute/fixtures/cloudstack/listPortForwardingRules_default.json
@@ -1 +1 @@
-{ "listportforwardingrulesresponse" : { "count":1 ,"portforwardingrule" : [  
{"id":"bc7ea3ee-a2c3-4b86-a53f-01bdaa1b2e32","privateport":"33","privateendport":"33","protocol":"tcp","publicport":"33","publicendport":"33","virtualmachineid":"2600","virtualmachinename":"testlib","virtualmachinedisplayname":"testlib","ipaddressid":"96dac96f-0b5d-42c1-b5de-8a97f3e34c43","ipaddress":"1.1.1.116","state":"Active","cidrlist":"","tags":[]}
 ] } }
+{ "listportforwardingrulesresponse" : { "count":1 ,"portforwardingrule" : [  
{"id":"bc7ea3ee-a2c3-4b86-a53f-01bdaa1b2e32","privateport":"33","privateendport":"34","protocol":"tcp","publicport":"33","publicendport":"34","virtualmachineid":"2600","virtualmachinename":"testlib","virtualmachinedisplayname":"testlib","ipaddressid":"96dac96f-0b5d-42c1-b5de-8a97f3e34c43","ipaddress":"1.1.1.116","state":"Active","cidrlist":"","tags":[]}
 ] } }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/create_snapshot.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/create_snapshot.xml 
b/libcloud/test/compute/fixtures/ec2/create_snapshot.xml
new file mode 100644
index 0000000..c395ded
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/create_snapshot.xml
@@ -0,0 +1,11 @@
+<CreateSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+  <requestId>59dbff89-35bd-4eac-99ed-be587</requestId>
+  <snapshotId>snap-a7cb2hd9</snapshotId>
+  <volumeId>vol-4282672b</volumeId>
+  <status>pending</status>
+  <startTime>2013-08-15T16:22:30.000Z</startTime>
+  <progress>60%</progress>
+  <ownerId>1836219348</ownerId>
+  <volumeSize>10</volumeSize>
+  <description>Test description</description>
+</CreateSnapshotResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/delete_snapshot.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/delete_snapshot.xml 
b/libcloud/test/compute/fixtures/ec2/delete_snapshot.xml
new file mode 100644
index 0000000..2e11312
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/delete_snapshot.xml
@@ -0,0 +1,4 @@
+<DeleteSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+  <requestId>5cd6fa89-35bd-4aac-99ed-na8af7</requestId>
+  <return>true</return>
+</DeleteSnapshotResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/deregister_image.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/deregister_image.xml 
b/libcloud/test/compute/fixtures/ec2/deregister_image.xml
new file mode 100644
index 0000000..f7b97c4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/deregister_image.xml
@@ -0,0 +1,4 @@
+<DeregisterImageResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+  <requestId>d06f248d-444e-475d-a8f8-1ebb4ac39842</requestId>
+  <return>true</return>
+</DeregisterImageResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/describe_snapshots.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_snapshots.xml 
b/libcloud/test/compute/fixtures/ec2/describe_snapshots.xml
new file mode 100644
index 0000000..ed3ea68
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/describe_snapshots.xml
@@ -0,0 +1,39 @@
+<DescribeSnapshotsResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+   <requestId>and4xcasi-35bd-4e3c-89ab-cb183</requestId>
+   <snapshotSet>
+      <item>
+         <snapshotId>snap-428abd35</snapshotId>
+         <volumeId>vol-e020df80</volumeId>
+         <status>pending</status>
+         <startTime>2013-09-15T15:40:30.000Z</startTime>
+         <progress>90%</progress>
+         <ownerId>1938218230</ownerId>
+         <volumeSize>30</volumeSize>
+         <description>Daily Backup</description>
+         <tagSet>
+            <item>
+               <key>Keyone</key>
+               <value>DB_Backup</value>
+            </item>
+         </tagSet>
+      </item>
+   </snapshotSet>
+   <snapshotSet>
+      <item>
+         <snapshotId>snap-18349159</snapshotId>
+         <volumeId>vol-b5a2c1v9</volumeId>
+         <status>pending</status>
+         <startTime>2013-09-15T16:00:30.000Z</startTime>
+         <progress>30%</progress>
+         <ownerId>1938218230</ownerId>
+         <volumeSize>15</volumeSize>
+         <description>Weekly backup</description>
+         <tagSet>
+            <item>
+               <key>Key2</key>
+               <value>db_backup</value>
+            </item>
+         </tagSet>
+      </item>
+   </snapshotSet>
+</DescribeSnapshotsResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/describe_volumes.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_volumes.xml 
b/libcloud/test/compute/fixtures/ec2/describe_volumes.xml
new file mode 100644
index 0000000..704f5ee
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/describe_volumes.xml
@@ -0,0 +1,23 @@
+<DescribeVolumesResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+    <requestId>766b978a-f574-4c8d-a974-57547a8c304e</requestId>
+    <volumeSet>
+        <item>
+            <volumeId>vol-10ae5e2b</volumeId>
+            <size>1</size>
+            <snapshotId/>
+            <availabilityZone>us-east-1d</availabilityZone>
+            <status>available</status>
+            <createTime>2013-10-09T05:41:37.000Z</createTime>
+            <attachmentSet/>
+        </item>
+        <item>
+            <volumeId>vol-v24bfh75</volumeId>
+            <size>11</size>
+            <snapshotId/>
+            <availabilityZone>us-east-1c</availabilityZone>
+            <status>available</status>
+            <createTime>2013-10-08T19:36:49.000Z</createTime>
+            <attachmentSet/>
+        </item>
+    </volumeSet>
+</DescribeVolumesResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/fixtures/ec2/modify_image_attribute.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/modify_image_attribute.xml 
b/libcloud/test/compute/fixtures/ec2/modify_image_attribute.xml
new file mode 100644
index 0000000..d4401fa
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/modify_image_attribute.xml
@@ -0,0 +1,3 @@
+<ModifyImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/";>
+    <return>true</return>
+</ModifyImageAttributeResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/test_cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_cloudstack.py 
b/libcloud/test/compute/test_cloudstack.py
index c0a55e7..565fb05 100644
--- a/libcloud/test/compute/test_cloudstack.py
+++ b/libcloud/test/compute/test_cloudstack.py
@@ -127,7 +127,7 @@ class CloudStackNodeDriverTest(unittest.TestCase, 
TestCaseMixin):
                                        size=size,
                                        ex_keyname='foobar')
         self.assertEqual(node.name, 'test')
-        self.assertEqual(node.extra['key_name'], 'foobar')
+        self.assertEqual(node.extra['keyname'], 'foobar')
 
     def test_list_images_no_images_available(self):
         CloudStackMockHttp.fixture_tag = 'notemplates'
@@ -242,7 +242,7 @@ class CloudStackNodeDriverTest(unittest.TestCase, 
TestCaseMixin):
         self.assertEqual('test', nodes[0].name)
         self.assertEqual('2600', nodes[0].id)
         self.assertEqual([], nodes[0].extra['securitygroup'])
-        self.assertEqual(None, nodes[0].extra['key_name'])
+        self.assertEqual(None, nodes[0].extra['keyname'])
 
     def test_list_locations(self):
         location = self.driver.list_locations()[0]
@@ -358,25 +358,36 @@ class CloudStackNodeDriverTest(unittest.TestCase, 
TestCaseMixin):
         node = self.driver.list_nodes()[0]
         address = self.driver.ex_list_public_ips()[0]
         private_port = 33
+        private_end_port = 34
         public_port = 33
+        public_end_port = 34
+        openfirewall = True
         protocol = 'TCP'
         rule = self.driver.ex_create_port_forwarding_rule(address,
                                                           private_port,
                                                           public_port,
                                                           protocol,
-                                                          node)
+                                                          node,
+                                                          public_end_port,
+                                                          private_end_port,
+                                                          openfirewall)
         self.assertEqual(rule.address, address)
         self.assertEqual(rule.protocol, protocol)
         self.assertEqual(rule.public_port, public_port)
+        self.assertEqual(rule.public_end_port, public_end_port)
         self.assertEqual(rule.private_port, private_port)
+        self.assertEqual(rule.private_end_port, private_end_port)
 
     def test_ex_list_port_forwarding_rules(self):
         rules = self.driver.ex_list_port_forwarding_rules()
         self.assertEqual(len(rules), 1)
         rule = rules[0]
+        self.assertTrue(rule.node)
         self.assertEqual(rule.protocol, 'tcp')
         self.assertEqual(rule.public_port, '33')
+        self.assertEqual(rule.public_end_port, '34')
         self.assertEqual(rule.private_port, '33')
+        self.assertEqual(rule.private_end_port, '34')
         self.assertEqual(rule.address.address, '1.1.1.116')
 
     def test_ex_delete_port_forwarding_rule(self):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f15bc5ed/libcloud/test/compute/test_ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_ec2.py 
b/libcloud/test/compute/test_ec2.py
index ffbf93b..159eca6 100644
--- a/libcloud/test/compute/test_ec2.py
+++ b/libcloud/test/compute/test_ec2.py
@@ -17,6 +17,7 @@ from __future__ import with_statement
 
 import os
 import sys
+from datetime import datetime
 
 from mock import Mock
 
@@ -37,7 +38,7 @@ from libcloud.compute.drivers.ec2 import REGION_DETAILS
 from libcloud.compute.drivers.ec2 import ExEC2AvailabilityZone
 from libcloud.utils.py3 import urlparse
 from libcloud.compute.base import Node, NodeImage, NodeSize, NodeLocation
-from libcloud.compute.base import StorageVolume
+from libcloud.compute.base import StorageVolume, VolumeSnapshot
 
 from libcloud.test import MockHttpTestCase, LibcloudTestCase
 from libcloud.test.compute import TestCaseMixin
@@ -327,6 +328,13 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(images[0].name,
                     'ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml')
 
+    def ex_destroy_image(self):
+        images = self.driver.list_images()
+        image = images[0]
+
+        resp = self.driver.ex_destroy_image(image)
+        self.assertTrue(resp)
+
     def test_ex_list_availability_zones(self):
         availability_zones = self.driver.ex_list_availability_zones()
         availability_zone = availability_zones[0]
@@ -357,6 +365,10 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(keypair2['keyName'], 'gsg-keypair')
         self.assertEqual(keypair2['keyFingerprint'], null_fingerprint)
 
+    def ex_delete_keypair(self):
+        resp = self.driver.ex_delete_keypair('testkey')
+        self.assertTrue(resp)
+
     def test_ex_describe_tags(self):
         node = Node('i-4382922a', None, None, None, None, self.driver)
         tags = self.driver.ex_describe_tags(resource=node)
@@ -456,12 +468,28 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         result = self.driver.ex_change_node_size(node=node, new_size=size)
         self.assertTrue(result)
 
+    def test_list_volumes(self):
+        volumes = self.driver.list_volumes()
+
+        self.assertEqual(len(volumes), 2)
+
+        self.assertEqual('vol-10ae5e2b', volumes[0].id)
+        self.assertEqual(1, volumes[0].size)
+        self.assertEqual('available', volumes[0].extra['state'])
+
+        self.assertEqual('vol-v24bfh75', volumes[1].id)
+        self.assertEqual(11, volumes[1].size)
+        self.assertEqual('available', volumes[1].extra['state'])
+
+
     def test_create_volume(self):
         location = self.driver.list_locations()[0]
         vol = self.driver.create_volume(10, 'vol', location)
 
         self.assertEqual(10, vol.size)
         self.assertEqual('vol', vol.name)
+        self.assertEqual('creating', vol.extra['state'])
+        self.assertTrue(isinstance(vol.extra['create-time'], datetime))
 
     def test_destroy_volume(self):
         vol = StorageVolume(
@@ -489,6 +517,45 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         retValue = self.driver.detach_volume(vol)
         self.assertTrue(retValue)
 
+    def test_create_volume_snapshot(self):
+        vol = StorageVolume(
+                id='vol-4282672b', name='test',
+                size=10, driver=self.driver)
+        snap = self.driver.create_volume_snapshot(vol, 'Test description')
+
+        self.assertEqual('snap-a7cb2hd9', snap.id)
+        self.assertEqual(vol.size, snap.size)
+        self.assertEqual('Test description', snap.extra['description'])
+        self.assertEqual(vol.id, snap.extra['volume_id'])
+        self.assertEqual('pending', snap.extra['state'])
+
+    def test_list_snapshots(self):
+        snaps = self.driver.list_snapshots()
+
+        self.assertEqual(len(snaps), 2)
+
+        self.assertEqual('snap-428abd35', snaps[0].id)
+        self.assertEqual('vol-e020df80', snaps[0].extra['volume_id'])
+        self.assertEqual(30, snaps[0].size)
+        self.assertEqual('Daily Backup', snaps[0].extra['description'])
+
+        self.assertEqual('snap-18349159', snaps[1].id)
+        self.assertEqual('vol-b5a2c1v9', snaps[1].extra['volume_id'])
+        self.assertEqual(15, snaps[1].size)
+        self.assertEqual('Weekly backup', snaps[1].extra['description'])
+
+    def test_destroy_snapshot(self):
+        snap = VolumeSnapshot(id='snap-428abd35', size=10, driver=self.driver)
+        resp = snap.destroy()
+        self.assertTrue(resp)
+
+    def test_ex_modify_image_attribute(self):
+        images = self.driver.list_images()
+        image = images[0]
+
+        resp = self.driver.ex_modify_image_attribute(image, 
{'LaunchPermission.Add.1.Group': 'all'})
+        self.assertTrue(resp)
+
     def test_create_node_ex_security_groups(self):
         EC2MockHttp.type = 'ex_security_groups'
 
@@ -734,6 +801,35 @@ class EC2MockHttp(MockHttpTestCase):
         body = self.fixtures.load('detach_volume.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _DescribeVolumes(self, method, url, body, headers):
+        body = self.fixtures.load('describe_volumes.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _CreateSnapshot(self, method, url, body, headers):
+        body = self.fixtures.load('create_snapshot.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _DescribeSnapshots(self, method, url, body, headers):
+        body = self.fixtures.load('describe_snapshots.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _DeleteSnapshot(self, method, url, body, headers):
+        body = self.fixtures.load('delete_snapshot.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _DeregisterImage(self, method, url, body, headers):
+        body = self.fixtures.load('deregister_image.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _DeleteKeypair(self, method, url, body, headers):
+        body = self.fixtures.load('delete_keypair.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _ModifyImageAttribute(self, method, url, body, headers):
+        body = self.fixtures.load('modify_image_attribute.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
 
 class EucMockHttp(EC2MockHttp):
     fixtures = ComputeFileFixtures('ec2')

Reply via email to