Skip to content

feat: Copy backup samples #1802

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

Merged
merged 13 commits into from
Apr 22, 2022
5 changes: 3 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.23.2'
implementation 'com.google.cloud:google-cloud-spanner:6.23.3'
```

If you are using SBT, add this to your dependencies

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.23.2"
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.23.3"
```

## Authentication
Expand Down Expand Up @@ -252,6 +252,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
| Async Runner Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AsyncRunnerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AsyncRunnerExample.java) |
| Async Transaction Manager Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AsyncTransactionManagerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AsyncTransactionManagerExample.java) |
| Batch Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/BatchSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/BatchSample.java) |
| Copy Backup Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/CopyBackupSample.java) |
| Create Backup With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/CreateBackupWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/CreateBackupWithEncryptionKey.java) |
| Create Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithDefaultLeaderSample.java) |
| Create Database With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/CreateDatabaseWithEncryptionKey.java) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2022 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.spanner;

// [START spanner_copy_backup]

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.Backup;
import com.google.cloud.spanner.BackupId;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.spanner.admin.database.v1.CopyBackupMetadata;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class CopyBackupSample {
static void copyBackup() {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String sourceBackupId = "my-backup";
String destinationBackupId = "my-destination-backup";
try (Spanner spanner =
SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
DatabaseAdminClient databaseAdminClient = spanner.getDatabaseAdminClient();
copyBackup(databaseAdminClient, projectId, instanceId, sourceBackupId, destinationBackupId);
}
}

static void copyBackup(
DatabaseAdminClient databaseAdminClient,
String projectId,
String instanceId,
String sourceBackupId,
String destinationBackupId) {

Timestamp expireTime =
Timestamp.ofTimeMicroseconds(
TimeUnit.MICROSECONDS.convert(
System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14),
TimeUnit.MILLISECONDS));
// Creates a copy of an existing backup.
Backup destinationBackup =
databaseAdminClient
.newBackupBuilder(BackupId.of(projectId, instanceId, destinationBackupId))
.setExpireTime(expireTime)
.build();

// Initiate the request which returns an OperationFuture.
System.out.println("Copying backup [" + destinationBackup.getId() + "]...");
OperationFuture<Backup, CopyBackupMetadata> operation =
databaseAdminClient.copyBackup(
BackupId.of(projectId, instanceId, sourceBackupId), destinationBackup);
try {
// Wait for the backup operation to complete.
destinationBackup = operation.get();
System.out.println("Copied backup [" + destinationBackup.getId() + "]");
} catch (ExecutionException e) {
throw (SpannerException) e.getCause();
} catch (InterruptedException e) {
throw SpannerExceptionFactory.propagateInterrupt(e);
}
// Load the metadata of the new backup from the server.
destinationBackup = destinationBackup.reload();
System.out.println(
String.format(
"Backup %s of size %d bytes was copied at %s for version of database at %s",
destinationBackup.getId().getName(),
destinationBackup.getSize(),
LocalDateTime.ofEpochSecond(
destinationBackup.getProto().getCreateTime().getSeconds(),
destinationBackup.getProto().getCreateTime().getNanos(),
OffsetDateTime.now().getOffset()),
LocalDateTime.ofEpochSecond(
destinationBackup.getProto().getVersionTime().getSeconds(),
destinationBackup.getProto().getVersionTime().getNanos(),
OffsetDateTime.now().getOffset())));
return;
}
}
// [END spanner_copy_backup]
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import com.google.common.io.BaseEncoding;
import com.google.longrunning.Operation;
import com.google.protobuf.InvalidProtocolBufferException;
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.OptimizeRestoredDatabaseMetadata;
Expand Down Expand Up @@ -1659,21 +1660,23 @@ static void cancelCreateBackup(
// [END spanner_cancel_backup_create]

// [START spanner_list_backup_operations]
static void listBackupOperations(InstanceAdminClient instanceAdminClient, DatabaseId databaseId) {
static void listBackupOperations(
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this a breaking change? Maybe customers won't care because it is in the sample , but I don't know what the policy is here. Yoshi team might know

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@olavloite pointed out that this is a non public method and hence can't be called so this will not be a breaking change

Copy link
Contributor

Choose a reason for hiding this comment

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

That is true, but we publish these samples in our public documentation, so we would be changing the signature. Anyway, if your team is ok with this, I am ok.

InstanceAdminClient instanceAdminClient, DatabaseId databaseId, BackupId backupId) {
Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance());
// Get create backup operations for the sample database.
Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert(
TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24,
TimeUnit.HOURS), 0);
String filter =
String.format(
"(metadata.database:%s) AND "
+ "(metadata.@type:type.googleapis.com/"
+ "google.spanner.admin.database.v1.CreateBackupMetadata) AND "
+ "(metadata.progress.start_time > \"%s\")",
databaseId.getName(), last24Hours);
Page<Operation> operations = instance.listBackupOperations(Options.filter(filter));
for (Operation op : operations.iterateAll()) {
"(metadata.@type:type.googleapis.com/"
+ "google.spanner.admin.database.v1.CreateBackupMetadata) "
+ "AND (metadata.database:%s)",
databaseId.getName());
Page<Operation> createBackupOperations = instance.listBackupOperations(
Options.filter(filter));
System.out.println("Create Backup Operations:");
for (Operation op : createBackupOperations.iterateAll()) {
try {
CreateBackupMetadata metadata = op.getMetadata().unpack(CreateBackupMetadata.class);
System.out.println(
Expand All @@ -1687,6 +1690,30 @@ static void listBackupOperations(InstanceAdminClient instanceAdminClient, Databa
System.err.println(e.getMessage());
}
}
// Get copy backup operations for the sample database.
filter =
String.format(
"(metadata.@type:type.googleapis.com/"
+ "google.spanner.admin.database.v1.CopyBackupMetadata) "
+ "AND (metadata.source_backup:%s)",
backupId.getName());
Page<Operation> copyBackupOperations = instance.listBackupOperations(Options.filter(filter));
System.out.println("Copy Backup Operations:");
for (Operation op : copyBackupOperations.iterateAll()) {
try {
CopyBackupMetadata copyBackupMetadata =
op.getMetadata().unpack(CopyBackupMetadata.class);
System.out.println(
String.format(
"Copy Backup %s on backup %s pending: %d%% complete",
copyBackupMetadata.getName(),
copyBackupMetadata.getSourceBackup(),
copyBackupMetadata.getProgress().getProgressPercent()));
} catch (InvalidProtocolBufferException e) {
// The returned operation does not contain CopyBackupMetadata.
System.err.println(e.getMessage());
}
}
}
// [END spanner_list_backup_operations]

Expand Down Expand Up @@ -1840,13 +1867,19 @@ static void updateBackup(DatabaseAdminClient dbAdminClient, BackupId backupId) {
TimeUnit.SECONDS.toMicros(backup.getExpireTime().getSeconds())
+ TimeUnit.NANOSECONDS.toMicros(backup.getExpireTime().getNanos())
+ TimeUnit.DAYS.toMicros(30L));
// New Expire Time must be less than Max Expire Time
expireTime = expireTime.compareTo(backup.getMaxExpireTime())
< 0 ? expireTime : backup.getMaxExpireTime();
int timeDiff = expireTime.compareTo(backup.getExpireTime());
Timestamp newExpireTime = (timeDiff < 0) ? expireTime : backup.getExpireTime();

System.out.println(String.format(
"Updating expire time of backup [%s] to %s...",
backupId.toString(),
LocalDateTime.ofEpochSecond(
expireTime.getSeconds(),
expireTime.getNanos(),
OffsetDateTime.now().getOffset()).toString()));
expireTime.getSeconds(),
expireTime.getNanos(),
OffsetDateTime.now().getOffset()).toString()));
Copy link
Collaborator

