Repository: getmoto/moto
Pull request #7697 (2024-05-16 08:31:10)

EC2: Eni detach error
Resolves https://github.com/getmoto/moto/issues/7678

rafcio19: @bblommers I skipped the new test in server mode as it was failing and I couldn't repro locally.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7697?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.33%. Comparing base [(`fefe530`)](https://app.codecov.io/gh/getmoto/moto/commit/fefe5309aa739ac8abe72d9a66830b0c377ef260?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`b1511d4`)](https://app.codecov.io/gh/getmoto/moto/pull/7697?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #7697   +/-   ##
=======================================
  Coverage   94.33%   94.33%           
=======================================
  Files        1087     1087           
  Lines       92757    92765    +8     
=======================================
+ Hits        87500    87508    +8     
  Misses       5257     5257           
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7697/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7697/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.45% <54.54%> (+<0.01%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7697/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.30% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7697?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py
index 831e615bc..06e923974 100644
--- a/moto/ec2/exceptions.py
+++ b/moto/ec2/exceptions.py
@@ -580,7 +580,7 @@ class UnsupportedTenancy(EC2ClientError):
         )
 
 
-class OperationNotPermitted(EC2ClientError):
+class VPCCidrBlockAssociationError(EC2ClientError):
     def __init__(self, association_id: str):
         super().__init__(
             "OperationNotPermitted",
@@ -588,6 +588,18 @@ class OperationNotPermitted(EC2ClientError):
         )
 
 
+class OperationNotPermitted(EC2ClientError):
+    def __init__(self, message: str):
+        super().__init__("OperationNotPermitted", message)
+
+
+class LastEniDetachError(OperationNotPermitted):
+    def __init__(self) -> None:
+        super().__init__(
+            "The network interface at device index 0 and networkCard index 0 cannot be detached."
+        )
+
+
 class InvalidAvailabilityZoneError(EC2ClientError):
     def __init__(
         self, availability_zone_value: Optional[str], valid_availability_zones: str
diff --git a/moto/ec2/models/elastic_network_interfaces.py b/moto/ec2/models/elastic_network_interfaces.py
index c770c580c..a3a733644 100644
--- a/moto/ec2/models/elastic_network_interfaces.py
+++ b/moto/ec2/models/elastic_network_interfaces.py
@@ -2,7 +2,11 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
 
 from moto.core.common_models import CloudFormationModel
 
-from ..exceptions import InvalidNetworkAttachmentIdError, InvalidNetworkInterfaceIdError
+from ..exceptions import (
+    InvalidNetworkAttachmentIdError,
+    InvalidNetworkInterfaceIdError,
+    LastEniDetachError,
+)
 from .core import TaggedEC2Resource
 from .security_groups import SecurityGroup
 
@@ -331,6 +335,8 @@ class NetworkInterfaceBackend:
     def detach_network_interface(self, attachment_id: str) -> None:
         for eni in self.enis.values():
             if eni.attachment_id == attachment_id:
+                if eni.instance and len(eni.instance.nics) == 1:
+                    raise LastEniDetachError
                 eni.instance.detach_eni(eni)  # type: ignore
                 return
         raise InvalidNetworkAttachmentIdError(attachment_id)
diff --git a/moto/ec2/models/vpcs.py b/moto/ec2/models/vpcs.py
index 614c9ea90..47dddd790 100644
--- a/moto/ec2/models/vpcs.py
+++ b/moto/ec2/models/vpcs.py
@@ -22,8 +22,8 @@ from ..exceptions import (
     InvalidVpcEndPointIdError,
     InvalidVPCIdError,
     InvalidVPCRangeError,
-    OperationNotPermitted,
     UnsupportedTenancy,
+    VPCCidrBlockAssociationError,
 )
 from ..utils import (
     create_dns_entries,
@@ -675,7 +675,7 @@ class VPC(TaggedEC2Resource, CloudFormationModel):
         if self.cidr_block == self.cidr_block_association_set.get(
             association_id, {}
         ).get("cidr_block"):
-            raise OperationNotPermitted(association_id)
+            raise VPCCidrBlockAssociationError(association_id)
 
         entry = response = self.cidr_block_association_set.get(association_id, {})
         if entry:


Test patch:
diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py
index 3e387189d..2bd53e327 100644
--- a/tests/test_ec2/test_elastic_network_interfaces.py
+++ b/tests/test_ec2/test_elastic_network_interfaces.py
@@ -13,14 +13,10 @@ from tests import EXAMPLE_AMI_ID
 
 @mock_aws
 def test_elastic_network_interfaces():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     with pytest.raises(ClientError) as ex:
-        ec2.create_network_interface(SubnetId=subnet.id, DryRun=True)
+        ec2resource.create_network_interface(SubnetId=subnet.id, DryRun=True)
     assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 412
     assert ex.value.response["Error"]["Code"] == "DryRunOperation"
     assert (
@@ -28,9 +24,9 @@ def test_elastic_network_interfaces():
         == "An error occurred (DryRunOperation) when calling the CreateNetworkInterface operation: Request would have succeeded, but DryRun flag is set"
     )
 
-    eni_id = ec2.create_network_interface(SubnetId=subnet.id).id
+    eni_id = ec2resource.create_network_interface(SubnetId=subnet.id).id
 
-    my_enis = client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
+    my_enis = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
         "NetworkInterfaces"
     ]
     assert len(my_enis) == 1
@@ -40,7 +36,7 @@ def test_elastic_network_interfaces():
     assert eni["PrivateIpAddresses"][0]["PrivateIpAddress"].startswith("10.") is True
 
     with pytest.raises(ClientError) as ex:
-        client.delete_network_interface(NetworkInterfaceId=eni_id, DryRun=True)
+        ec2client.delete_network_interface(NetworkInterfaceId=eni_id, DryRun=True)
     assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 412
     assert ex.value.response["Error"]["Code"] == "DryRunOperation"
     assert (
@@ -48,19 +44,19 @@ def test_elastic_network_interfaces():
         == "An error occurred (DryRunOperation) when calling the DeleteNetworkInterface operation: Request would have succeeded, but DryRun flag is set"
     )
 
-    client.delete_network_interface(NetworkInterfaceId=eni_id)
+    ec2client.delete_network_interface(NetworkInterfaceId=eni_id)
 
-    all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
+    all_enis = ec2client.describe_network_interfaces()["NetworkInterfaces"]
     assert eni_id not in [eni["NetworkInterfaceId"] for eni in all_enis]
 
     with pytest.raises(ClientError) as ex:
-        client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])
+        ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])
     assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
     assert "RequestId" in ex.value.response["ResponseMetadata"]
     assert ex.value.response["Error"]["Code"] == "InvalidNetworkInterfaceID.NotFound"
 
     with pytest.raises(ClientError) as ex:
