You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2014/01/11 22:14:21 UTC

[2/3] git commit: Issue LIBCLOUD-470: Add VPC Elastic IP support. This not only adds full EIP support for EC2-Classic/VPC, but also adds a new ElasticIP abstraction that will make future promotion into the base API much easier. New tests were added to va

Issue LIBCLOUD-470: Add VPC Elastic IP support. This not only adds full EIP
support for EC2-Classic/VPC, but also adds a new ElasticIP abstraction
that will make future promotion into the base API much easier. New tests
were added to validate VPC association/disassociation/release calls work
as expected.

Closes #220.

Signed-off-by: Tomaz Muraus <to...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/f44c3648
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f44c3648
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f44c3648

Branch: refs/heads/trunk
Commit: f44c3648119f8a1e9e3f190bca1bc8e52a2012a3
Parents: 7c1e4fd
Author: Chris DeRamus <ch...@divvycloud.com>
Authored: Sat Jan 11 15:55:15 2014 -0500
Committer: Tomaz Muraus <to...@apache.org>
Committed: Sat Jan 11 22:13:31 2014 +0100

----------------------------------------------------------------------
 libcloud/compute/drivers/ec2.py                 | 275 ++++++++++++++-----
 .../fixtures/ec2/allocate_vpc_address.xml       |   6 +
 .../fixtures/ec2/associate_vpc_address.xml      |   5 +
 .../compute/fixtures/ec2/describe_addresses.xml |   8 +-
 .../fixtures/ec2/describe_addresses_all.xml     |  50 ++--
 libcloud/test/compute/test_ec2.py               |  79 +++++-
 6 files changed, 318 insertions(+), 105 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index a40bc24..e22180b 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -495,6 +495,28 @@ RESOURCE_EXTRA_ATTRIBUTES_MAP = {
             'transform_func': int
         }
     },
