Part 4 : Reading versioned object and Delete Markers.

Anirudha | Wed, 03/18/2020 - 06:20

In the previous post we saw, how to enable versioning on bucket and how each versions are represented in the bucket. Now we will take a look at reading different version of object.

Get_object without VersionId :

If you don’t specify VersionId in get_object call, then this will always return the latest object version. If you see the previous code snippet, last version uploaded has content = 'The God Father'

>>> s3c.get_object(Bucket=bucket, Key=objname)["Body"].read()

'The God Father'

>>>

And that’s exactly we see in get_object without version-id  .

Get_object with VersionId :

If you give the VersionId (which was returned in the previous output) with get_object, this will return the respective object content.

>>> s3c.get_object(Bucket=bucket, Key=objname, VersionId="209394")["Body"].read()

'Star Wars'

>>> s3c.get_object(Bucket=bucket, Key=objname, VersionId="209393")["Body"].read()

'The Dark Knight'

>>>

Get-object call without version-id will always return latest object-version, while to access specific object-version, just execute same API with version-id. Simple, isnt it.

Deleting Object :

Similar to get-object, you will have to specify version-id in delete-object API call in order to to delete the specific object version. If you don't specify Version-id in the delete call, then this will end up creating a delete marker on the object instead of deleting it.

What is Delete Marker :

Delete marker is technically a new object version. Delete call without VersionId will create a delete marker on the object, which becomes the latest version of the object. And any get_object call without versionID would fail. Existence of the delete marker means, latest version of the object is deleted . Though all the previous versions of the object stay intact.

>>> s3c.delete_object(Bucket=bucket, Key=objname)

{u'VersionId': '209396', 'ResponseMetadata': {'HTTPStatusCode': 204, 'RetryAttempts': 0, 'HostId': '', 'RequestId': '15FCC095DD310205', 'HTTPHeaders': {'accept-ranges': 'bytes', 'x-amz-delete-marker': 'true', 'vary': 'Origin', 'server': 'NutanixS3', 'x-amz-request-id': '15FCC095DD310205', 'date': 'Mon, 16 Mar 2020 10:01:58 GMT', 'x-amz-version-id': '209396'}}, u'DeleteMarker': True}

>>>

You can see in the above output, delete_object API execution has returned VersionID='209396' and 'DeleteMarker'=True . You will not find the DeleteMarker field in previous code execution when we created multiple object versions (with valid data).

If you execute get_object API now, it will return an error :

>>> s3c.get_object(Bucket=bucket, Key=objname)

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "/Users/anirudha.sonar/Library/Python/2.7/lib/python/site-packages/botocore/client.py", line 316, in _api_call

    return self._make_api_call(operation_name, kwargs)

  File "/Users/anirudha.sonar/Library/Python/2.7/lib/python/site-packages/botocore/client.py", line 626, in _make_api_call

    raise error_class(parsed_response, operation_name)

botocore.errorfactory.NoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.

>>>

It just means, latest object version here is DeleteMarker and you can not read this object. 

To get previous object, just specify VersionId :

>>> s3c.get_object(Bucket=bucket, Key=objname,VersionId="209395")["Body"].read()

'The God Father'

>>>

 

You can delete delete marker by initiating another delete_object api with VersionId (of delete marker)

>>> s3c.delete_object(Bucket=bucket, Key=objname, VersionId='209396')

{u'VersionId': '209396', 'ResponseMetadata': {'HTTPStatusCode': 204, 'RetryAttempts': 0, 'HostId': '', 'RequestId': '15FCC1C12785F8C1', 'HTTPHeaders': {'accept-ranges': 'bytes', 'vary': 'Origin', 'server': 'NutanixS3', 'x-amz-request-id': '15FCC1C12785F8C1', 'date': 'Mon, 16 Mar 2020 10:23:24 GMT', 'x-amz-version-id': '209396'}}}

>>>

Delete_object call without Versionid will create a new DeleteMarker with every API execution. So you may end up in having one to many delete markers on the same object.

Now execute get_object call :

>>> s3c.get_object(Bucket=bucket, Key=objname,VersionId="209395")["Body"].read()

'The God Father'

>>>

So here delete_object call has deleted the previously created DeleteMarker which made object-version='209395'  as the latest version of the object. And get_object call without VersionId  always returns latest version of the object, hence above get_object returned content = 'The God Father'

Now lets delete object-version='209395'

>>> s3c.delete_object(Bucket=bucket, Key=objname,VersionId='209395')

{u'VersionId': '209395', 'ResponseMetadata': {'HTTPStatusCode': 204, 'RetryAttempts': 0, 'HostId': '', 'RequestId': '15FCC116FDBFB779', 'HTTPHeaders': {'accept-ranges': 'bytes', 'vary': 'Origin', 'server': 'NutanixS3', 'x-amz-request-id': '15FCC116FDBFB779', 'date': 'Mon, 16 Mar 2020 10:11:13 GMT', 'x-amz-version-id': '209395'}}}

>>>

This will permanently delete a specific version of the object. In case if this was the latest version, then the previously created version becomes the latest version . To confirm this , if I initiate get_object call now, I should get content=  “Star Wars” (Refer : From above code and output, this is how object version to content mapping look section for versioned-content mapping)