Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new method to set/get object retention & Legal Hold #836

Merged
merged 4 commits into from
Feb 13, 2020

Conversation

sinhaashish
Copy link
Contributor

@sinhaashish sinhaashish commented Jan 27, 2020

Add new method to set/get object retention and Legal Hold

Function test not added as it will make the object WORM enabled and cant be deleted after successful run of test.

@sinhaashish sinhaashish force-pushed the get-set-object-retention branch 2 times, most recently from 4546da1 to b5c1c80 Compare January 27, 2020 17:21
harshavardhana
harshavardhana previously approved these changes Feb 2, 2020
@sinhaashish sinhaashish changed the title Add new method to set/get object retention Add new method to set/get object retention & Legal Hold Feb 2, 2020
@sinhaashish
Copy link
Contributor Author

sinhaashish commented Feb 2, 2020

@balamurugana Need one confirmation .
I am using public static final DateTimeFormatter RETENTION_DATE_FORMAT = EXPIRATION_DATE_FORMAT; for date format.
this is leading to an exception against MinIo but works fine against S3.

The exception is :

Exception in thread "main" java.lang.IllegalArgumentException: Invalid format: "2020-02-03T22:30:00Z" is malformed at "Z"
	at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:899)
	at io.minio.messages.ObjectRetentionConfiguration.retainUntil(ObjectRetentionConfiguration.java:80)
	at SetGetObjectRetentionConfig.main(SetGetObjectRetentionConfig.java:66)

As per S3 the retainUntil Date is of this form 2020-01-05T00:00:00.000Z Refer

But on Minio Server it is stored as 2020-01-05T00:00:00.00Z . Notice one 0 is less in the date format when stored in Minio. This leads to exception while parsing against Minio.

Can you please suggest that is it a server side bug , or do i need to have some change in client side.

@balamurugana
Copy link
Member

@balamurugana Need one confirmation .
I am using public static final DateTimeFormatter RETENTION_DATE_FORMAT = EXPIRATION_DATE_FORMAT; for date format.

You don't need to have RETENTION_DATE_FORMAT, instead use EXPIRATION_DATE_FORMAT directly.

this is leading to an exception against MinIo but works fine against S3.

The exception is :

Exception in thread "main" java.lang.IllegalArgumentException: Invalid format: "2020-02-03T22:30:00Z" is malformed at "Z"
	at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:899)
	at io.minio.messages.ObjectRetentionConfiguration.retainUntil(ObjectRetentionConfiguration.java:80)
	at SetGetObjectRetentionConfig.main(SetGetObjectRetentionConfig.java:66)

As per S3 the retainUntil Date is of this form 2020-01-05T00:00:00.000Z Refer

But on Minio Server it is stored as 2020-01-05T00:00:00.00Z . Notice one 0 is less in the date format when stored in Minio. This leads to exception while parsing against Minio.

Can you please suggest that is it a server side bug , or do i need to have some change in client side.

Server needs to comply with the spec, else its server bug.

String[] hashes = Digest.sha256Md5Hashes(data, len);
sha256Hash = hashes[0];
md5Hash = hashes[1];
headerMap.remove("retention","");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a good approach to pass and remove headers or params for sake of checksum calculation. You could pass a flag from higher level calls whether MD5 checksum calculation is required or not. You could add this improvement and remove these nested if guesses.


