Skip to content

Commit dc79366

Browse files
asthamohtaansh0lolavloitegcf-owl-bot[bot]
authored
feat: Copy Backup Support (#1778)
* 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 <[email protected]> Co-authored-by: Knut Olav Løite <[email protected]> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 61cf909 commit dc79366

File tree

11 files changed

+415
-35
lines changed

11 files changed

+415
-35
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
5656
If you are using Gradle without BOM, add this to your dependencies
5757

5858
```Groovy
59-
implementation 'com.google.cloud:google-cloud-spanner:6.21.2'
59+
implementation 'com.google.cloud:google-cloud-spanner:6.22.0'
6060
```
6161

6262
If you are using SBT, add this to your dependencies
6363

6464
```Scala
65-
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.21.2"
65+
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.22.0"
6666
```
6767

6868
## Authentication

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,29 @@
3535
<className>com/google/cloud/spanner/connection/ConnectionOptions</className>
3636
<method>com.google.cloud.spanner.Dialect getDialect()</method>
3737
</difference>
38+
<difference>
39+
<differenceType>7013</differenceType>
40+
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
41+
<method>com.google.cloud.spanner.BackupInfo$Builder setMaxExpireTime(com.google.cloud.Timestamp)</method>
42+
</difference>
43+
<difference>
44+
<differenceType>7013</differenceType>
45+
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
46+
<method>com.google.cloud.spanner.BackupInfo$Builder setReferencingBackup(com.google.protobuf.ProtocolStringList)</method>
47+
</difference>
48+
<difference>
49+
<differenceType>7012</differenceType>
50+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
51+
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(java.lang.String, java.lang.String, java.lang.String, com.google.cloud.Timestamp)</method>
52+
</difference>
53+
<difference>
54+
<differenceType>7012</differenceType>
55+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
56+
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.Backup)</method>
57+
</difference>
58+
<difference>
59+
<differenceType>7012</differenceType>
60+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
61+
<method>com.google.api.gax.longrunning.OperationFuture copyBackup(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.Backup)</method>
62+
</difference>
3863
</differences>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ static Backup fromProto(
183183
.setDatabase(DatabaseId.of(proto.getDatabase()))
184184
.setEncryptionInfo(EncryptionInfo.fromProtoOrNull(proto.getEncryptionInfo()))
185185
.setProto(proto)
186+
.setMaxExpireTime(Timestamp.fromProto(proto.getMaxExpireTime()))
187+
.addAllReferencingBackups(proto.getReferencingBackupsList())
186188
.build();
187189
}
188190

google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.cloud.spanner.encryption.BackupEncryptionConfig;
2222
import com.google.cloud.spanner.encryption.EncryptionInfo;
2323
import com.google.spanner.admin.database.v1.Database;
24+
import java.util.List;
2425
import java.util.Objects;
2526
import javax.annotation.Nullable;
2627

@@ -84,6 +85,24 @@ public abstract static class Builder {
8485

8586
/** Builds the backup from this builder. */
8687
public abstract Backup build();
88+
89+
/**
90+
* Output Only.
91+
*
92+
* <p>Returns the max allowed expiration time of the backup, with microseconds granularity.
93+
*/
94+
protected Builder setMaxExpireTime(Timestamp maxExpireTime) {
95+
throw new UnsupportedOperationException("Unimplemented");
96+
}
97+
98+
/**
99+
* Output Only.
100+
*
101+
* <p>Returns the names of the destination backups being created by copying this source backup.
102+
*/
103+
protected Builder addAllReferencingBackups(List<String> referencingBackups) {
104+
throw new UnsupportedOperationException("Unimplemented");
105+
}
87106
}
88107

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

