1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Added versioned delete_multiple_objects support

Added the VersionId support in the delete_multiple_objects method.
Assume that all object versions are specified in the versionId hash.
The hash maps an object name to an array of versions. If a multiple
versions are to be deleted, the caller must insert the object
multiple times into the object_names array and insert all the versions
into the array associated with that object_name.
This commit is contained in:
Timur Alperovich 2012-05-20 18:13:04 -07:00
parent 89a7a9fc39
commit fd1acc69bf
2 changed files with 165 additions and 5 deletions

View file

@ -29,19 +29,32 @@ module Fog
# ==== See Also
# http://docs.amazonwebservices.com/AmazonS3/latest/API/multiobjectdeleteapi.html
# bucket_name -- name of the bucket to use
# object_names -- filename
# For versioned deletes, options should include a version_ids hash, which
# maps from filename to an array of versions.
# The semantics are that for each (object_name, version) tuple, the
# caller must insert the object_name and an associated version (if
# desired), so for n versions, the object must be inserted n times.
def delete_multiple_objects(bucket_name, object_names, options = {})
data = "<Delete>"
data << "<Quiet>true</Quiet>" if options.delete(:quiet)
version_ids = options.delete('versionId')
object_names.each do |object_name|
data << "<Object>"
data << "<Key>#{object_name}</Key>"
data << "<Key>#{CGI.escape(object_name)}</Key>"
object_version = version_ids.nil? ? nil : version_ids[object_name]
if object_version
data << "<VersionId>#{CGI.escape(object_version)}</VersionId>"
end
data << "</Object>"
end
data << "</Delete>"
headers = options
headers['Content-Length'] = data.length
headers['Content-MD5'] = Base64.encode64(Digest::MD5.digest(data)).strip
headers['Content-MD5'] = Base64.encode64(Digest::MD5.digest(data)).
gsub("\n", '')
request({
:body => data,
@ -63,10 +76,12 @@ module Fog
if bucket = self.data[:buckets][bucket_name]
response.status = 200
response.body = { 'DeleteResult' => [] }
version_ids = options.delete('versionId')
object_names.each do |object_name|
bucket[:objects].delete(object_name)
deleted_entry = { 'Deleted' => { 'Key' => object_name } }
response.body['DeleteResult'] << deleted_entry
object_version = version_ids.nil? ? nil : version_ids[object_name]
response.body['DeleteResult'] << delete_object_helper(bucket,
object_name,
object_version)
end
else
response.status = 404
@ -75,6 +90,81 @@ module Fog
response
end
private
def delete_object_helper(bucket, object_name, version_id)
response = { 'Deleted' => {} }
if bucket[:versioning]
bucket[:objects][object_name] ||= []
if version_id
version = bucket[:objects][object_name].find { |object| object['VersionId'] == version_id}
# S3 special cases the 'null' value to not error out if no such version exists.
if version || (version_id == 'null')
bucket[:objects][object_name].delete(version)
bucket[:objects].delete(object_name) if bucket[:objects][object_name].empty?
response['Deleted'] = { 'Key' => object_name,
'VersionId' => version_id,
'DeleteMarker' => 'true',
'DeleteMarkerVersionId' => version_id
}
else
response = delete_error_body(object_name,
version_id,
'InvalidVersion',
'Invalid version ID specified')
end
else
delete_marker = {
:delete_marker => true,
'Key' => object_name,
'VersionId' => bucket[:versioning] == 'Enabled' ? Fog::Mock.random_base64(32) : 'null',
'Last-Modified' => Fog::Time.now.to_date_header
}
# When versioning is suspended, a delete marker is placed if the last object ID is not the value 'null',
# otherwise the last object is replaced.
if bucket[:versioning] == 'Suspended' && bucket[:objects][object_name].first['VersionId'] == 'null'
bucket[:objects][object_name].shift
end
bucket[:objects][object_name].unshift(delete_marker)
response['Deleted'] = { 'Key' => object_name,
'VersionId' => delete_marker['VersionId'],
'DeleteMarkerVersionId' =>
delete_marker['VersionId'],
'DeleteMarker' => 'true',
}
end
else
if version_id && version_id != 'null'
response = delete_error_body(object_name,
version_id,
'InvalidVersion',
'Invalid version ID specified')
response = invalid_version_id_payload(version_id)
else
bucket[:objects].delete(object_name)
response['Deleted'] = { 'Key' => object_name }
end
end
response
end
def delete_error_body(key, version_id, message, code)
{
'Error' => {
'Code' => code,
'Message' => message,
'VersionId' => version_id,
'Key' => key,
}
}
end
end
end
end

View file

@ -134,6 +134,75 @@ Shindo.tests('Fog::Storage[:aws] | versioning', [:aws]) do
end
end
tests("deleting_multiple_objects('#{@aws_bucket_name}", 'file') do
clear_bucket
bucket = Fog::Storage[:aws].directories.get(@aws_bucket_name)
file_count = 5
file_names = []
files = {}
file_count.times do |id|
file_names << "file_#{id}"
files[file_names.last] = bucket.files.create(:body => 'a',
:key => file_names.last)
end
tests("deleting an object just stores a delete marker").returns(true) do
Fog::Storage[:aws].delete_multiple_objects(@aws_bucket_name,
file_names)
versions = Fog::Storage[:aws].get_bucket_object_versions(
@aws_bucket_name)
all_versions = {}
versions.body['Versions'].each do |version|
object = version[version.keys.first]
next if file_names.index(object['Key']).nil?
if !all_versions.has_key?(object['Key'])
all_versions[object['Key']] = version.has_key?('DeleteMarker')
else
all_versions[object['Key']] |= version.has_key?('DeleteMarker')
end
end
all_true = true
all_versions.values.each do |marker|
all_true = false if !marker
end
all_true
end
tests("there are two versions: the original and the delete marker").
returns(file_count*2) do
versions = Fog::Storage[:aws].get_bucket_object_versions(
@aws_bucket_name)
versions.body['Versions'].size
end
tests("deleting the delete marker makes the object available again").
returns(true) do
versions = Fog::Storage[:aws].get_bucket_object_versions(
@aws_bucket_name)
delete_markers = []
file_versions = {}
versions.body['Versions'].each do |version|
object = version[version.keys.first]
next if object['VersionId'] == files[object['Key']].version
file_versions[object['Key']] = object['VersionId']
end
Fog::Storage[:aws].delete_multiple_objects(@aws_bucket_name,
file_names,
'versionId' => file_versions)
all_true = true
file_names.each do |file|
res = Fog::Storage[:aws].get_object(@aws_bucket_name, file)
all_true = false if res.headers['x-amz-version-id'] !=
files[file].version
end
all_true
end
end
tests("get_bucket('#{@aws_bucket_name}'") do
clear_bucket
@ -181,6 +250,7 @@ Shindo.tests('Fog::Storage[:aws] | versioning', [:aws]) do
tests("#delete_object('#{@aws_bucket_name}', '#{file.key}', 'versionId' => 'bad_version'").raises(Excon::Errors::BadRequest) do
Fog::Storage[:aws].delete_object(@aws_bucket_name, file.key, 'versionId' => '-1')
end
end
# don't keep the bucket around