diff --git a/moto/ec2/models/availability_zones_and_regions.py b/moto/ec2/models/availability_zones_and_regions.py index eb46d01ab..77f05b6a1 100644 --- a/moto/ec2/models/availability_zones_and_regions.py +++ b/moto/ec2/models/availability_zones_and_regions.py @@ -24,6 +24,7 @@ class Zone: self.region_name = region_name self.zone_id = zone_id self.zone_type = zone_type + self.state = "available" class RegionsAndZonesBackend: @@ -318,8 +319,15 @@ class RegionsAndZonesBackend: return ret def describe_availability_zones( - self, filters: Optional[List[Dict[str, Any]]] = None + self, + filters: Optional[List[Dict[str, Any]]] = None, + zone_names: Optional[List[str]] = None, + zone_ids: Optional[List[str]] = None, ) -> List[Zone]: + """ + The following parameters are supported: ZoneIds, ZoneNames, Filters + The following filters are supported: zone-id, zone-type, zone-name, region-name, state + """ # We might not have any zones for the current region, if it was introduced recently zones = self.zones.get(self.region_name, []) # type: ignore[attr-defined] attr_pairs = ( @@ -327,10 +335,13 @@ class RegionsAndZonesBackend: ("zone-type", "zone_type"), ("zone-name", "name"), ("region-name", "region_name"), + ("state", "state"), ) result = zones if filters: result = filter_resources(zones, filters, attr_pairs) + result = [r for r in result if not zone_ids or r.zone_id in zone_ids] + result = [r for r in result if not zone_names or r.name in zone_names] return result def get_zone_by_name(self, name: str) -> Optional[Zone]: diff --git a/moto/ec2/responses/availability_zones_and_regions.py b/moto/ec2/responses/availability_zones_and_regions.py index 0ef8238d0..15948c7cb 100644 --- a/moto/ec2/responses/availability_zones_and_regions.py +++ b/moto/ec2/responses/availability_zones_and_regions.py @@ -5,7 +5,11 @@ class AvailabilityZonesAndRegions(EC2BaseResponse): def describe_availability_zones(self) -> str: self.error_on_dryrun() filters = self._filters_from_querystring() - zones = self.ec2_backend.describe_availability_zones(filters) + zone_names = self._get_multi_param("ZoneName") + zone_ids = self._get_multi_param("ZoneId") + zones = self.ec2_backend.describe_availability_zones( + filters, zone_names=zone_names, zone_ids=zone_ids + ) template = self.response_template(DESCRIBE_ZONES_RESPONSE) return template.render(zones=zones) diff --git a/tests/test_ec2/test_availability_zones_and_regions.py b/tests/test_ec2/test_availability_zones_and_regions.py index 7d593bdd5..a0886b75f 100644 --- a/tests/test_ec2/test_availability_zones_and_regions.py +++ b/tests/test_ec2/test_availability_zones_and_regions.py @@ -38,6 +38,45 @@ def test_boto3_availability_zones(): assert region in rec["ZoneName"] +@mock_ec2 +def test_availability_zones__parameters(): + us_east = boto3.client("ec2", "us-east-1") + zones = us_east.describe_availability_zones(ZoneNames=["us-east-1b"])[ + "AvailabilityZones" + ] + assert len(zones) == 1 + assert zones[0]["ZoneId"] == "use1-az1" + + zones = us_east.describe_availability_zones(ZoneNames=["us-east-1a", "us-east-1b"])[ + "AvailabilityZones" + ] + assert len(zones) == 2 + assert set([zone["ZoneId"] for zone in zones]) == {"use1-az1", "use1-az6"} + + zones = us_east.describe_availability_zones(ZoneIds=["use1-az1"])[ + "AvailabilityZones" + ] + assert len(zones) == 1 + assert zones[0]["ZoneId"] == "use1-az1" + + zones = us_east.describe_availability_zones( + Filters=[{"Name": "state", "Values": ["unavailable"]}] + )["AvailabilityZones"] + assert zones == [] + + zones = us_east.describe_availability_zones( + Filters=[{"Name": "zone-id", "Values": ["use1-az2"]}] + )["AvailabilityZones"] + assert len(zones) == 1 + assert zones[0]["ZoneId"] == "use1-az2" + + zones = us_east.describe_availability_zones( + Filters=[{"Name": "zone-name", "Values": ["us-east-1b"]}] + )["AvailabilityZones"] + assert len(zones) == 1 + assert zones[0]["ZoneId"] == "use1-az1" + + @mock_ec2 def test_describe_availability_zones_dryrun(): client = boto3.client("ec2", region_name="us-east-1")