Add instance type validation on add instance method (#5132)
This commit is contained in:
parent
6ae0aa5272
commit
dbcee3c196
@ -633,6 +633,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
|
||||
tags={"instance": propagated_tags},
|
||||
placement=random.choice(self.availability_zones),
|
||||
launch_config=self.launch_config,
|
||||
is_instance_type_default=False,
|
||||
)
|
||||
for instance in reservation.instances:
|
||||
instance.autoscaling_group = self
|
||||
|
@ -1110,6 +1110,7 @@ class BatchBackend(BaseBackend):
|
||||
subnet_id=next(subnet_cycle),
|
||||
key_name=compute_resources.get("ec2KeyPair", "AWS_OWNED"),
|
||||
security_group_ids=compute_resources["securityGroupIds"],
|
||||
is_instance_type_default=False,
|
||||
)
|
||||
|
||||
new_comp_env.add_instance(reservation.instances[0])
|
||||
|
@ -45,24 +45,25 @@ class InstanceTypeOfferingBackend(object):
|
||||
location_type = location_type or "region"
|
||||
matches = INSTANCE_TYPE_OFFERINGS[location_type]
|
||||
matches = matches.get(self.region_name, [])
|
||||
matches = [
|
||||
o for o in matches if self.matches_filters(o, filters or {}, location_type)
|
||||
]
|
||||
return matches
|
||||
|
||||
def matches_filters(offering, filters):
|
||||
def matches_filter(key, values):
|
||||
if key == "location":
|
||||
if location_type in ("availability-zone", "availability-zone-id"):
|
||||
return offering.get("Location") in values
|
||||
elif location_type == "region":
|
||||
return any(
|
||||
v for v in values if offering.get("Location").startswith(v)
|
||||
)
|
||||
else:
|
||||
return False
|
||||
elif key == "instance-type":
|
||||
return offering.get("InstanceType") in values
|
||||
def matches_filters(self, offering, filters, location_type):
|
||||
def matches_filter(key, values):
|
||||
if key == "location":
|
||||
if location_type in ("availability-zone", "availability-zone-id"):
|
||||
return offering.get("Location") in values
|
||||
elif location_type == "region":
|
||||
return any(
|
||||
v for v in values if offering.get("Location").startswith(v)
|
||||
)
|
||||
else:
|
||||
return False
|
||||
elif key == "instance-type":
|
||||
return offering.get("InstanceType") in values
|
||||
else:
|
||||
return False
|
||||
|
||||
return all([matches_filter(key, values) for key, values in filters.items()])
|
||||
|
||||
matches = [o for o in matches if matches_filters(o, filters or {})]
|
||||
return matches
|
||||
return all([matches_filter(key, values) for key, values in filters.items()])
|
||||
|
@ -2,11 +2,15 @@ import copy
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from moto import settings
|
||||
|
||||
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.ec2.models.instance_types import (
|
||||
INSTANCE_TYPE_OFFERINGS,
|
||||
InstanceTypeOfferingBackend,
|
||||
)
|
||||
from moto.packages.boto.ec2.blockdevicemapping import BlockDeviceMapping
|
||||
from moto.packages.boto.ec2.instance import Instance as BotoInstance
|
||||
from moto.packages.boto.ec2.instance import Reservation
|
||||
@ -15,6 +19,7 @@ from ..exceptions import (
|
||||
AvailabilityZoneNotFromRegionError,
|
||||
EC2ClientError,
|
||||
InvalidInstanceIdError,
|
||||
InvalidInstanceTypeError,
|
||||
InvalidParameterValueErrorUnknownAttribute,
|
||||
OperationNotPermitted4,
|
||||
)
|
||||
@ -275,6 +280,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
||||
count=1,
|
||||
security_group_names=group_names,
|
||||
instance_type=properties.get("InstanceType", "m1.small"),
|
||||
is_instance_type_default=not properties.get("InstanceType"),
|
||||
subnet_id=properties.get("SubnetId"),
|
||||
key_name=properties.get("KeyName"),
|
||||
private_ip=properties.get("PrivateIpAddress"),
|
||||
@ -543,6 +549,7 @@ class InstanceBackend(object):
|
||||
|
||||
def add_instances(self, image_id, count, user_data, security_group_names, **kwargs):
|
||||
location_type = "availability-zone" if kwargs.get("placement") else "region"
|
||||
default_region = "us-east-1"
|
||||
valid_instance_types = INSTANCE_TYPE_OFFERINGS[location_type]
|
||||
if "region_name" in kwargs and kwargs.get("placement"):
|
||||
valid_availability_zones = {
|
||||
@ -551,6 +558,24 @@ class InstanceBackend(object):
|
||||
}
|
||||
if kwargs["placement"] not in valid_availability_zones:
|
||||
raise AvailabilityZoneNotFromRegionError(kwargs["placement"])
|
||||
match_filters = InstanceTypeOfferingBackend().matches_filters
|
||||
if not kwargs["is_instance_type_default"] and not any(
|
||||
{
|
||||
match_filters(
|
||||
valid_instance,
|
||||
{"instance-type": kwargs["instance_type"]},
|
||||
location_type,
|
||||
)
|
||||
for valid_instance in valid_instance_types.get(
|
||||
kwargs["region_name"]
|
||||
if "region_name" in kwargs
|
||||
else default_region,
|
||||
{},
|
||||
)
|
||||
},
|
||||
):
|
||||
if settings.EC2_ENABLE_INSTANCE_TYPE_VALIDATION:
|
||||
raise InvalidInstanceTypeError(kwargs["instance_type"])
|
||||
new_reservation = Reservation()
|
||||
new_reservation.id = random_reservation_id()
|
||||
|
||||
|
@ -104,6 +104,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
|
||||
count=1,
|
||||
user_data=self.user_data,
|
||||
instance_type=self.launch_specification.instance_type,
|
||||
is_instance_type_default=not self.launch_specification.instance_type,
|
||||
subnet_id=self.launch_specification.subnet_id,
|
||||
key_name=self.launch_specification.key_name,
|
||||
security_group_names=[],
|
||||
|
@ -49,6 +49,7 @@ class InstanceResponse(EC2BaseResponse):
|
||||
security_group_names = self._get_multi_param("SecurityGroup")
|
||||
kwargs = {
|
||||
"instance_type": self._get_param("InstanceType", if_none="m1.small"),
|
||||
"is_instance_type_default": not self._get_param("InstanceType"),
|
||||
"placement": self._get_param("Placement.AvailabilityZone"),
|
||||
"region_name": self.region,
|
||||
"subnet_id": self._get_param("SubnetId"),
|
||||
|
@ -435,6 +435,7 @@ class ElasticMapReduceBackend(BaseBackend):
|
||||
|
||||
def add_instances(self, cluster_id, instances, instance_group):
|
||||
cluster = self.clusters[cluster_id]
|
||||
instances["is_instance_type_default"] = not instances.get("instance_type")
|
||||
response = self.ec2_backend.add_instances(
|
||||
EXAMPLE_AMI_ID, instances["instance_count"], "", [], **instances
|
||||
)
|
||||
|
@ -101,6 +101,7 @@ class OpsworkInstance(BaseModel):
|
||||
security_group_names=[],
|
||||
security_group_ids=self.security_group_ids,
|
||||
instance_type=self.instance_type,
|
||||
is_instance_type_default=not self.instance_type,
|
||||
key_name=self.ssh_keyname,
|
||||
ebs_optimized=self.ebs_optimized,
|
||||
subnet_id=self.subnet_id,
|
||||
|
@ -18,6 +18,10 @@ S3_IGNORE_SUBDOMAIN_BUCKETNAME = os.environ.get(
|
||||
# How many seconds to wait before we "validate" a new certificate in ACM.
|
||||
ACM_VALIDATION_WAIT = int(os.environ.get("MOTO_ACM_VALIDATION_WAIT", "60"))
|
||||
|
||||
EC2_ENABLE_INSTANCE_TYPE_VALIDATION = bool(
|
||||
os.environ.get("MOTO_EC2_ENABLE_INSTANCE_TYPE_VALIDATION", False)
|
||||
)
|
||||
|
||||
|
||||
def get_sf_execution_history_type():
|
||||
"""
|
||||
|
@ -1,7 +1,7 @@
|
||||
from botocore.exceptions import ClientError, ParamValidationError
|
||||
|
||||
import pytest
|
||||
from unittest import SkipTest
|
||||
from unittest import SkipTest, mock
|
||||
|
||||
import base64
|
||||
import ipaddress
|
||||
@ -1162,6 +1162,32 @@ def test_run_instance_with_placement():
|
||||
instance.placement.should.have.key("AvailabilityZone").equal("us-east-1b")
|
||||
|
||||
|
||||
@mock_ec2
|
||||
@mock.patch(
|
||||
"moto.ec2.models.instances.settings.EC2_ENABLE_INSTANCE_TYPE_VALIDATION",
|
||||
new_callable=mock.PropertyMock(return_value=True),
|
||||
)
|
||||
def test_run_instance_with_invalid_instance_type(m_flag):
|
||||
if settings.TEST_SERVER_MODE:
|
||||
raise SkipTest(
|
||||
"It is not possible to set the environment variable in server mode"
|
||||
)
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
ec2.create_instances(
|
||||
ImageId=EXAMPLE_AMI_ID,
|
||||
InstanceType="invalid_type",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
Placement={"AvailabilityZone": "us-east-1b"},
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"The instance type 'invalid_type' does not exist"
|
||||
)
|
||||
assert m_flag is True
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_run_instance_with_availability_zone_not_from_region():
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
|
@ -48,7 +48,7 @@ input_instance_groups = [
|
||||
{
|
||||
"InstanceCount": 6,
|
||||
"InstanceRole": "TASK",
|
||||
"InstanceType": "c1.large",
|
||||
"InstanceType": "c3.large",
|
||||
"Market": "SPOT",
|
||||
"Name": "task-1",
|
||||
"BidPrice": "0.07",
|
||||
|
Loading…
Reference in New Issue
Block a user