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

feat: adds support for restore token #2768

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,12 @@ Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) {
return this;
}

@Override
Builder setRestoreToken(String restoreToken){
infoBuilder.setRestoreToken(restoreToken);
return this;
}

@Override
public Builder setRetention(Retention retention) {
infoBuilder.setRetention(retention);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public class BlobInfo implements Serializable {
private final Retention retention;
private final OffsetDateTime softDeleteTime;
private final OffsetDateTime hardDeleteTime;
private final String restoreToken;
private final transient ImmutableSet<NamedField> modifiedFields;

/** This class is meant for internal use only. Users are discouraged from using this class. */
Expand Down Expand Up @@ -531,6 +532,8 @@ Builder setRetentionExpirationTimeOffsetDateTime(OffsetDateTime retentionExpirat

abstract Builder setHardDeleteTime(OffsetDateTime hardDeleteTIme);

abstract Builder setRestoreToken(String restoreToken);

public abstract Builder setRetention(Retention retention);

/** Creates a {@code BlobInfo} object. */
Expand Down Expand Up @@ -634,6 +637,7 @@ static final class BuilderImpl extends Builder {
private Retention retention;
private OffsetDateTime softDeleteTime;
private OffsetDateTime hardDeleteTime;
private String restoreToken;
private final ImmutableSet.Builder<NamedField> modifiedFields = ImmutableSet.builder();

BuilderImpl(BlobId blobId) {
Expand Down Expand Up @@ -674,6 +678,7 @@ static final class BuilderImpl extends Builder {
retention = blobInfo.retention;
softDeleteTime = blobInfo.softDeleteTime;
hardDeleteTime = blobInfo.hardDeleteTime;
restoreToken = blobInfo.restoreToken;
}

@Override
Expand Down Expand Up @@ -1065,6 +1070,15 @@ Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) {
return this;
}

@Override
Builder setRestoreToken(String restoreToken) {
if(!Objects.equals(this.restoreToken, restoreToken)){
modifiedFields.add(BlobField.RESTORE_TOKEN);
}
this.restoreToken = restoreToken;
return this;
}

@Override
public Builder setRetention(Retention retention) {
// todo: b/308194853
Expand Down Expand Up @@ -1299,6 +1313,7 @@ Builder clearRetentionExpirationTime() {
retention = builder.retention;
softDeleteTime = builder.softDeleteTime;
hardDeleteTime = builder.hardDeleteTime;
restoreToken = builder.restoreToken;
modifiedFields = builder.modifiedFields.build();
}

Expand Down Expand Up @@ -1704,6 +1719,14 @@ public OffsetDateTime getHardDeleteTime() {
return hardDeleteTime;
}

/**
* If this is a soft-deleted object in an HNS-enabled bucket, returns the restore token which will
* be necessary to restore it if there's a name conflict with another object.
*/
public String getRestoreToken() {
return restoreToken;
}

/** Returns the object's Retention policy. */
public Retention getRetention() {
return retention;
Expand Down Expand Up @@ -1761,7 +1784,8 @@ public int hashCode() {
retention,
retentionExpirationTime,
softDeleteTime,
hardDeleteTime);
hardDeleteTime,
restoreToken);
}

@Override
Expand Down Expand Up @@ -1805,7 +1829,8 @@ public boolean equals(Object o) {
&& Objects.equals(retentionExpirationTime, blobInfo.retentionExpirationTime)
&& Objects.equals(retention, blobInfo.retention)
&& Objects.equals(softDeleteTime, blobInfo.softDeleteTime)
&& Objects.equals(hardDeleteTime, blobInfo.hardDeleteTime);
&& Objects.equals(hardDeleteTime, blobInfo.hardDeleteTime)
&& Objects.equals(restoreToken, blobInfo.restoreToken);
}

ImmutableSet<NamedField> getModifiedFields() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,8 @@ private Object blobInfoEncode(BlobInfo from) {
ifNonNull(from.getCustomTimeOffsetDateTime(), timestampCodec::encode, toBuilder::setCustomTime);
ifNonNull(from.getSoftDeleteTime(), timestampCodec::encode, toBuilder::setSoftDeleteTime);
ifNonNull(from.getHardDeleteTime(), timestampCodec::encode, toBuilder::setHardDeleteTime);
// TODO: uncomment when grpc is available
//ifNonNull(from.getRestoreToken(), toBuilder::setRestoreToken);
ifNonNull(
from.getCustomerEncryption(),
customerEncryptionCodec::encode,
Expand Down Expand Up @@ -957,6 +959,10 @@ private BlobInfo blobInfoDecode(Object from) {
if (from.hasHardDeleteTime()) {
toBuilder.setHardDeleteTime(timestampCodec.decode(from.getHardDeleteTime()));
}
/* TODO: uncomment when grpc is available
if (from.hasRestoreToken()) {
toBuilder.setRestoreToken(from.getRestoreToken());
}*/
String storageClass = from.getStorageClass();
if (!storageClass.isEmpty()) {
toBuilder.setStorageClass(StorageClass.valueOf(storageClass));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ private StorageObject blobInfoEncode(BlobInfo from) {

ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::encode, to::setSoftDeleteTime);
ifNonNull(from.getHardDeleteTime(), dateTimeCodec::encode, to::setHardDeleteTime);
ifNonNull(from.getRestoreToken(), to::setRestoreToken);

// todo: clean this up once retention is enabled in grpc
// This is a workaround so that explicitly null retention objects are only included when the
Expand Down Expand Up @@ -338,6 +339,7 @@ private BlobInfo blobInfoDecode(StorageObject from) {
ifNonNull(from.getRetention(), this::retentionDecode, to::setRetention);
ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::decode, to::setSoftDeleteTime);
ifNonNull(from.getHardDeleteTime(), dateTimeCodec::decode, to::setHardDeleteTime);
ifNonNull(from.getRestoreToken(), to::setRestoreToken);
return to.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,10 @@ enum BlobField implements FieldSelector, NamedField {

@TransportCompatibility({Transport.HTTP, Transport.GRPC})
HARD_DELETE_TIME(
"hardDeleteTime", "hard_delete_time", com.google.api.client.util.DateTime.class);
"hardDeleteTime", "hard_delete_time", com.google.api.client.util.DateTime.class),

@TransportCompatibility({Transport.HTTP, Transport.GRPC})
RESTORE_TOKEN("restoreToken", "restore_token", String.class);
static final List<NamedField> REQUIRED_FIELDS = ImmutableList.of(BUCKET, NAME);
private static final Map<String, BlobField> JSON_FIELD_NAME_INDEX;

Expand Down Expand Up @@ -1687,6 +1689,15 @@ public static BlobGetOption softDeleted(boolean softDeleted) {
return new BlobGetOption(UnifiedOpts.softDeleted(softDeleted));
}

/**
* Returns an option that must be specified when getting a soft-deleted object from an HNS-enabled
* bucket that has a name/generation conflict with another object in the same bucket.
*/
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
public static BlobGetOption restoreToken(String restoreToken){
return new BlobGetOption(UnifiedOpts.restoreToken(restoreToken));
}

/**
* Deduplicate any options which are the same parameter. The value which comes last in {@code
* os} will be the value included in the return.
Expand Down Expand Up @@ -1775,6 +1786,15 @@ public static BlobRestoreOption metagenerationNotMatch(long generation) {
public static BlobRestoreOption copySourceAcl(boolean copySourceAcl) {
return new BlobRestoreOption(UnifiedOpts.copySourceAcl(copySourceAcl));
}

/**
* Returns an option that must be specified when getting a soft-deleted object from an HNS-enabled
* bucket that has a name/generation conflict with another object in the same bucket.
*/
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
public static BlobRestoreOption restoreToken(String restoreToken) {
return new BlobRestoreOption(UnifiedOpts.restoreToken(restoreToken));
}
}

/** Class for specifying bucket list options. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ static SoftDeleted softDeleted(boolean softDeleted) {
return new SoftDeleted(softDeleted);
}

static RestoreToken restoreToken(String restoreToken){
return new RestoreToken(restoreToken);
}

static CopySourceAcl copySourceAcl(boolean copySourceAcl) {
return new CopySourceAcl(copySourceAcl);
}
Expand Down Expand Up @@ -719,6 +723,27 @@ public Mapper<GetObjectRequest.Builder> getObject() {
}
}

static final class RestoreToken extends RpcOptVal<String> implements ObjectSourceOpt {

private static final long serialVersionUID = 4215757108268532746L;

private RestoreToken(String val) {
super(StorageRpc.Option.RESTORE_TOKEN, val);
}

/* TODO: uncomment when grpc is available
@Override
public Mapper<RestoreObjectRequest.Builder> restoreObject() {
return b -> b.setRestoreToken(val);
}

@Override
public Mapper<GetObjectRequest.Builder> getObject() {
return b-> b.setRestoreToken(val);
}
*/
}

static final class CopySourceAcl extends RpcOptVal<Boolean> implements ObjectSourceOpt {

private static final long serialVersionUID = 2033755749149128119L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ enum Option {
RETURN_RAW_INPUT_STREAM("returnRawInputStream"),
OVERRIDE_UNLOCKED_RETENTION("overrideUnlockedRetention"),
SOFT_DELETED("softDeleted"),
RESTORE_TOKEN("restoreToken"),
COPY_SOURCE_ACL("copySourceAcl"),
GENERATION("generation"),
INCLUDE_FOLDERS_AS_PREFIXES("includeFoldersAsPrefixes"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1525,4 +1525,38 @@ public void testUpdateBlob_noModification() {
Blob gen2 = storage.update(gen1);
assertThat(gen2).isEqualTo(gen1);
}

@Test
@Exclude(transports = Transport.GRPC) // TODO: remove when grpc is available
public void testRestoreToken() {
String bucketName = generator.randomBucketName();
storage.create(
BucketInfo.newBuilder(bucketName)
.setHierarchicalNamespace(
BucketInfo.HierarchicalNamespace.newBuilder().setEnabled(true).build())
.setIamConfiguration(
BucketInfo.IamConfiguration.newBuilder()
.setIsUniformBucketLevelAccessEnabled(true)
.build())
.build());
BlobInfo info = BlobInfo.newBuilder(bucketName, generator.randomObjectName()).build();
try {
Blob delobj = storage.create(info);
storage.delete(delobj.getBlobId());

Blob got = storage.get(delobj.getBlobId(), BlobGetOption.softDeleted(true));
assertThat(got.getRestoreToken()).isNotNull();

Blob gotWithRestoreToken = storage.get(delobj.getBlobId(), BlobGetOption.softDeleted(true), BlobGetOption.restoreToken(got.getRestoreToken()));
assertThat(gotWithRestoreToken).isNotNull();

storage.restore(got.getBlobId(), Storage.BlobRestoreOption.restoreToken(got.getRestoreToken()));
assertThat(storage.get(bucketName, delobj.getName())).isNotNull();;

} finally {
storage.delete(info.getBlobId());
storage.delete(bucketName);
}

}
}
Loading