Skip to content

Commit ed3874a

Browse files
authored
feat: add backup support (#100)
1 parent ae22ac3 commit ed3874a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4308
-64
lines changed

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

+136
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,140 @@
1111
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
1212
<method>* asyncDeleteSession(*)</method>
1313
</difference>
14+
15+
<!-- Adding Backup feature -->
16+
<difference>
17+
<differenceType>7012</differenceType>
18+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
19+
<method>void cancelOperation(java.lang.String)</method>
20+
</difference>
21+
<difference>
22+
<differenceType>7012</differenceType>
23+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
24+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(java.lang.String, java.lang.String, java.lang.String, com.google.cloud.Timestamp)</method>
25+
</difference>
26+
<difference>
27+
<differenceType>7012</differenceType>
28+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
29+
<method>void deleteBackup(java.lang.String, java.lang.String)</method>
30+
</difference>
31+
<difference>
32+
<differenceType>7012</differenceType>
33+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
34+
<method>com.google.cloud.spanner.Backup getBackup(java.lang.String, java.lang.String)</method>
35+
</difference>
36+
<difference>
37+
<differenceType>7012</differenceType>
38+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
39+
<method>com.google.cloud.spanner.Backup getBackup(java.lang.String, java.lang.String)</method>
40+
</difference>
41+
<difference>
42+
<differenceType>7012</differenceType>
43+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
44+
<method>com.google.cloud.Policy getBackupIAMPolicy(java.lang.String, java.lang.String)</method>
45+
</difference>
46+
<difference>
47+
<differenceType>7012</differenceType>
48+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
49+
<method>com.google.longrunning.Operation getOperation(java.lang.String)</method>
50+
</difference>
51+
<difference>
52+
<differenceType>7012</differenceType>
53+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
54+
<method>com.google.api.gax.paging.Page listBackupOperations(java.lang.String, com.google.cloud.spanner.Options$ListOption[])</method>
55+
</difference>
56+
<difference>
57+
<differenceType>7012</differenceType>
58+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
59+
<method>com.google.api.gax.paging.Page listBackups(java.lang.String, com.google.cloud.spanner.Options$ListOption[])</method>
60+
</difference>
61+
<difference>
62+
<differenceType>7012</differenceType>
63+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
64+
<method>com.google.api.gax.paging.Page listDatabaseOperations(java.lang.String, com.google.cloud.spanner.Options$ListOption[])</method>
65+
</difference>
66+
<difference>
67+
<differenceType>7012</differenceType>
68+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
69+
<method>com.google.cloud.spanner.Backup$Builder newBackupBuilder(com.google.cloud.spanner.BackupId)</method>
70+
</difference>
71+
<difference>
72+
<differenceType>7012</differenceType>
73+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
74+
<method>com.google.cloud.spanner.Backup$Builder newBackupBuilder(com.google.cloud.spanner.BackupId)</method>
75+
</difference>
76+
<difference>
77+
<differenceType>7012</differenceType>
78+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
79+
<method>com.google.api.gax.longrunning.OperationFuture restoreDatabase(java.lang.String, java.lang.String, java.lang.String, java.lang.String)</method>
80+
</difference>
81+
<difference>
82+
<differenceType>7012</differenceType>
83+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
84+
<method>com.google.cloud.Policy setBackupIAMPolicy(java.lang.String, java.lang.String, com.google.cloud.Policy)</method>
85+
</difference>
86+
<difference>
87+
<differenceType>7012</differenceType>
88+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
89+
<method>java.lang.Iterable testBackupIAMPermissions(java.lang.String, java.lang.String, java.lang.Iterable)</method>
90+
</difference>
91+
<difference>
92+
<differenceType>7012</differenceType>
93+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
94+
<method>com.google.cloud.spanner.Backup updateBackup(java.lang.String, java.lang.String, com.google.cloud.Timestamp)</method>
95+
</difference>
96+
97+
<difference>
98+
<differenceType>7012</differenceType>
99+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
100+
<method>void cancelOperation(java.lang.String)</method>
101+
</difference>
102+
<difference>
103+
<differenceType>7012</differenceType>
104+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
105+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(java.lang.String, java.lang.String, com.google.spanner.admin.database.v1.Backup)</method>
106+
</difference>
107+
<difference>
108+
<differenceType>7012</differenceType>
109+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
110+
<method>void deleteBackup(java.lang.String)</method>
111+
</difference>
112+
<difference>
113+
<differenceType>7012</differenceType>
114+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
115+
<method>com.google.spanner.admin.database.v1.Backup getBackup(java.lang.String)</method>
116+
</difference>
117+
<difference>
118+
<differenceType>7012</differenceType>
119+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
120+
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listBackupOperations(java.lang.String, int, java.lang.String, java.lang.String)</method>
121+
</difference>
122+
<difference>
123+
<differenceType>7012</differenceType>
124+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
125+
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listBackups(java.lang.String, int, java.lang.String, java.lang.String)</method>
126+
</difference>
127+
<difference>
128+
<differenceType>7012</differenceType>
129+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
130+
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listDatabaseOperations(java.lang.String, int, java.lang.String, java.lang.String)</method>
131+
</difference>
132+
<difference>
133+
<differenceType>7012</differenceType>
134+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
135+
<method>com.google.api.gax.longrunning.OperationFuture restoreDatabase(java.lang.String, java.lang.String, java.lang.String)</method>
136+
</difference>
137+
<difference>
138+
<differenceType>7012</differenceType>
139+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
140+
<method>com.google.spanner.admin.database.v1.Backup updateBackup(com.google.spanner.admin.database.v1.Backup, com.google.protobuf.FieldMask)</method>
141+
</difference>
142+
143+
<difference>
144+
<differenceType>7004</differenceType>
145+
<className>com/google/cloud/spanner/Instance</className>
146+
<!-- Added varargs (ListOption... options) -->
147+
<method>com.google.api.gax.paging.Page listDatabases()</method>
148+
</difference>
149+
14150
</differences>

google-cloud-spanner/pom.xml

+32-9
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@
6262
</execution>
6363
</executions>
6464
</plugin>
65+
<plugin>
66+
<groupId>org.apache.maven.plugins</groupId>
67+
<artifactId>maven-failsafe-plugin</artifactId>
68+
<configuration>
69+
<systemPropertyVariables>
70+
<spanner.testenv.config.class>com.google.cloud.spanner.GceTestEnvConfig</spanner.testenv.config.class>
71+
<spanner.testenv.instance>projects/gcloud-devel/instances/spanner-testing</spanner.testenv.instance>
72+
</systemPropertyVariables>
73+
<forkedProcessTimeoutInSeconds>3000</forkedProcessTimeoutInSeconds>
74+
</configuration>
75+
<executions>
76+
<execution>
77+
<id>default</id>
78+
<configuration>
79+
<groups>com.google.cloud.spanner.IntegrationTest</groups>
80+
<excludedGroups>com.google.cloud.spanner.FlakyTest,com.google.cloud.spanner.TracerTest,com.google.cloud.spanner.ParallelIntegrationTest</excludedGroups>
81+
</configuration>
82+
</execution>
83+
<execution>
84+
<id>parallel-integration-test</id>
85+
<goals>
86+
<goal>integration-test</goal>
87+
</goals>
88+
<configuration>
89+
<groups>com.google.cloud.spanner.ParallelIntegrationTest</groups>
90+
<excludedGroups>com.google.cloud.spanner.FlakyTest,com.google.cloud.spanner.TracerTest,com.google.cloud.spanner.IntegrationTest</excludedGroups>
91+
<forkCount>8</forkCount>
92+
<reuseForks>true</reuseForks>
93+
</configuration>
94+
</execution>
95+
</executions>
96+
</plugin>
6597
</plugins>
6698
<pluginManagement>
6799
<plugins>
@@ -74,15 +106,6 @@
74106
<groupId>org.apache.maven.plugins</groupId>
75107
<artifactId>maven-failsafe-plugin</artifactId>
76108
<version>3.0.0-M4</version>
77-
<configuration>
78-
<systemPropertyVariables>
79-
<spanner.testenv.config.class>com.google.cloud.spanner.GceTestEnvConfig</spanner.testenv.config.class>
80-
<spanner.testenv.instance>projects/gcloud-devel/instances/spanner-testing</spanner.testenv.instance>
81-
</systemPropertyVariables>
82-
<groups>com.google.cloud.spanner.IntegrationTest</groups>
83-
<excludedGroups>com.google.cloud.spanner.FlakyTest,com.google.cloud.spanner.TracerTest</excludedGroups>
84-
<forkedProcessTimeoutInSeconds>2400</forkedProcessTimeoutInSeconds>
85-
</configuration>
86109
</plugin>
87110
<plugin>
88111
<groupId>org.apache.maven.plugins</groupId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.api.client.util.Preconditions;
22+
import com.google.api.gax.longrunning.OperationFuture;
23+
import com.google.api.gax.paging.Page;
24+
import com.google.cloud.Policy;
25+
import com.google.cloud.Timestamp;
26+
import com.google.longrunning.Operation;
27+
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
28+
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
29+
30+
/**
31+
* Represents a Cloud Spanner database backup. {@code Backup} adds a layer of service related
32+
* functionality over {@code BackupInfo}.
33+
*/
34+
public class Backup extends BackupInfo {
35+
public static class Builder extends BackupInfo.BuilderImpl {
36+
private final DatabaseAdminClient dbClient;
37+
38+
Builder(DatabaseAdminClient dbClient, BackupId backupId) {
39+
super(backupId);
40+
this.dbClient = Preconditions.checkNotNull(dbClient);
41+
}
42+
43+
private Builder(Backup backup) {
44+
super(backup);
45+
this.dbClient = backup.dbClient;
46+
}
47+
48+
@Override
49+
public Backup build() {
50+
return new Backup(this);
51+
}
52+
}
53+
54+
private static final String FILTER_BACKUP_OPERATIONS_TEMPLATE = "name:backups/%s";
55+
private final DatabaseAdminClient dbClient;
56+
57+
Backup(Builder builder) {
58+
super(builder);
59+
this.dbClient = Preconditions.checkNotNull(builder.dbClient);
60+
}
61+
62+
/** Creates a backup on the server based on the source of this {@link Backup} instance. */
63+
public OperationFuture<Backup, CreateBackupMetadata> create() {
64+
Preconditions.checkState(
65+
getExpireTime() != null, "Cannot create a backup without an expire time");
66+
Preconditions.checkState(
67+
getDatabase() != null, "Cannot create a backup without a source database");
68+
return dbClient.createBackup(instance(), backup(), sourceDatabase(), getExpireTime());
69+
}
70+
71+
/**
72+
* Returns <code>true</code> if a backup with the id of this {@link Backup} exists on Cloud
73+
* Spanner.
74+
*/
75+
public boolean exists() {
76+
try {
77+
dbClient.getBackup(instance(), backup());
78+
} catch (SpannerException e) {
79+
if (e.getErrorCode() == ErrorCode.NOT_FOUND) {
80+
return false;
81+
}
82+
throw e;
83+
}
84+
return true;
85+
}
86+
87+
/**
88+
* Returns <code>true</code> if this backup is ready to use. The value returned by this method
89+
* could be out-of-sync with the value returned by {@link #getState()}, as this method will make a
90+
* round-trip to the server and return a value based on the response from the server.
91+
*/
92+
public boolean isReady() {
93+
return reload().getState() == State.READY;
94+
}
95+
96+
/**
97+
* Fetches the backup's current information and returns a new {@link Backup} instance. It does not
98+
* update this instance.
99+
*/
100+
public Backup reload() throws SpannerException {
101+
return dbClient.getBackup(instance(), backup());
102+
}
103+
104+
/** Deletes this backup on Cloud Spanner. */
105+
public void delete() throws SpannerException {
106+
dbClient.deleteBackup(instance(), backup());
107+
}
108+
109+
/**
110+
* Updates the expire time of this backup on Cloud Spanner. If this {@link Backup} does not have
111+
* an expire time, the method will throw an {@link IllegalStateException}.
112+
*/
113+
public void updateExpireTime() {
114+
Preconditions.checkState(getExpireTime() != null, "This backup has no expire time");
115+
dbClient.updateBackup(instance(), backup(), getExpireTime());
116+
}
117+
118+
/**
119+
* Restores this backup to the specified database. The database must not already exist and will be
120+
* created by this call. The database may be created in a different instance than where the backup
121+
* is stored.
122+
*/
123+
public OperationFuture<Database, RestoreDatabaseMetadata> restore(DatabaseId database) {
124+
Preconditions.checkNotNull(database);
125+
return dbClient.restoreDatabase(
126+
instance(), backup(), database.getInstanceId().getInstance(), database.getDatabase());
127+
}
128+
129+
/** Returns all long-running backup operations for this {@link Backup}. */
130+
public Page<Operation> listBackupOperations() {
131+
return dbClient.listBackupOperations(
132+
instance(), Options.filter(String.format(FILTER_BACKUP_OPERATIONS_TEMPLATE, backup())));
133+
}
134+
135+
/** Returns the IAM {@link Policy} for this backup. */
136+
public Policy getIAMPolicy() {
137+
return dbClient.getBackupIAMPolicy(instance(), backup());
138+
}
139+
140+
/**
141+
* Updates the IAM policy for this backup and returns the resulting policy. It is highly
142+
* recommended to first get the current policy and base the updated policy on the returned policy.
143+
* See {@link Policy.Builder#setEtag(String)} for information on the recommended read-modify-write
144+
* cycle.
145+
*/
146+
public Policy setIAMPolicy(Policy policy) {
147+
return dbClient.setBackupIAMPolicy(instance(), backup(), policy);
148+
}
149+
150+
/**
151+
* Tests for the given permissions on this backup for the caller.
152+
*
153+
* @param permissions the permissions to test for. Permissions with wildcards (such as '*',
154+
* 'spanner.*', 'spanner.instances.*') are not allowed.
155+
* @return the subset of the tested permissions that the caller is allowed.
156+
*/
157+
public Iterable<String> testIAMPermissions(Iterable<String> permissions) {
158+
return dbClient.testBackupIAMPermissions(instance(), backup(), permissions);
159+
}
160+
161+
public Builder toBuilder() {
162+
return new Builder(this);
163+
}
164+
165+
private String instance() {
166+
return getInstanceId().getInstance();
167+
}
168+
169+
private String backup() {
170+
return getId().getBackup();
171+
}
172+
173+
private String sourceDatabase() {
174+
return getDatabase().getDatabase();
175+
}
176+
177+
static Backup fromProto(
178+
com.google.spanner.admin.database.v1.Backup proto, DatabaseAdminClient client) {
179+
checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field");
180+
checkArgument(!proto.getDatabase().isEmpty(), "Missing expected 'database' field");
181+
return new Backup.Builder(client, BackupId.of(proto.getName()))
182+
.setState(fromProtoState(proto.getState()))
183+
.setSize(proto.getSizeBytes())
184+
.setExpireTime(Timestamp.fromProto(proto.getExpireTime()))
185+
.setDatabase(DatabaseId.of(proto.getDatabase()))
186+
.setProto(proto)
187+
.build();
188+
}
189+
190+
static BackupInfo.State fromProtoState(
191+
com.google.spanner.admin.database.v1.Backup.State protoState) {
192+
switch (protoState) {
193+
case STATE_UNSPECIFIED:
194+
return BackupInfo.State.UNSPECIFIED;
195+
case CREATING:
196+
return BackupInfo.State.CREATING;
197+
case READY:
198+
return BackupInfo.State.READY;
199+
default:
200+
throw new IllegalArgumentException("Unrecognized state " + protoState);
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)