diff --git a/moto/ec2/models/dhcp_options.py b/moto/ec2/models/dhcp_options.py index 8f8e2ebe7..397407147 100644 --- a/moto/ec2/models/dhcp_options.py +++ b/moto/ec2/models/dhcp_options.py @@ -69,6 +69,11 @@ class DHCPOptionsSetBackend: dhcp_options.vpc = vpc vpc.dhcp_options = dhcp_options + def disassociate_dhcp_options(self, vpc: Any) -> None: + if vpc.dhcp_options: + vpc.dhcp_options.vpc = None + vpc.dhcp_options = None + def create_dhcp_options( self, domain_name_servers: Optional[List[str]] = None, diff --git a/moto/ec2/responses/dhcp_options.py b/moto/ec2/responses/dhcp_options.py index 6c3160004..153943eed 100644 --- a/moto/ec2/responses/dhcp_options.py +++ b/moto/ec2/responses/dhcp_options.py @@ -6,10 +6,13 @@ class DHCPOptions(EC2BaseResponse): dhcp_opt_id = self._get_param("DhcpOptionsId") vpc_id = self._get_param("VpcId") - dhcp_opt = self.ec2_backend.describe_dhcp_options([dhcp_opt_id])[0] vpc = self.ec2_backend.get_vpc(vpc_id) - self.ec2_backend.associate_dhcp_options(dhcp_opt, vpc) + if dhcp_opt_id == "default": + self.ec2_backend.disassociate_dhcp_options(vpc) + else: + dhcp_opt = self.ec2_backend.describe_dhcp_options([dhcp_opt_id])[0] + self.ec2_backend.associate_dhcp_options(dhcp_opt, vpc) template = self.response_template(ASSOCIATE_DHCP_OPTIONS_RESPONSE) return template.render() diff --git a/moto/ec2/responses/vpcs.py b/moto/ec2/responses/vpcs.py index 2f0f2e91f..93a80f90c 100644 --- a/moto/ec2/responses/vpcs.py +++ b/moto/ec2/responses/vpcs.py @@ -374,7 +374,7 @@ CREATE_VPC_RESPONSE = """ {% endfor %} {% endif %} - {% if vpc.dhcp_options %}{{ vpc.dhcp_options.id }}{% else %}dopt-1a2b3c4d2{% endif %} + {% if vpc.dhcp_options %}{{ vpc.dhcp_options.id }}{% else %}default{% endif %} {{ vpc.instance_tenancy }} {{ vpc.owner_id }} @@ -475,7 +475,7 @@ DESCRIBE_VPCS_RESPONSE = """ {% endfor %} {% endif %} - {% if vpc.dhcp_options %}{{ vpc.dhcp_options.id }}{% else %}dopt-7a8b9c2d{% endif %} + {% if vpc.dhcp_options %}{{ vpc.dhcp_options.id }}{% else %}default{% endif %} {{ vpc.instance_tenancy }} {{ vpc.is_default }} {{ vpc.owner_id }} diff --git a/tests/test_ec2/test_dhcp_options.py b/tests/test_ec2/test_dhcp_options.py index d63a48838..7910c0c81 100644 --- a/tests/test_ec2/test_dhcp_options.py +++ b/tests/test_ec2/test_dhcp_options.py @@ -66,6 +66,34 @@ def test_dhcp_options_associate_invalid_vpc_id(): assert ex.value.response["Error"]["Code"] == "InvalidVpcID.NotFound" +@mock_ec2 +def test_dhcp_options_disassociation(): + """Ensure that VPCs can be set to the 'default' DHCP options set for disassociation.""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + # Ensure newly created VPCs without any DHCP options can be disassociated + client.associate_dhcp_options(DhcpOptionsId="default", VpcId=vpc.id) + vpc.reload() + assert vpc.dhcp_options_id == "default" + + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc.id) + vpc.reload() + assert vpc.dhcp_options_id == dhcp_options.id + + # Ensure VPCs with already associated DHCP options can be disassociated + client.associate_dhcp_options(DhcpOptionsId="default", VpcId=vpc.id) + vpc.reload() + assert vpc.dhcp_options_id == "default" + + @mock_ec2 def test_dhcp_options_delete_with_vpc(): """Test deletion of dhcp options with vpc"""