Skip to content

Commit

Permalink
feat: Copy Backup Support (#1778)
Browse files Browse the repository at this point in the history
* feat: copy backup - porting code changes

* feat: copy backup - porting partial sample

* feat: copy backup - cleaning up tests

* feat: copy backup - cleaning up samples

* feat: copy backup - cleaning up samples

* feat: copy backup signature fixes

* feat: copy backup sample fixes

* feat: copy backup - review fixes

* feat: copy backup - review fixes

* feat: copy backup checkstyle fixes

* feat: make CopyBackup sample runnable

* fix: checkstyle violation

* feat: adding max expire time and get referencing database support

* samples: adding copy backup operation support

* adding documentation

* linting changes

* changes as per review

* removing samples

* review changes

* changes as per review

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

Co-authored-by: Anshul Goyal <anshulgoyal@google.com>
Co-authored-by: Knut Olav Løite <koloite@gmail.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Mar 28, 2022
1 parent 61cf909 commit dc79366
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 35 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
If you are using Gradle without BOM, add this to your dependencies

```Groovy
implementation 'com.google.cloud:google-cloud-spanner:6.21.2'
implementation 'com.google.cloud:google-cloud-spanner:6.22.0'
```

If you are using SBT, add this to your dependencies

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.21.2"
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.22.0"
```

## Authentication
Expand Down
25 changes: 25 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,29 @@
<className>com/google/cloud/spanner/connection/ConnectionOptions</className>
<method>com.google.cloud.spanner.Dialect getDialect()</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
<method>com.google.cloud.spanner.BackupInfo$Builder setMaxExpireTime(com.google.cloud.Timestamp)</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
<method>com.google.cloud.spanner.BackupInfo$Builder setReferencingBackup(com.google.protobuf.ProtocolStringList)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(java.lang.String, java.lang.String, java.lang.String, com.google.cloud.Timestamp)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.Backup)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.Backup)</method>
</difference>
</differences>
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ static Backup fromProto(
.setDatabase(DatabaseId.of(proto.getDatabase()))
.setEncryptionInfo(EncryptionInfo.fromProtoOrNull(proto.getEncryptionInfo()))
.setProto(proto)
.setMaxExpireTime(Timestamp.fromProto(proto.getMaxExpireTime()))
.addAllReferencingBackups(proto.getReferencingBackupsList())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.cloud.spanner.encryption.BackupEncryptionConfig;
import com.google.cloud.spanner.encryption.EncryptionInfo;
import com.google.spanner.admin.database.v1.Database;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -84,6 +85,24 @@ public abstract static class Builder {

/** Builds the backup from this builder. */
public abstract Backup build();

/**
* Output Only.
*
* <p>Returns the max allowed expiration time of the backup, with microseconds granularity.
*/
protected Builder setMaxExpireTime(Timestamp maxExpireTime) {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* Output Only.
*
* <p>Returns the names of the destination backups being created by copying this source backup.
*/
protected Builder addAllReferencingBackups(List<String> referencingBackups) {
throw new UnsupportedOperationException("Unimplemented");
}
}

abstract static class BuilderImpl extends Builder {
Expand All @@ -96,6 +115,8 @@ abstract static class BuilderImpl extends Builder {
private BackupEncryptionConfig encryptionConfig;
private EncryptionInfo encryptionInfo;
private com.google.spanner.admin.database.v1.Backup proto;
private Timestamp maxExpireTime;
private List<String> referencingBackups;

BuilderImpl(BackupId id) {
this.id = Preconditions.checkNotNull(id);
Expand All @@ -111,6 +132,8 @@ abstract static class BuilderImpl extends Builder {
this.encryptionConfig = other.encryptionConfig;
this.encryptionInfo = other.encryptionInfo;
this.proto = other.proto;
this.maxExpireTime = other.maxExpireTime;
this.referencingBackups = other.referencingBackups;
}

@Override
Expand Down Expand Up @@ -163,6 +186,18 @@ Builder setProto(@Nullable com.google.spanner.admin.database.v1.Backup proto) {
this.proto = proto;
return this;
}

@Override
protected Builder setMaxExpireTime(Timestamp maxExpireTime) {
this.maxExpireTime = Preconditions.checkNotNull(maxExpireTime);
return this;
}

@Override
protected Builder addAllReferencingBackups(List<String> referencingBackups) {
this.referencingBackups = Preconditions.checkNotNull(referencingBackups);
return this;
}
}

/** State of the backup. */
Expand All @@ -184,6 +219,8 @@ public enum State {
private final BackupEncryptionConfig encryptionConfig;
private final EncryptionInfo encryptionInfo;
private final com.google.spanner.admin.database.v1.Backup proto;
private final Timestamp maxExpireTime;
private final List<String> referencingBackups;

BackupInfo(BuilderImpl builder) {
this.id = builder.id;
Expand All @@ -195,6 +232,8 @@ public enum State {
this.versionTime = builder.versionTime;
this.database = builder.database;
this.proto = builder.proto;
this.maxExpireTime = builder.maxExpireTime;
this.referencingBackups = builder.referencingBackups;
}

/** Returns the backup id. */
Expand Down Expand Up @@ -253,6 +292,19 @@ public DatabaseId getDatabase() {
return proto;
}

/** Returns the max expire time of this {@link Backup}. */
public Timestamp getMaxExpireTime() {
return maxExpireTime;
}

/**
* Returns the names of the destination backups being created by copying this source backup {@link
* Backup}.
*/
public List<String> getReferencingBackups() {
return referencingBackups;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -269,26 +321,39 @@ public boolean equals(Object o) {
&& Objects.equals(encryptionInfo, that.encryptionInfo)
&& Objects.equals(expireTime, that.expireTime)
&& Objects.equals(versionTime, that.versionTime)
&& Objects.equals(database, that.database);
&& Objects.equals(database, that.database)
&& Objects.equals(maxExpireTime, that.maxExpireTime)
&& Objects.equals(referencingBackups, that.referencingBackups);
}

@Override
public int hashCode() {
return Objects.hash(
id, state, size, encryptionConfig, encryptionInfo, expireTime, versionTime, database);
id,
state,
size,
encryptionConfig,
encryptionInfo,
expireTime,
versionTime,
database,
maxExpireTime,
referencingBackups);
}

@Override
public String toString() {
return String.format(
"Backup[%s, %s, %d, %s, %s, %s, %s, %s]",
"Backup[%s, %s, %d, %s, %s, %s, %s, %s, %s, %s]",
id.getName(),
state,
size,
encryptionConfig,
encryptionInfo,
expireTime,
versionTime,
database);
database,
maxExpireTime,
referencingBackups);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.Options.ListOption;
import com.google.longrunning.Operation;
import com.google.spanner.admin.database.v1.CopyBackupMetadata;
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
Expand Down Expand Up @@ -178,6 +179,73 @@ OperationFuture<Backup, CreateBackupMetadata> createBackup(
*/
OperationFuture<Backup, CreateBackupMetadata> createBackup(Backup backup) throws SpannerException;

/**
* Creates a copy of backup from an existing backup in a Cloud Spanner instance.
*
* <p>Example to copy a backup.
*
* <pre>{@code
* String instanceId ="my_instance_id";
* String sourceBackupId ="source_backup_id";
* String destinationBackupId ="destination_backup_id";
* Timestamp expireTime =Timestamp.ofTimeMicroseconds(micros);
* OperationFuture<Backup, CopyBackupMetadata> op = dbAdminClient
* .copyBackup(
* instanceId,
* sourceBackupId,
* destinationBackupId,
* expireTime);
* Backup backup = op.get();
* }</pre>
*
* @param instanceId the id of the instance where the source backup is located and where the new
* backup will be created.
* @param sourceBackupId the source backup id.
* @param destinationBackupId the id of the backup which will be created. It must conform to the
* regular expression [a-z][a-z0-9_\-]*[a-z0-9] and be between 2 and 60 characters in length.
* @param expireTime the time that the new backup will automatically expire.
*/
default OperationFuture<Backup, CopyBackupMetadata> copyBackup(
String instanceId, String sourceBackupId, String destinationBackupId, Timestamp expireTime) {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* Creates a copy of backup from an existing backup in Cloud Spanner in the same instance. Any
* configuration options in the {@link Backup} instance will be included in the {@link
* com.google.spanner.admin.database.v1.CopyBackupRequest}.
*
* <p>The expire time of the new backup must be set and be at least 6 hours and at most 366 days
* after the creation time of the existing backup that is being copied.
*
* <p>Example to create a copy of a backup.
*
* <pre>{@code
* BackupId sourceBackupId = BackupId.of("source-project", "source-instance", "source-backup-id");
* BackupId destinationBackupId = BackupId.of("destination-project", "destination-instance", "new-backup-id");
* Timestamp expireTime = Timestamp.ofTimeMicroseconds(expireTimeMicros);
* EncryptionConfig encryptionConfig =
* EncryptionConfig.ofKey(
* "projects/my-project/locations/some-location/keyRings/my-keyring/cryptoKeys/my-key"));
*
* Backup destinationBackup = dbAdminClient
* .newBackupBuilder(destinationBackupId)
* .setExpireTime(expireTime)
* .setEncryptionConfig(encryptionConfig)
* .build();
*
* OperationFuture<Backup, CopyBackupMetadata> op = dbAdminClient.copyBackup(sourceBackupId, destinationBackup);
* Backup copiedBackup = op.get();
* }</pre>
*
* @param sourceBackupId the backup to be copied
* @param destinationBackup the new backup to create
*/
default OperationFuture<Backup, CopyBackupMetadata> copyBackup(
BackupId sourceBackupId, Backup destinationBackup) {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* Restore a database from a backup. The database that is restored will be created and may not
* already exist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
import com.google.longrunning.Operation;
import com.google.protobuf.Empty;
import com.google.protobuf.FieldMask;
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.admin.database.v1.*;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -165,6 +162,49 @@ public OperationFuture<Backup, CreateBackupMetadata> createBackup(Backup backupI
});
}

@Override
public OperationFuture<Backup, CopyBackupMetadata> copyBackup(
String instanceId, String sourceBackupId, String destinationBackupId, Timestamp expireTime)
throws SpannerException {
final Backup destinationBackup =
newBackupBuilder(BackupId.of(projectId, instanceId, destinationBackupId))
.setExpireTime(expireTime)
.build();

return copyBackup(BackupId.of(projectId, instanceId, sourceBackupId), destinationBackup);
}

@Override
public OperationFuture<Backup, CopyBackupMetadata> copyBackup(
BackupId sourceBackupId, Backup destinationBackup) throws SpannerException {
Preconditions.checkNotNull(sourceBackupId);
Preconditions.checkNotNull(destinationBackup);

final OperationFuture<com.google.spanner.admin.database.v1.Backup, CopyBackupMetadata>
rawOperationFuture = rpc.copyBackup(sourceBackupId, destinationBackup);

return new OperationFutureImpl<>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
snapshot -> {
com.google.spanner.admin.database.v1.Backup proto =
ProtoOperationTransformers.ResponseTransformer.create(
com.google.spanner.admin.database.v1.Backup.class)
.apply(snapshot);
return Backup.fromProto(
com.google.spanner.admin.database.v1.Backup.newBuilder(proto)
.setName(proto.getName())
.setExpireTime(proto.getExpireTime())
.setEncryptionInfo(proto.getEncryptionInfo())
.build(),
DatabaseAdminClientImpl.this);
},
ProtoOperationTransformers.MetadataTransformer.create(CopyBackupMetadata.class),
e -> {
throw SpannerExceptionFactory.newSpannerException(e);
});
}

@Override
public Backup updateBackup(String instanceId, String backupId, Timestamp expireTime) {
String backupName = getBackupName(instanceId, backupId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner.encryption;

import com.google.spanner.admin.database.v1.CopyBackupEncryptionConfig;
import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig;
import com.google.spanner.admin.database.v1.EncryptionConfig;
import com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig;
Expand Down Expand Up @@ -50,6 +51,28 @@ public static CreateBackupEncryptionConfig createBackupEncryptionConfig(
}
}

/** Returns an encryption config to be used for a copy backup. */
public static CopyBackupEncryptionConfig copyBackupEncryptionConfig(
BackupEncryptionConfig config) {
if (config instanceof CustomerManagedEncryption) {
return CopyBackupEncryptionConfig.newBuilder()
.setEncryptionType(CopyBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION)
.setKmsKeyName(((CustomerManagedEncryption) config).getKmsKeyName())
.build();
} else if (config instanceof GoogleDefaultEncryption) {
return CopyBackupEncryptionConfig.newBuilder()
.setEncryptionType(CopyBackupEncryptionConfig.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION)
.build();
} else if (config instanceof UseBackupEncryption) {
return CopyBackupEncryptionConfig.newBuilder()
.setEncryptionType(
CopyBackupEncryptionConfig.EncryptionType.USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION)
.build();
} else {
throw new IllegalArgumentException("Unknown backup encryption configuration " + config);
}
}

/** Returns an encryption config to be used for a database restore. */
public static RestoreDatabaseEncryptionConfig restoreDatabaseEncryptionConfig(
RestoreEncryptionConfig config) {
Expand Down
Loading

0 comments on commit dc79366

Please sign in to comment.