+    'elastic_ip': {
+        'allocation_id': {
+            'xpath': 'allocationId',
+            'transform_func': str,
+        },
+        'association_id': {
+            'xpath': 'associationId',
+            'transform_func': str,
+        },
+        'interface_id': {
+            'xpath': 'networkInterfaceId',
+            'transform_func': str,
+        },
+        'owner_id': {
+            'xpath': 'networkInterfaceOwnerId',
+            'transform_func': str,
+        },
+        'private_ip': {
+            'xpath': 'privateIp',
+            'transform_func': str,
+        }
+    },
     'image': {
         'state': {
             'xpath': 'imageState',
@@ -924,6 +946,38 @@ class EC2NetworkInterface(object):
                 % (self.id, self.name))
 
 
+class ElasticIP(object):
+    """
+    Represents information about an elastic IP adddress
+
+    :param      ip: The elastic IP address
+    :type       ip: ``str``
+
+    :param      domain: The domain that the IP resides in (EC2-Classic/VPC).
+                        EC2 classic is represented with standard and VPC
+                        is represented with vpc.
+    :type       domain: ``str``
+
+    :param      instance_id: The identifier of the instance which currently
+                             has the IP associated.
+    :type       instance_id: ``str``
+
+    Note: This class is used to support both EC2 and VPC IPs.
+          For VPC specific attributes are stored in the extra
+          dict to make promotion to the base API easier.
+    """
+
+    def __init__(self, ip, domain, instance_id, extra=None):
+        self.ip = ip
+        self.domain = domain
+        self.instance_id = instance_id
+        self.extra = extra or {}
+
+    def __repr__(self):
+        return (('<ElasticIP: ip=%s, domain=%s, instance_id=%s>')
+                % (self.ip, self.domain, self.instance_id))
+
+
 class BaseEC2NodeDriver(NodeDriver):
     """
     Base Amazon EC2 node driver.
@@ -1236,6 +1290,48 @@ class BaseEC2NodeDriver(NodeDriver):
 
         return EC2Network(vpc_id, name, cidr_block, extra=extra)
 
+    def _to_addresses(self, response, only_associated):
+        """
+        Builds a list of dictionaries containing elastic IP properties.
+
+        :param    only_associated: If true, return only those addresses
+                                   that are associated with an instance.
+                                   If false, return all addresses.
+        :type     only_associated: ``bool``
+
+        :rtype:   ``list`` of :class:`ElasticIP`
+        """
+        addresses = []
+        for el in response.findall(fixxpath(xpath='addressesSet/item',
+                                            namespace=NAMESPACE)):
+            addr = self._to_address(el, only_associated)
+            if addr is not None:
+                addresses.append(addr)
+
+        return addresses
+
+    def _to_address(self, element, only_associated):
+        instance_id = findtext(element=element, xpath='instanceId',
+                               namespace=NAMESPACE)
+
+        public_ip = findtext(element=element,
+                             xpath='publicIp',
+                             namespace=NAMESPACE)
+
+        domain = findtext(element=element,
+                          xpath='domain',
+                          namespace=NAMESPACE)
+
+        # Build our extra dict
+        extra = self._get_extra_dict(
+            element, RESOURCE_EXTRA_ATTRIBUTES_MAP['elastic_ip'])
+
+        # Return NoneType if only associated IPs are requested
+        if only_associated and not instance_id:
+            return None
+
+        return ElasticIP(public_ip, domain, instance_id, extra=extra)
+
     def _to_subnets(self, response):
         return [self._to_subnet(el) for el in response.findall(
             fixxpath(xpath='subnetSet/item', namespace=NAMESPACE))
@@ -2582,113 +2678,142 @@ class BaseEC2NodeDriver(NodeDriver):
             'Filter.0.Value.0': node.id
         })
 
-    def ex_allocate_address(self):
+    def ex_allocate_address(self, domain='standard'):
         """
-        Allocate a new Elastic IP address
+        Allocate a new Elastic IP address for EC2 classic or VPC
 
-        :return: String representation of allocated IP address
-        :rtype: ``str``
+        :param      domain: The domain to allocate the new address in
+                            (standard/vpc)
+        :type       domain: ``str``
+
+        :return:    Instance of ElasticIP
+        :rtype:     :class:`ElasticIP`
         """
         params = {'Action': 'AllocateAddress'}
 
+        if domain == 'vpc':
+            params['Domain'] = domain
+
         response = self.connection.request(self.path, params=params).object
-        public_ip = findtext(element=response, xpath='publicIp',
-                             namespace=NAMESPACE)
-        return public_ip
 
-    def ex_release_address(self, elastic_ip_address):
+        return self._to_address(response, only_associated=False)
+
+    def ex_release_address(self, elastic_ip, domain=None):
         """
-        Release an Elastic IP address
+        Release an Elastic IP address using the IP (EC2-Classic) or
+        using the allocation ID (VPC)
 
-        :param      elastic_ip_address: Elastic IP address which should be used
-        :type       elastic_ip_address: ``str``
+        :param      elastic_ip: Elastic IP instance
+        :type       elastic_ip: :class:`ElasticIP`
 
-        :return: True on success, False otherwise.
-        :rtype: ``bool``
+        :param      domain: The domain where the IP resides (vpc only)
+        :type       domain: ``str``
+
+        :return:    True on success, False otherwise.
+        :rtype:     ``bool``
         """
         params = {'Action': 'ReleaseAddress'}
 
-        params.update({'PublicIp': elastic_ip_address})
+        if domain is not None and domain != 'vpc':
+            raise AttributeError('Domain can only be set to vpc')
+
+        if domain is None:
+            params['PublicIp'] = elastic_ip.ip
+        else:
+            params['AllocationId'] = elastic_ip.extra['allocation_id']
+
         response = self.connection.request(self.path, params=params).object
         return self._get_boolean(response)
 
-    def ex_describe_all_addresses(self, only_allocated=False):
+    def ex_describe_all_addresses(self, only_associated=False):
         """
         Return all the Elastic IP addresses for this account
-        optionally, return only the allocated addresses
+        optionally, return only addresses associated with nodes
 
-        :param    only_allocated: If true, return only those addresses
-                                  that are associated with an instance
-        :type     only_allocated: ``str``
+        :param    only_associated: If true, return only those addresses
+                                   that are associated with an instance.
+        :type     only_associated: ``bool``
 
-        :return:   list list of elastic ips for this particular account.
-        :rtype: ``list`` of ``str``
+        :return:  List of ElasticIP instances.
+        :rtype:   ``list`` of :class:`ElasticIP`
         """
         params = {'Action': 'DescribeAddresses'}
 
-        result = self.connection.request(self.path,
-                                         params=params.copy()).object
-
-        # the list which we return
-        elastic_ip_addresses = []
-        for element in findall(element=result, xpath='addressesSet/item',
-                               namespace=NAMESPACE):
-            instance_id = findtext(element=element, xpath='instanceId',
-                                   namespace=NAMESPACE)
-
-            # if only allocated addresses are requested
-            if only_allocated and not instance_id:
-                continue
-
-            ip_address = findtext(element=element, xpath='publicIp',
-                                  namespace=NAMESPACE)
-
-            elastic_ip_addresses.append(ip_address)
+        response = self.connection.request(self.path, params=params).object
 
-        return elastic_ip_addresses
+        # We will send our only_associated boolean over to
+        # shape how the return data is sent back
+        return self._to_addresses(response, only_associated)
 
-    def ex_associate_address_with_node(self, node, elastic_ip_address):
+    def ex_associate_address_with_node(self, node, elastic_ip, domain=None):
         """
         Associate an Elastic IP address with a particular node.
 
         :param      node: Node instance
         :type       node: :class:`Node`
 
-        :param      elastic_ip_address: IP address which should be used
-        :type       elastic_ip_address: ``str``
+        :param      elastic_ip: Elastic IP instance
+        :type       elastic_ip: :class:`ElasticIP`
 
-        :return: True on success, False otherwise.
-        :rtype: ``bool``
+        :param      domain: The domain where the IP resides (vpc only)
+        :type       domain: ``str``
+
+        :return:    A string representation of the association ID which is
+                    required for VPC disassociation. EC2/standard
+                    addresses return None
+        :rtype:     ``None`` or ``str``
         """
-        params = {'Action': 'AssociateAddress'}
+        params = {'Action': 'AssociateAddress', 'InstanceId': node.id}
 
-        params.update({'InstanceId': node.id})
-        params.update({'PublicIp': elastic_ip_address})
-        res = self.connection.request(self.path, params=params).object
-        return self._get_boolean(res)
+        if domain is not None and domain != 'vpc':
+            raise AttributeError('Domain can only be set to vpc')
 
-    def ex_associate_addresses(self, node, elastic_ip_address):
+        if domain is None:
+            params.update({'PublicIp': elastic_ip.ip})
+        else:
+            params.update({'AllocationId': elastic_ip.extra['allocation_id']})
+
+        response = self.connection.request(self.path, params=params).object
+        association_id = findtext(element=response,
+                                  xpath='associationId',
+                                  namespace=NAMESPACE)
+        return association_id
+
+    def ex_associate_addresses(self, node, elastic_ip, domain=None):
         """
         Note: This method has been deprecated in favor of
         the ex_associate_address_with_node method.
         """
+
         return self.ex_associate_address_with_node(node=node,
-                                                   elastic_ip_address=
-                                                   elastic_ip_address)
+                                                   elastic_ip=elastic_ip,
+                                                   domain=domain)
 
-    def ex_disassociate_address(self, elastic_ip_address):
+    def ex_disassociate_address(self, elastic_ip, domain=None):
         """
-        Disassociate an Elastic IP address
+        Disassociate an Elastic IP address using the IP (EC2-Classic)
+        or the association ID (VPC)
 
-        :param      elastic_ip_address: Elastic IP address which should be used
-        :type       elastic_ip_address: ``str``
+        :param      elastic_ip: ElasticIP instance
+        :type       elastic_ip: :class:`ElasticIP`
 
-        :return: True on success, False otherwise.
-        :rtype: ``bool``
+        :param      domain: The domain where the IP resides (vpc only)
+        :type       domain: ``str``
+
+        :return:    True on success, False otherwise.
+        :rtype:     ``bool``
         """
         params = {'Action': 'DisassociateAddress'}
 
-        params.update({'PublicIp': elastic_ip_address})
+        if domain is not None and domain != 'vpc':
+            raise AttributeError('Domain can only be set to vpc')
+
+        if domain is None:
+            params['PublicIp'] = elastic_ip.ip
+
+        else:
+            params['AssociationId'] = elastic_ip.extra['association_id']
+
         res = self.connection.request(self.path, params=params).object
         return self._get_boolean(res)
 
@@ -2699,9 +2824,10 @@ class BaseEC2NodeDriver(NodeDriver):
         :param      nodes: List of :class:`Node` instances
         :type       nodes: ``list`` of :class:`Node`
 
-        :return: Dictionary where a key is a node ID and the value is a
-            list with the Elastic IP addresses associated with this node.
-        :rtype: ``dict``
+        :return:    Dictionary where a key is a node ID and the value is a
+                    list with the Elastic IP addresses associated with
+                    this node.
+        :rtype:     ``dict``
         """
         if not nodes:
             return {}
@@ -2711,25 +2837,26 @@ class BaseEC2NodeDriver(NodeDriver):
         if len(nodes) == 1:
             self._add_instance_filter(params, nodes[0])
 
-        result = self.connection.request(self.path,
-                                         params=params.copy()).object
+        result = self.connection.request(self.path, params=params).object
 
         node_instance_ids = [node.id for node in nodes]
         nodes_elastic_ip_mappings = {}
 
+        # We will set only_associated to True so that we only get back
+        # IPs which are associated with instances
+        only_associated = True
+
         for node_id in node_instance_ids:
             nodes_elastic_ip_mappings.setdefault(node_id, [])
-        for element in findall(element=result, xpath='addressesSet/item',
-                               namespace=NAMESPACE):
-            instance_id = findtext(element=element, xpath='instanceId',
-                                   namespace=NAMESPACE)
-            ip_address = findtext(element=element, xpath='publicIp',
-                                  namespace=NAMESPACE)
+            for addr in self._to_addresses(result,
+                                           only_associated):
 
-            if instance_id not in node_instance_ids:
-                continue
+                instance_id = addr.instance_id
+
+                if node_id == instance_id:
+                    nodes_elastic_ip_mappings[instance_id].append(
+                        addr.ip)
 
-            nodes_elastic_ip_mappings[instance_id].append(ip_address)
         return nodes_elastic_ip_mappings
 
     def ex_describe_addresses_for_node(self, node):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/test/compute/fixtures/ec2/allocate_vpc_address.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/allocate_vpc_address.xml b/libcloud/test/compute/fixtures/ec2/allocate_vpc_address.xml
new file mode 100644
index 0000000..9029d67
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/allocate_vpc_address.xml
@@ -0,0 +1,6 @@
+<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+    <requestId>eb920193-37a9-401a-8fff-089783b2c153</requestId>
+    <publicIp>192.0.2.2</publicIp>
+    <domain>vpc</domain>
+    <allocationId>eipalloc-666d7f04</allocationId>
+</AllocateAddressResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/test/compute/fixtures/ec2/associate_vpc_address.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/associate_vpc_address.xml b/libcloud/test/compute/fixtures/ec2/associate_vpc_address.xml
new file mode 100644
index 0000000..46433e2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/associate_vpc_address.xml
@@ -0,0 +1,5 @@
+<AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+    <requestId>1b3bf0d9-6c51-4443-a218-f25ecdc98c2a</requestId>
+    <return>true</return>
+    <associationId>eipassoc-167a8073</associationId>
+</AssociateAddressResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/test/compute/fixtures/ec2/describe_addresses.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_addresses.xml b/libcloud/test/compute/fixtures/ec2/describe_addresses.xml
index 47f40c5..e6147c5 100644
--- a/libcloud/test/compute/fixtures/ec2/describe_addresses.xml
+++ b/libcloud/test/compute/fixtures/ec2/describe_addresses.xml
@@ -3,7 +3,13 @@
   <addressesSet>
      <item>
         <publicIp>1.2.3.4</publicIp>
+        <allocationId>eipalloc-602b5d01</allocationId>
+        <domain>vpc</domain>
         <instanceId>i-4382922a</instanceId>
+        <associationId>eipassoc-cea049ab</associationId>
+        <networkInterfaceId>eni-83e3c5c5</networkInterfaceId>
+        <networkInterfaceOwnerId>123456789098</networkInterfaceOwnerId>
+        <privateIpAddress>192.168.1.5</privateIpAddress>
      </item>
    </addressesSet>
-</DescribeAddressesResponse>
+</DescribeAddressesResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/test/compute/fixtures/ec2/describe_addresses_all.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_addresses_all.xml b/libcloud/test/compute/fixtures/ec2/describe_addresses_all.xml
index 5809f11..2803dce 100644
--- a/libcloud/test/compute/fixtures/ec2/describe_addresses_all.xml
+++ b/libcloud/test/compute/fixtures/ec2/describe_addresses_all.xml
@@ -1,17 +1,35 @@
 <DescribeAddressesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
-  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
-  <addressesSet>
-     <item>
-        <publicIp>1.2.3.4</publicIp>
-        <instanceId>i-4382922a</instanceId>
-     </item>
-     <item>
-        <publicIp>1.2.3.6</publicIp>
-        <instanceId>i-4382922b</instanceId>
-     </item>
-     <item>
-        <publicIp>1.2.3.5</publicIp>
-        <instanceId /> 
-     </item>
-</addressesSet>
-</DescribeAddressesResponse>
+    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+    <addressesSet>
+        <item>
+            <publicIp>1.2.3.4</publicIp>
+            <allocationId>eipalloc-602b5d01</allocationId>
+            <domain>vpc</domain>
+            <instanceId>i-4382922a</instanceId>
+            <associationId>eipassoc-cea049ab</associationId>
+            <networkInterfaceId>eni-83e3c5c5</networkInterfaceId>
+            <networkInterfaceOwnerId>123456789098</networkInterfaceOwnerId>
+            <privateIpAddress>192.168.1.5</privateIpAddress>
+        </item>
+        <item>
+            <publicIp>1.2.3.5</publicIp>
+            <allocationId>eipalloc-998195fb</allocationId>
+            <domain>vpc</domain>
+            <instanceId>i-4382922b</instanceId>
+            <associationId>eipassoc-cea049ac</associationId>
+            <networkInterfaceId>eni-83e3c5c6</networkInterfaceId>
+            <networkInterfaceOwnerId>123456789098</networkInterfaceOwnerId>
+            <privateIpAddress>192.168.1.6</privateIpAddress>
+        </item>
+        <item>
+            <publicIp>1.2.3.6</publicIp>
+            <allocationId>eipalloc-922a5cf3</allocationId>
+            <domain>standard</domain>
+        </item>
+        <item>
+            <publicIp>1.2.3.7</publicIp>
+            <allocationId>eipalloc-992a5cf8</allocationId>
+            <domain>vpc</domain>
+        </item>
+    </addressesSet>
+</DescribeAddressesResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f44c3648/libcloud/test/compute/test_ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py
index e8e4a14..9a16e05 100644
--- a/libcloud/test/compute/test_ec2.py
+++ b/libcloud/test/compute/test_ec2.py
@@ -606,33 +606,76 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         EC2MockHttp.type = 'all_addresses'
         elastic_ips1 = self.driver.ex_describe_all_addresses()
         elastic_ips2 = self.driver.ex_describe_all_addresses(
-            only_allocated=True)
-
-        self.assertEqual(len(elastic_ips1), 3)
-        self.assertTrue('1.2.3.5' in elastic_ips1)
+            only_associated=True)
+        self.assertEqual('1.2.3.7', elastic_ips1[3].ip)
+        self.assertEqual('vpc', elastic_ips1[3].domain)
+        self.assertEqual('eipalloc-992a5cf8', elastic_ips1[3].extra['allocation_id'])
 
         self.assertEqual(len(elastic_ips2), 2)
