Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Common
Compute
~~~~~~~

- [EC2] Allow Elastic IP association with a specific network interface, so
instances with multiple interfaces can select the required target. Existing
calls continue to associate by instance ID.
(GITHUB-1799)
[Ishu Patel - @ishu7w]

- [OpenStack] Select service catalog endpoints by service type without filtering
by the default service name. The service name is only used as a filter when
explicitly provided via ``ex_force_service_name``.
Expand Down
24 changes: 20 additions & 4 deletions libcloud/compute/drivers/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3290,7 +3290,7 @@ def ex_describe_all_addresses(self, only_associated=False):
# 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, domain=None):
def ex_associate_address_with_node(self, node, elastic_ip, domain=None, network_interface=None):
"""
Associate an Elastic IP address with a particular node.

Expand All @@ -3303,12 +3303,26 @@ def ex_associate_address_with_node(self, node, elastic_ip, domain=None):
:param domain: The domain where the IP resides (vpc only)
:type domain: ``str``

:param network_interface: Optional interface to associate the IP
with instead of the node. Required for nodes with multiple
network interfaces.
:type network_interface: :class:`EC2NetworkInterface`

For a VPC address on an instance with multiple interfaces, pass
``domain="vpc"`` and the desired interface from
:meth:`ex_list_network_interfaces`. The request uses its interface ID
instead of the instance ID, as required by EC2.

: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", "InstanceId": node.id}
params = {"Action": "AssociateAddress"}
if network_interface is not None:
params["NetworkInterfaceId"] = network_interface.id
else:
params["InstanceId"] = node.id

if domain is not None and domain != "vpc":
raise AttributeError("Domain can only be set to vpc")
Expand All @@ -3322,13 +3336,15 @@ def ex_associate_address_with_node(self, node, elastic_ip, domain=None):
association_id = findtext(element=response, xpath="associationId", namespace=NAMESPACE)
return association_id

def ex_associate_addresses(self, node, elastic_ip, domain=None):
def ex_associate_addresses(self, node, elastic_ip, domain=None, network_interface=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=elastic_ip, domain=domain)
return self.ex_associate_address_with_node(
node=node, elastic_ip=elastic_ip, domain=domain, network_interface=network_interface
)

def ex_disassociate_address(self, elastic_ip, domain=None):
"""
Expand Down
28 changes: 28 additions & 0 deletions libcloud/test/compute/test_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
EC2NetworkSubnet,
NimbusNodeDriver,
EC2PlacementGroup,
EC2NetworkInterface,
IdempotentParamError,
ExEC2AvailabilityZone,
OutscaleSASNodeDriver,
Expand Down Expand Up @@ -886,6 +887,21 @@ def test_ex_associate_address_with_node(self):
domain="bogus",
)

def test_ex_associate_address_with_network_interface(self):
node = Node("i-4382922a", None, None, None, None, self.driver)
interface = EC2NetworkInterface("eni-12345678", "secondary", "attached")
EC2MockHttp.type = "all_addresses"
elastic_ip = self.driver.ex_describe_all_addresses()[3]
EC2MockHttp.type = "interface"
association_id = self.driver.ex_associate_address_with_node(
node, elastic_ip, domain="vpc", network_interface=interface
)
self.assertEqual("eipassoc-167a8073", association_id)
association_id = self.driver.ex_associate_addresses(
node, elastic_ip, domain="vpc", network_interface=interface
)
self.assertEqual("eipassoc-167a8073", association_id)

def test_ex_disassociate_address(self):
EC2MockHttp.type = "all_addresses"
elastic_ips = self.driver.ex_describe_all_addresses()
Expand Down Expand Up @@ -1681,6 +1697,18 @@ def _AssociateAddress(self, method, url, body, headers):
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _vpc_AssociateAddress(self, method, url, body, headers):
params = parse_qs(url.split("?", 1)[1])
self.assertEqual(["i-4382922a"], params["InstanceId"])
self.assertNotIn("NetworkInterfaceId", params)
body = self.fixtures.load("associate_vpc_address.xml")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _interface_AssociateAddress(self, method, url, body, headers):
params = parse_qs(url.split("?", 1)[1])
self.assertEqual(["eni-12345678"], params["NetworkInterfaceId"])
self.assertNotIn("InstanceId", params)
self.assertIn("AllocationId", params)
self.assertNotIn("PublicIp", params)
body = self.fixtures.load("associate_vpc_address.xml")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

Expand Down