diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 65f00f132..62f866c0f 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -586,6 +586,14 @@ class InvalidAvailabilityZoneError(EC2ClientError): ) +class AvailabilityZoneNotFromRegionError(EC2ClientError): + def __init__(self, availability_zone_value): + super().__init__( + "InvalidParameterValue", + "Invalid Availability Zone ({0})".format(availability_zone_value), + ) + + class NetworkAclEntryAlreadyExistsError(EC2ClientError): def __init__(self, rule_number): super().__init__( diff --git a/moto/ec2/models/instances.py b/moto/ec2/models/instances.py index e613c3aa0..f7c93cbf1 100644 --- a/moto/ec2/models/instances.py +++ b/moto/ec2/models/instances.py @@ -6,24 +6,28 @@ from datetime import datetime from moto.core import get_account_id from moto.core import CloudFormationModel from moto.core.utils import camelcase_to_underscores +from moto.ec2.models.instance_types import INSTANCE_TYPE_OFFERINGS from moto.packages.boto.ec2.blockdevicemapping import BlockDeviceMapping -from moto.packages.boto.ec2.instance import Instance as BotoInstance, Reservation +from moto.packages.boto.ec2.instance import Instance as BotoInstance +from moto.packages.boto.ec2.instance import Reservation + from ..exceptions import ( + AvailabilityZoneNotFromRegionError, EC2ClientError, InvalidInstanceIdError, InvalidParameterValueErrorUnknownAttribute, OperationNotPermitted4, ) -from .core import TaggedEC2Resource from ..utils import ( + convert_tag_spec, + filter_reservations, random_eni_attach_id, random_instance_id, random_private_ip, random_reservation_id, - filter_reservations, utc_date_and_time, - convert_tag_spec, ) +from .core import TaggedEC2Resource class InstanceState(object): @@ -538,6 +542,15 @@ class InstanceBackend(object): raise InvalidInstanceIdError(instance_id) def add_instances(self, image_id, count, user_data, security_group_names, **kwargs): + location_type = "availability-zone" if kwargs.get("placement") else "region" + valid_instance_types = INSTANCE_TYPE_OFFERINGS[location_type] + if "region_name" in kwargs and kwargs.get("placement"): + valid_availability_zones = { + instance["Location"] + for instance in valid_instance_types[kwargs["region_name"]] + } + if kwargs["placement"] not in valid_availability_zones: + raise AvailabilityZoneNotFromRegionError(kwargs["placement"]) new_reservation = Reservation() new_reservation.id = random_reservation_id() diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index 77b16cb5e..a2f1e0ab2 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -1162,6 +1162,24 @@ def test_run_instance_with_placement(): instance.placement.should.have.key("AvailabilityZone").equal("us-east-1b") +@mock_ec2 +def test_run_instance_with_availability_zone_not_from_region(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + with pytest.raises(ClientError) as ex: + ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, + InstanceType="t2.nano", + MinCount=1, + MaxCount=1, + Placement={"AvailabilityZone": "us-west-1b"}, + ) + + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal( + "Invalid Availability Zone (us-west-1b)" + ) + + @mock_ec2 def test_run_instance_with_subnet(): client = boto3.client("ec2", region_name="eu-central-1")