if (!versionId.isEmpty()) {
queryParamMap.put("versionId", versionId);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Treat null versionId as empty string to support minio server and add it into query param i.e.

if (versionId == null) {
  queryParamMap.put("versionId", "");
} else {
  queryParamMap.put("versionId", versionId);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to use this logic in all methods accepting versionId

* @throws InvalidResponseException upon a non-xml response from server
*/
public void setObjectRetention(String bucketName, String objectName, String versionId,
boolean bypassGovernanceRetention, ObjectRetentionConfiguration config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this boolean as last param looks like its optional.

headerMap.put("x-amz-bypass-governance-retention", "True");
}

HttpResponse response = executePut(bucketName, objectName, headerMap, queryParamMap, config, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config must be non-null and Mode and RetainUntilDate are mandatory configuration. You would need to add this validation properly.

* @throws InvalidResponseException upon a non-xml response from server
*/
public void setObjectLegalHold(String bucketName, String objectName, String versionId,
boolean legalHold)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would need to have below more friendlier APIs than boolean flag

public void enableObjectLegalHold(String bucketName, String objectName, String versionId)
public void disableObjectLegalHold(String bucketName, String objectName, String versionId)
public boolean isObjectLegalHoldEnabled(String bucketName, String objectName, String versionId)

* Indicates whether the specified object has a Legal Hold in place.
*/
public String getStatus() {
return status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to retrurn boolean depending on status; not string

stream = new BufferedInputStream(stream);
}
if (objectLockRetention) {
headerMap.put("retention","");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things you can do at the time of put object.

  1. retention (Mode and RetainUntilDate are required)
  2. legal hold (boolean to say ON or OFF)

You could send these changes in put object as separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will send a separate PR for this

@sinhaashish sinhaashish force-pushed the get-set-object-retention branch 3 times, most recently from 47d23da to e6668cb Compare February 6, 2020 06:23
@sinhaashish
Copy link
Contributor Author

@balamurugana PTAL

* Indicates whether the specified object has a Legal Hold in place or not.
*/
public Boolean getStatus() {
return Boolean.parseBoolean(status);
Copy link
Member

@balamurugana balamurugana Feb 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. have status(), not getStatus()
  2. I don't see javadoc of Boolean parses "ON"/"OFF" string. Are you sure it works?
  3. Return boolean not Boolean

/**
* Constructs a new CustomRetention object with given retention.
*/
public ObjectLockLegalHold(boolean legalHold) throws XmlPullParserException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argument is status not legalHold

public ObjectRetentionConfiguration(RetentionMode mode, DateTime retainUntilDate) throws XmlPullParserException,
InvalidArgumentException {
super();
super.name = "Retention";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call this() than repeating these two lines

*/
public ObjectLockLegalHold(boolean legalHold) throws XmlPullParserException {
super();
super.name = "LegalHold";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use this() and don't repeat logic

super();
super.name = "Retention";
if (mode == null) {
throw new InvalidArgumentException("null is not allowed in mode. Valid values are 'COMPLIANCE' and 'GOVERNANCE'");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change error message as null mode is not allowed

}
this.mode = mode.toString();
if (retainUntilDate == null) {
throw new InvalidArgumentException("retain until date cant be null, it must be a valid date.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change error message as null retainUntilDate is not allowed

/**
* Returns retain until date.
*/
public Date retainUntil() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method name should be same as member field retainUntilDate() and return DateTime not Date i.e. should be same as argument type in the constructor

@@ -1402,11 +1400,12 @@ private HttpResponse executeDelete(String bucketName, String objectName, Map<Str
* @param data HTTP request body data.
*/
private HttpResponse executePost(String bucketName, String objectName, Map<String,String> headerMap,
Map<String,String> queryParamMap, Object data)
Map<String,String> queryParamMap, Object data, boolean md5Required)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have overloaded executePost() i.e. with/without md5Required argument.

@@ -1439,13 +1438,13 @@ private HttpResponse executePost(String bucketName, String objectName, Map<Strin
* @param length Length of HTTP request body data.
*/
private HttpResponse executePut(String bucketName, String objectName, Map<String,String> headerMap,
Map<String,String> queryParamMap, String region, Object data, int length)
Map<String,String> queryParamMap, String region, Object data, int length, boolean md5Required)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have overloaded executePut() i.e. with/without md5Required

@@ -1461,11 +1460,12 @@ private HttpResponse executePut(String bucketName, String objectName, Map<String
* @param length Length of HTTP request body data.
*/
private HttpResponse executePut(String bucketName, String objectName, Map<String,String> headerMap,
Map<String,String> queryParamMap, Object data, int length)
Map<String,String> queryParamMap, Object data, int length, boolean md5Required)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@@ -2388,7 +2388,7 @@ public void copyObject(String bucketName, String objectName, Map<String,String>
headerMap.putAll(copyConditions.getConditions());
}

HttpResponse response = executePut(bucketName, objectName, headerMap, null, "", 0);
HttpResponse response = executePut(bucketName, objectName, headerMap, null, "", 0, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use overloaded executePut()/executePost() accepting md5Required argument to avoid unnecessary diffs.

* Returns mode.
*/
public RetentionMode mode() {
return RetentionMode.fromString(mode);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mode can be null. You need to return null accordingly

if (status.equals("ON")) {
return true;
}
return false;
Copy link
Member

@balamurugana balamurugana Feb 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return status != null && status.equals("ON"); is correct and sufficient


if ( !(versionId == null || versionId.isEmpty())) {
queryParamMap.put("versionId",versionId);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why you are missing out of versionId handling. Are you deferring in other places?

* @throws InsufficientDataException upon getting EOFException while reading given
*/
public void putObject(String bucketName, String objectName, String fileName, Long size, Map<String, String> headerMap,
ServerSideEncryption sse, String contentType, boolean objectLockRetention)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we agreed previously, you are going to send separate PR to have putObject() with retention and legal hold. Are you still considering to add in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that i will send as another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, why are doing this change here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, why are doing this change here?

This change is needed .
There are two cases to this flow:
User creates a bucket with lock function enabled.
Case 1 : User can put the object on the bucket and then at later point of time, set the retenetion. This case is handled in this PR.
Case 2 : User asks for retention/legalhold at the time of putting object. This will be handled in another PR.

If we dont have the below overloaded putObject then we wont be able to put the object on
lock enabled buckets and hence not able to test the PR.

  public void putObject(String bucketName, String objectName, String fileName, Long size, Map<String, String> headerMap,
                        ServerSideEncryption sse, String contentType, boolean objectLockRetention) 

If you think that case 2 should also be present in this PR , then i can come up with that .

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot add any changes to putObject() if you plan for separate PR.

@sinhaashish
Copy link
Contributor Author

@balamurugana PTAL

@harshavardhana harshavardhana merged commit 8427afc into minio:master Feb 13, 2020
@sinhaashish sinhaashish deleted the get-set-object-retention branch April 1, 2020 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants