Skip to content

feat: Spanner copy backup ct #1718

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

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.19.0'
implementation 'com.google.cloud:google-cloud-spanner:6.20.0'
```

If you are using SBT, add this to your dependencies

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.19.0"
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.20.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()))
.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);

/**
* Output Only.
*
* <p>Returns the names of the destination backups being created by copying this source backup.
*/
protected abstract Builder setReferencingBackup(ProtocolStringList referencingBackup);
}

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;

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) {
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;

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);
* 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.
*/
OperationFuture<Backup, CopyBackupMetadata> copyBackup(
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need a sourceBackupId?

* .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(
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(
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
Loading