-        client.delete_network_interface(NetworkInterfaceId=eni_id)
+        ec2client.delete_network_interface(NetworkInterfaceId=eni_id)
     assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
     assert "RequestId" in ex.value.response["ResponseMetadata"]
     assert ex.value.response["Error"]["Code"] == "InvalidNetworkInterfaceID.NotFound"
@@ -68,10 +64,10 @@ def test_elastic_network_interfaces():
 
 @mock_aws
 def test_elastic_network_interfaces_subnet_validation():
-    client = boto3.client("ec2", "us-east-1")
+    ec2client = boto3.client("ec2", "us-east-1")
 
     with pytest.raises(ClientError) as ex:
-        client.create_network_interface(SubnetId="subnet-abcd1234")
+        ec2client.create_network_interface(SubnetId="subnet-abcd1234")
     assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
     assert "RequestId" in ex.value.response["ResponseMetadata"]
     assert ex.value.response["Error"]["Code"] == "InvalidSubnetID.NotFound"
@@ -79,19 +75,17 @@ def test_elastic_network_interfaces_subnet_validation():
 
 @mock_aws
 def test_elastic_network_interfaces_with_private_ip():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     private_ip = "54.0.0.1"
-    eni = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=private_ip)
+    eni = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=private_ip
+    )
 
-    all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
+    all_enis = ec2client.describe_network_interfaces()["NetworkInterfaces"]
     assert eni.id in [eni["NetworkInterfaceId"] for eni in all_enis]
 
-    my_enis = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])[
+    my_enis = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])[
         "NetworkInterfaces"
     ]
 
@@ -104,17 +98,17 @@ def test_elastic_network_interfaces_with_private_ip():
 
 @mock_aws
 def test_elastic_network_interfaces_with_groups():
-    ec2 = boto3.resource("ec2", region_name="us-east-1")
-    client = boto3.client("ec2", "us-east-1")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18")
-
-    sec_group1 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
-    sec_group2 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
+    sec_group1 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
+    sec_group2 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
     my_eni = subnet.create_network_interface(Groups=[sec_group1.id, sec_group2.id])
 
-    all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
+    all_enis = ec2client.describe_network_interfaces()["NetworkInterfaces"]
     assert my_eni.id in [eni["NetworkInterfaceId"] for eni in all_enis]
 
     my_eni_description = [
@@ -126,7 +120,7 @@ def test_elastic_network_interfaces_with_groups():
         sec_group2.id,
     }
 
