Skip to content

Commit

Permalink
feat: adds support for restore token (#2768)
Browse files Browse the repository at this point in the history
* feat: adds support for restore token

* add grpc

* IT fixes
  • Loading branch information
JesseLovelace authored Oct 31, 2024
1 parent aef367d commit 0394354
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,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 @@ -834,6 +834,7 @@ 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);
ifNonNull(from.getRestoreToken(), toBuilder::setRestoreToken);
ifNonNull(
from.getCustomerEncryption(),
customerEncryptionCodec::encode,
Expand Down Expand Up @@ -905,6 +906,9 @@ private BlobInfo blobInfoDecode(Object from) {
if (from.hasHardDeleteTime()) {
toBuilder.setHardDeleteTime(timestampCodec.decode(from.getHardDeleteTime()));
}
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 @@ -1656,6 +1658,16 @@ 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 @@ -1741,6 +1753,16 @@ 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 @@ -477,6 +477,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 @@ -694,6 +698,25 @@ 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);
}

@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 @@ -203,7 +203,8 @@ public ImmutableList<?> parameters() {
BlobField.RETENTION,
LazyAssertion.skip("TODO: jesse fill in buganizer bug here")),
new Args<>(BlobField.SOFT_DELETE_TIME, LazyAssertion.equal()),
new Args<>(BlobField.HARD_DELETE_TIME, LazyAssertion.equal()));
new Args<>(BlobField.HARD_DELETE_TIME, LazyAssertion.equal()),
new Args<>(BlobField.RESTORE_TOKEN, LazyAssertion.equal()));
List<String> argsDefined =
args.stream().map(Args::getField).map(Enum::name).sorted().collect(Collectors.toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1524,4 +1524,42 @@ public void testUpdateBlob_noModification() {
Blob gen2 = storage.update(gen1);
assertThat(gen2).isEqualTo(gen1);
}

@Test
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ public void storage_BlobGetOption_fields_BlobField() {
"updated",
"retention",
"softDeleteTime",
"hardDeleteTime");
"hardDeleteTime",
"restoreToken");
s.get(o.getBlobId(), BlobGetOption.fields(BlobField.values()));
requestAuditing.assertQueryParam("fields", expected, splitOnCommaToSet());
}
Expand Down Expand Up @@ -923,7 +924,8 @@ public void storage_BlobListOption_fields_BlobField() {
"items/updated",
"items/retention",
"items/softDeleteTime",
"items/hardDeleteTime");
"items/hardDeleteTime",
"items/restoreToken");
s.list(b.getName(), BlobListOption.fields(BlobField.values()));
requestAuditing.assertQueryParam("fields", expected, splitOnCommaToSet());
}
Expand Down

0 comments on commit 0394354

Please sign in to comment.