EC2 - implement network attachments (#4998)
This commit is contained in:
parent
3718cde444
commit
b5f03b0a0e
@ -266,6 +266,8 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
||||
self.subnet = self.ec2_backend.get_subnet(subnet)
|
||||
self.instance = None
|
||||
self.attachment_id = None
|
||||
self.attach_time = None
|
||||
self.delete_on_termination = False
|
||||
self.description = description
|
||||
self.source_dest_check = True
|
||||
|
||||
@ -274,7 +276,6 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
||||
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
|
||||
@ -640,6 +641,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
||||
super().__init__()
|
||||
self.ec2_backend = ec2_backend
|
||||
self.id = random_instance_id()
|
||||
self.owner_id = OWNER_ID
|
||||
self.lifecycle = kwargs.get("lifecycle")
|
||||
|
||||
nics = kwargs.get("nics", {})
|
||||
@ -1047,6 +1049,8 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
||||
# This is used upon associate/disassociate public IP.
|
||||
eni.instance = self
|
||||
eni.attachment_id = random_eni_attach_id()
|
||||
eni.attach_time = utc_date_and_time()
|
||||
eni.status = "in-use"
|
||||
eni.device_index = device_index
|
||||
|
||||
return eni.attachment_id
|
||||
@ -1055,6 +1059,8 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
||||
self.nics.pop(eni.device_index, None)
|
||||
eni.instance = None
|
||||
eni.attachment_id = None
|
||||
eni.attach_time = None
|
||||
eni.status = "available"
|
||||
eni.device_index = None
|
||||
|
||||
@classmethod
|
||||
|
@ -1,3 +1,4 @@
|
||||
from moto.ec2.exceptions import InvalidParameterValueErrorUnknownAttribute
|
||||
from moto.ec2.utils import get_attribute_value, add_tag_specification
|
||||
from ._base_response import EC2BaseResponse
|
||||
|
||||
@ -39,16 +40,38 @@ class ElasticNetworkInterfaces(EC2BaseResponse):
|
||||
return template.render()
|
||||
|
||||
def describe_network_interface_attribute(self):
|
||||
raise NotImplementedError(
|
||||
"ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented"
|
||||
)
|
||||
eni_id = self._get_param("NetworkInterfaceId")
|
||||
attribute = self._get_param("Attribute")
|
||||
if self.is_not_dryrun("DescribeNetworkInterfaceAttribute"):
|
||||
eni = self.ec2_backend.get_all_network_interfaces([eni_id])[0]
|
||||
|
||||
if attribute == "description":
|
||||
template = self.response_template(
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_DESCRIPTION
|
||||
)
|
||||
elif attribute == "groupSet":
|
||||
template = self.response_template(
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_GROUPSET
|
||||
)
|
||||
elif attribute == "sourceDestCheck":
|
||||
template = self.response_template(
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_SOURCEDESTCHECK
|
||||
)
|
||||
elif attribute == "attachment":
|
||||
template = self.response_template(
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_ATTACHMENT
|
||||
)
|
||||
else:
|
||||
raise InvalidParameterValueErrorUnknownAttribute(attribute)
|
||||
return template.render(eni=eni)
|
||||
|
||||
def describe_network_interfaces(self):
|
||||
eni_ids = self._get_multi_param("NetworkInterfaceId")
|
||||
filters = self._filters_from_querystring()
|
||||
enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
|
||||
template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
||||
return template.render(enis=enis)
|
||||
if self.is_not_dryrun("DescribeNetworkInterfaces"):
|
||||
enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
|
||||
template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
||||
return template.render(enis=enis)
|
||||
|
||||
def attach_network_interface(self):
|
||||
eni_id = self._get_param("NetworkInterfaceId")
|
||||
@ -277,6 +300,18 @@ DESCRIBE_NETWORK_INTERFACES_RESPONSE = """<DescribeNetworkInterfacesResponse xml
|
||||
<natEnabled>true</natEnabled>
|
||||
</association>
|
||||
{% endif %}
|
||||
{% if eni.attachment_id %}
|
||||
<attachment>
|
||||
<attachTime>{{ eni.attach_time }}</attachTime>
|
||||
<attachmentId>{{ eni.attachment_id }}</attachmentId>
|
||||
<deleteOnTermination>{{ eni.delete_on_termination }}</deleteOnTermination>
|
||||
<deviceIndex>{{ eni.device_index }}</deviceIndex>
|
||||
<networkCardIndex>0</networkCardIndex>
|
||||
<instanceId>{{ eni.instance.id }}</instanceId>
|
||||
<instanceOwnerId>{{ eni.instance.owner_id }}</instanceOwnerId>
|
||||
<status>attached</status>
|
||||
</attachment>
|
||||
{% endif %}
|
||||
<tagSet>
|
||||
{% for tag in eni.get_tags() %}
|
||||
<item>
|
||||
@ -329,3 +364,49 @@ DELETE_NETWORK_INTERFACE_RESPONSE = """
|
||||
<requestId>34b5b3b4-d0c5-49b9-b5e2-a468ef6adcd8</requestId>
|
||||
<return>true</return>
|
||||
</DeleteNetworkInterfaceResponse>"""
|
||||
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_DESCRIPTION = """
|
||||
<DescribeNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
|
||||
<description>
|
||||
<value>{{ eni.description }}</value>
|
||||
</description>
|
||||
</DescribeNetworkInterfaceAttributeResponse>"""
|
||||
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_GROUPSET = """
|
||||
<DescribeNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
|
||||
<groupSet>
|
||||
{% for group in eni.group_set %}
|
||||
<item>
|
||||
<groupId>{{ group.id }}</groupId>
|
||||
<groupName>{{ group.name }}</groupName>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</groupSet>
|
||||
</DescribeNetworkInterfaceAttributeResponse>"""
|
||||
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_SOURCEDESTCHECK = """
|
||||
<DescribeNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
|
||||
<sourceDestCheck>
|
||||
<value>{{ "true" if eni.source_dest_check == True else "false" }}</value>
|
||||
</sourceDestCheck>
|
||||
</DescribeNetworkInterfaceAttributeResponse>"""
|
||||
|
||||
DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_ATTACHMENT = """
|
||||
<DescribeNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
|
||||
{% if eni.attachment_id %}
|
||||
<attachment>
|
||||
<attachTime>{{ eni.attach_time }}</attachTime>
|
||||
<attachmentId>{{ eni.attachment_id }}</attachmentId>
|
||||
<deleteOnTermination>{{ eni.delete_on_termination }}</deleteOnTermination>
|
||||
<deviceIndex>{{ eni.device_index }}</deviceIndex>
|
||||
<networkCardIndex>0</networkCardIndex>
|
||||
<instanceId>{{ eni.instance.id }}</instanceId>
|
||||
<instanceOwnerId>{{ eni.instance.owner_id }}</instanceOwnerId>
|
||||
<status>attached</status>
|
||||
</attachment>
|
||||
{% endif %}
|
||||
</DescribeNetworkInterfaceAttributeResponse>"""
|
||||
|
@ -120,9 +120,20 @@ def test_elastic_network_interfaces_with_groups_boto3():
|
||||
all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
|
||||
[eni["NetworkInterfaceId"] for eni in all_enis].should.contain(my_eni.id)
|
||||
|
||||
my_eni = [eni for eni in all_enis if eni["NetworkInterfaceId"] == my_eni.id][0]
|
||||
my_eni["Groups"].should.have.length_of(2)
|
||||
set([group["GroupId"] for group in my_eni["Groups"]]).should.equal(
|
||||
my_eni_description = [
|
||||
eni for eni in all_enis if eni["NetworkInterfaceId"] == my_eni.id
|
||||
][0]
|
||||
my_eni_description["Groups"].should.have.length_of(2)
|
||||
set([group["GroupId"] for group in my_eni_description["Groups"]]).should.equal(
|
||||
set([sec_group1.id, sec_group2.id])
|
||||
)
|
||||
|
||||
eni_groups_attribute = client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=my_eni.id, Attribute="groupSet"
|
||||
).get("Groups")
|
||||
|
||||
eni_groups_attribute.should.have.length_of(2)
|
||||
set([group["GroupId"] for group in eni_groups_attribute]).should.equal(
|
||||
set([sec_group1.id, sec_group2.id])
|
||||
)
|
||||
|
||||
@ -970,3 +981,56 @@ def test_unassign_ipv6_addresses():
|
||||
my_eni.should.have.key("Ipv6Addresses").length_of(2)
|
||||
my_eni.should.have.key("Ipv6Addresses").should.contain({"Ipv6Address": ipv6_orig})
|
||||
my_eni.should.have.key("Ipv6Addresses").should.contain({"Ipv6Address": ipv6_3})
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_elastic_network_interfaces_describe_attachment():
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
client = boto3.client("ec2", "us-east-1")
|
||||
|
||||
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
|
||||
subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18")
|
||||
eni_id = subnet.create_network_interface(Description="A network interface").id
|
||||
instance_id = client.run_instances(ImageId="ami-12c6146b", MinCount=1, MaxCount=1)[
|
||||
"Instances"
|
||||
][0]["InstanceId"]
|
||||
|
||||
client.attach_network_interface(
|
||||
NetworkInterfaceId=eni_id, InstanceId=instance_id, DeviceIndex=1
|
||||
)
|
||||
|
||||
my_eni_attachment = client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=eni_id, Attribute="attachment"
|
||||
).get("Attachment")
|
||||
my_eni_attachment["InstanceId"].should.equal(instance_id)
|
||||
my_eni_attachment["DeleteOnTermination"].should.be.false
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=eni_id, Attribute="attach"
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Value (attach) for parameter attribute is invalid. Unknown attribute."
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=eni_id, Attribute="attachment", DryRun=True
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"An error occurred (DryRunOperation) when calling the DescribeNetworkInterfaceAttribute operation: Request would have succeeded, but DryRun flag is set"
|
||||
)
|
||||
|
||||
my_eni_description = client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=eni_id, Attribute="description"
|
||||
).get("Description")
|
||||
my_eni_description["Value"].should.be.equal("A network interface")
|
||||
|
||||
my_eni_source_dest_check = client.describe_network_interface_attribute(
|
||||
NetworkInterfaceId=eni_id, Attribute="sourceDestCheck"
|
||||
).get("SourceDestCheck")
|
||||
my_eni_source_dest_check["Value"].should.be.equal(True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user