-    eni_groups_attribute = client.describe_network_interface_attribute(
+    eni_groups_attribute = ec2client.describe_network_interface_attribute(
         NetworkInterfaceId=my_eni.id, Attribute="groupSet"
     ).get("Groups")
 
@@ -140,15 +134,11 @@ def test_elastic_network_interfaces_with_groups():
 @mock_aws
 def test_elastic_network_interfaces_without_group():
     # ENI should use the default SecurityGroup if not provided
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     my_eni = subnet.create_network_interface()
 
-    all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
+    all_enis = ec2client.describe_network_interfaces()["NetworkInterfaces"]
     assert my_eni.id in [eni["NetworkInterfaceId"] for eni in all_enis]
 
     my_eni = [eni for eni in all_enis if eni["NetworkInterfaceId"] == my_eni.id][0]
@@ -158,16 +148,16 @@ def test_elastic_network_interfaces_without_group():
 
 @mock_aws
 def test_elastic_network_interfaces_modify_attribute():
-    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")
-    sec_group1 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
-    sec_group2 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
+    sec_group1 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
+    sec_group2 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
     eni_id = subnet.create_network_interface(Groups=[sec_group1.id]).id
 
-    my_eni = client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
+    my_eni = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
         "NetworkInterfaces"
     ][0]
 
@@ -175,7 +165,7 @@ def test_elastic_network_interfaces_modify_attribute():
     assert my_eni["Groups"][0]["GroupId"] == sec_group1.id
 
     with pytest.raises(ClientError) as ex:
-        client.modify_network_interface_attribute(
+        ec2client.modify_network_interface_attribute(
             NetworkInterfaceId=eni_id, Groups=[sec_group2.id], DryRun=True
         )
     assert ex.value.response["Error"]["Code"] == "DryRunOperation"
@@ -185,11 +175,11 @@ def test_elastic_network_interfaces_modify_attribute():
         == "An error occurred (DryRunOperation) when calling the ModifyNetworkInterfaceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
-    client.modify_network_interface_attribute(
+    ec2client.modify_network_interface_attribute(
         NetworkInterfaceId=eni_id, Groups=[sec_group2.id]
     )
 
-    my_eni = client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
+    my_eni = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
         "NetworkInterfaces"
     ][0]
     assert len(my_eni["Groups"]) == 1
@@ -198,40 +188,40 @@ def test_elastic_network_interfaces_modify_attribute():
 
 @mock_aws
 def test_elastic_network_interfaces_filtering():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    sec_group1 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
-    sec_group2 = ec2.create_security_group(GroupName=str(uuid4()), Description="n/a")
+    sec_group1 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
+    sec_group2 = ec2resource.create_security_group(
+        GroupName=str(uuid4()), Description="n/a"
+    )
 
     eni1 = subnet.create_network_interface(Groups=[sec_group1.id, sec_group2.id])
     eni2 = subnet.create_network_interface(Groups=[sec_group1.id])
     eni3 = subnet.create_network_interface(Description=str(uuid4()))
 
-    all_enis = client.describe_network_interfaces()["NetworkInterfaces"]
+    all_enis = ec2client.describe_network_interfaces()["NetworkInterfaces"]
     assert eni1.id in [eni["NetworkInterfaceId"] for eni in all_enis]
     assert eni2.id in [eni["NetworkInterfaceId"] for eni in all_enis]
     assert eni3.id in [eni["NetworkInterfaceId"] for eni in all_enis]
 
     # Filter by NetworkInterfaceId
-    enis_by_id = client.describe_network_interfaces(NetworkInterfaceIds=[eni1.id])[
+    enis_by_id = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni1.id])[
         "NetworkInterfaces"
     ]
     assert len(enis_by_id) == 1
     assert [eni["NetworkInterfaceId"] for eni in enis_by_id] == [eni1.id]
 
     # Filter by ENI ID