-        self.assertTrue('1.2.3.5' not in elastic_ips2)
+        self.assertEqual('1.2.3.5', elastic_ips2[1].ip)
+        self.assertEqual('vpc', elastic_ips2[1].domain)
 
     def test_ex_allocate_address(self):
-        ret = self.driver.ex_allocate_address()
-        self.assertTrue(ret)
+        elastic_ip = self.driver.ex_allocate_address()
+        self.assertEqual('192.0.2.1', elastic_ip.ip)
+        self.assertEqual('standard', elastic_ip.domain)
+        EC2MockHttp.type = 'vpc'
+        elastic_ip = self.driver.ex_allocate_address(domain='vpc')
+        self.assertEqual('192.0.2.2', elastic_ip.ip)
+        self.assertEqual('vpc', elastic_ip.domain)
+        self.assertEqual('eipalloc-666d7f04', elastic_ip.extra['allocation_id'])
 
     def test_ex_release_address(self):
-        ret = self.driver.ex_release_address('1.2.3.4')
+        EC2MockHttp.type = 'all_addresses'
+        elastic_ips = self.driver.ex_describe_all_addresses()
+        EC2MockHttp.type = ''
+        ret = self.driver.ex_release_address(elastic_ips[2])
         self.assertTrue(ret)
+        ret = self.driver.ex_release_address(elastic_ips[0], domain='vpc')
+        self.assertTrue(ret)
+        self.assertRaises(AttributeError,
+                          self.driver.ex_release_address,
+                          elastic_ips[0],
+                          domain='bogus')
 
     def test_ex_associate_address_with_node(self):
         node = Node('i-4382922a', None, None, None, None, self.driver)
