From f84ba7d6ec2ca32a00b9eaca6152e6185b5b3503 Mon Sep 17 00:00:00 2001 From: Mohit Alonja Date: Fri, 17 Sep 2021 02:37:18 +0530 Subject: [PATCH] Fix NetworkInterface PrivateIP support and EIP association (#4288) --- moto/ec2/models.py | 61 ++++-- .../responses/elastic_network_interfaces.py | 180 +++++++++--------- moto/ec2/utils.py | 8 + tests/terraform-tests.failures.txt | 1 + tests/terraform-tests.success.txt | 1 + tests/test_ec2/test_subnets.py | 8 +- 6 files changed, 156 insertions(+), 103 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index d754fd4bf..4a9ba1460 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -131,6 +131,7 @@ from .utils import ( random_internet_gateway_id, random_egress_only_internet_gateway_id, random_ip, + random_mac_address, random_ipv6_cidr, random_transit_gateway_attachment_id, random_transit_gateway_route_table_id, @@ -277,15 +278,16 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel): private_ip_address, private_ip_addresses=None, device_index=0, - public_ip_auto_assign=True, + public_ip_auto_assign=False, group_ids=None, description=None, + tags=None, ): self.ec2_backend = ec2_backend self.id = random_eni_id() self.device_index = device_index - self.private_ip_address = private_ip_address or random_private_ip() - self.private_ip_addresses = private_ip_addresses + self.private_ip_address = private_ip_address or None + self.private_ip_addresses = private_ip_addresses or [] self.subnet = subnet self.instance = None self.attachment_id = None @@ -295,13 +297,35 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel): self.public_ip = None self.public_ip_auto_assign = public_ip_auto_assign self.start() - + self.add_tags(tags or {}) + self.status = "available" self.attachments = [] - + self.mac_address = random_mac_address() + self.interface_type = "interface" # Local set to the ENI. When attached to an instance, @property group_set # returns groups for both self and the attached instance. self._group_set = [] + if not self.private_ip_address: + if self.private_ip_addresses: + for ip in self.private_ip_addresses: + if isinstance(ip, list) and ip.get("Primary", False) in [ + "true", + True, + "True", + ]: + self.private_ip_address = ip.get("PrivateIpAddress") + if isinstance(ip, str): + self.private_ip_address = self.private_ip_addresses[0] + break + else: + self.private_ip_address = random_private_ip() + + if not self.private_ip_addresses and self.private_ip_address: + self.private_ip_addresses.append( + {"Primary": True, "PrivateIpAddress": self.private_ip_address} + ) + group = None if group_ids: for group_id in group_ids: @@ -319,6 +343,21 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel): if group: self._group_set.append(group) + @property + def owner_id(self): + return ACCOUNT_ID + + @property + def association(self): + association = {} + if self.public_ip: + eips = self.ec2_backend.address_by_ip([self.public_ip]) + eip = eips[0] if len(eips) > 0 else None + if eip: + association["allocationId"] = eip.allocation_id or None + association["associationId"] = eip.association_id or None + return association + @staticmethod def cloudformation_name_type(): return None @@ -430,10 +469,9 @@ class NetworkInterfaceBackend(object): private_ip_addresses, group_ids=group_ids, description=description, + tags=tags, **kwargs ) - if tags: - eni.add_tags(tags) self.enis[eni.id] = eni return eni @@ -4031,9 +4069,7 @@ class Subnet(TaggedEC2Resource, CloudFormationModel): for eni in self.ec2_backend.get_all_network_interfaces() if eni.subnet.id == self.id ] - addresses_taken = [ - eni.private_ip_address for eni in enis if eni.private_ip_address - ] + addresses_taken = [] for eni in enis: if eni.private_ip_addresses: addresses_taken.extend(eni.private_ip_addresses) @@ -7704,8 +7740,9 @@ class NatGateway(CloudFormationModel, TaggedEC2Resource): @property def public_ip(self): - eips = self._backend.address_by_allocation([self.allocation_id]) - return eips[0].public_ip + if self.allocation_id: + eips = self._backend.address_by_allocation([self.allocation_id]) + return eips[0].public_ip if self.allocation_id else None @staticmethod def cloudformation_name_type(): diff --git a/moto/ec2/responses/elastic_network_interfaces.py b/moto/ec2/responses/elastic_network_interfaces.py index f4dce86c7..409951f98 100644 --- a/moto/ec2/responses/elastic_network_interfaces.py +++ b/moto/ec2/responses/elastic_network_interfaces.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import filters_from_querystring +from moto.ec2.utils import filters_from_querystring, add_tag_specification class ElasticNetworkInterfaces(BaseResponse): @@ -11,9 +11,9 @@ class ElasticNetworkInterfaces(BaseResponse): groups = self._get_multi_param("SecurityGroupId") subnet = self.ec2_backend.get_subnet(subnet_id) description = self._get_param("Description") - tags = self._parse_tag_specification("TagSpecification").get( - "network-interface" - ) + tags = self._get_multi_param("TagSpecification") + tags = add_tag_specification(tags) + if self.is_not_dryrun("CreateNetworkInterface"): eni = self.ec2_backend.create_network_interface( subnet, @@ -84,19 +84,20 @@ CREATE_NETWORK_INTERFACE_RESPONSE = """ 2c6021ec-d705-445a-9780-420d0c7ab793 + + {{ eni.id }} {{ eni.subnet.id }} {{ eni.subnet.vpc_id }} - us-west-2a + {{ eni.subnet.availability_zone }} {% if eni.description %} {{ eni.description }} - {% else %} - {% endif %} - 498654062920 - false - pending - 02:07:a9:b6:12:51 + {{ eni.owner_id }} + AIDARCSPW2WNREUEN7XFM + False + {{ eni.status }} + {{ eni.mac_address }} {% if eni.private_ip_address %} {{ eni.private_ip_address }} {% endif %} @@ -109,17 +110,36 @@ CREATE_NETWORK_INTERFACE_RESPONSE = """ {% endfor %} - - {% if eni.private_ip_address %} - - - {{ eni.private_ip_address }} - true - - - {% else %} - + {% if eni.association %} + + {{ eni.public_ip }} + {{ eni.owner_id }} + {{ eni.association.allocationId }} + {{ eni.association.associationId }} + true + {% endif %} + + {% for tag in eni.get_tags() %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + + {% for address in eni.private_ip_addresses %} + + {{ address.PrivateIpAddress }} + {% if address.privateDnsName %} + {{ address.privateDnsName }} + {% endif %} + {{ address.Primary }} + + {% endfor %} + + + {{ eni.interface_type }} """ @@ -129,74 +149,60 @@ DESCRIBE_NETWORK_INTERFACES_RESPONSE = """ {% for eni in enis %} - {{ eni.id }} - {{ eni.subnet.id }} - {{ eni.subnet.vpc_id }} - us-west-2a - {{ eni.description or "" }} - 190610284047 - false - {% if eni.attachment_id %} - in-use - {% else %} - available - {% endif %} - 0e:a3:a7:7b:95:a7 - {% if eni.private_ip_address %} - {{ eni.private_ip_address }} - {% endif %} - ip-10-0-0-134.us-west-2.compute.internal - {{ eni.source_dest_check }} - - {% for group in eni.group_set %} - - {{ group.id }} - {{ group.name }} + {{ eni.id }} + {{ eni.subnet.id }} + {{ eni.subnet.vpc_id }} + {{ eni.subnet.availability_zone }} + {% if eni.description %} + {{ eni.description }} + {% endif %} + {{ eni.owner_id }} + AIDARCSPW2WNREUEN7XFM + False + {{ eni.status }} + {{ eni.mac_address }} + {% if eni.private_ip_address %} + {{ eni.private_ip_address }} + {% endif %} + {{ eni.source_dest_check }} + + {% for group in eni.group_set %} + + {{ group.id }} + {{ group.name }} + + {% endfor %} + + {% if eni.association %} + + {{ eni.public_ip }} + {{ eni.owner_id }} + {{ eni.association.allocationId }} + {{ eni.association.associationId }} + true + + {% endif %} + + {% for tag in eni.get_tags() %} + + {{ tag.key }} + {{ tag.value }} {% endfor %} - - - {% for tag in eni.get_tags() %} - - {{ tag.key }} - {{ tag.value }} - - {% endfor %} - - {% if eni.instance %} - - {{ eni.attachment_id }} - {{ eni.instance.id }} - 190610284047 - {{ eni.device_index }} - attached - 2013-10-04T17:38:53.000Z - true - - {% endif %} - - {{ eni.public_ip }} - ec2-54-200-86-47.us-west-2.compute.amazonaws.com - amazon - - {% if eni.private_ip_address %} - - - {{ eni.private_ip_address }} - ip-10-0-0-134.us-west-2.compute.internal - true - {% if eni.public_ip %} - - {{ eni.public_ip }} - ec2-54-200-86-47.us-west-2.compute.amazonaws.com - amazon - - {% endif %} - - - {% else %} - - {% endif %} + + + {% for address in eni.private_ip_addresses %} + + {{ address.PrivateIpAddress }} + {% if address.privateDnsName %} + {{ address.privateDnsName }} + {% endif %} + {{ address.Primary }} + + {% endfor %} + + + {{ eni.interface_type }} {% endfor %} diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index d230af37b..8ceeb1e16 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -241,6 +241,14 @@ def random_ip(): ) +def random_mac_address(): + return "02:00:00:%02x:%02x:%02x" % ( + random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255), + ) + + def randor_ipv4_cidr(): return "10.0.{}.{}/16".format(random.randint(0, 255), random.randint(0, 255)) diff --git a/tests/terraform-tests.failures.txt b/tests/terraform-tests.failures.txt index b3d78799f..5f8cb73a3 100644 --- a/tests/terraform-tests.failures.txt +++ b/tests/terraform-tests.failures.txt @@ -7,3 +7,4 @@ TestAccAWSIAMRolePolicy TestAccAWSSecurityGroup_forceRevokeRules_ TestAccAWSSSMDocument_package TestAccAWSDefaultSecurityGroup_Classic_ +TestAccDataSourceAwsNetworkInterface_CarrierIPAssociation diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index 96a9c59f6..83394e12e 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -124,4 +124,5 @@ TestAccAWSSecurityGroupRule_ TestAccAWSVpnGateway TestAccAWSVpnGatewayAttachment TestAccAWSEc2CarrierGateway +TestAccDataSourceAwsNetworkInterface_ TestAccAWSNatGateway diff --git a/tests/test_ec2/test_subnets.py b/tests/test_ec2/test_subnets.py index 3f439a5c9..c1bcefa1f 100644 --- a/tests/test_ec2/test_subnets.py +++ b/tests/test_ec2/test_subnets.py @@ -618,14 +618,14 @@ def validate_subnet_details_after_creating_eni( ], )["NetworkInterface"] enis_created.append(eni) - ip_addresses_assigned = ip_addresses_assigned + nr_of_ip_addresses + 1 # + ip_addresses_assigned = ip_addresses_assigned + nr_of_ip_addresses # + # Verify that the nr of available IP addresses takes these ENIs into account updated_subnet = client.describe_subnets(SubnetIds=[subnet["SubnetId"]])["Subnets"][ 0 ] - private_addresses = [ - eni["PrivateIpAddress"] for eni in enis_created if eni["PrivateIpAddress"] - ] + + private_addresses = [] for eni in enis_created: private_addresses.extend( [address["PrivateIpAddress"] for address in eni["PrivateIpAddresses"]]