EC2 - Error when deleting snapshots in use (#4721)
This commit is contained in:
parent
71daf79ffd
commit
973c55a36c
@ -275,6 +275,14 @@ class InvalidSnapshotIdError(EC2ClientError):
|
|||||||
) # Note: AWS returns empty message for this, as of 2014.08.22.
|
) # Note: AWS returns empty message for this, as of 2014.08.22.
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSnapshotInUse(EC2ClientError):
|
||||||
|
def __init__(self, snapshot_id, ami_id):
|
||||||
|
super().__init__(
|
||||||
|
"InvalidSnapshot.InUse",
|
||||||
|
f"The snapshot {snapshot_id} is currently in use by {ami_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidVolumeIdError(EC2ClientError):
|
class InvalidVolumeIdError(EC2ClientError):
|
||||||
def __init__(self, volume_id):
|
def __init__(self, volume_id):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
@ -84,6 +84,7 @@ from .exceptions import (
|
|||||||
InvalidSecurityGroupDuplicateError,
|
InvalidSecurityGroupDuplicateError,
|
||||||
InvalidSecurityGroupNotFoundError,
|
InvalidSecurityGroupNotFoundError,
|
||||||
InvalidSnapshotIdError,
|
InvalidSnapshotIdError,
|
||||||
|
InvalidSnapshotInUse,
|
||||||
InvalidSubnetConflictError,
|
InvalidSubnetConflictError,
|
||||||
InvalidSubnetIdError,
|
InvalidSubnetIdError,
|
||||||
InvalidSubnetRangeError,
|
InvalidSubnetRangeError,
|
||||||
@ -1668,6 +1669,7 @@ class Ami(TaggedEC2Resource):
|
|||||||
root_device_name="/dev/sda1",
|
root_device_name="/dev/sda1",
|
||||||
sriov="simple",
|
sriov="simple",
|
||||||
region_name="us-east-1a",
|
region_name="us-east-1a",
|
||||||
|
snapshot_description=None,
|
||||||
):
|
):
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.id = ami_id
|
self.id = ami_id
|
||||||
@ -1721,8 +1723,11 @@ class Ami(TaggedEC2Resource):
|
|||||||
|
|
||||||
# AWS auto-creates these, we should reflect the same.
|
# AWS auto-creates these, we should reflect the same.
|
||||||
volume = self.ec2_backend.create_volume(size=15, zone_name=region_name)
|
volume = self.ec2_backend.create_volume(size=15, zone_name=region_name)
|
||||||
|
snapshot_description = (
|
||||||
|
snapshot_description or "Auto-created snapshot for AMI %s" % self.id
|
||||||
|
)
|
||||||
self.ebs_snapshot = self.ec2_backend.create_snapshot(
|
self.ebs_snapshot = self.ec2_backend.create_snapshot(
|
||||||
volume.id, "Auto-created snapshot for AMI %s" % self.id, owner_id
|
volume.id, snapshot_description, owner_id, from_ami=ami_id
|
||||||
)
|
)
|
||||||
self.ec2_backend.delete_volume(volume.id)
|
self.ec2_backend.delete_volume(volume.id)
|
||||||
|
|
||||||
@ -1802,6 +1807,7 @@ class AmiBackend(object):
|
|||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
owner_id=OWNER_ID,
|
owner_id=OWNER_ID,
|
||||||
|
snapshot_description=f"Created by CreateImage({instance_id}) for {ami_id}",
|
||||||
)
|
)
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
ami.add_tag(tag["Key"], tag["Value"])
|
ami.add_tag(tag["Key"], tag["Value"])
|
||||||
@ -3470,6 +3476,7 @@ class Snapshot(TaggedEC2Resource):
|
|||||||
description,
|
description,
|
||||||
encrypted=False,
|
encrypted=False,
|
||||||
owner_id=OWNER_ID,
|
owner_id=OWNER_ID,
|
||||||
|
from_ami=None,
|
||||||
):
|
):
|
||||||
self.id = snapshot_id
|
self.id = snapshot_id
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
@ -3481,6 +3488,7 @@ class Snapshot(TaggedEC2Resource):
|
|||||||
self.status = "completed"
|
self.status = "completed"
|
||||||
self.encrypted = encrypted
|
self.encrypted = encrypted
|
||||||
self.owner_id = owner_id
|
self.owner_id = owner_id
|
||||||
|
self.from_ami = from_ami
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(self, filter_name):
|
||||||
if filter_name == "description":
|
if filter_name == "description":
|
||||||
@ -3609,12 +3617,14 @@ class EBSBackend(object):
|
|||||||
volume.attachment = None
|
volume.attachment = None
|
||||||
return old_attachment
|
return old_attachment
|
||||||
|
|
||||||
def create_snapshot(self, volume_id, description, owner_id=None):
|
def create_snapshot(self, volume_id, description, owner_id=None, from_ami=None):
|
||||||
snapshot_id = random_snapshot_id()
|
snapshot_id = random_snapshot_id()
|
||||||
volume = self.get_volume(volume_id)
|
volume = self.get_volume(volume_id)
|
||||||
params = [self, snapshot_id, volume, description, volume.encrypted]
|
params = [self, snapshot_id, volume, description, volume.encrypted]
|
||||||
if owner_id:
|
if owner_id:
|
||||||
params.append(owner_id)
|
params.append(owner_id)
|
||||||
|
if from_ami:
|
||||||
|
params.append(from_ami)
|
||||||
snapshot = Snapshot(*params)
|
snapshot = Snapshot(*params)
|
||||||
self.snapshots[snapshot_id] = snapshot
|
self.snapshots[snapshot_id] = snapshot
|
||||||
return snapshot
|
return snapshot
|
||||||
@ -3653,6 +3663,9 @@ class EBSBackend(object):
|
|||||||
|
|
||||||
def delete_snapshot(self, snapshot_id):
|
def delete_snapshot(self, snapshot_id):
|
||||||
if snapshot_id in self.snapshots:
|
if snapshot_id in self.snapshots:
|
||||||
|
snapshot = self.snapshots[snapshot_id]
|
||||||
|
if snapshot.from_ami and snapshot.from_ami in self.amis:
|
||||||
|
raise InvalidSnapshotInUse(snapshot_id, snapshot.from_ami)
|
||||||
return self.snapshots.pop(snapshot_id)
|
return self.snapshots.pop(snapshot_id)
|
||||||
raise InvalidSnapshotIdError(snapshot_id)
|
raise InvalidSnapshotIdError(snapshot_id)
|
||||||
|
|
||||||
|
@ -64,9 +64,7 @@ def test_ami_create_and_delete():
|
|||||||
)
|
)
|
||||||
[s.id for s in snapshots].should.contain(retrieved_image_snapshot_id)
|
[s.id for s in snapshots].should.contain(retrieved_image_snapshot_id)
|
||||||
snapshot = [s for s in snapshots if s.id == retrieved_image_snapshot_id][0]
|
snapshot = [s for s in snapshots if s.id == retrieved_image_snapshot_id][0]
|
||||||
snapshot.description.should.equal(
|
snapshot.description.should.match("Created by CreateImage")
|
||||||
"Auto-created snapshot for AMI {0}".format(retrieved_image.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
# root device should be in AMI's block device mappings
|
# root device should be in AMI's block device mappings
|
||||||
root_mapping = retrieved_image.block_device_mapping.get(
|
root_mapping = retrieved_image.block_device_mapping.get(
|
||||||
@ -167,8 +165,9 @@ def test_ami_create_and_delete_boto3():
|
|||||||
snapshot = [s for s in snapshots if s["SnapshotId"] == retrieved_image_snapshot_id][
|
snapshot = [s for s in snapshots if s["SnapshotId"] == retrieved_image_snapshot_id][
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
|
image_id = retrieved_image["ImageId"]
|
||||||
snapshot["Description"].should.equal(
|
snapshot["Description"].should.equal(
|
||||||
"Auto-created snapshot for AMI {0}".format(retrieved_image["ImageId"])
|
f"Created by CreateImage({instance_id}) for {image_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# root device should be in AMI's block device mappings
|
# root device should be in AMI's block device mappings
|
||||||
@ -1794,3 +1793,39 @@ def test_describe_images_dryrun():
|
|||||||
ex.value.response["Error"]["Message"].should.equal(
|
ex.value.response["Error"]["Message"].should.equal(
|
||||||
"An error occurred (DryRunOperation) when calling the DescribeImages operation: Request would have succeeded, but DryRun flag is set"
|
"An error occurred (DryRunOperation) when calling the DescribeImages operation: Request would have succeeded, but DryRun flag is set"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_delete_snapshot_from_create_image():
|
||||||
|
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
||||||
|
resp = ec2_client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
|
||||||
|
instance_id = resp["Instances"][0]["InstanceId"]
|
||||||
|
ami = ec2_client.create_image(InstanceId=instance_id, Name="test")
|
||||||
|
ami_id = ami["ImageId"]
|
||||||
|
|
||||||
|
snapshots = ec2_client.describe_snapshots(
|
||||||
|
Filters=[
|
||||||
|
{
|
||||||
|
"Name": "description",
|
||||||
|
"Values": ["Created by CreateImage(" + instance_id + "*"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)["Snapshots"]
|
||||||
|
snapshot_id = snapshots[0]["SnapshotId"]
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
ec2_client.delete_snapshot(SnapshotId=snapshot_id)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidSnapshot.InUse")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"The snapshot {snapshot_id} is currently in use by {ami_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Deregister the Ami first
|
||||||
|
ec2_client.deregister_image(ImageId=ami_id)
|
||||||
|
|
||||||
|
# Now we can delete the snapshot without problems
|
||||||
|
ec2_client.delete_snapshot(SnapshotId=snapshot_id)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
ec2_client.describe_snapshots(SnapshotIds=[snapshot_id])
|
||||||
|
exc.value.response["Error"]["Code"].should.equal("InvalidSnapshot.NotFound")
|
||||||
|
Loading…
Reference in New Issue
Block a user