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.subnet = self.ec2_backend.get_subnet(subnet)
|
||||||
self.instance = None
|
self.instance = None
|
||||||
self.attachment_id = None
|
self.attachment_id = None
|
||||||
|
self.attach_time = None
|
||||||
|
self.delete_on_termination = False
|
||||||
self.description = description
|
self.description = description
|
||||||
self.source_dest_check = True
|
self.source_dest_check = True
|
||||||
|
|
||||||
@ -274,7 +276,6 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self.start()
|
self.start()
|
||||||
self.add_tags(tags or {})
|
self.add_tags(tags or {})
|
||||||
self.status = "available"
|
self.status = "available"
|
||||||
self.attachments = []
|
|
||||||
self.mac_address = random_mac_address()
|
self.mac_address = random_mac_address()
|
||||||
self.interface_type = "interface"
|
self.interface_type = "interface"
|
||||||
# Local set to the ENI. When attached to an instance, @property group_set
|
# Local set to the ENI. When attached to an instance, @property group_set
|
||||||
@ -640,6 +641,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.id = random_instance_id()
|
self.id = random_instance_id()
|
||||||
|
self.owner_id = OWNER_ID
|
||||||
self.lifecycle = kwargs.get("lifecycle")
|
self.lifecycle = kwargs.get("lifecycle")
|
||||||
|
|
||||||
nics = kwargs.get("nics", {})
|
nics = kwargs.get("nics", {})
|
||||||
@ -1047,6 +1049,8 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
|||||||
# This is used upon associate/disassociate public IP.
|
# This is used upon associate/disassociate public IP.
|
||||||
eni.instance = self
|
eni.instance = self
|
||||||
eni.attachment_id = random_eni_attach_id()
|
eni.attachment_id = random_eni_attach_id()
|
||||||
|
eni.attach_time = utc_date_and_time()
|
||||||
|
eni.status = "in-use"
|
||||||
eni.device_index = device_index
|
eni.device_index = device_index
|
||||||
|
|
||||||
return eni.attachment_id
|
return eni.attachment_id
|
||||||
@ -1055,6 +1059,8 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
|||||||
self.nics.pop(eni.device_index, None)
|
self.nics.pop(eni.device_index, None)
|
||||||
eni.instance = None
|
eni.instance = None
|
||||||
eni.attachment_id = None
|
eni.attachment_id = None
|
||||||
|
eni.attach_time = None
|
||||||
|
eni.status = "available"
|
||||||
eni.device_index = None
|
eni.device_index = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from moto.ec2.exceptions import InvalidParameterValueErrorUnknownAttribute
|
||||||
from moto.ec2.utils import get_attribute_value, add_tag_specification
|
from moto.ec2.utils import get_attribute_value, add_tag_specification
|
||||||
from ._base_response import EC2BaseResponse
|
from ._base_response import EC2BaseResponse
|
||||||
|
|
||||||
@ -39,16 +40,38 @@ class ElasticNetworkInterfaces(EC2BaseResponse):
|
|||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_network_interface_attribute(self):
|
def describe_network_interface_attribute(self):
|
||||||
raise NotImplementedError(
|
eni_id = self._get_param("NetworkInterfaceId")
|
||||||
"ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented"
|
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):
|
def describe_network_interfaces(self):
|
||||||
eni_ids = self._get_multi_param("NetworkInterfaceId")
|
eni_ids = self._get_multi_param("NetworkInterfaceId")
|
||||||
filters = self._filters_from_querystring()
|
filters = self._filters_from_querystring()
|
||||||
enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
|
if self.is_not_dryrun("DescribeNetworkInterfaces"):
|
||||||
template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
|
||||||
return template.render(enis=enis)
|
template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
||||||
|
return template.render(enis=enis)
|
||||||
|
|
||||||
def attach_network_interface(self):
|
def attach_network_interface(self):
|
||||||
eni_id = self._get_param("NetworkInterfaceId")
|
eni_id = self._get_param("NetworkInterfaceId")
|
||||||
@ -277,6 +300,18 @@ DESCRIBE_NETWORK_INTERFACES_RESPONSE = """<DescribeNetworkInterfacesResponse xml
|
|||||||
<natEnabled>true</natEnabled>
|
<natEnabled>true</natEnabled>
|
||||||
</association>
|
</association>
|
||||||
{% endif %}
|
{% 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>
|
<tagSet>
|
||||||
{% for tag in eni.get_tags() %}
|
{% for tag in eni.get_tags() %}
|
||||||
<item>
|
<item>
|
||||||
@ -329,3 +364,49 @@ DELETE_NETWORK_INTERFACE_RESPONSE = """
|
|||||||
<requestId>34b5b3b4-d0c5-49b9-b5e2-a468ef6adcd8</requestId>
|
<requestId>34b5b3b4-d0c5-49b9-b5e2-a468ef6adcd8</requestId>
|
||||||
<return>true</return>
|
<return>true</return>
|
||||||
</DeleteNetworkInterfaceResponse>"""
|
</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"]
|
all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
|
||||||
[eni["NetworkInterfaceId"] for eni in all_enis].should.contain(my_eni.id)
|
[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_description = [
|
||||||
my_eni["Groups"].should.have.length_of(2)
|
eni for eni in all_enis if eni["NetworkInterfaceId"] == my_eni.id
|
||||||
set([group["GroupId"] for group in my_eni["Groups"]]).should.equal(
|
][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])
|
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").length_of(2)
|
||||||
my_eni.should.have.key("Ipv6Addresses").should.contain({"Ipv6Address": ipv6_orig})
|
my_eni.should.have.key("Ipv6Addresses").should.contain({"Ipv6Address": ipv6_orig})
|
||||||
my_eni.should.have.key("Ipv6Addresses").should.contain({"Ipv6Address": ipv6_3})
|
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