EC2 - create_snapshots (#4767)
This commit is contained in:
parent
44e01a298e
commit
d53dd23390
@ -643,6 +643,12 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
||||
# reset parent
|
||||
obj = []
|
||||
parent[keylist[i - 1]] = obj
|
||||
elif isinstance(obj, dict):
|
||||
# initialize dict
|
||||
obj[key] = {}
|
||||
# step into
|
||||
parent = obj
|
||||
obj = obj[key]
|
||||
elif key.isdigit():
|
||||
index = int(key) - 1
|
||||
if len(obj) <= index:
|
||||
@ -651,12 +657,6 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
||||
# step into
|
||||
parent = obj
|
||||
obj = obj[index]
|
||||
else:
|
||||
# initialize dict
|
||||
obj[key] = {}
|
||||
# step into
|
||||
parent = obj
|
||||
obj = obj[key]
|
||||
if isinstance(obj, list):
|
||||
obj.append(value)
|
||||
else:
|
||||
|
@ -741,6 +741,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
||||
self.platform = ami.platform if ami else None
|
||||
self.virtualization_type = ami.virtualization_type if ami else "paravirtual"
|
||||
self.architecture = ami.architecture if ami else "x86_64"
|
||||
self.root_device_name = ami.root_device_name if ami else None
|
||||
|
||||
# handle weird bug around user_data -- something grabs the repr(), so
|
||||
# it must be clean
|
||||
@ -3625,6 +3626,29 @@ class EBSBackend(object):
|
||||
self.snapshots[snapshot_id] = snapshot
|
||||
return snapshot
|
||||
|
||||
def create_snapshots(self, instance_spec, description, tags):
|
||||
"""
|
||||
The CopyTagsFromSource-parameter is not yet implemented.
|
||||
"""
|
||||
instance = self.get_instance(instance_spec["InstanceId"])
|
||||
block_device_mappings = instance.block_device_mapping
|
||||
|
||||
if str(instance_spec.get("ExcludeBootVolume", False)).lower() == "true":
|
||||
volumes = [
|
||||
m.volume_id
|
||||
for k, m in block_device_mappings.items()
|
||||
if k != instance.root_device_name
|
||||
]
|
||||
else:
|
||||
volumes = [m.volume_id for m in block_device_mappings.values()]
|
||||
|
||||
snapshots = [
|
||||
self.create_snapshot(v_id, description=description) for v_id in volumes
|
||||
]
|
||||
for snapshot in snapshots:
|
||||
snapshot.add_tags(tags)
|
||||
return snapshots
|
||||
|
||||
def describe_snapshots(self, snapshot_ids=None, filters=None):
|
||||
matches = self.snapshots.copy().values()
|
||||
if snapshot_ids:
|
||||
|
@ -39,6 +39,20 @@ class ElasticBlockStore(BaseResponse):
|
||||
template = self.response_template(CREATE_SNAPSHOT_RESPONSE)
|
||||
return template.render(snapshot=snapshot)
|
||||
|
||||
def create_snapshots(self):
|
||||
params = self._get_params()
|
||||
instance_spec = params.get("InstanceSpecification")
|
||||
description = params.get("Description", "")
|
||||
tags = self._parse_tag_specification("TagSpecification")
|
||||
snapshot_tags = tags.get("snapshot", {})
|
||||
|
||||
if self.is_not_dryrun("CreateSnapshots"):
|
||||
snapshots = self.ec2_backend.create_snapshots(
|
||||
instance_spec, description, snapshot_tags
|
||||
)
|
||||
template = self.response_template(CREATE_SNAPSHOTS_RESPONSE)
|
||||
return template.render(snapshots=snapshots)
|
||||
|
||||
def create_volume(self):
|
||||
size = self._get_param("Size")
|
||||
zone = self._get_param("AvailabilityZone")
|
||||
@ -286,6 +300,35 @@ CREATE_SNAPSHOT_RESPONSE = """<CreateSnapshotResponse xmlns="http://ec2.amazonaw
|
||||
</tagSet>
|
||||
</CreateSnapshotResponse>"""
|
||||
|
||||
CREATE_SNAPSHOTS_RESPONSE = """<CreateSnapshotsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<snapshotSet>
|
||||
{% for snapshot in snapshots %}
|
||||
<item>
|
||||
<snapshotId>{{ snapshot.id }}</snapshotId>
|
||||
<volumeId>{{ snapshot.volume.id }}</volumeId>
|
||||
<status>pending</status>
|
||||
<startTime>{{ snapshot.start_time}}</startTime>
|
||||
<progress>60%</progress>
|
||||
<ownerId>{{ snapshot.owner_id }}</ownerId>
|
||||
<volumeSize>{{ snapshot.volume.size }}</volumeSize>
|
||||
<description>{{ snapshot.description }}</description>
|
||||
<encrypted>{{ 'true' if snapshot.encrypted else 'false' }}</encrypted>
|
||||
<tagSet>
|
||||
{% for tag in snapshot.get_tags() %}
|
||||
<item>
|
||||
<resourceId>{{ tag.resource_id }}</resourceId>
|
||||
<resourceType>{{ tag.resource_type }}</resourceType>
|
||||
<key>{{ tag.key }}</key>
|
||||
<value>{{ tag.value }}</value>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</tagSet>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</snapshotSet>
|
||||
</CreateSnapshotsResponse>"""
|
||||
|
||||
COPY_SNAPSHOT_RESPONSE = """<CopySnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<snapshotId>{{ snapshot.id }}</snapshotId>
|
||||
|
@ -909,3 +909,143 @@ def test_create_volume_with_non_standard_type(volume_type):
|
||||
|
||||
volume = ec2.describe_volumes(VolumeIds=[volume["VolumeId"]])["Volumes"][0]
|
||||
volume["VolumeType"].should.equal(volume_type)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_snapshots_dryrun():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.create_snapshots(
|
||||
InstanceSpecification={"InstanceId": "asf"}, DryRun=True
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
|
||||
ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"An error occurred (DryRunOperation) when calling the CreateSnapshots operation: Request would have succeeded, but DryRun flag is set"
|
||||
)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_snapshots_with_tagspecification():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
||||
reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
|
||||
instance = reservation["Instances"][0]
|
||||
|
||||
resp = client.create_snapshots(
|
||||
Description="my tagged snapshots",
|
||||
InstanceSpecification={"InstanceId": instance["InstanceId"]},
|
||||
TagSpecifications=[
|
||||
{
|
||||
"ResourceType": "snapshot",
|
||||
"Tags": [
|
||||
{"Key": "key1", "Value": "val1"},
|
||||
{"Key": "key2", "Value": "val2"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
snapshots = resp["Snapshots"]
|
||||
|
||||
snapshots.should.have.length_of(1)
|
||||
snapshots[0].should.have.key("Description").equals("my tagged snapshots")
|
||||
snapshots[0].should.have.key("Tags").equals(
|
||||
[{"Key": "key1", "Value": "val1"}, {"Key": "key2", "Value": "val2"}]
|
||||
)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_snapshots_single_volume():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
|
||||
reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
|
||||
instance = reservation["Instances"][0]
|
||||
|
||||
instance = client.describe_instances(InstanceIds=[instance["InstanceId"]])[
|
||||
"Reservations"
|
||||
][0]["Instances"][0]
|
||||
boot_volume = instance["BlockDeviceMappings"][0]["Ebs"]
|
||||
|
||||
snapshots = client.create_snapshots(
|
||||
InstanceSpecification={"InstanceId": instance["InstanceId"]}
|
||||
)["Snapshots"]
|
||||
|
||||
snapshots.should.have.length_of(1)
|
||||
snapshots[0].should.have.key("Encrypted").equals(False)
|
||||
snapshots[0].should.have.key("VolumeId").equals(boot_volume["VolumeId"])
|
||||
snapshots[0].should.have.key("VolumeSize").equals(8)
|
||||
snapshots[0].should.have.key("SnapshotId")
|
||||
snapshots[0].should.have.key("Description").equals("")
|
||||
snapshots[0].should.have.key("Tags").equals([])
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_snapshots_multiple_volumes():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
|
||||
reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
|
||||
instance = reservation["Instances"][0]
|
||||
|
||||
instance = client.describe_instances(InstanceIds=[instance["InstanceId"]])[
|
||||
"Reservations"
|
||||
][0]["Instances"][0]
|
||||
boot_volume = instance["BlockDeviceMappings"][0]["Ebs"]
|
||||
|
||||
volume1 = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a")
|
||||
volume1.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdh")
|
||||
|
||||
volume2 = ec2.create_volume(Size=100, AvailabilityZone="us-east-1b")
|
||||
volume2.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdg")
|
||||
|
||||
snapshots = client.create_snapshots(
|
||||
InstanceSpecification={"InstanceId": instance["InstanceId"]}
|
||||
)["Snapshots"]
|
||||
|
||||
# 3 Snapshots ; 1 boot, two additional volumes
|
||||
snapshots.should.have.length_of(3)
|
||||
# 3 unique snapshot IDs
|
||||
set([s["SnapshotId"] for s in snapshots]).should.have.length_of(3)
|
||||
|
||||
boot_snapshot = next(
|
||||
s for s in snapshots if s["VolumeId"] == boot_volume["VolumeId"]
|
||||
)
|
||||
boot_snapshot.should.have.key("VolumeSize").equals(8)
|
||||
|
||||
snapshot1 = next(s for s in snapshots if s["VolumeId"] == volume1.volume_id)
|
||||
snapshot1.should.have.key("VolumeSize").equals(80)
|
||||
|
||||
snapshot2 = next(s for s in snapshots if s["VolumeId"] == volume2.volume_id)
|
||||
snapshot2.should.have.key("VolumeSize").equals(100)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_snapshots_multiple_volumes_without_boot():
|
||||
client = boto3.client("ec2", region_name="us-east-1")
|
||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||
|
||||
reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
|
||||
instance = reservation["Instances"][0]
|
||||
|
||||
volume1 = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a")
|
||||
volume1.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdh")
|
||||
|
||||
volume2 = ec2.create_volume(Size=100, AvailabilityZone="us-east-1b")
|
||||
volume2.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdg")
|
||||
|
||||
snapshots = client.create_snapshots(
|
||||
InstanceSpecification={
|
||||
"InstanceId": instance["InstanceId"],
|
||||
"ExcludeBootVolume": True,
|
||||
}
|
||||
)["Snapshots"]
|
||||
|
||||
# 1 Snapshots ; Only the additional volumes are returned
|
||||
snapshots.should.have.length_of(2)
|
||||
|
||||
snapshot1 = next(s for s in snapshots if s["VolumeId"] == volume1.volume_id)
|
||||
snapshot1.should.have.key("VolumeSize").equals(80)
|
||||
|
||||
snapshot2 = next(s for s in snapshots if s["VolumeId"] == volume2.volume_id)
|
||||
snapshot2.should.have.key("VolumeSize").equals(100)
|
||||
|
Loading…
Reference in New Issue
Block a user