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):
|
class InvalidParameterCombination(EC2ClientError):
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
super().__init__("InvalidParameterCombination", msg)
|
super().__init__("InvalidParameterCombination", msg)
|
||||||
|
@ -1155,6 +1155,7 @@ class InstanceBackend(object):
|
|||||||
"DeleteOnTermination", False
|
"DeleteOnTermination", False
|
||||||
)
|
)
|
||||||
kms_key_id = block_device["Ebs"].get("KmsKeyId")
|
kms_key_id = block_device["Ebs"].get("KmsKeyId")
|
||||||
|
if block_device.get("NoDevice") != "":
|
||||||
new_instance.add_block_device(
|
new_instance.add_block_device(
|
||||||
volume_size,
|
volume_size,
|
||||||
device_name,
|
device_name,
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
from moto.autoscaling import autoscaling_backends
|
from moto.autoscaling import autoscaling_backends
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from moto.core.utils import camelcase_to_underscores
|
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 (
|
from moto.ec2.utils import (
|
||||||
filters_from_querystring,
|
filters_from_querystring,
|
||||||
dict_from_querystring,
|
dict_from_querystring,
|
||||||
@ -320,6 +324,7 @@ class InstanceResponse(BaseResponse):
|
|||||||
device_mapping.get("ebs._encrypted", False)
|
device_mapping.get("ebs._encrypted", False)
|
||||||
)
|
)
|
||||||
device_template["Ebs"]["KmsKeyId"] = device_mapping.get("ebs._kms_key_id")
|
device_template["Ebs"]["KmsKeyId"] = device_mapping.get("ebs._kms_key_id")
|
||||||
|
device_template["NoDevice"] = device_mapping.get("no_device")
|
||||||
mappings.append(device_template)
|
mappings.append(device_template)
|
||||||
|
|
||||||
return mappings
|
return mappings
|
||||||
@ -327,6 +332,22 @@ class InstanceResponse(BaseResponse):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_block_device_mapping(device_mapping):
|
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.")):
|
if not any(mapping for mapping in device_mapping if mapping.startswith("ebs.")):
|
||||||
raise MissingParameterError("ebs")
|
raise MissingParameterError("ebs")
|
||||||
if (
|
if (
|
||||||
@ -349,6 +370,7 @@ class InstanceResponse(BaseResponse):
|
|||||||
BLOCK_DEVICE_MAPPING_TEMPLATE = {
|
BLOCK_DEVICE_MAPPING_TEMPLATE = {
|
||||||
"VirtualName": None,
|
"VirtualName": None,
|
||||||
"DeviceName": None,
|
"DeviceName": None,
|
||||||
|
"NoDevice": None,
|
||||||
"Ebs": {
|
"Ebs": {
|
||||||
"SnapshotId": None,
|
"SnapshotId": None,
|
||||||
"VolumeSize": None,
|
"VolumeSize": None,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, ParamValidationError
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest import SkipTest
|
from unittest import SkipTest
|
||||||
@ -174,6 +174,8 @@ def test_instance_terminate_keep_volumes_implicit():
|
|||||||
for volume in instance.volumes.all():
|
for volume in instance.volumes.all():
|
||||||
instance_volume_ids.append(volume.volume_id)
|
instance_volume_ids.append(volume.volume_id)
|
||||||
|
|
||||||
|
instance_volume_ids.shouldnt.be.empty
|
||||||
|
|
||||||
instance.terminate()
|
instance.terminate()
|
||||||
instance.wait_until_terminated()
|
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
|
@mock_ec2
|
||||||
def test_run_instance_with_block_device_mappings_missing_size():
|
def test_run_instance_with_block_device_mappings_missing_size():
|
||||||
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
||||||
|
Loading…
Reference in New Issue
Block a user