-    enis_by_id = client.describe_network_interfaces(
+    enis_by_id = ec2client.describe_network_interfaces(
         Filters=[{"Name": "network-interface-id", "Values": [eni1.id]}]
     )["NetworkInterfaces"]
     assert len(enis_by_id) == 1
     assert [eni["NetworkInterfaceId"] for eni in enis_by_id] == [eni1.id]
 
     # Filter by Security Group
-    enis_by_group = client.describe_network_interfaces(
+    enis_by_group = ec2client.describe_network_interfaces(
         Filters=[{"Name": "group-id", "Values": [sec_group1.id]}]
     )["NetworkInterfaces"]
     assert len(enis_by_group) == 2
@@ -241,7 +231,7 @@ def test_elastic_network_interfaces_filtering():
     }
 
     # Filter by ENI ID and Security Group
-    enis_by_group = client.describe_network_interfaces(
+    enis_by_group = ec2client.describe_network_interfaces(
         Filters=[
             {"Name": "network-interface-id", "Values": [eni1.id]},
             {"Name": "group-id", "Values": [sec_group1.id]},
@@ -251,7 +241,7 @@ def test_elastic_network_interfaces_filtering():
     assert [eni["NetworkInterfaceId"] for eni in enis_by_group] == [eni1.id]
 
     # Filter by Description
-    enis_by_description = client.describe_network_interfaces(
+    enis_by_description = ec2client.describe_network_interfaces(
         Filters=[{"Name": "description", "Values": [eni3.description]}]
     )["NetworkInterfaces"]
     assert len(enis_by_description) == 1
@@ -262,20 +252,14 @@ def test_elastic_network_interfaces_filtering():
         # ServerMode will just throw a generic 500
         with pytest.raises(NotImplementedError):
             filters = [{"Name": "not-implemented-filter", "Values": ["foobar"]}]
-            client.describe_network_interfaces(Filters=filters)
+            ec2client.describe_network_interfaces(Filters=filters)
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_tag_name():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
-
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, PrivateIpAddress="10.0.10.5"
     )
 
@@ -292,261 +276,219 @@ def test_elastic_network_interfaces_get_by_tag_name():
     eni1.create_tags(Tags=[{"Key": "Name", "Value": tag_value}])
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     filters = [{"Name": "tag:Name", "Values": [tag_value]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "tag:Name", "Values": ["wrong-name"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_availability_zone():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource = boto3.resource("ec2", region_name="us-west-2")
+    ec2client = boto3.client("ec2", region_name="us-west-2")
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet1 = ec2.create_subnet(
+    vpc = ec2resource.create_vpc(CidrBlock="10.0.0.0/16")
+    subnet1 = ec2resource.create_subnet(
         VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
     )
 
-    subnet2 = ec2.create_subnet(
+    subnet2 = ec2resource.create_subnet(
         VpcId=vpc.id, CidrBlock="10.0.1.0/24", AvailabilityZone="us-west-2b"
     )
 
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet1.id, PrivateIpAddress="10.0.0.15"
     )
 
-    eni2 = ec2.create_network_interface(
+    eni2 = ec2resource.create_network_interface(
         SubnetId=subnet2.id, PrivateIpAddress="10.0.1.15"
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id, eni2.id])
 
     filters = [{"Name": "availability-zone", "Values": ["us-west-2a"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert eni1.id in [eni.id for eni in enis]
     assert eni2.id not in [eni.id for eni in enis]
 
     filters = [{"Name": "availability-zone", "Values": ["us-west-2c"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert eni1.id not in [eni.id for eni in enis]
     assert eni2.id not in [eni.id for eni in enis]
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_private_ip():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
     random_ip = ".".join(map(str, (random.randint(0, 99) for _ in range(4))))
-
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
+    eni1 = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=random_ip
     )
 
-    eni1 = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=random_ip)
-
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     filters = [{"Name": "private-ip-address", "Values": [random_ip]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "private-ip-address", "Values": ["10.0.10.10"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
     filters = [{"Name": "addresses.private-ip-address", "Values": [random_ip]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "addresses.private-ip-address", "Values": ["10.0.10.10"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_vpc_id():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
-
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, PrivateIpAddress="10.0.10.5"
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     filters = [{"Name": "vpc-id", "Values": [subnet.vpc_id]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "vpc-id", "Values": ["vpc-aaaa1111"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_subnet_id():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
-
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, PrivateIpAddress="10.0.10.5"
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     filters = [{"Name": "subnet-id", "Values": [subnet.id]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "subnet-id", "Values": ["subnet-aaaa1111"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_description():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
-
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     desc = str(uuid4())
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, PrivateIpAddress="10.0.10.5", Description=desc
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     filters = [{"Name": "description", "Values": [eni1.description]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 1
 
     filters = [{"Name": "description", "Values": ["bad description"]}]
-    enis = list(ec2.network_interfaces.filter(Filters=filters))
+    enis = list(ec2resource.network_interfaces.filter(Filters=filters))
     assert len(enis) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_attachment_instance_id():
-    ec2_resource = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
-
-    vpc = ec2_resource.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2_resource.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    security_group1 = ec2_resource.create_security_group(
+    security_group1 = ec2resource.create_security_group(
         GroupName=str(uuid4()), Description="desc"
     )
 
-    create_instances_result = ec2_resource.create_instances(
+    create_instances_result = ec2resource.create_instances(
         ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
     )
     instance = create_instances_result[0]
 
     # we should have one ENI attached to our ec2 instance by default
     filters = [{"Name": "attachment.instance-id", "Values": [instance.id]}]
-    enis = ec2_client.describe_network_interfaces(Filters=filters)
+    enis = ec2client.describe_network_interfaces(Filters=filters)
     assert len(enis.get("NetworkInterfaces")) == 1
 
     # attach another ENI to our existing instance, total should be 2
-    eni1 = ec2_resource.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, Groups=[security_group1.id]
     )
-    ec2_client.attach_network_interface(
+    ec2client.attach_network_interface(
         NetworkInterfaceId=eni1.id, InstanceId=instance.id, DeviceIndex=1
     )
 
     filters = [{"Name": "attachment.instance-id", "Values": [instance.id]}]
-    enis = ec2_client.describe_network_interfaces(Filters=filters)
+    enis = ec2client.describe_network_interfaces(Filters=filters)
     assert len(enis.get("NetworkInterfaces")) == 2
 
     # we shouldn't find any ENIs that are attached to this fake instance ID
     filters = [{"Name": "attachment.instance-id", "Values": ["this-doesnt-match-lol"]}]
-    enis = ec2_client.describe_network_interfaces(Filters=filters)
+    enis = ec2client.describe_network_interfaces(Filters=filters)
     assert len(enis.get("NetworkInterfaces")) == 0
 
 
 @mock_aws
 def test_elastic_network_interfaces_get_by_attachment_instance_owner_id():
-    ec2_resource = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    vpc = ec2_resource.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2_resource.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
-
-    security_group1 = ec2_resource.create_security_group(
+    security_group1 = ec2resource.create_security_group(
         GroupName=str(uuid4()), Description="desc"
     )
 
-    create_instances_result = ec2_resource.create_instances(
+    create_instances_result = ec2resource.create_instances(
         ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
     )
     instance = create_instances_result[0]
 
-    eni1 = ec2_resource.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, Groups=[security_group1.id]
     )
-    ec2_client.attach_network_interface(
+    ec2client.attach_network_interface(
         NetworkInterfaceId=eni1.id, InstanceId=instance.id, DeviceIndex=1
     )
 
     filters = [{"Name": "attachment.instance-owner-id", "Values": [ACCOUNT_ID]}]
-    enis = ec2_client.describe_network_interfaces(Filters=filters)["NetworkInterfaces"]
+    enis = ec2client.describe_network_interfaces(Filters=filters)["NetworkInterfaces"]
     eni_ids = [eni["NetworkInterfaceId"] for eni in enis]
     assert eni1.id in eni_ids
 
 
 @mock_aws
 def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
-
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
     random_ip = ".".join(map(str, (random.randint(0, 99) for _ in range(4))))
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
-
-    sg = ec2_client.create_security_group(Description="test", GroupName=str(uuid4()))
+    sg = ec2client.create_security_group(Description="test", GroupName=str(uuid4()))
     sg_id = sg["GroupId"]
 
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id,
         PrivateIpAddress=random_ip,
         Description=str(uuid4()),
@@ -554,11 +496,11 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
     # Filter by network-interface-id
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "network-interface-id", "Values": [eni1.id]}]
     )
     assert len(response["NetworkInterfaces"]) == 1
@@ -568,19 +510,19 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
     assert interface["Description"] == eni1.description
 
     # Filter by network-interface-id
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "group-id", "Values": [sg_id]}]
     )
     assert len(response["NetworkInterfaces"]) == 1
     assert response["NetworkInterfaces"][0]["NetworkInterfaceId"] == eni1.id
 
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "network-interface-id", "Values": ["bad-id"]}]
     )
     assert len(response["NetworkInterfaces"]) == 0
 
     # Filter by private-ip-address
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "private-ip-address", "Values": [eni1.private_ip_address]}]
     )
     assert len(response["NetworkInterfaces"]) == 1
@@ -589,13 +531,13 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
     assert interface["PrivateIpAddress"] == eni1.private_ip_address
     assert interface["Description"] == eni1.description
 
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "private-ip-address", "Values": ["11.11.11.11"]}]
     )
     assert len(response["NetworkInterfaces"]) == 0
 
     # Filter by sunet-id
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "subnet-id", "Values": [eni1.subnet.id]}]
     )
     assert len(response["NetworkInterfaces"]) == 1
@@ -605,13 +547,13 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
     )
     assert response["NetworkInterfaces"][0]["Description"] == eni1.description
 
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "subnet-id", "Values": ["sn-bad-id"]}]
     )
     assert len(response["NetworkInterfaces"]) == 0
 
     # Filter by description
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "description", "Values": [eni1.description]}]
     )
     assert len(response["NetworkInterfaces"]) == 1
@@ -621,13 +563,13 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
     )
     assert response["NetworkInterfaces"][0]["Description"] == eni1.description
 
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[{"Name": "description", "Values": ["bad description"]}]
     )
     assert len(response["NetworkInterfaces"]) == 0
 
     # Filter by multiple filters
-    response = ec2_client.describe_network_interfaces(
+    response = ec2client.describe_network_interfaces(
         Filters=[
             {"Name": "private-ip-address", "Values": [eni1.private_ip_address]},
             {"Name": "network-interface-id", "Values": [eni1.id]},
@@ -644,18 +586,12 @@ def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
 
 @mock_aws
 def test_elastic_network_interfaces_filter_by_tag():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
-
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     dev_env = f"dev-{str(uuid4())[0:4]}"
     prod_env = f"prod-{str(uuid4())[0:4]}"
 
-    eni_dev = ec2.create_network_interface(
+    eni_dev = ec2resource.create_network_interface(
         SubnetId=subnet.id,
         PrivateIpAddress="10.0.10.5",
         Description="dev interface",
@@ -667,7 +603,7 @@ def test_elastic_network_interfaces_filter_by_tag():
         ],
     )
 
-    eni_prod = ec2.create_network_interface(
+    eni_prod = ec2resource.create_network_interface(
         SubnetId=subnet.id,
         PrivateIpAddress="10.0.10.6",
         Description="prod interface",
@@ -680,27 +616,27 @@ def test_elastic_network_interfaces_filter_by_tag():
     )
 
     for eni in [eni_dev, eni_prod]:
-        waiter = ec2_client.get_waiter("network_interface_available")
+        waiter = ec2client.get_waiter("network_interface_available")
         waiter.wait(NetworkInterfaceIds=[eni.id])
 
-    resp = ec2_client.describe_network_interfaces(
+    resp = ec2client.describe_network_interfaces(
         Filters=[{"Name": "tag:environment", "Values": ["staging"]}]
     )
     assert len(resp["NetworkInterfaces"]) == 0
 
-    resp = ec2_client.describe_network_interfaces(
+    resp = ec2client.describe_network_interfaces(
         Filters=[{"Name": "tag:environment", "Values": [dev_env]}]
     )
     assert len(resp["NetworkInterfaces"]) == 1
     assert resp["NetworkInterfaces"][0]["Description"] == "dev interface"
 
-    resp = ec2_client.describe_network_interfaces(
+    resp = ec2client.describe_network_interfaces(
         Filters=[{"Name": "tag:environment", "Values": [prod_env]}]
     )
     assert len(resp["NetworkInterfaces"]) == 1
     assert resp["NetworkInterfaces"][0]["Description"] == "prod interface"
 
-    resp = ec2_client.describe_network_interfaces(
+    resp = ec2client.describe_network_interfaces(
         Filters=[{"Name": "tag:environment", "Values": [dev_env, prod_env]}]
     )
     assert len(resp["NetworkInterfaces"]) == 2
@@ -708,23 +644,17 @@ def test_elastic_network_interfaces_filter_by_tag():
 
 @mock_aws
 def test_elastic_network_interfaces_auto_create_securitygroup():
-    ec2 = boto3.resource("ec2", region_name="us-west-2")
-    ec2_client = boto3.client("ec2", region_name="us-west-2")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
-    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
-    subnet = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
-    )
-
-    eni1 = ec2.create_network_interface(
+    eni1 = ec2resource.create_network_interface(
         SubnetId=subnet.id, PrivateIpAddress="10.0.10.5", Groups=["testgroup"]
     )
 
     # The status of the new interface should be 'available'
-    waiter = ec2_client.get_waiter("network_interface_available")
+    waiter = ec2client.get_waiter("network_interface_available")
     waiter.wait(NetworkInterfaceIds=[eni1.id])
 
-    sgs = ec2_client.describe_security_groups()["SecurityGroups"]
+    sgs = ec2client.describe_security_groups()["SecurityGroups"]
     found_sg = [sg for sg in sgs if sg["GroupId"] == "testgroup"]
     assert len(found_sg) == 1
 
@@ -734,17 +664,15 @@ def test_elastic_network_interfaces_auto_create_securitygroup():
 
 @mock_aws
 def test_assign_private_ip_addresses__by_address():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     primary_ip = "54.0.0.1"
     secondary_ip = "80.0.0.1"
-    eni = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=primary_ip)
+    eni = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=primary_ip
+    )
 
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     resp_eni = resp["NetworkInterfaces"][0]
     assert resp_eni["PrivateIpAddress"] == primary_ip
     assert resp_eni["PrivateIpAddresses"] == [
@@ -752,12 +680,12 @@ def test_assign_private_ip_addresses__by_address():
     ]
 
     # Pass IP address to assign rather than SecondaryPrivateIpAddressCount.
-    client.assign_private_ip_addresses(
+    ec2client.assign_private_ip_addresses(
         NetworkInterfaceId=eni.id, PrivateIpAddresses=[secondary_ip]
     )
 
     # Verify secondary IP address is now present.
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     resp_eni = resp["NetworkInterfaces"][0]
     assert resp_eni["PrivateIpAddress"] == primary_ip
     assert resp_eni["PrivateIpAddresses"] == [
@@ -769,7 +697,7 @@ def test_assign_private_ip_addresses__by_address():
     eni.assign_private_ip_addresses(PrivateIpAddresses=[secondary_ip])
 
     # Verify nothing changes.
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     resp_eni = resp["NetworkInterfaces"][0]
     assert resp_eni["PrivateIpAddress"] == primary_ip
     assert resp_eni["PrivateIpAddresses"] == [
@@ -780,21 +708,19 @@ def test_assign_private_ip_addresses__by_address():
 
 @mock_aws
 def test_assign_private_ip_addresses__with_secondary_count():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     private_ip = "54.0.0.1"
-    eni = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=private_ip)
+    eni = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=private_ip
+    )
 
-    client.assign_private_ip_addresses(
+    ec2client.assign_private_ip_addresses(
         NetworkInterfaceId=eni.id, SecondaryPrivateIpAddressCount=2
     )
 
     # Verify second ip's are added
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
 
     assert my_eni["PrivateIpAddress"] == "54.0.0.1"
@@ -809,29 +735,27 @@ def test_assign_private_ip_addresses__with_secondary_count():
 
 @mock_aws
 def test_unassign_private_ip_addresses():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     private_ip = "54.0.0.1"
-    eni = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=private_ip)
+    eni = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=private_ip
+    )
 
-    client.assign_private_ip_addresses(
+    ec2client.assign_private_ip_addresses(
         NetworkInterfaceId=eni.id, SecondaryPrivateIpAddressCount=2
     )
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     ips_before = [addr["PrivateIpAddress"] for addr in my_eni["PrivateIpAddresses"]]
 
     # Remove IP
-    client.unassign_private_ip_addresses(
+    ec2client.unassign_private_ip_addresses(
         NetworkInterfaceId=eni.id, PrivateIpAddresses=[ips_before[1]]
     )
 
     # Verify it's gone
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["PrivateIpAddresses"]) == 2
     assert {"Primary": True, "PrivateIpAddress": "54.0.0.1"} in my_eni[
@@ -844,29 +768,27 @@ def test_unassign_private_ip_addresses():
 
 @mock_aws
 def test_unassign_private_ip_addresses__multiple():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     private_ip = "54.0.0.1"
-    eni = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=private_ip)
+    eni = ec2resource.create_network_interface(
+        SubnetId=subnet.id, PrivateIpAddress=private_ip
+    )
 
-    client.assign_private_ip_addresses(
+    ec2client.assign_private_ip_addresses(
         NetworkInterfaceId=eni.id, SecondaryPrivateIpAddressCount=5
     )
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     ips_before = [addr["PrivateIpAddress"] for addr in my_eni["PrivateIpAddresses"]]
 
     # Remove IP
-    client.unassign_private_ip_addresses(
+    ec2client.unassign_private_ip_addresses(
         NetworkInterfaceId=eni.id, PrivateIpAddresses=[ips_before[1], ips_before[2]]
     )
 
     # Verify it's gone
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["PrivateIpAddresses"]) == 4
     assert {"Primary": True, "PrivateIpAddress": "54.0.0.1"} in my_eni[
@@ -885,27 +807,23 @@ def test_unassign_private_ip_addresses__multiple():
 
 @mock_aws
 def test_assign_ipv6_addresses__by_address():
-    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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     ipv6_orig = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_2 = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_3 = random_private_ip("2001:db8::/101", ipv6=True)
-    eni = ec2.create_network_interface(
+    eni = ec2resource.create_network_interface(
         SubnetId=subnet.id, Ipv6Addresses=[{"Ipv6Address": ipv6_orig}]
     )
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert my_eni["Ipv6Addresses"] == [{"Ipv6Address": ipv6_orig}]
 
-    client.assign_ipv6_addresses(
+    ec2client.assign_ipv6_addresses(
         NetworkInterfaceId=eni.id, Ipv6Addresses=[ipv6_2, ipv6_3]
     )
 
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["Ipv6Addresses"]) == 3
     assert {"Ipv6Address": ipv6_orig} in my_eni["Ipv6Addresses"]
@@ -915,22 +833,16 @@ def test_assign_ipv6_addresses__by_address():
 
 @mock_aws
 def test_assign_ipv6_addresses__by_count():
-    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", Ipv6CidrBlock="2001:db8::/64"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     ipv6_orig = random_private_ip("2001:db8::/101", ipv6=True)
-    eni = ec2.create_network_interface(
+    eni = ec2resource.create_network_interface(
         SubnetId=subnet.id, Ipv6Addresses=[{"Ipv6Address": ipv6_orig}]
     )
 
-    client.assign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6AddressCount=3)
+    ec2client.assign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6AddressCount=3)
 
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["Ipv6Addresses"]) == 4
     assert {"Ipv6Address": ipv6_orig} in my_eni["Ipv6Addresses"]
@@ -938,27 +850,21 @@ def test_assign_ipv6_addresses__by_count():
 
 @mock_aws
 def test_assign_ipv6_addresses__by_address_and_count():
-    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", Ipv6CidrBlock="2001:db8::/64"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     ipv6_orig = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_2 = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_3 = random_private_ip("2001:db8::/101", ipv6=True)
-    eni = ec2.create_network_interface(
+    eni = ec2resource.create_network_interface(
         SubnetId=subnet.id, Ipv6Addresses=[{"Ipv6Address": ipv6_orig}]
     )
 
-    client.assign_ipv6_addresses(
+    ec2client.assign_ipv6_addresses(
         NetworkInterfaceId=eni.id, Ipv6Addresses=[ipv6_2, ipv6_3]
     )
-    client.assign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6AddressCount=2)
+    ec2client.assign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6AddressCount=2)
 
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["Ipv6Addresses"]) == 5
     assert {"Ipv6Address": ipv6_orig} in my_eni["Ipv6Addresses"]
@@ -968,28 +874,22 @@ def test_assign_ipv6_addresses__by_address_and_count():
 
 @mock_aws
 def test_unassign_ipv6_addresses():
-    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", Ipv6CidrBlock="2001:db8::/64"
-    )
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
 
     ipv6_orig = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_2 = random_private_ip("2001:db8::/101", ipv6=True)
     ipv6_3 = random_private_ip("2001:db8::/101", ipv6=True)
-    eni = ec2.create_network_interface(
+    eni = ec2resource.create_network_interface(
         SubnetId=subnet.id, Ipv6Addresses=[{"Ipv6Address": ipv6_orig}]
     )
 
-    client.assign_ipv6_addresses(
+    ec2client.assign_ipv6_addresses(
         NetworkInterfaceId=eni.id, Ipv6Addresses=[ipv6_2, ipv6_3]
     )
 
-    client.unassign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6Addresses=[ipv6_2])
+    ec2client.unassign_ipv6_addresses(NetworkInterfaceId=eni.id, Ipv6Addresses=[ipv6_2])
 
-    resp = client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
+    resp = ec2client.describe_network_interfaces(NetworkInterfaceIds=[eni.id])
     my_eni = resp["NetworkInterfaces"][0]
     assert len(my_eni["Ipv6Addresses"]) == 2
     assert {"Ipv6Address": ipv6_orig} in my_eni["Ipv6Addresses"]
@@ -998,28 +898,24 @@ def test_unassign_ipv6_addresses():
 
 @mock_aws
 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")
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
     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"]
+    instance_id = ec2client.run_instances(
+        ImageId="ami-12c6146b", MinCount=1, MaxCount=1
+    )["Instances"][0]["InstanceId"]
 
-    client.attach_network_interface(
+    ec2client.attach_network_interface(
         NetworkInterfaceId=eni_id, InstanceId=instance_id, DeviceIndex=1
     )
 
-    my_eni_attachment = client.describe_network_interface_attribute(
+    my_eni_attachment = ec2client.describe_network_interface_attribute(
         NetworkInterfaceId=eni_id, Attribute="attachment"
     ).get("Attachment")
     assert my_eni_attachment["InstanceId"] == instance_id
     assert my_eni_attachment["DeleteOnTermination"] is False
 
     with pytest.raises(ClientError) as ex:
-        client.describe_network_interface_attribute(
+        ec2client.describe_network_interface_attribute(
             NetworkInterfaceId=eni_id, Attribute="attach"
         )
     assert ex.value.response["Error"]["Code"] == "InvalidParameterValue"
@@ -1030,7 +926,7 @@ def test_elastic_network_interfaces_describe_attachment():
     )
 
     with pytest.raises(ClientError) as ex:
-        client.describe_network_interface_attribute(
+        ec2client.describe_network_interface_attribute(
             NetworkInterfaceId=eni_id, Attribute="attachment", DryRun=True
         )
     assert ex.value.response["Error"]["Code"] == "DryRunOperation"
@@ -1040,12 +936,45 @@ def test_elastic_network_interfaces_describe_attachment():
         == "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(
+    my_eni_description = ec2client.describe_network_interface_attribute(
         NetworkInterfaceId=eni_id, Attribute="description"
     ).get("Description")
     assert my_eni_description["Value"] == "A network interface"
 
-    my_eni_source_dest_check = client.describe_network_interface_attribute(
+    my_eni_source_dest_check = ec2client.describe_network_interface_attribute(
         NetworkInterfaceId=eni_id, Attribute="sourceDestCheck"
     ).get("SourceDestCheck")
     assert my_eni_source_dest_check["Value"] is True
+
+
+@mock_aws
+def test_eni_detachment():
+    # Setup
+    ec2resource, ec2client, vpc, subnet = setup_vpc(boto3)
+    resp = ec2client.run_instances(ImageId="ami-12c6146b", MinCount=1, MaxCount=1)[
+        "Instances"
+    ][0]
+
+    # Execute
+    eni_id = resp["NetworkInterfaces"][0]["Attachment"]["AttachmentId"]
+    with pytest.raises(ClientError) as ex:
+        ec2client.detach_network_interface(AttachmentId=eni_id)
+
+    # Verify
+    assert ex.value.response["Error"]["Code"] == "OperationNotPermitted"
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+    assert (
+        ex.value.response["Error"]["Message"]
+        == "The network interface at device index 0 and networkCard index 0 cannot be detached."
+    )
+
+
+def setup_vpc(boto3):  # pylint: disable=W0621
+    ec2resource = boto3.resource("ec2", region_name="us-east-1")
+    ec2client = boto3.client("ec2", "us-east-1")
+
+    vpc = ec2resource.create_vpc(CidrBlock="10.0.0.0/16")
+    subnet = ec2resource.create_subnet(
+        VpcId=vpc.id, CidrBlock="10.0.0.0/18", Ipv6CidrBlock="2001:db8::/64"
+    )
+    return ec2resource, ec2client, vpc, subnet