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: Copy Backup Support #1778

Merged
merged 21 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from 16 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
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>
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
</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()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we test with null max expire time and null referencingBackupsList?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

.setReferencingBackup(proto.getReferencingBackupsList())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.encryption.BackupEncryptionConfig;
import com.google.cloud.spanner.encryption.EncryptionInfo;
import com.google.protobuf.ProtocolStringList;
import com.google.spanner.admin.database.v1.Database;
import java.util.Objects;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -84,6 +85,20 @@ 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 abstract Builder setMaxExpireTime(Timestamp maxExpireTime);
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
asthamohta marked this conversation as resolved.
Show resolved Hide resolved

/**
* Output Only.
*
* <p>Returns the names of the destination backups being created by copying this source backup.
*/
protected abstract Builder setReferencingBackup(ProtocolStringList referencingBackup);
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not use the protobuf specific types here. Instead of ProtocolStringList, could you use List<String>?

}

abstract static class BuilderImpl extends Builder {
Expand All @@ -96,6 +111,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 ProtocolStringList referencingBackup;
asthamohta marked this conversation as resolved.
Show resolved Hide resolved

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

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

@Override
public Builder setMaxExpireTime(Timestamp maxExpireTime) {
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
this.maxExpireTime = Preconditions.checkNotNull(maxExpireTime);
return this;
}

@Override
public Builder setReferencingBackup(ProtocolStringList referencingBackup) {
this.referencingBackup = Preconditions.checkNotNull(referencingBackup);
return this;
}
}

/** State of the backup. */
Expand All @@ -184,6 +215,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 ProtocolStringList referencingBackup;
asthamohta marked this conversation as resolved.
Show resolved Hide resolved

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

/** Returns the backup id. */
Expand Down Expand Up @@ -253,6 +288,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 ProtocolStringList getReferencingBackup() {
return referencingBackup;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -269,26 +317,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(referencingBackup, that.referencingBackup);
}

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

@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,
referencingBackup);
}
}
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,70 @@ 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);
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
* 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
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
* 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.
*/
OperationFuture<Backup, CopyBackupMetadata> copyBackup(
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
String instanceId, String sourceBackupId, String destinationBackupId, Timestamp expireTime)
throws SpannerException;

/**
* Creates a copy of backup from an existing backup in Cloud Spanner. 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
*/
OperationFuture<Backup, CopyBackupMetadata> copyBackup(
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
BackupId sourceBackupId, Backup destinationBackup) throws SpannerException;

/**
* 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,6 +32,7 @@
import com.google.longrunning.Operation;
import com.google.protobuf.Empty;
import com.google.protobuf.FieldMask;
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.RestoreDatabaseMetadata;
Expand Down Expand Up @@ -165,6 +166,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(
asthamohta marked this conversation as resolved.
Show resolved Hide resolved
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