100121
BuilderImpl(BackupId id) {
101122
this.id = Preconditions.checkNotNull(id);
@@ -111,6 +132,8 @@ abstract static class BuilderImpl extends Builder {
111132
this.encryptionConfig = other.encryptionConfig;
112133
this.encryptionInfo = other.encryptionInfo;
113134
this.proto = other.proto;
135+
this.maxExpireTime = other.maxExpireTime;
136+
this.referencingBackups = other.referencingBackups;
114137
}
115138

116139
@Override
@@ -163,6 +186,18 @@ Builder setProto(@Nullable com.google.spanner.admin.database.v1.Backup proto) {
163186
this.proto = proto;
164187
return this;
165188
}
189+
190+
@Override
191+
protected Builder setMaxExpireTime(Timestamp maxExpireTime) {
192+
this.maxExpireTime = Preconditions.checkNotNull(maxExpireTime);
193+
return this;
194+
}
195+
196+
@Override
197+
protected Builder addAllReferencingBackups(List<String> referencingBackups) {
198+
this.referencingBackups = Preconditions.checkNotNull(referencingBackups);
199+
return this;
200+
}
166201
}
167202

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

188225
BackupInfo(BuilderImpl builder) {
189226
this.id = builder.id;
@@ -195,6 +232,8 @@ public enum State {
195232
this.versionTime = builder.versionTime;
196233
this.database = builder.database;
197234
this.proto = builder.proto;
235+
this.maxExpireTime = builder.maxExpireTime;
236+
this.referencingBackups = builder.referencingBackups;
198237
}
199238

200239
/** Returns the backup id. */
@@ -253,6 +292,19 @@ public DatabaseId getDatabase() {
253292
return proto;
254293
}
255294

295+
/** Returns the max expire time of this {@link Backup}. */
296+
public Timestamp getMaxExpireTime() {
297+
return maxExpireTime;
298+
}
299+
300+
/**
301+
* Returns the names of the destination backups being created by copying this source backup {@link
302+
* Backup}.
303+
*/
304+
public List<String> getReferencingBackups() {
305+
return referencingBackups;
306+
}
307+
256308
@Override
257309
public boolean equals(Object o) {
258310
if (this == o) {
@@ -269,26 +321,39 @@ public boolean equals(Object o) {
269321
&& Objects.equals(encryptionInfo, that.encryptionInfo)
270322
&& Objects.equals(expireTime, that.expireTime)
271323
&& Objects.equals(versionTime, that.versionTime)
272-
&& Objects.equals(database, that.database);
324+
&& Objects.equals(database, that.database)
325+
&& Objects.equals(maxExpireTime, that.maxExpireTime)
326+
&& Objects.equals(referencingBackups, that.referencingBackups);
273327
}
274328

275329
@Override
276330
public int hashCode() {
277331
return Objects.hash(
278-
id, state, size, encryptionConfig, encryptionInfo, expireTime, versionTime, database);
332+
id,
333+
state,
334+
size,
335+
encryptionConfig,
336+
encryptionInfo,
337+
expireTime,
338+
versionTime,
339+
database,
340+
maxExpireTime,
341+
referencingBackups);
279342
}
280343

281344
@Override
282345
public String toString() {
283346
return String.format(
284-
"Backup[%s, %s, %d, %s, %s, %s, %s, %s]",
347+
"Backup[%s, %s, %d, %s, %s, %s, %s, %s, %s, %s]",
285348
id.getName(),
286349
state,
287350
size,
288351
encryptionConfig,
289352
encryptionInfo,
290353
expireTime,
291354
versionTime,
292-
database);
355+
database,
356+
maxExpireTime,
357+
referencingBackups);
293358
}
294359
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.cloud.Timestamp;
2323
import com.google.cloud.spanner.Options.ListOption;
2424
import com.google.longrunning.Operation;
25+
import com.google.spanner.admin.database.v1.CopyBackupMetadata;
2526
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
2627
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
2728
import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
@@ -178,6 +179,73 @@ OperationFuture<Backup, CreateBackupMetadata> createBackup(
178179
*/
179180
OperationFuture<Backup, CreateBackupMetadata> createBackup(Backup backup) throws SpannerException;
180181

182+
/**
183+
* Creates a copy of backup from an existing backup in a Cloud Spanner instance.
184+
*
185+
* <p>Example to copy a backup.
186+
*
187+
* <pre>{@code
188+
* String instanceId ="my_instance_id";
189+
* String sourceBackupId ="source_backup_id";
190+
* String destinationBackupId ="destination_backup_id";
191+
* Timestamp expireTime =Timestamp.ofTimeMicroseconds(micros);
192+
* OperationFuture<Backup, CopyBackupMetadata> op = dbAdminClient
193+
* .copyBackup(
194+
* instanceId,
195+
* sourceBackupId,
196+
* destinationBackupId,
197+
* expireTime);
198+
* Backup backup = op.get();
199+
* }</pre>
200+
*
201+
* @param instanceId the id of the instance where the source backup is located and where the new
202+
* backup will be created.
203+
* @param sourceBackupId the source backup id.
204+
* @param destinationBackupId the id of the backup which will be created. It must conform to the
205+
* regular expression [a-z][a-z0-9_\-]*[a-z0-9] and be between 2 and 60 characters in length.
206+
* @param expireTime the time that the new backup will automatically expire.
207+
*/
208+
default OperationFuture<Backup, CopyBackupMetadata> copyBackup(
209+
String instanceId, String sourceBackupId, String destinationBackupId, Timestamp expireTime) {
210+
throw new UnsupportedOperationException("Unimplemented");
211+
}
212+
213+
/**
214+
* Creates a copy of backup from an existing backup in Cloud Spanner in the same instance. Any
215+
* configuration options in the {@link Backup} instance will be included in the {@link
216+
* com.google.spanner.admin.database.v1.CopyBackupRequest}.
217+
*
218+
* <p>The expire time of the new backup must be set and be at least 6 hours and at most 366 days
219+
* after the creation time of the existing backup that is being copied.
220+
*
221+
* <p>Example to create a copy of a backup.
222+
*
223+
* <pre>{@code
224+
* BackupId sourceBackupId = BackupId.of("source-project", "source-instance", "source-backup-id");
225+
* BackupId destinationBackupId = BackupId.of("destination-project", "destination-instance", "new-backup-id");
226+
* Timestamp expireTime = Timestamp.ofTimeMicroseconds(expireTimeMicros);
227+
* EncryptionConfig encryptionConfig =
228+
* EncryptionConfig.ofKey(
229+
* "projects/my-project/locations/some-location/keyRings/my-keyring/cryptoKeys/my-key"));
230+
*
231+
* Backup destinationBackup = dbAdminClient
232+
* .newBackupBuilder(destinationBackupId)
233+
* .setExpireTime(expireTime)
234+
* .setEncryptionConfig(encryptionConfig)
235+
* .build();
236+
*
237+
* OperationFuture<Backup, CopyBackupMetadata> op = dbAdminClient.copyBackup(sourceBackupId, destinationBackup);
238+
* Backup copiedBackup = op.get();
239+
* }</pre>
240+
*
241+
* @param sourceBackupId the backup to be copied
242+
* @param destinationBackup the new backup to create
243+
*/
244+
default OperationFuture<Backup, CopyBackupMetadata> copyBackup(
245+
BackupId sourceBackupId, Backup destinationBackup) {
246+
throw new UnsupportedOperationException("Unimplemented");
247+
}
248+
181249
/**
182250
* Restore a database from a backup. The database that is restored will be created and may not
183251
* already exist.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@
3232
import com.google.longrunning.Operation;
3333
import com.google.protobuf.Empty;
3434
import com.google.protobuf.FieldMask;
35-
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
36-
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
37-
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
38-
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
35+
import com.google.spanner.admin.database.v1.*;
3936
import java.util.List;
4037
import java.util.UUID;
4138
import javax.annotation.Nullable;
@@ -165,6 +162,49 @@ public OperationFuture<Backup, CreateBackupMetadata> createBackup(Backup backupI
165162
});
166163
}
167164

165+
@Override
166+
public OperationFuture<Backup, CopyBackupMetadata> copyBackup(
167+
String instanceId, String sourceBackupId, String destinationBackupId, Timestamp expireTime)
168+
throws SpannerException {
169+
final Backup destinationBackup =
170+
newBackupBuilder(BackupId.of(projectId, instanceId, destinationBackupId))
171+
.setExpireTime(expireTime)
172+
.build();
173+
174+
return copyBackup(BackupId.of(projectId, instanceId, sourceBackupId), destinationBackup);
175+
}
176+
177+
@Override
178+
public OperationFuture<Backup, CopyBackupMetadata> copyBackup(
179+
BackupId sourceBackupId, Backup destinationBackup) throws SpannerException {
180+
Preconditions.checkNotNull(sourceBackupId);
181+
Preconditions.checkNotNull(destinationBackup);
182+
183+
final OperationFuture<com.google.spanner.admin.database.v1.Backup, CopyBackupMetadata>
184+
rawOperationFuture = rpc.copyBackup(sourceBackupId, destinationBackup);
185+
186+
return new OperationFutureImpl<>(
187+
rawOperationFuture.getPollingFuture(),
188+
rawOperationFuture.getInitialFuture(),
189+
snapshot -> {
190+
com.google.spanner.admin.database.v1.Backup proto =
191+
ProtoOperationTransformers.ResponseTransformer.create(
192+
com.google.spanner.admin.database.v1.Backup.class)
193+
.apply(snapshot);
194+
return Backup.fromProto(
195+
com.google.spanner.admin.database.v1.Backup.newBuilder(proto)
196+
.setName(proto.getName())
197+
.setExpireTime(proto.getExpireTime())
198+
.setEncryptionInfo(proto.getEncryptionInfo())
199+
.build(),
200+
DatabaseAdminClientImpl.this);
201+
},
202+
ProtoOperationTransformers.MetadataTransformer.create(CopyBackupMetadata.class),
203+
e -> {
204+
throw SpannerExceptionFactory.newSpannerException(e);
205+
});
206+
}
207+
168208
@Override
169209
public Backup updateBackup(String instanceId, String backupId, Timestamp expireTime) {
170210
String backupName = getBackupName(instanceId, backupId);

google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/EncryptionConfigProtoMapper.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner.encryption;
1818

19+
import com.google.spanner.admin.database.v1.CopyBackupEncryptionConfig;
1920
import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig;
2021
import com.google.spanner.admin.database.v1.EncryptionConfig;
2122
import com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig;
@@ -50,6 +51,28 @@ public static CreateBackupEncryptionConfig createBackupEncryptionConfig(
5051
}
5152
}
5253

54+
/** Returns an encryption config to be used for a copy backup. */
55+
public static CopyBackupEncryptionConfig copyBackupEncryptionConfig(
56+
BackupEncryptionConfig config) {
57+
if (config instanceof CustomerManagedEncryption) {
58+
return CopyBackupEncryptionConfig.newBuilder()
59+
.setEncryptionType(CopyBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION)
60+
.setKmsKeyName(((CustomerManagedEncryption) config).getKmsKeyName())
61+
.build();
62+
} else if (config instanceof GoogleDefaultEncryption) {
63+
return CopyBackupEncryptionConfig.newBuilder()
64+
.setEncryptionType(CopyBackupEncryptionConfig.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION)
65+
.build();
66+
} else if (config instanceof UseBackupEncryption) {
67+
return CopyBackupEncryptionConfig.newBuilder()
68+
.setEncryptionType(
69+
CopyBackupEncryptionConfig.EncryptionType.USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION)
70+
.build();
71+
} else {
72+
throw new IllegalArgumentException("Unknown backup encryption configuration " + config);
73+
}
74+
}
75+
5376
/** Returns an encryption config to be used for a database restore. */
5477
public static RestoreDatabaseEncryptionConfig restoreDatabaseEncryptionConfig(
5578
RestoreEncryptionConfig config) {

0 commit comments

Comments
 (0)