Skip to content
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

feat: copy backup API support #1398

Merged
merged 22 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
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,87 @@ 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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 javax.annotation.Nullable;

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 setDestinationClusterId(String clusterId) {
// Preconditions.checkNotNull(clusterId);
// this.destClusterId = clusterId;
// return this;
// }
TracyCuiCan marked this conversation as resolved.
Show resolved Hide resolved

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();
}
}
Original file line number Diff line number Diff line change
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,74 @@ 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
Loading
Loading