parent
155f9f20eb
commit
ea81377cd0
@ -49,6 +49,11 @@ class InvalidDHCPOptionsIdError(EC2ClientError):
|
||||
)
|
||||
|
||||
|
||||
class InvalidRequest(EC2ClientError):
|
||||
def __init__(self):
|
||||
super().__init__("InvalidRequest", "The request received was invalid")
|
||||
|
||||
|
||||
class InvalidParameterCombination(EC2ClientError):
|
||||
def __init__(self, msg):
|
||||
super().__init__("InvalidParameterCombination", msg)
|
||||
|
@ -1155,14 +1155,15 @@ class InstanceBackend(object):
|
||||
"DeleteOnTermination", False
|
||||
)
|
||||
kms_key_id = block_device["Ebs"].get("KmsKeyId")
|
||||
new_instance.add_block_device(
|
||||
volume_size,
|
||||
device_name,
|
||||
snapshot_id,
|
||||
encrypted,
|
||||
delete_on_termination,
|
||||
kms_key_id,
|
||||
)
|
||||
if block_device.get("NoDevice") != "":
|
||||
new_instance.add_block_device(
|
||||
volume_size,
|
||||
device_name,
|
||||
snapshot_id,
|
||||
encrypted,
|
||||
delete_on_termination,
|
||||
kms_key_id,
|
||||
)
|
||||
else:
|
||||
new_instance.setup_defaults()
|
||||
if kwargs.get("instance_market_options"):
|
||||
|
@ -1,7 +1,11 @@
|
||||
from moto.autoscaling import autoscaling_backends
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.core.utils import camelcase_to_underscores
|
||||
from moto.ec2.exceptions import MissingParameterError, InvalidParameterCombination
|
||||
from moto.ec2.exceptions import (
|
||||
MissingParameterError,
|
||||
InvalidParameterCombination,
|
||||
InvalidRequest,
|
||||
)
|
||||
from moto.ec2.utils import (
|
||||
filters_from_querystring,
|
||||
dict_from_querystring,
|
||||
@ -320,6 +324,7 @@ class InstanceResponse(BaseResponse):
|
||||
device_mapping.get("ebs._encrypted", False)
|
||||
)
|
||||
device_template["Ebs"]["KmsKeyId"] = device_mapping.get("ebs._kms_key_id")
|
||||
device_template["NoDevice"] = device_mapping.get("no_device")
|
||||
mappings.append(device_template)
|
||||
|
||||
return mappings
|
||||
@ -327,6 +332,22 @@ class InstanceResponse(BaseResponse):
|
||||
@staticmethod
|
||||
def _validate_block_device_mapping(device_mapping):
|
||||
|
||||
from botocore import __version__ as botocore_version
|
||||
|
||||
if "no_device" in device_mapping:
|
||||
assert isinstance(
|
||||
device_mapping["no_device"], str
|
||||
), "botocore {} isn't limiting NoDevice to str type anymore, it is type:{}".format(
|
||||
botocore_version, type(device_mapping["no_device"])
|
||||
)
|
||||
if device_mapping["no_device"] == "":
|
||||
# the only legit value it can have is empty string
|
||||
# and none of the other checks here matter if NoDevice
|
||||
# is being used
|
||||
return
|
||||
else:
|
||||
raise InvalidRequest()
|
||||
|
||||
if not any(mapping for mapping in device_mapping if mapping.startswith("ebs.")):
|
||||
raise MissingParameterError("ebs")
|
||||
if (
|
||||
@ -349,6 +370,7 @@ class InstanceResponse(BaseResponse):
|
||||
BLOCK_DEVICE_MAPPING_TEMPLATE = {
|
||||
"VirtualName": None,
|
||||
"DeviceName": None,
|
||||
"NoDevice": None,
|
||||
"Ebs": {
|
||||
"SnapshotId": None,
|
||||
"VolumeSize": None,
|
||||
|
@ -1,4 +1,4 @@
|
||||
from botocore.exceptions import ClientError
|
||||
from botocore.exceptions import ClientError, ParamValidationError
|
||||
|
||||
import pytest
|
||||
from unittest import SkipTest
|
||||
@ -174,6 +174,8 @@ def test_instance_terminate_keep_volumes_implicit():
|
||||
for volume in instance.volumes.all():
|
||||
instance_volume_ids.append(volume.volume_id)
|
||||
|
||||
instance_volume_ids.shouldnt.be.empty
|
||||
|
||||
instance.terminate()
|
||||
instance.wait_until_terminated()
|
||||
|
||||
@ -1530,6 +1532,48 @@ def test_run_instance_with_block_device_mappings_missing_ebs():
|
||||
)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_run_instance_with_block_device_mappings_using_no_device():
|
||||
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
||||
kwargs = {
|
||||
"MinCount": 1,
|
||||
"MaxCount": 1,
|
||||
"ImageId": EXAMPLE_AMI_ID,
|
||||
"KeyName": "the_key",
|
||||
"InstanceType": "t1.micro",
|
||||
"BlockDeviceMappings": [{"DeviceName": "/dev/sda2", "NoDevice": ""}],
|
||||
}
|
||||
resp = ec2_client.run_instances(**kwargs)
|
||||
instance_id = resp["Instances"][0]["InstanceId"]
|
||||
|
||||
instances = ec2_client.describe_instances(InstanceIds=[instance_id])
|
||||
# Assuming that /dev/sda2 is not the root device and that there is a /dev/sda1, boto would
|
||||
# create an instance with one block device instead of two. However, moto's modeling of
|
||||
# BlockDeviceMappings is simplified, so we will accept that moto creates an instance without
|
||||
# block devices for now
|
||||
# instances["Reservations"][0]["Instances"][0].shouldnt.have.key("BlockDeviceMappings")
|
||||
|
||||
# moto gives the key with an empty list instead of not having it at all, that's also fine
|
||||
instances["Reservations"][0]["Instances"][0]["BlockDeviceMappings"].should.be.empty
|
||||
|
||||
# passing None with NoDevice should raise ParamValidationError
|
||||
kwargs["BlockDeviceMappings"][0]["NoDevice"] = None
|
||||
with pytest.raises(ParamValidationError) as ex:
|
||||
ec2_client.run_instances(**kwargs)
|
||||
|
||||
# passing a string other than "" with NoDevice should raise InvalidRequest
|
||||
kwargs["BlockDeviceMappings"][0]["NoDevice"] = "yes"
|
||||
with pytest.raises(ClientError) as ex:
|
||||
ec2_client.run_instances(**kwargs)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidRequest")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"The request received was invalid"
|
||||
)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_run_instance_with_block_device_mappings_missing_size():
|
||||
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
Loading…
Reference in New Issue
Block a user