-
-        ret1 = self.driver.ex_associate_address_with_node(node, '1.2.3.4')
-        ret2 = self.driver.ex_associate_addresses(node, '1.2.3.4')
-        self.assertTrue(ret1)
-        self.assertTrue(ret2)
+        EC2MockHttp.type = 'all_addresses'
+        elastic_ips = self.driver.ex_describe_all_addresses()
+        EC2MockHttp.type = ''
+        ret1 = self.driver.ex_associate_address_with_node(
+            node, elastic_ips[2])
+        ret2 = self.driver.ex_associate_addresses(
+            node, elastic_ips[2])
+        self.assertEqual(None, ret1)
+        self.assertEqual(None, ret2)
+        EC2MockHttp.type = 'vpc'
+        ret3 = self.driver.ex_associate_address_with_node(
+            node, elastic_ips[3], domain='vpc')
+        ret4 = self.driver.ex_associate_addresses(
+            node, elastic_ips[3], domain='vpc')
+        self.assertEqual('eipassoc-167a8073', ret3)
+        self.assertEqual('eipassoc-167a8073', ret4)
+        self.assertRaises(AttributeError,
+                          self.driver.ex_associate_address_with_node,
+                          node,
+                          elastic_ips[1],
+                          domain='bogus')
 
     def test_ex_disassociate_address(self):
