EC2: implement ModifyVolumes and DescribeVolumesModifications (#5399)
This commit is contained in:
parent
52c1edce23
commit
37fa5f8bf4
@ -1856,7 +1856,7 @@
|
||||
- [ ] describe_volume_attribute
|
||||
- [ ] describe_volume_status
|
||||
- [X] describe_volumes
|
||||
- [ ] describe_volumes_modifications
|
||||
- [X] describe_volumes_modifications
|
||||
- [X] describe_vpc_attribute
|
||||
- [ ] describe_vpc_classic_link
|
||||
- [ ] describe_vpc_classic_link_dns_support
|
||||
@ -1998,7 +1998,7 @@
|
||||
- [X] modify_transit_gateway
|
||||
- [ ] modify_transit_gateway_prefix_list_reference
|
||||
- [X] modify_transit_gateway_vpc_attachment
|
||||
- [ ] modify_volume
|
||||
- [X] modify_volume
|
||||
- [ ] modify_volume_attribute
|
||||
- [X] modify_vpc_attribute
|
||||
- [ ] modify_vpc_endpoint
|
||||
@ -6418,4 +6418,4 @@
|
||||
- workspaces
|
||||
- workspaces-web
|
||||
- xray
|
||||
</details>
|
||||
</details>
|
||||
|
@ -9,6 +9,7 @@ from ..exceptions import (
|
||||
InvalidVolumeAttachmentError,
|
||||
InvalidVolumeDetachmentError,
|
||||
InvalidParameterDependency,
|
||||
InvalidParameterValueError,
|
||||
)
|
||||
from .core import TaggedEC2Resource
|
||||
from ..utils import (
|
||||
@ -19,6 +20,35 @@ from ..utils import (
|
||||
)
|
||||
|
||||
|
||||
class VolumeModification(object):
|
||||
def __init__(self, volume, target_size=None, target_volume_type=None):
|
||||
if not any([target_size, target_volume_type]):
|
||||
raise InvalidParameterValueError(
|
||||
"Invalid input: Must specify at least one of size or type"
|
||||
)
|
||||
|
||||
self.volume = volume
|
||||
self.original_size = volume.size
|
||||
self.original_volume_type = volume.volume_type
|
||||
self.target_size = target_size or volume.size
|
||||
self.target_volume_type = target_volume_type or volume.volume_type
|
||||
|
||||
self.start_time = utc_date_and_time()
|
||||
self.end_time = utc_date_and_time()
|
||||
|
||||
def get_filter_value(self, filter_name):
|
||||
if filter_name == "original-size":
|
||||
return self.original_size
|
||||
elif filter_name == "original-volume-type":
|
||||
return self.original_volume_type
|
||||
elif filter_name == "target-size":
|
||||
return self.target_size
|
||||
elif filter_name == "target-volume-type":
|
||||
return self.target_volume_type
|
||||
elif filter_name == "volume-id":
|
||||
return self.volume.id
|
||||
|
||||
|
||||
class VolumeAttachment(CloudFormationModel):
|
||||
def __init__(self, volume, instance, device, status):
|
||||
self.volume = volume
|
||||
@ -78,6 +108,16 @@ class Volume(TaggedEC2Resource, CloudFormationModel):
|
||||
self.ec2_backend = ec2_backend
|
||||
self.encrypted = encrypted
|
||||
self.kms_key_id = kms_key_id
|
||||
self.modifications = []
|
||||
|
||||
def modify(self, target_size=None, target_volume_type=None):
|
||||
modification = VolumeModification(
|
||||
volume=self, target_size=target_size, target_volume_type=target_volume_type
|
||||
)
|
||||
self.modifications.append(modification)
|
||||
|
||||
self.size = modification.target_size
|
||||
self.volume_type = modification.target_volume_type
|
||||
|
||||
@staticmethod
|
||||
def cloudformation_name_type():
|
||||
@ -237,6 +277,20 @@ class EBSBackend:
|
||||
matches = generic_filter(filters, matches)
|
||||
return matches
|
||||
|
||||
def modify_volume(self, volume_id, target_size=None, target_volume_type=None):
|
||||
volume = self.get_volume(volume_id)
|
||||
volume.modify(target_size=target_size, target_volume_type=target_volume_type)
|
||||
return volume
|
||||
|
||||
def describe_volumes_modifications(self, volume_ids=None, filters=None):
|
||||
volumes = self.describe_volumes(volume_ids)
|
||||
modifications = []
|
||||
for volume in volumes:
|
||||
modifications.extend(volume.modifications)
|
||||
if filters:
|
||||
modifications = generic_filter(filters, modifications)
|
||||
return modifications
|
||||
|
||||
def get_volume(self, volume_id):
|
||||
volume = self.volumes.get(volume_id, None)
|
||||
if not volume:
|
||||
|
@ -74,6 +74,27 @@ class ElasticBlockStore(EC2BaseResponse):
|
||||
template = self.response_template(CREATE_VOLUME_RESPONSE)
|
||||
return template.render(volume=volume)
|
||||
|
||||
def modify_volume(self):
|
||||
volume_id = self._get_param("VolumeId")
|
||||
target_size = self._get_param("Size")
|
||||
target_volume_type = self._get_param("VolumeType")
|
||||
|
||||
if self.is_not_dryrun("ModifyVolume"):
|
||||
volume = self.ec2_backend.modify_volume(
|
||||
volume_id, target_size, target_volume_type
|
||||
)
|
||||
template = self.response_template(MODIFY_VOLUME_RESPONSE)
|
||||
return template.render(volume=volume)
|
||||
|
||||
def describe_volumes_modifications(self):
|
||||
filters = self._filters_from_querystring()
|
||||
volume_ids = self._get_multi_param("VolumeId")
|
||||
modifications = self.ec2_backend.describe_volumes_modifications(
|
||||
volume_ids=volume_ids, filters=filters
|
||||
)
|
||||
template = self.response_template(DESCRIBE_VOLUMES_MODIFICATIONS_RESPONSE)
|
||||
return template.render(modifications=modifications)
|
||||
|
||||
def delete_snapshot(self):
|
||||
snapshot_id = self._get_param("SnapshotId")
|
||||
if self.is_not_dryrun("DeleteSnapshot"):
|
||||
@ -402,3 +423,40 @@ MODIFY_SNAPSHOT_ATTRIBUTE_RESPONSE = """
|
||||
<return>true</return>
|
||||
</ModifySnapshotAttributeResponse>
|
||||
"""
|
||||
|
||||
MODIFY_VOLUME_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ModifyVolumeResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<volumeModification>
|
||||
{% set volume_modification = volume.modifications[-1] %}
|
||||
<modificationState>modifying</modificationState>
|
||||
<originalSize>{{ volume_modification.original_size }}</originalSize>
|
||||
<originalVolumeType>{{ volume_modification.original_volume_type }}</originalVolumeType>
|
||||
<progress>0</progress>
|
||||
<startTime>{{ volume_modification.start_time }}</startTime>
|
||||
<targetSize>{{ volume_modification.target_size }}</targetSize>
|
||||
<targetVolumeType>{{ volume_modification.target_volume_type }}</targetVolumeType>
|
||||
<volumeId>{{ volume.id }}</volumeId>
|
||||
</volumeModification>
|
||||
</ModifyVolumeResponse>"""
|
||||
|
||||
DESCRIBE_VOLUMES_MODIFICATIONS_RESPONSE = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<DescribeVolumesModificationsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<volumeModificationSet>
|
||||
{% for modification in modifications %}
|
||||
<item>
|
||||
<endTime>{{ modification.end_time }}</endTime>
|
||||
<modificationState>completed</modificationState>
|
||||
<originalSize>{{ modification.original_size }}</originalSize>
|
||||
<originalVolumeType>{{ modification.original_volume_type }}</originalVolumeType>
|
||||
<progress>100</progress>
|
||||
<startTime>{{ modification.start_time }}</startTime>
|
||||
<targetSize>{{ modification.target_size }}</targetSize>
|
||||
<targetVolumeType>{{ modification.target_volume_type }}</targetVolumeType>
|
||||
<volumeId>{{ modification.volume.id }}</volumeId>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</volumeModificationSet>
|
||||
</DescribeVolumesModificationsResponse>"""
|
||||
|
@ -46,6 +46,42 @@ def test_create_and_delete_volume():
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidVolume.NotFound")
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_modify_volumes():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
|
||||
old_size = 80
|
||||
new_size = 160
|
||||
new_type = "io2"
|
||||
|
||||
volume_id = ec2.create_volume(Size=old_size, AvailabilityZone="us-east-1a").id
|
||||
|
||||
# Ensure no modification records exist
|
||||
modifications = client.describe_volumes_modifications()
|
||||
modifications["VolumesModifications"].should.have.length_of(0)
|
||||
|
||||
# Ensure volume size can be modified
|
||||
response = client.modify_volume(VolumeId=volume_id, Size=new_size)
|
||||
response["VolumeModification"]["OriginalSize"].should.equal(old_size)
|
||||
response["VolumeModification"]["TargetSize"].should.equal(new_size)
|
||||
client.describe_volumes(VolumeIds=[volume_id])["Volumes"][0]["Size"].should.equal(
|
||||
new_size
|
||||
)
|
||||
|
||||
# Ensure volume type can be modified
|
||||
response = client.modify_volume(VolumeId=volume_id, VolumeType=new_type)
|
||||
response["VolumeModification"]["OriginalVolumeType"].should.equal("gp2")
|
||||
response["VolumeModification"]["TargetVolumeType"].should.equal(new_type)
|
||||
client.describe_volumes(VolumeIds=[volume_id])["Volumes"][0][
|
||||
"VolumeType"
|
||||
].should.equal(new_type)
|
||||
|
||||
# Ensure volume modifications are tracked
|
||||
modifications = client.describe_volumes_modifications()
|
||||
modifications["VolumesModifications"].should.have.length_of(2)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_delete_attached_volume():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
Loading…
Reference in New Issue
Block a user