Skip to content

Commit

Permalink
feat: copy backup API support (#1398)
Browse files Browse the repository at this point in the history
* feat: copy backup API support

* revert auto gen changes/files for copy backup API

* Revert "revert auto gen changes/files for copy backup API"

This reverts commit b6efd2d.

* address comments

* Revert "Revert "revert auto gen changes/files for copy backup API""

This reverts commit f446cd0.

* change params order in private CopyBackupRequest and remove dependency-reduced-pom.xml

* Rename variables in CopyBackupRequest to reduce confusion.

* update request code

* udpate test

* update CopyBackup method

* remove dead code

* format new files

---------

Co-authored-by: Tracy Cui <tracycui@google.com>
  • Loading branch information
TracyCuiCan and Tracy Cui committed Aug 17, 2023
1 parent d6e934f commit 558a408
Show file tree
Hide file tree
Showing 8 changed files with 625 additions and 0 deletions.
Expand Up @@ -40,6 +40,7 @@
import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListTablesPagedResponse;
import com.google.cloud.bigtable.admin.v2.internal.NameUtil;
import com.google.cloud.bigtable.admin.v2.models.Backup;
import com.google.cloud.bigtable.admin.v2.models.CopyBackupRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateBackupRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;
import com.google.cloud.bigtable.admin.v2.models.EncryptionInfo;
Expand Down Expand Up @@ -1317,6 +1318,88 @@ public ApiFuture<Void> awaitOptimizeRestoredTableAsync(
stub.awaitOptimizeRestoredTableCallable().resumeFutureCall(token.getOperationName()));
}

/**
* Copy an existing backup to a new backup in a Cloud Bigtable cluster with the specified
* configuration.
*
* <p>Sample code Note: You want to create the client with project and instance where you want the
* new backup to be copied to.
*
* <pre>{@code
* BigtableTableAdminClient client = BigtableTableAdminClient.create("[PROJECT]", "[INSTANCE]");
* CopyBackupRequest request =
* CopyBackupRequest.of(sourceClusterId, sourceBackupId)
* .setDestination(clusterId, backupId)
* .setExpireTime(expireTime);
* Backup response = client.copyBackup(request);
* }</pre>
*
* If the source backup is located in a different instance
*
* <pre>{@code
* CopyBackupRequest request =
* CopyBackupRequest.of(sourceClusterId, sourceBackupId)
* .setSourceInstance(sourceInstanceId)
* .setDestination(clusterId, backupId)
* .setExpireTime(expireTime);
* Backup response = client.copyBackup(request);
* }</pre>
*
* If the source backup is located in a different project
*
* <pre>{@code
* CopyBackupRequest request =
* CopyBackupRequest.of(sourceClusterId, sourceBackupId)
* .setSourceInstance(sourceProjectId, sourceInstanceId)
* .setDestination(clusterId, backupId)
* .setExpireTime(expireTime);
* Backup response = client.copyBackup(request);
* }</pre>
*/
public Backup copyBackup(CopyBackupRequest request) {
return ApiExceptions.callAndTranslateApiException(copyBackupAsync(request));
}

/**
* Creates a copy of a backup from an existing backup in a Cloud Bigtable cluster with the
* specified configuration asynchronously.
*
* <p>Sample code
*
* <pre>{@code
* CopyBackupRequest request =
* CopyBackupRequest.of(sourceClusterId, sourceBackupId)
* .setDestination(clusterId, backupId)
* .setExpireTime(expireTime);
* ApiFuture<Backup> future = client.copyBackupAsync(request);
*
* ApiFutures.addCallback(
* future,
* new ApiFutureCallback<Backup>() {
* public void onSuccess(Backup backup) {
* System.out.println("Successfully create the backup " + backup.getId());
* }
*
* public void onFailure(Throwable t) {
* t.printStackTrace();
* }
* },
* MoreExecutors.directExecutor()
* );
* }</pre>
*/
public ApiFuture<Backup> copyBackupAsync(CopyBackupRequest request) {
return ApiFutures.transform(
stub.copyBackupOperationCallable().futureCall(request.toProto(projectId, instanceId)),
new ApiFunction<com.google.bigtable.admin.v2.Backup, Backup>() {
@Override
public Backup apply(com.google.bigtable.admin.v2.Backup backupProto) {
return Backup.fromProto(backupProto);
}
},
MoreExecutors.directExecutor());
}