-        ret = self.driver.ex_disassociate_address('1.2.3.4')
+        EC2MockHttp.type = 'all_addresses'
+        elastic_ips = self.driver.ex_describe_all_addresses()
+        EC2MockHttp.type = ''
+        ret = self.driver.ex_disassociate_address(elastic_ips[2])
+        self.assertTrue(ret)
+        # Test a VPC disassociation
+        ret = self.driver.ex_disassociate_address(elastic_ips[1],
+                                                  domain='vpc')
         self.assertTrue(ret)
+        self.assertRaises(AttributeError,
+                          self.driver.ex_disassociate_address,
+                          elastic_ips[1],
+                          domain='bogus')
 
     def test_ex_change_node_size_same_size(self):
         size = NodeSize('m1.small', 'Small Instance',
@@ -1115,10 +1158,18 @@ class EC2MockHttp(MockHttpTestCase):
         body = self.fixtures.load('allocate_address.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _vpc_AllocateAddress(self, method, url, body, headers):
+        body = self.fixtures.load('allocate_vpc_address.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _AssociateAddress(self, method, url, body, headers):
         body = self.fixtures.load('associate_address.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _vpc_AssociateAddress(self, method, url, body, headers):
+        body = self.fixtures.load('associate_vpc_address.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _DisassociateAddress(self, method, url, body, headers):
         body = self.fixtures.load('disassociate_address.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])