From 2fda901afb443ce7e941d5b2a62bbb36ae7d9f0f Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 5 Sep 2018 19:46:46 -0700 Subject: [PATCH] [Storage] Replay GCS Bucket Lock (#3645) * Add support for defaultEventBasedHold * Add support for Blob eventBasedHold * Add support for blob temporary holds * Remove Beta launch annotations for CMEK * Add support for lockRetentionPolicy and RetentionPolicy * Update FakeStorageRPC * codacy-bot review fix (final only) * Update getRetentionPolicyIsLocked() -> retentionPolicyIsLocked() and misc. * Address comments * Address comments * Address comments. * Small nits * Fix additional comments --- .../contrib/nio/testing/FakeStorageRpc.java | 6 + .../java/com/google/cloud/storage/Blob.java | 19 ++- .../com/google/cloud/storage/BlobInfo.java | 82 +++++++++- .../java/com/google/cloud/storage/Bucket.java | 48 +++++- .../com/google/cloud/storage/BucketInfo.java | 116 +++++++++++++- .../com/google/cloud/storage/Storage.java | 32 +++- .../com/google/cloud/storage/StorageImpl.java | 17 ++ .../cloud/storage/spi/v1/HttpStorageRpc.java | 17 ++ .../storage/spi/v1/HttpStorageRpcSpans.java | 2 + .../cloud/storage/spi/v1/StorageRpc.java | 9 +- .../google/cloud/storage/BlobInfoTest.java | 15 ++ .../com/google/cloud/storage/BlobTest.java | 16 ++ .../google/cloud/storage/BucketInfoTest.java | 16 ++ .../com/google/cloud/storage/BucketTest.java | 33 ++++ .../google/cloud/storage/StorageImplTest.java | 25 ++- .../cloud/storage/it/ITStorageTest.java | 148 ++++++++++++++++++ 16 files changed, 579 insertions(+), 22 deletions(-) diff --git a/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java b/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java index f8c4125b8557..d27664683db8 100644 --- a/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java +++ b/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java @@ -65,6 +65,7 @@ *
  • createBatch *
  • checksums, etags *
  • IAM operations
  • + *
  • BucketLock operations
  • * * */ @@ -520,6 +521,11 @@ public Notification createNotification(String bucket, Notification notification) throw new UnsupportedOperationException(); } + @Override + public Bucket lockRetentionPolicy(Bucket bucket, Map options) { + throw new UnsupportedOperationException(); + } + @Override public ServiceAccount getServiceAccount(String projectId) { return null; diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java index a5d14126e0f6..b29ce5455040 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java @@ -402,13 +402,30 @@ Builder setCustomerEncryption(CustomerEncryption customerEncryption) { return this; } - @GcpLaunchStage.Beta @Override Builder setKmsKeyName(String kmsKeyName) { infoBuilder.setKmsKeyName(kmsKeyName); return this; } + @Override + public Builder setEventBasedHold(Boolean eventBasedHold) { + infoBuilder.setEventBasedHold(eventBasedHold); + return this; + } + + @Override + public Builder setTemporaryHold(Boolean temporaryHold) { + infoBuilder.setTemporaryHold(temporaryHold); + return this; + } + + @Override + Builder setRetentionExpirationTime(Long retentionExpirationTime) { + infoBuilder.setRetentionExpirationTime(retentionExpirationTime); + return this; + } + @Override public Blob build() { return new Blob(storage, infoBuilder); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java index 386daa63390a..b130b880ce55 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java @@ -85,6 +85,9 @@ public StorageObject apply(BlobInfo blobInfo) { private final boolean isDirectory; private final CustomerEncryption customerEncryption; private final String kmsKeyName; + private final Boolean eventBasedHold; + private final Boolean temporaryHold; + private final Long retentionExpirationTime; /** * This class is meant for internal use only. Users are discouraged from using this class. @@ -268,12 +271,19 @@ public abstract static class Builder { abstract Builder setCustomerEncryption(CustomerEncryption customerEncryption); + abstract Builder setKmsKeyName(String kmsKeyName); + /** - * - * Sets the blob's kmsKeyName. + * Sets the blob's event based hold. */ - @GcpLaunchStage.Beta - abstract Builder setKmsKeyName(String kmsKeyName); + public abstract Builder setEventBasedHold(Boolean eventBasedHold); + + /** + * Sets the blob's temporary hold. + */ + public abstract Builder setTemporaryHold(Boolean temporaryHold); + + abstract Builder setRetentionExpirationTime(Long retentionExpirationTime); /** * Creates a {@code BlobInfo} object. @@ -308,6 +318,9 @@ static final class BuilderImpl extends Builder { private CustomerEncryption customerEncryption; private StorageClass storageClass; private String kmsKeyName; + private Boolean eventBasedHold; + private Boolean temporaryHold; + private Long retentionExpirationTime; BuilderImpl(BlobId blobId) { this.blobId = blobId; @@ -339,6 +352,9 @@ static final class BuilderImpl extends Builder { isDirectory = blobInfo.isDirectory; storageClass = blobInfo.storageClass; kmsKeyName = blobInfo.kmsKeyName; + eventBasedHold = blobInfo.eventBasedHold; + temporaryHold = blobInfo.temporaryHold; + retentionExpirationTime = blobInfo.retentionExpirationTime; } @Override @@ -486,13 +502,30 @@ Builder setCustomerEncryption(CustomerEncryption customerEncryption) { return this; } - @GcpLaunchStage.Beta @Override Builder setKmsKeyName(String kmsKeyName) { this.kmsKeyName = kmsKeyName; return this; } + @Override + public Builder setEventBasedHold(Boolean eventBasedHold) { + this.eventBasedHold = eventBasedHold; + return this; + } + + @Override + public Builder setTemporaryHold(Boolean temporaryHold) { + this.temporaryHold = temporaryHold; + return this; + } + + @Override + Builder setRetentionExpirationTime(Long retentionExpirationTime) { + this.retentionExpirationTime = retentionExpirationTime; + return this; + } + @Override public BlobInfo build() { checkNotNull(blobId); @@ -526,6 +559,9 @@ public BlobInfo build() { isDirectory = firstNonNull(builder.isDirectory, Boolean.FALSE); storageClass = builder.storageClass; kmsKeyName = builder.kmsKeyName; + eventBasedHold = builder.eventBasedHold; + temporaryHold = builder.temporaryHold; + retentionExpirationTime = builder.retentionExpirationTime; } /** @@ -759,11 +795,31 @@ public StorageClass getStorageClass() { /** * Returns the Cloud KMS key used to encrypt the blob, if any. */ - @GcpLaunchStage.Beta public String getKmsKeyName() { return kmsKeyName; } + /** + * Returns the event based hold status of the blob, if any. + */ + public Boolean getEventBasedHold() { + return eventBasedHold; + } + + /** + * Returns the temporary hold status of the blob, if any. + */ + public Boolean getTemporaryHold() { + return temporaryHold; + } + + /** + * Returns the retention expiration time of the blob, if a retention period is defined. + */ + public Long getRetentionExpirationTime() { + return retentionExpirationTime; + } + /** * Returns a builder for the current blob. */ @@ -836,8 +892,13 @@ public ObjectAccessControl apply(Acl acl) { if (customerEncryption != null) { storageObject.setCustomerEncryption(customerEncryption.toPb()); } + if (retentionExpirationTime != null) { + storageObject.setRetentionExpirationTime(new DateTime(retentionExpirationTime)); + } storageObject.setKmsKeyName(kmsKeyName); + storageObject.setEventBasedHold(eventBasedHold); + storageObject.setTemporaryHold(temporaryHold); storageObject.setMetadata(pbMetadata); storageObject.setCacheControl(cacheControl); storageObject.setContentEncoding(contentEncoding); @@ -971,6 +1032,15 @@ public Acl apply(ObjectAccessControl objectAccessControl) { if (storageObject.getKmsKeyName() != null) { builder.setKmsKeyName(storageObject.getKmsKeyName()); } + if (storageObject.getEventBasedHold() != null) { + builder.setEventBasedHold(storageObject.getEventBasedHold()); + } + if (storageObject.getTemporaryHold() != null) { + builder.setTemporaryHold(storageObject.getTemporaryHold()); + } + if (storageObject.getRetentionExpirationTime() != null) { + builder.setRetentionExpirationTime(storageObject.getRetentionExpirationTime().getValue()); + } return builder.build(); } } diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java index 179e8a5cbfef..c06ed6f6958a 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java @@ -628,13 +628,36 @@ public Builder setLabels(Map labels) { return this; } - @GcpLaunchStage.Beta @Override public Builder setDefaultKmsKeyName(String defaultKmsKeyName) { infoBuilder.setDefaultKmsKeyName(defaultKmsKeyName); return this; } + @Override + public Builder setDefaultEventBasedHold(Boolean defaultEventBasedHold) { + infoBuilder.setDefaultEventBasedHold(defaultEventBasedHold); + return this; + } + + @Override + Builder setRetentionEffectiveTime(Long retentionEffectiveTime) { + infoBuilder.setRetentionEffectiveTime(retentionEffectiveTime); + return this; + } + + @Override + Builder setRetentionPolicyIsLocked(Boolean retentionIsLocked) { + infoBuilder.setRetentionPolicyIsLocked(retentionIsLocked); + return this; + } + + @Override + public Builder setRetentionPeriod(Long retentionPeriod) { + infoBuilder.setRetentionPeriod(retentionPeriod); + return this; + } + @Override public Bucket build() { return new Bucket(storage, infoBuilder); @@ -1111,6 +1134,29 @@ public List listDefaultAcls() { return storage.listDefaultAcls(getName()); } + /** + * Locks bucket retention policy. Requires a local metageneration value in the request. Review example below. + * + *

    Accepts an optional userProject {@link BucketTargetOption} option which defines the project id + * to assign operational costs. + * + *

    Warning: Once a retention policy is locked, it can't be unlocked, removed, or shortened. + * + *

    Example of locking a retention policy on a bucket, only if its local metageneration value matches the bucket's + * service metageneration otherwise a {@link StorageException} is thrown. + *

     {@code
    +   * String bucketName = "my_unique_bucket";
    +   * Bucket bucket = storage.get(bucketName, BucketGetOption.fields(BucketField.METAGENERATION));
    +   * storage.lockRetentionPolicy(bucket, BucketTargetOption.metagenerationMatch());
    +   * }
    + * + * @return a {@code Bucket} object of the locked bucket + * @throws StorageException upon failure + */ + public Bucket lockRetentionPolicy(BucketTargetOption... options) { + return storage.lockRetentionPolicy(this, options); + } + /** * Returns the bucket's {@code Storage} object used to issue requests. */ diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java index 6e0b3e2c38e9..e24a5a7a89eb 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java @@ -88,6 +88,10 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo) private final StorageClass storageClass; private final Map labels; private final String defaultKmsKeyName; + private final Boolean defaultEventBasedHold; + private final Long retentionEffectiveTime; + private final Boolean retentionPolicyIsLocked; + private final Long retentionPeriod; /** * Base class for bucket's delete rules. Allows to configure automatic deletion of blobs and blobs @@ -346,7 +350,6 @@ public abstract static class Builder { * Sets whether a user accessing the bucket or an object it contains should assume the transit costs * related to the access. * - * GcpLaunchStage.Alpha */ public abstract Builder setRequesterPays(Boolean requesterPays); @@ -429,9 +432,23 @@ public abstract static class Builder { /** * Sets the default Cloud KMS key name for this bucket. */ - @GcpLaunchStage.Beta public abstract Builder setDefaultKmsKeyName(String defaultKmsKeyName); + /** + * Sets the default event based hold for this bucket. + */ + public abstract Builder setDefaultEventBasedHold(Boolean defaultEventBasedHold); + + abstract Builder setRetentionEffectiveTime(Long retentionEffectiveTime); + + abstract Builder setRetentionPolicyIsLocked(Boolean retentionPolicyIsLocked); + + /** + * If policy is not locked this value can be cleared, increased, and decreased. + * If policy is locked the retention period can only be increased. + */ + public abstract Builder setRetentionPeriod(Long retentionPeriod); + /** * Creates a {@code BucketInfo} object. */ @@ -459,6 +476,10 @@ static final class BuilderImpl extends Builder { private List defaultAcl; private Map labels; private String defaultKmsKeyName; + private Boolean defaultEventBasedHold; + private Long retentionEffectiveTime; + private Boolean retentionPolicyIsLocked; + private Long retentionPeriod; BuilderImpl(String name) { this.name = name; @@ -484,6 +505,10 @@ static final class BuilderImpl extends Builder { labels = bucketInfo.labels; requesterPays = bucketInfo.requesterPays; defaultKmsKeyName = bucketInfo.defaultKmsKeyName; + defaultEventBasedHold = bucketInfo.defaultEventBasedHold; + retentionEffectiveTime = bucketInfo.retentionEffectiveTime; + retentionPolicyIsLocked = bucketInfo.retentionPolicyIsLocked; + retentionPeriod = bucketInfo.retentionPeriod; } @Override @@ -516,7 +541,6 @@ public Builder setVersioningEnabled(Boolean enable) { return this; } - /** GcpLaunchStage.Alpha */ @Override public Builder setRequesterPays(Boolean enable) { this.requesterPays = firstNonNull(enable, Data.nullOf(Boolean.class)); @@ -595,7 +619,6 @@ public Builder setLabels(Map labels) { return this; } - @GcpLaunchStage.Beta @Override public Builder setDefaultKmsKeyName(String defaultKmsKeyName) { this.defaultKmsKeyName = defaultKmsKeyName != null @@ -603,6 +626,30 @@ public Builder setDefaultKmsKeyName(String defaultKmsKeyName) { return this; } + @Override + public Builder setDefaultEventBasedHold(Boolean defaultEventBasedHold) { + this.defaultEventBasedHold = firstNonNull(defaultEventBasedHold, Data.nullOf(Boolean.class)); + return this; + } + + @Override + Builder setRetentionEffectiveTime(Long retentionEffectiveTime) { + this.retentionEffectiveTime = firstNonNull(retentionEffectiveTime, Data.nullOf(Long.class)); + return this; + } + + @Override + Builder setRetentionPolicyIsLocked(Boolean retentionPolicyIsLocked) { + this.retentionPolicyIsLocked = firstNonNull(retentionPolicyIsLocked, Data.nullOf(Boolean.class)); + return this; + } + + @Override + public Builder setRetentionPeriod(Long retentionPeriod) { + this.retentionPeriod = firstNonNull(retentionPeriod, Data.nullOf(Long.class)); + return this; + } + @Override public BucketInfo build() { checkNotNull(name); @@ -630,6 +677,10 @@ public BucketInfo build() { labels = builder.labels; requesterPays = builder.requesterPays; defaultKmsKeyName = builder.defaultKmsKeyName; + defaultEventBasedHold = builder.defaultEventBasedHold; + retentionEffectiveTime = builder.retentionEffectiveTime; + retentionPolicyIsLocked = builder.retentionPolicyIsLocked; + retentionPeriod = builder.retentionPeriod; } /** @@ -667,12 +718,10 @@ public Boolean versioningEnabled() { return Data.isNull(versioningEnabled) ? null : versioningEnabled; } - /** * Returns {@code true} if a user accessing the bucket or an object it contains should assume the transit costs * related to the access, {@code false} otherwise. * - * GcpLaunchStage.Alpha */ public Boolean requesterPays() { return Data.isNull(requesterPays) ? null : requesterPays; @@ -785,11 +834,32 @@ public Map getLabels() { /** * Returns the default Cloud KMS key to be applied to newly inserted objects in this bucket. */ - @GcpLaunchStage.Beta public String getDefaultKmsKeyName() { return defaultKmsKeyName; } + /** + * Returns the default event based hold value used for inserted objects in this bucket. + */ + public Boolean getDefaultEventBasedHold() { return defaultEventBasedHold; } + + /** + * Returns the retention effective time a policy took effect if a retention policy is defined. + */ + public Long getRetentionEffectiveTime() { return retentionEffectiveTime; } + + /** + * Returns {@code true} if the bucket retention policy is locked, {@code false} otherwise. + */ + public Boolean retentionPolicyIsLocked() { + return Data.isNull(retentionPolicyIsLocked) ? null : retentionPolicyIsLocked; + } + + /** + * Returns the retention policy retention period. + */ + public Long getRetentionPeriod() { return retentionPeriod; } + /** * Returns a builder for the current bucket. */ @@ -888,6 +958,23 @@ public Rule apply(DeleteRule deleteRule) { if (defaultKmsKeyName != null) { bucketPb.setEncryption(new Encryption().setDefaultKmsKeyName(defaultKmsKeyName)); } + if (defaultEventBasedHold != null) { + bucketPb.setDefaultEventBasedHold(defaultEventBasedHold); + } + if (retentionPeriod != null || retentionEffectiveTime != null || retentionPolicyIsLocked != null) { + Bucket.RetentionPolicy retentionPolicy = new Bucket.RetentionPolicy(); + if (retentionPeriod != null) { + retentionPolicy.setRetentionPeriod(retentionPeriod); + } + if (retentionEffectiveTime != null) { + retentionPolicy.setEffectiveTime(new DateTime(retentionEffectiveTime)); + } + if (retentionPolicyIsLocked != null) { + retentionPolicy.setIsLocked(retentionPolicyIsLocked); + } + bucketPb.setRetentionPolicy(retentionPolicy); + } + return bucketPb; } @@ -979,6 +1066,21 @@ public DeleteRule apply(Rule rule) { if (encryption != null && encryption.getDefaultKmsKeyName() != null && !encryption.getDefaultKmsKeyName().isEmpty()) { builder.setDefaultKmsKeyName(encryption.getDefaultKmsKeyName()); } + if (bucketPb.getDefaultEventBasedHold() != null) { + builder.setDefaultEventBasedHold(bucketPb.getDefaultEventBasedHold()); + } + Bucket.RetentionPolicy retentionPolicy = bucketPb.getRetentionPolicy(); + if (retentionPolicy != null) { + if (retentionPolicy.getEffectiveTime() != null) { + builder.setRetentionEffectiveTime(retentionPolicy.getEffectiveTime().getValue()); + } + if (retentionPolicy.getIsLocked() != null) { + builder.setRetentionPolicyIsLocked(retentionPolicy.getIsLocked()); + } + if (retentionPolicy.getRetentionPeriod() != null) { + builder.setRetentionPeriod(retentionPolicy.getRetentionPeriod()); + } + } return builder.build(); } } diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 78f9390a67e4..b67fe2645541 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -93,9 +93,10 @@ enum BucketField implements FieldSelector { CORS("cors"), STORAGE_CLASS("storageClass"), ETAG("etag"), - @GcpLaunchStage.Beta ENCRYPTION("encryption"), - BILLING("billing"); + BILLING("billing"), + DEFAULT_EVENT_BASED_HOLD("defaultEventBasedHold"), + RETENTION_POLICY("retentionPolicy"); static final List REQUIRED_FIELDS = ImmutableList.of(NAME); @@ -136,8 +137,10 @@ enum BlobField implements FieldSelector { SIZE("size"), STORAGE_CLASS("storageClass"), TIME_DELETED("timeDeleted"), - @GcpLaunchStage.Beta KMS_KEY_NAME("kmsKeyName"), + EVENT_BASED_HOLD("eventBasedHold"), + TEMPORARY_HOLD("temporaryHold"), + RETENTION_EXPIRATION_TIME("retentionExpirationTime"), UPDATED("updated"); static final List REQUIRED_FIELDS = ImmutableList.of(BUCKET, NAME); @@ -388,7 +391,6 @@ public static BlobTargetOption encryptionKey(String key) { /** * Returns an option to set a customer-managed key for server-side encryption of the blob. */ - @GcpLaunchStage.Beta public static BlobTargetOption kmsKeyName(String kmsKeyName) { return new BlobTargetOption(StorageRpc.Option.KMS_KEY_NAME, kmsKeyName); } @@ -550,7 +552,6 @@ public static BlobWriteOption encryptionKey(String key) { * * @param kmsKeyName the KMS key resource id */ - @GcpLaunchStage.Beta public static BlobWriteOption kmsKeyName(String kmsKeyName) { return new BlobWriteOption(Option.KMS_KEY_NAME, kmsKeyName); } @@ -1537,6 +1538,27 @@ public static Builder newBuilder() { */ Bucket get(String bucket, BucketGetOption... options); + /** + * Locks bucket retention policy. Requires a local metageneration value in the request. Review example below. + * + *

    Accepts an optional userProject {@link BucketTargetOption} option which defines the project id + * to assign operational costs. + * + *

    Warning: Once a retention policy is locked, it can't be unlocked, removed, or shortened. + * + *

    Example of locking a retention policy on a bucket, only if its local metageneration value matches the bucket's + * service metageneration otherwise a {@link StorageException} is thrown. + *

     {@code
    +   * String bucketName = "my_unique_bucket";
    +   * Bucket bucket = storage.get(bucketName, BucketGetOption.fields(BucketField.METAGENERATION));
    +   * storage.lockRetentionPolicy(bucket, BucketTargetOption.metagenerationMatch());
    +   * }
    + * + * @return a {@code Bucket} object of the locked bucket + * @throws StorageException upon failure + */ + Bucket lockRetentionPolicy(BucketInfo bucket, BucketTargetOption... options); + /** * Returns the requested blob or {@code null} if not found. * diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index e49445d13646..787388006571 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -983,6 +983,23 @@ public Boolean apply(String permission) { } } + @Override + public Bucket lockRetentionPolicy(BucketInfo bucketInfo, BucketTargetOption... options) { + final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb(); + final Map optionsMap = optionMap(bucketInfo, options); + try { + return Bucket.fromPb(this, runWithRetries( + new Callable() { + @Override + public com.google.api.services.storage.model.Bucket call() { + return storageRpc.lockRetentionPolicy(bucketPb, optionsMap); + } + }, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock())); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + @Override public ServiceAccount getServiceAccount(final String projectId) { try { diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 03db33dae835..39cbc3f99ef2 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -1208,6 +1208,23 @@ public Notification createNotification(String bucket, Notification notification) } } + @Override + public Bucket lockRetentionPolicy(Bucket bucket, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_LOCK_RETENTION_POLICY); + Scope scope = tracer.withSpan(span); + try { + return storage.buckets().lockRetentionPolicy(bucket.getName(), Option.IF_METAGENERATION_MATCH.getLong(options)) + .setUserProject(Option.USER_PROJECT.getString(options)).execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + + } + @Override public ServiceAccount getServiceAccount(String projectId) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_SERVICE_ACCOUNT); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java index ceb8a3c83e53..41c877f8eefc 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java @@ -90,6 +90,7 @@ class HttpStorageRpcSpans { "listNotifications(String)"); static final String SPAN_NAME_CREATE_NOTIFICATION = getTraceSpanName( "createNotification(String,Notification)"); + static final String SPAN_LOCK_RETENTION_POLICY = getTraceSpanName("lockRetentionPolicy(String,Long)"); static final String SPAN_NAME_GET_SERVICE_ACCOUNT = getTraceSpanName( "getServiceAccount(String)"); static final String SPAN_NAME_BATCH_SUBMIT = getTraceSpanName( @@ -136,6 +137,7 @@ class HttpStorageRpcSpans { SPAN_NAME_LIST_NOTIFICATIONS, SPAN_NAME_CREATE_NOTIFICATION, SPAN_NAME_GET_SERVICE_ACCOUNT, + SPAN_LOCK_RETENTION_POLICY, SPAN_NAME_BATCH_SUBMIT); static String getTraceSpanName(String methodDescriptor) { diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java index 780d8b864cfd..2a62a66a9967 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java @@ -57,7 +57,6 @@ enum Option { FIELDS("fields"), CUSTOMER_SUPPLIED_KEY("customerSuppliedKey"), USER_PROJECT("userProject"), - @GcpLaunchStage.Beta KMS_KEY_NAME("kmsKeyName"); private final String value; @@ -460,6 +459,14 @@ void write(String uploadId, byte[] toWrite, int toWriteOffset, long destOffset, */ Notification createNotification(String bucket, Notification notification); + /** + * Lock retention policy for the provided bucket. + * + * @return a {@code Bucket} object of the locked bucket + * @throws StorageException upon failure + */ + Bucket lockRetentionPolicy(Bucket bucket, Map options); + /** * Returns the service account associated with the given project. * diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java index 460bdb1ea811..88914f34a4c8 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java @@ -68,6 +68,9 @@ public class BlobInfoTest { new CustomerEncryption(ENCRYPTION_ALGORITHM, KEY_SHA256); private static final String KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final StorageClass STORAGE_CLASS = StorageClass.COLDLINE; + private static final Boolean EVENT_BASED_HOLD = true; + private static final Boolean TEMPORARY_HOLD = true; + private static final Long RETENTION_EXPIRATION_TIME = 10L; private static final BlobInfo BLOB_INFO = BlobInfo.newBuilder("b", "n", GENERATION) .setAcl(ACL) @@ -93,6 +96,9 @@ public class BlobInfoTest { .setCreateTime(CREATE_TIME) .setStorageClass(STORAGE_CLASS) .setKmsKeyName(KMS_KEY_NAME) + .setEventBasedHold(EVENT_BASED_HOLD) + .setTemporaryHold(TEMPORARY_HOLD) + .setRetentionExpirationTime(RETENTION_EXPIRATION_TIME) .build(); private static final BlobInfo DIRECTORY_INFO = BlobInfo.newBuilder("b", "n/") .setSize(0L) @@ -156,6 +162,9 @@ public void testBuilder() { assertEquals(CREATE_TIME, BLOB_INFO.getCreateTime()); assertEquals(STORAGE_CLASS, BLOB_INFO.getStorageClass()); assertEquals(KMS_KEY_NAME, BLOB_INFO.getKmsKeyName()); + assertEquals(EVENT_BASED_HOLD, BLOB_INFO.getEventBasedHold()); + assertEquals(TEMPORARY_HOLD, BLOB_INFO.getTemporaryHold()); + assertEquals(RETENTION_EXPIRATION_TIME, BLOB_INFO.getRetentionExpirationTime()); assertFalse(BLOB_INFO.isDirectory()); assertEquals("b", DIRECTORY_INFO.getBucket()); assertEquals("n/", DIRECTORY_INFO.getName()); @@ -212,6 +221,9 @@ private void compareBlobs(BlobInfo expected, BlobInfo value) { assertEquals(expected.getUpdateTime(), value.getUpdateTime()); assertEquals(expected.getStorageClass(), value.getStorageClass()); assertEquals(expected.getKmsKeyName(), value.getKmsKeyName()); + assertEquals(expected.getEventBasedHold(), value.getEventBasedHold()); + assertEquals(expected.getTemporaryHold(), value.getTemporaryHold()); + assertEquals(expected.getRetentionExpirationTime(), value.getRetentionExpirationTime()); } private void compareCustomerEncryptions(CustomerEncryption expected, CustomerEncryption value) { @@ -260,6 +272,9 @@ public void testToPbAndFromPb() { assertNull(blobInfo.getUpdateTime()); assertNull(blobInfo.getStorageClass()); assertNull(blobInfo.getKmsKeyName()); + assertNull(blobInfo.getEventBasedHold()); + assertNull(blobInfo.getTemporaryHold()); + assertNull(blobInfo.getRetentionExpirationTime()); assertTrue(blobInfo.isDirectory()); } diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java index f1b52a526b71..8db0793409a6 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java @@ -92,6 +92,9 @@ public class BlobTest { private static final BlobInfo.CustomerEncryption CUSTOMER_ENCRYPTION = new BlobInfo.CustomerEncryption(ENCRYPTION_ALGORITHM, KEY_SHA256); private static final String KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; + private static final Boolean EVENT_BASED_HOLD = true; + private static final Boolean TEMPORARY_HOLD = true; + private static final Long RETENTION_EXPIRATION_TIME = 10L; private static final BlobInfo FULL_BLOB_INFO = BlobInfo.newBuilder("b", "n", GENERATION) .setAcl(ACLS) .setComponentCount(COMPONENT_COUNT) @@ -115,6 +118,9 @@ public class BlobTest { .setCreateTime(CREATE_TIME) .setCustomerEncryption(CUSTOMER_ENCRYPTION) .setKmsKeyName(KMS_KEY_NAME) + .setEventBasedHold(EVENT_BASED_HOLD) + .setTemporaryHold(TEMPORARY_HOLD) + .setRetentionExpirationTime(RETENTION_EXPIRATION_TIME) .build(); private static final BlobInfo BLOB_INFO = BlobInfo.newBuilder("b", "n") .setMetageneration(42L) @@ -472,6 +478,9 @@ public void testBuilder() { .setCreateTime(CREATE_TIME) .setCustomerEncryption(CUSTOMER_ENCRYPTION) .setKmsKeyName(KMS_KEY_NAME) + .setEventBasedHold(EVENT_BASED_HOLD) + .setTemporaryHold(TEMPORARY_HOLD) + .setRetentionExpirationTime(RETENTION_EXPIRATION_TIME) .setDeleteTime(DELETE_TIME) .setEtag(ETAG) .setGeneratedId(GENERATED_ID) @@ -496,6 +505,10 @@ public void testBuilder() { assertEquals(CRC32, blob.getCrc32c()); assertEquals(CREATE_TIME, blob.getCreateTime()); assertEquals(CUSTOMER_ENCRYPTION, blob.getCustomerEncryption()); + assertEquals(KMS_KEY_NAME, blob.getKmsKeyName()); + assertEquals(EVENT_BASED_HOLD, blob.getEventBasedHold()); + assertEquals(TEMPORARY_HOLD, blob.getTemporaryHold()); + assertEquals(RETENTION_EXPIRATION_TIME, blob.getRetentionExpirationTime()); assertEquals(DELETE_TIME, blob.getDeleteTime()); assertEquals(ETAG, blob.getEtag()); assertEquals(GENERATED_ID, blob.getGeneratedId()); @@ -527,6 +540,9 @@ public void testBuilder() { assertNull(blob.getCreateTime()); assertNull(blob.getCustomerEncryption()); assertNull(blob.getKmsKeyName()); + assertNull(blob.getEventBasedHold()); + assertNull(blob.getTemporaryHold()); + assertNull(blob.getRetentionExpirationTime()); assertNull(blob.getDeleteTime()); assertNull(blob.getEtag()); assertNull(blob.getGeneratedId()); diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java index d31dd0246e0b..f371af93bc0a 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java @@ -64,6 +64,10 @@ public class BucketInfoTest { private static final Boolean VERSIONING_ENABLED = true; private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); private static final Boolean REQUESTER_PAYS = true; + private static final Boolean DEFAULT_EVENT_BASED_HOLD = true; + private static final Long RETENTION_EFFECTIVE_TIME = 10L; + private static final Long RETENTION_PERIOD = 10L; + private static final Boolean RETENTION_POLICY_IS_LOCKED = false; private static final BucketInfo BUCKET_INFO = BucketInfo.newBuilder("b") .setAcl(ACL) .setEtag(ETAG) @@ -83,6 +87,10 @@ public class BucketInfoTest { .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) + .setDefaultEventBasedHold(DEFAULT_EVENT_BASED_HOLD) + .setRetentionEffectiveTime(RETENTION_EFFECTIVE_TIME) + .setRetentionPeriod(RETENTION_PERIOD) + .setRetentionPolicyIsLocked(RETENTION_POLICY_IS_LOCKED) .build(); @Test @@ -128,6 +136,10 @@ public void testBuilder() { assertEquals(VERSIONING_ENABLED, BUCKET_INFO.versioningEnabled()); assertEquals(BUCKET_LABELS, BUCKET_INFO.getLabels()); assertEquals(REQUESTER_PAYS, BUCKET_INFO.requesterPays()); + assertEquals(DEFAULT_EVENT_BASED_HOLD, BUCKET_INFO.getDefaultEventBasedHold()); + assertEquals(RETENTION_EFFECTIVE_TIME, BUCKET_INFO.getRetentionEffectiveTime()); + assertEquals(RETENTION_PERIOD, BUCKET_INFO.getRetentionPeriod()); + assertEquals(RETENTION_POLICY_IS_LOCKED, BUCKET_INFO.retentionPolicyIsLocked()); } @Test @@ -158,6 +170,10 @@ private void compareBuckets(BucketInfo expected, BucketInfo value) { assertEquals(expected.versioningEnabled(), value.versioningEnabled()); assertEquals(expected.getLabels(), value.getLabels()); assertEquals(expected.requesterPays(), value.requesterPays()); + assertEquals(expected.getDefaultEventBasedHold(), value.getDefaultEventBasedHold()); + assertEquals(expected.getRetentionEffectiveTime(), value.getRetentionEffectiveTime()); + assertEquals(expected.getRetentionPeriod(), value.getRetentionPeriod()); + assertEquals(expected.retentionPolicyIsLocked(), value.retentionPolicyIsLocked()); } @Test diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java index 707a9f3c7565..9f0324b205b3 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java @@ -82,6 +82,10 @@ public class BucketTest { private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); private static final Boolean REQUESTER_PAYS = true; private static final String USER_PROJECT = "test-project"; + private static final Boolean DEFAULT_EVENT_BASED_HOLD = true; + private static final Long RETENTION_EFFECTIVE_TIME = 10L; + private static final Long RETENTION_PERIOD = 10L; + private static final Boolean RETENTION_POLICY_IS_LOCKED = false; private static final BucketInfo FULL_BUCKET_INFO = BucketInfo.newBuilder("b") .setAcl(ACLS) .setEtag(ETAG) @@ -101,6 +105,10 @@ public class BucketTest { .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) + .setDefaultEventBasedHold(DEFAULT_EVENT_BASED_HOLD) + .setRetentionEffectiveTime(RETENTION_EFFECTIVE_TIME) + .setRetentionPeriod(RETENTION_PERIOD) + .setRetentionPolicyIsLocked(RETENTION_POLICY_IS_LOCKED) .build(); private static final BucketInfo BUCKET_INFO = BucketInfo.newBuilder("b").setMetageneration(42L).build(); @@ -659,6 +667,23 @@ public void testListDefaultAcls() throws Exception { assertEquals(ACLS, bucket.listDefaultAcls()); } + @Test + public void testLockRetention() throws Exception { + initializeExpectedBucket(5); + Bucket expectedRetentionLockedBucket = expectedBucket.toBuilder().setRetentionPeriod(RETENTION_PERIOD) + .setRetentionPolicyIsLocked(true).build(); + expect(storage.getOptions()).andReturn(mockOptions).times(2); + expect(storage.lockRetentionPolicy(expectedRetentionLockedBucket, Storage.BucketTargetOption.metagenerationMatch(), + Storage.BucketTargetOption.userProject(USER_PROJECT))).andReturn(expectedRetentionLockedBucket); + replay(storage); + initializeBucket(); + Bucket lockedRetentionPolicyBucket = new Bucket(storage, new BucketInfo.BuilderImpl(expectedRetentionLockedBucket)); + Bucket actualRetentionLockedBucket = lockedRetentionPolicyBucket + .lockRetentionPolicy(Storage.BucketTargetOption.metagenerationMatch(), + Storage.BucketTargetOption.userProject(USER_PROJECT)); + assertEquals(expectedRetentionLockedBucket, actualRetentionLockedBucket); + } + @Test public void testToBuilder() { expect(storage.getOptions()).andReturn(mockOptions).times(4); @@ -694,6 +719,10 @@ public void testBuilder() { .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) + .setDefaultEventBasedHold(DEFAULT_EVENT_BASED_HOLD) + .setRetentionEffectiveTime(RETENTION_EFFECTIVE_TIME) + .setRetentionPeriod(RETENTION_PERIOD) + .setRetentionPolicyIsLocked(RETENTION_POLICY_IS_LOCKED) .build(); assertEquals("b", bucket.getName()); assertEquals(ACLS, bucket.getAcl()); @@ -714,6 +743,10 @@ public void testBuilder() { assertEquals(BUCKET_LABELS, bucket.getLabels()); assertEquals(REQUESTER_PAYS, bucket.requesterPays()); assertEquals(DEFAULT_KMS_KEY_NAME, bucket.getDefaultKmsKeyName()); + assertEquals(DEFAULT_EVENT_BASED_HOLD, bucket.getDefaultEventBasedHold()); + assertEquals(RETENTION_EFFECTIVE_TIME, bucket.getRetentionEffectiveTime()); + assertEquals(RETENTION_PERIOD, bucket.getRetentionPeriod()); + assertEquals(RETENTION_POLICY_IS_LOCKED, bucket.retentionPolicyIsLocked()); assertEquals(storage.getOptions(), bucket.getStorage().getOptions()); } } diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index 9fea3887c464..574c02ea001f 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -92,6 +92,7 @@ public class StorageImplTest { private static final String BUCKET_NAME1 = "b1"; private static final String BUCKET_NAME2 = "b2"; + private static final String BUCKET_NAME3 = "b3"; private static final String BLOB_NAME1 = "n1"; private static final String BLOB_NAME2 = "n2"; private static final String BLOB_NAME3 = "n3"; @@ -104,11 +105,15 @@ public class StorageImplTest { new SecretKeySpec(BaseEncoding.base64().decode(BASE64_KEY), "AES256"); private static final String KMS_KEY_NAME = "projects/gcloud-devel/locations/us/keyRings/gcs_kms_key_ring_us/cryptoKeys/key"; + private static final Long RETENTION_PERIOD = 10L; + private static final String USER_PROJECT = "test-project"; // BucketInfo objects private static final BucketInfo BUCKET_INFO1 = BucketInfo.newBuilder(BUCKET_NAME1).setMetageneration(42L).build(); private static final BucketInfo BUCKET_INFO2 = BucketInfo.newBuilder(BUCKET_NAME2).build(); + private static final BucketInfo BUCKET_INFO3 = BucketInfo.newBuilder(BUCKET_NAME3) + .setRetentionPeriod(RETENTION_PERIOD).setRetentionPolicyIsLocked(true).setMetageneration(42L).build(); // BlobInfo objects private static final BlobInfo BLOB_INFO1 = @@ -128,10 +133,16 @@ public class StorageImplTest { Storage.BucketTargetOption.metagenerationMatch(); private static final Storage.BucketTargetOption BUCKET_TARGET_PREDEFINED_ACL = Storage.BucketTargetOption.predefinedAcl(Storage.PredefinedAcl.PRIVATE); + private static final Storage.BucketTargetOption BUCKET_TARGET_USER_PROJECT = + Storage.BucketTargetOption.userProject(USER_PROJECT); private static final Map BUCKET_TARGET_OPTIONS = ImmutableMap.of( StorageRpc.Option.IF_METAGENERATION_MATCH, BUCKET_INFO1.getMetageneration(), StorageRpc.Option.PREDEFINED_ACL, BUCKET_TARGET_PREDEFINED_ACL.getValue()); + private static final Map BUCKET_TARGET_OPTIONS_LOCK_RETENTION_POLICY = + ImmutableMap.of( + StorageRpc.Option.IF_METAGENERATION_MATCH, BUCKET_INFO3.getMetageneration(), + StorageRpc.Option.USER_PROJECT, USER_PROJECT); // Blob target options (create, update, compose) private static final BlobTargetOption BLOB_TARGET_GENERATION = BlobTargetOption.generationMatch(); @@ -320,7 +331,7 @@ public long millisTime() { private Storage storage; private Blob expectedBlob1, expectedBlob2, expectedBlob3; - private Bucket expectedBucket1, expectedBucket2; + private Bucket expectedBucket1, expectedBucket2, expectedBucket3; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -367,6 +378,7 @@ private void initializeServiceDependentObjects() { expectedBlob3 = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO3)); expectedBucket1 = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO1)); expectedBucket2 = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO2)); + expectedBucket3 = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO3)); } @Test @@ -2325,6 +2337,17 @@ public void testTestIamPermissionsNonNull() { assertEquals(expectedPermissions, storage.testIamPermissions(BUCKET_NAME1, checkedPermissions)); } + @Test + public void testLockRetentionPolicy() { + EasyMock.expect(storageRpcMock + .lockRetentionPolicy(BUCKET_INFO3.toPb(), BUCKET_TARGET_OPTIONS_LOCK_RETENTION_POLICY)) + .andReturn(BUCKET_INFO3.toPb()); + EasyMock.replay(storageRpcMock); + initializeService(); + Bucket bucket = storage.lockRetentionPolicy(BUCKET_INFO3, BUCKET_TARGET_METAGENERATION, BUCKET_TARGET_USER_PROJECT); + assertEquals(expectedBucket3, bucket); + } + @Test public void testGetServiceAccount() { EasyMock.expect(storageRpcMock.getServiceAccount("projectId")) diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index a1330e946738..a771abe4704e 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -132,6 +132,8 @@ public class ITStorageTest { private static final byte[] COMPRESSED_CONTENT = BaseEncoding.base64() .decode("H4sIAAAAAAAAAPNIzcnJV3DPz0/PSVVwzskvTVEILskvSkxPVQQA/LySchsAAAA="); private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); + private static final Long RETENTION_PERIOD = 5L; + private static final Long RETENTION_PERIOD_IN_MILLISECONDS = RETENTION_PERIOD * 1000; private static final String SERVICE_ACCOUNT_EMAIL_SUFFIX = "@gs-project-accounts.iam.gserviceaccount.com"; private static final String KMS_KEY_RING_NAME = "gcs_test_kms_key_ring"; private static final String KMS_KEY_RING_LOCATION = "us"; @@ -2037,6 +2039,152 @@ public void testListBucketDefaultKmsKeyName() throws ExecutionException, Interru } } + @Test + public void testRetentionPolicyNoLock() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket remoteBucket = storage.create(BucketInfo.newBuilder(bucketName) + .setRetentionPeriod(RETENTION_PERIOD).build()); + try { + assertEquals(RETENTION_PERIOD, remoteBucket.getRetentionPeriod()); + assertNotNull(remoteBucket.getRetentionEffectiveTime()); + assertNull(remoteBucket.retentionPolicyIsLocked()); + remoteBucket = storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.RETENTION_POLICY)); + assertEquals(RETENTION_PERIOD, remoteBucket.getRetentionPeriod()); + assertNotNull(remoteBucket.getRetentionEffectiveTime()); + assertNull(remoteBucket.retentionPolicyIsLocked()); + String blobName = "test-create-with-retention-policy-hold"; + BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build(); + Blob remoteBlob = storage.create(blobInfo); + assertNotNull(remoteBlob.getRetentionExpirationTime()); + remoteBucket = remoteBucket.toBuilder().setRetentionPeriod(null).build().update(); + assertNull(remoteBucket.getRetentionPeriod()); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + + @Test + public void testRetentionPolicyLock() throws ExecutionException, InterruptedException { + retentionPolicyLockRequesterPays(true); + retentionPolicyLockRequesterPays(false); + } + + private void retentionPolicyLockRequesterPays(boolean requesterPays) throws ExecutionException, InterruptedException { + String projectId = remoteStorageHelper.getOptions().getProjectId(); + String bucketName = RemoteStorageHelper.generateBucketName(); + BucketInfo bucketInfo; + if (requesterPays) { + bucketInfo = BucketInfo.newBuilder(bucketName).setRetentionPeriod(RETENTION_PERIOD) + .setRequesterPays(true).build(); + } else { + bucketInfo = BucketInfo.newBuilder(bucketName).setRetentionPeriod(RETENTION_PERIOD).build(); + } + Bucket remoteBucket = storage.create(bucketInfo); + try { + assertFalse(remoteBucket.retentionPolicyIsLocked()); + assertNotNull(remoteBucket.getRetentionEffectiveTime()); + assertNotNull(remoteBucket.getMetageneration()); + if (requesterPays) { + remoteBucket = storage.lockRetentionPolicy(remoteBucket, Storage.BucketTargetOption.metagenerationMatch(), + Storage.BucketTargetOption.userProject(projectId)); + } else { + remoteBucket = storage.lockRetentionPolicy(remoteBucket, Storage.BucketTargetOption.metagenerationMatch()); + } + assertTrue(remoteBucket.retentionPolicyIsLocked()); + assertNotNull(remoteBucket.getRetentionEffectiveTime()); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + + @Test + public void testAttemptObjectDeleteWithRetentionPolicy() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket remoteBucket = storage.create(BucketInfo.newBuilder(bucketName) + .setRetentionPeriod(RETENTION_PERIOD).build()); + assertEquals(RETENTION_PERIOD, remoteBucket.getRetentionPeriod()); + String blobName = "test-create-with-retention-policy"; + BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build(); + Blob remoteBlob = storage.create(blobInfo); + assertNotNull(remoteBlob.getRetentionExpirationTime()); + try { + remoteBlob.delete(); + fail("Expected failure on delete from retentionPolicy"); + } catch (StorageException ex) { + // expected + } finally { + Thread.sleep(RETENTION_PERIOD_IN_MILLISECONDS); + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + + @Test + public void testEnableDisableBucketDefaultEventBasedHold() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket remoteBucket = storage.create(BucketInfo.newBuilder(bucketName).setDefaultEventBasedHold(true).build()); + try { + assertTrue(remoteBucket.getDefaultEventBasedHold()); + remoteBucket = storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.DEFAULT_EVENT_BASED_HOLD)); + assertTrue(remoteBucket.getDefaultEventBasedHold()); + String blobName = "test-create-with-event-based-hold"; + BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build(); + Blob remoteBlob = storage.create(blobInfo); + assertTrue(remoteBlob.getEventBasedHold()); + remoteBlob = storage.get(blobInfo.getBlobId(), Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD)); + assertTrue(remoteBlob.getEventBasedHold()); + remoteBlob = remoteBlob.toBuilder().setEventBasedHold(false).build().update(); + assertFalse(remoteBlob.getEventBasedHold()); + remoteBucket = remoteBucket.toBuilder().setDefaultEventBasedHold(false).build().update(); + assertFalse(remoteBucket.getDefaultEventBasedHold()); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + + @Test + public void testEnableDisableTemporaryHold() { + String blobName = "test-create-with-temporary-hold"; + BlobInfo blobInfo = BlobInfo.newBuilder(BUCKET, blobName).setTemporaryHold(true).build(); + Blob remoteBlob = storage.create(blobInfo); + assertTrue(remoteBlob.getTemporaryHold()); + remoteBlob = storage.get(remoteBlob.getBlobId(), Storage.BlobGetOption.fields(BlobField.TEMPORARY_HOLD)); + assertTrue(remoteBlob.getTemporaryHold()); + remoteBlob = remoteBlob.toBuilder().setTemporaryHold(false).build().update(); + assertFalse(remoteBlob.getTemporaryHold()); + } + + @Test + public void testAttemptObjectDeleteWithEventBasedHold() { + String blobName = "test-create-with-event-based-hold"; + BlobInfo blobInfo = BlobInfo.newBuilder(BUCKET, blobName).setEventBasedHold(true).build(); + Blob remoteBlob = storage.create(blobInfo); + assertTrue(remoteBlob.getEventBasedHold()); + try { + remoteBlob.delete(); + fail("Expected failure on delete from eventBasedHold"); + } catch (StorageException ex) { + // expected + } finally { + remoteBlob.toBuilder().setEventBasedHold(false).build().update(); + } + } + + @Test + public void testAttemptDeletionObjectTemporaryHold() { + String blobName = "test-create-with-temporary-hold"; + BlobInfo blobInfo = BlobInfo.newBuilder(BUCKET, blobName).setTemporaryHold(true).build(); + Blob remoteBlob = storage.create(blobInfo); + assertTrue(remoteBlob.getTemporaryHold()); + try { + remoteBlob.delete(); + fail("Expected failure on delete from temporaryHold"); + } catch (StorageException ex) { + // expected + } finally { + remoteBlob.toBuilder().setEventBasedHold(false).build().update(); + } + } + @Test public void testGetServiceAccount() throws InterruptedException { String projectId = remoteStorageHelper.getOptions().getProjectId();