/**
* Returns a future that is resolved when replication has caught up to the point when this method
* was called. This allows callers to make sure that their mutations have been replicated across
Expand Down
Expand Up @@ -111,6 +111,8 @@ public String toString() {
.add("getSnapshotSettings", stubSettings.getSnapshotSettings())
.add("listSnapshotsSettings", stubSettings.listSnapshotsSettings())
.add("deleteSnapshotSettings", stubSettings.deleteSnapshotSettings())
.add("copyBackupSettings", stubSettings.copyBackupSettings())
.add("copyBackupOperationSettings", stubSettings.copyBackupOperationSettings())
.add("createBackupSettings", stubSettings.createBackupSettings())
.add("createBackupOperationSettings", stubSettings.createBackupOperationSettings())
.add("getBackupSettings", stubSettings.getBackupSettings())
Expand Down
Expand Up @@ -106,6 +106,11 @@ public String getSourceTableId() {
return NameUtil.extractTableIdFromTableName(proto.getSourceTable());
}

/** Get the source backup ID from which the backup is copied. */
public String getSourceBackupId() {
return NameUtil.extractBackupIdFromBackupName(proto.getSourceBackup());
}

/** Get the instance ID where this backup is located. */
public String getInstanceId() {
return instanceId;
Expand Down
@@ -0,0 +1,126 @@
/*
* Copyright 2022 Google LLC
*
* 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
*
* https://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.google.cloud.bigtable.admin.v2.models;

import com.google.api.core.InternalApi;
import com.google.cloud.bigtable.admin.v2.internal.NameUtil;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.protobuf.util.Timestamps;
import javax.annotation.Nonnull;
import org.threeten.bp.Instant;

/** Build CopyBackupRequest for {@link com.google.bigtable.admin.v2.CopyBackupRequest}. */
public final class CopyBackupRequest {
private final com.google.bigtable.admin.v2.CopyBackupRequest.Builder requestBuilder =
com.google.bigtable.admin.v2.CopyBackupRequest.newBuilder();
private final String sourceBackupId;
private final String sourceClusterId;
private String sourceInstanceId;
private String sourceProjectId;

private String destClusterId;

/**
* Create a {@link CopyBackupRequest} object. It assumes the source backup is located in the same
* instance and project as the destination backup, which is where the BigtableTableAdminClient is
* created in. use setSourceInstance("[INSTANCE]") if the source backup is located in a different
* instance. use setSourceInstance("[PROJECT]", "[INSTANCE]") if the source backup is located in a
* different project.
*/
public static CopyBackupRequest of(String sourceClusterId, String sourceBackupId) {
CopyBackupRequest request = new CopyBackupRequest(sourceClusterId, sourceBackupId);
return request;
}

private CopyBackupRequest(@Nonnull String sourceClusterId, @Nonnull String sourceBackupId) {
Preconditions.checkNotNull(sourceClusterId);
Preconditions.checkNotNull(sourceBackupId);
this.sourceClusterId = sourceClusterId;
this.sourceBackupId = sourceBackupId;
}

public CopyBackupRequest setSourceInstance(String instanceId) {
Preconditions.checkNotNull(instanceId);
this.sourceInstanceId = instanceId;
return this;
}

public CopyBackupRequest setSourceInstance(String projectId, String instanceId) {
Preconditions.checkNotNull(projectId);
Preconditions.checkNotNull(instanceId);
this.sourceProjectId = projectId;
this.sourceInstanceId = instanceId;
return this;
}

public CopyBackupRequest setDestination(String clusterId, String backupId) {
Preconditions.checkNotNull(backupId);
Preconditions.checkNotNull(clusterId);
requestBuilder.setBackupId(backupId);
this.destClusterId = clusterId;
return this;
}

public CopyBackupRequest setExpireTime(Instant expireTime) {
Preconditions.checkNotNull(expireTime);
requestBuilder.setExpireTime(Timestamps.fromMillis(expireTime.toEpochMilli()));
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CopyBackupRequest that = (CopyBackupRequest) o;
return Objects.equal(requestBuilder.getBackupId(), that.requestBuilder.getBackupId())
&& Objects.equal(sourceBackupId, that.sourceBackupId)
&& Objects.equal(sourceClusterId, that.sourceClusterId)
&& Objects.equal(sourceInstanceId, that.sourceInstanceId)
&& Objects.equal(sourceProjectId, that.sourceProjectId);
}

@Override
public int hashCode() {
return Objects.hashCode(
requestBuilder.getBackupId(),
sourceBackupId,
sourceClusterId,
sourceInstanceId,
sourceProjectId);
}

@InternalApi
public com.google.bigtable.admin.v2.CopyBackupRequest toProto(
@Nonnull String projectId, @Nonnull String instanceId) {
Preconditions.checkNotNull(projectId);
Preconditions.checkNotNull(instanceId);

return requestBuilder
.setParent(NameUtil.formatClusterName(projectId, instanceId, destClusterId))
.setSourceBackup(
NameUtil.formatBackupName(
sourceProjectId == null ? projectId : sourceProjectId,
sourceInstanceId == null ? instanceId : sourceInstanceId,
sourceClusterId,
sourceBackupId))
.build();
}
}
Expand Up @@ -31,6 +31,7 @@
import com.google.bigtable.admin.v2.BackupInfo;
import com.google.bigtable.admin.v2.ChangeStreamConfig;
import com.google.bigtable.admin.v2.ColumnFamily;
import com.google.bigtable.admin.v2.CopyBackupMetadata;
import com.google.bigtable.admin.v2.CreateBackupMetadata;
import com.google.bigtable.admin.v2.DeleteBackupRequest;
import com.google.bigtable.admin.v2.DeleteTableRequest;
Expand All @@ -56,6 +57,7 @@
import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListTablesPagedResponse;
import com.google.cloud.bigtable.admin.v2.internal.NameUtil;
import com.google.cloud.bigtable.admin.v2.models.Backup;
import com.google.cloud.bigtable.admin.v2.models.CopyBackupRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateBackupRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;
import com.google.cloud.bigtable.admin.v2.models.EncryptionInfo;
Expand Down Expand Up @@ -172,6 +174,13 @@ public class BigtableTableAdminClientTests {
RestoreTableMetadata>
mockRestoreTableOperationCallable;

@Mock
private OperationCallable<
com.google.bigtable.admin.v2.CopyBackupRequest,
com.google.bigtable.admin.v2.Backup,
CopyBackupMetadata>
mockCopyBackupOperationCallable;

@Mock
private UnaryCallable<com.google.iam.v1.GetIamPolicyRequest, com.google.iam.v1.Policy>
mockGetIamPolicyCallable;
Expand Down Expand Up @@ -775,6 +784,73 @@ public void testListBackups() {
assertThat(actualResults).containsExactlyElementsIn(expectedResults);
}

@Test
public void testCopyBackup() {
// Setup
Mockito.when(mockStub.copyBackupOperationCallable())
.thenReturn(mockCopyBackupOperationCallable);

Timestamp startTime = Timestamp.newBuilder().setSeconds(1234).build();
Timestamp endTime = Timestamp.newBuilder().setSeconds(5678).build();

// Create CopyBackupRequest from different source project:
String srcProjectId = "src-project";
String srcInstanceId = "src-instance";
String srcTableId = "src-table";
String srcClusterId = "src-cluster";
String srcBackupId = "src-backup";
Instant expireTime = Instant.now().plus(org.threeten.bp.Duration.ofDays(15));
long sizeBytes = 123456789;

String dstBackupName =
NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID);
String srcBackupName =
NameUtil.formatBackupName(srcProjectId, srcProjectId, srcClusterId, srcBackupId);
String srcTableName = NameUtil.formatTableName(srcProjectId, srcInstanceId, srcTableId);

CopyBackupRequest req =
CopyBackupRequest.of(srcClusterId, srcBackupId)
.setSourceInstance(srcProjectId, srcInstanceId)
.setDestination(CLUSTER_ID, BACKUP_ID)
.setExpireTime(expireTime);
mockOperationResult(
mockCopyBackupOperationCallable,
req.toProto(PROJECT_ID, INSTANCE_ID),
com.google.bigtable.admin.v2.Backup.newBuilder()
.setName(dstBackupName)
.setSourceTable(srcTableName)
.setSourceBackup(srcBackupName)
.setStartTime(startTime)
.setEndTime(endTime)
.setExpireTime(Timestamps.fromMillis(expireTime.toEpochMilli()))
.setSizeBytes(sizeBytes)
.build(),
CopyBackupMetadata.newBuilder()
.setName(dstBackupName)
.setSourceBackupInfo(
BackupInfo.newBuilder()
.setBackup(srcBackupId)
.setSourceTable(srcTableName)
.setStartTime(startTime)
.setEndTime(endTime)
.build())
.build());

// Execute
Backup actualResult = adminClient.copyBackup(req);

// Verify
assertThat(actualResult.getId()).isEqualTo(BACKUP_ID);
assertThat(actualResult.getSourceTableId()).isEqualTo(srcTableId);
assertThat(actualResult.getSourceBackupId()).isEqualTo(srcBackupId);
assertThat(actualResult.getStartTime())
.isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(startTime)));
assertThat(actualResult.getEndTime())
.isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(endTime)));
assertThat(actualResult.getExpireTime()).isEqualTo(expireTime);
assertThat(actualResult.getSizeBytes()).isEqualTo(sizeBytes);
}

@Test
public void testGetBackupIamPolicy() {
// Setup
Expand Down

0 comments on commit 558a408

Please sign in to comment.