Choose a reason for hiding this comment

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

super nit: the .toString() is superfluous


// Update expire time.
backup = backup.toBuilder().setExpireTime(expireTime).build();
Expand Down Expand Up @@ -2048,7 +2081,7 @@ static void run(
BackupId.of(backup.getInstanceId(), backup.getBackup() + "_cancel"));
break;
case "listbackupoperations":
listBackupOperations(instanceAdminClient, database);
listBackupOperations(instanceAdminClient, database, backup);
break;
case "listdatabaseoperations":
listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId());
Expand Down Expand Up @@ -2144,14 +2177,14 @@ static void printUsageAndExit() {
System.err.println(" SpannerExample querywithqueryoptions my-instance example-db");
System.err.println(" SpannerExample createbackup my-instance example-db");
System.err.println(" SpannerExample listbackups my-instance example-db");
System.err.println(" SpannerExample listbackupoperations my-instance example-db");
System.err.println(" SpannerExample listbackupoperations my-instance example-db backup-id");
System.err.println(" SpannerExample listdatabaseoperations my-instance example-db");
System.err.println(" SpannerExample restorebackup my-instance example-db");
System.exit(1);
}

public static void main(String[] args) throws Exception {
if (args.length != 3) {
if (args.length != 3 && args.length != 4) {
printUsageAndExit();
}
// [START init_client]
Expand All @@ -2176,6 +2209,9 @@ public static void main(String[] args) throws Exception {
"%s_%02d",
db.getDatabase(), LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR));
BackupId backup = BackupId.of(db.getInstanceId(), backupName);
if (args.length == 4) {
backupName = args[3];
}

// [START init_client]
DatabaseClient dbClient = spanner.getDatabaseClient(db);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.example.spanner;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;

import com.google.cloud.Timestamp;
import com.google.cloud.spanner.Backup;
Expand Down Expand Up @@ -328,6 +329,8 @@ public void testSample() throws Exception {
"Backup %s on database %s pending:",
backupId.getName(),
dbId.getName()));
assertTrue("Out does not contain copy backup operations", out.contains(
"Copy Backup Operations"));
} catch (SpannerException e) {
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT);
assertThat(e.getMessage()).contains("Cannot evaluate filter expression");
Expand Down