Sample code + * + *
{@code + * CreateBackupRequest request = + * CreateBackupRequest.of(clusterId, backupId) + * .setSourceTableId(tableId) + * .setExpireTime(expireTime); + * Backup response = client.createBackup(request); + * }+ */ + public Backup createBackup(CreateBackupRequest request) { + return ApiExceptions.callAndTranslateApiException(createBackupAsync(request)); + } + + /** + * Creates a backup with the specified configuration asynchronously. + * + *
Sample code + * + *
{@code + * CreateBackupRequest request = + * CreateBackupRequest.of(clusterId, backupId) + * .setSourceTableId(tableId) + * .setExpireTime(expireTime); + * ApiFuture+ */ + public ApiFuturefuture = client.createBackupAsync(request); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(Backup backup) { + * System.out.println("Successfully create the backup " + backup.getId()); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * }
Sample code + * + *
{@code + * Backup backup = client.getBackup(clusterId, backupId); + * }+ */ + public Backup getBackup(String clusterId, String backupId) { + return ApiExceptions.callAndTranslateApiException(getBackupAsync(clusterId, backupId)); + } + + /** + * Gets a backup with the specified backup ID in the specified cluster asynchronously. + * + *
Sample code + * + *
{@code + * ApiFuture+ */ + public ApiFuturefuture = client.getBackupAsync(clusterId, backupId); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(Backup backup) { + * System.out.println("Successfully get the backup " + backup.getId()); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * }
Sample code + * + *
{@code + * List+ */ + public Listbackups = client.listBackups(clusterId); + * }
Sample code: + * + *
{@code + * ApiFuture+ */ + public ApiFuture> listFuture = client.listBackupsAsync(clusterId); + * + * ApiFutures.addCallback( + * listFuture, + * new ApiFutureCallback
>() { + * public void onSuccess(List
backupIds) { + * System.out.println("Got list of backups:"); + * for (String backupId : backupIds) { + * System.out.println(backupId); + * } + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * }
Sample code + * + *
{@code + * client.deleteBackup(clusterId, backupId); + * }+ */ + public void deleteBackup(String clusterId, String backupId) { + ApiExceptions.callAndTranslateApiException(deleteBackupAsync(clusterId, backupId)); + } + + /** + * Deletes a backup with the specified backup ID in the specified cluster asynchronously. + * + *
Sample code + * + *
{@code + * ApiFuture+ */ + public ApiFuturefuture = client.deleteBackupAsync(clusterId, backupId); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(Void unused) { + * System.out.println("Successfully delete the backup."); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * }
Sample code + * + *
{@code + * Backup backup = client.updateBackup(clusterId, backupId); + * }+ */ + public Backup updateBackup(UpdateBackupRequest request) { + return ApiExceptions.callAndTranslateApiException(updateBackupAsync(request)); + } + + /** + * Updates a backup with the specified configuration asynchronously. + * + *
Sample code + * + *
{@code + * ApiFuture+ */ + public ApiFuturefuture = client.updateBackupAsync(clusterId, backupId); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(Backup backup) { + * System.out.println("Successfully update the backup " + backup.getId()); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * }
Sample code + * + *
{@code + * RestoredTableResult result = + * client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId)); + * }+ */ + public RestoredTableResult restoreTable(RestoreTableRequest request) + throws ExecutionException, InterruptedException { + return ApiExceptions.callAndTranslateApiException(restoreTableAsync(request)); + } + + /** Restores a backup to a new table with the specified configuration asynchronously. + *
Sample code + * + *
{@code + * ApiFuture+ * */ + public ApiFuturefuture = client.restoreTableAsync( + * RestoreTableRequest.of(clusterId, backupId).setTableId(tableId)); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(RestoredTableResult result) { + * System.out.println("Successfully restore the table."); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + *
Sample code + * + *
{@code + * RestoredTableResult result = + * client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId)); + * client.awaitOptimizeRestoredTable(result.getOptimizeRestoredTableOperationToken()); + * }+ */ + public void awaitOptimizeRestoredTable(OptimizeRestoredTableOperationToken token) + throws ExecutionException, InterruptedException { + awaitOptimizeRestoredTableAsync(token).get(); + } + + /** Awaits a restored table is fully optimized asynchronously. + * + *
Sample code + * + *
{@code + * RestoredTableResult result = + * client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId)); + * ApiFuturefuture = client.awaitOptimizeRestoredTableAsync( + * result.getOptimizeRestoredTableOperationToken()); + * + * ApiFutures.addCallback( + * future, + * new ApiFutureCallback () { + * public void onSuccess(Void unused) { + * System.out.println("The optimization of the restored table is done."); + * } + * + * public void onFailure(Throwable t) { + * t.printStackTrace(); + * } + * }, + * MoreExecutors.directExecutor() + * ); + * */ + public ApiFuture awaitOptimizeRestoredTableAsync( + OptimizeRestoredTableOperationToken token) { + return transformToVoid( + stub.awaitOptimizeRestoredTableCallable().resumeFutureCall(token.getOperationName())); + } + /** * 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 diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/internal/NameUtil.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/internal/NameUtil.java index 54fc1f88fa..8cccf3d578 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/internal/NameUtil.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/internal/NameUtil.java @@ -31,6 +31,8 @@ public class NameUtil { Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)"); private static final Pattern LOCATION_PATTERN = Pattern.compile("projects/([^/]+)/locations/([^/]+)"); + private static final Pattern BACKUP_PATTERN = + Pattern.compile("projects/([^/]+)/instances/([^/]+)/clusters/([^/]+)/backups/([^/]+)"); public static String formatProjectName(String projectId) { return "projects/" + projectId; @@ -48,6 +50,11 @@ public static String formatLocationName(String projectId, String zone) { return formatProjectName(projectId) + "/locations/" + zone; } + public static String formatBackupName( + String projectId, String instanceId, String clusterId, String backupId) { + return formatClusterName(projectId, instanceId, clusterId) + "/backups/" + backupId; + } + public static String extractTableIdFromTableName(String fullTableName) { Matcher matcher = TABLE_PATTERN.matcher(fullTableName); if (!matcher.matches()) { @@ -56,6 +63,14 @@ public static String extractTableIdFromTableName(String fullTableName) { return matcher.group(3); } + public static String extractBackupIdFromBackupName(String fullBackupName) { + Matcher matcher = BACKUP_PATTERN.matcher(fullBackupName); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid backup name: " + fullBackupName); + } + return matcher.group(4); + } + public static String extractZoneIdFromLocationName(String fullLocationName) { Matcher matcher = LOCATION_PATTERN.matcher(fullLocationName); if (!matcher.matches()) { diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Backup.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Backup.java new file mode 100644 index 0000000000..54002da634 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Backup.java @@ -0,0 +1,161 @@ +/* + * Copyright 2020 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.bigtable.admin.v2.BackupName; +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; + +/** + * A backup lets you save a copy of a table's schema and data and restore the backup to a new table + * at a later time. + */ +public class Backup { + public enum State { + /** Not specified. */ + STATE_UNSPECIFIED(com.google.bigtable.admin.v2.Backup.State.STATE_UNSPECIFIED), + + /** + * The pending backup is still being created. Operations on the backup may fail with + * `FAILED_PRECONDITION` in this state. + */ + CREATING(com.google.bigtable.admin.v2.Backup.State.CREATING), + /** The backup is complete and ready for use. */ + READY(com.google.bigtable.admin.v2.Backup.State.READY), + + /** The state of the backup is not known by this client. Please upgrade your client. */ + UNRECOGNIZED(com.google.bigtable.admin.v2.Backup.State.UNRECOGNIZED); + + private final com.google.bigtable.admin.v2.Backup.State proto; + + State(com.google.bigtable.admin.v2.Backup.State proto) { + this.proto = proto; + } + + /** + * Wraps the protobuf. This method is considered an internal implementation detail and not meant + * to be used by applications. + */ + @InternalApi + public static Backup.State fromProto(com.google.bigtable.admin.v2.Backup.State proto) { + for (Backup.State state : values()) { + if (state.proto.equals(proto)) { + return state; + } + } + return STATE_UNSPECIFIED; + } + + /** + * Creates the request protobuf. This method is considered an internal implementation detail and + * not meant to be used by applications. + */ + @InternalApi + public com.google.bigtable.admin.v2.Backup.State toProto() { + return proto; + } + } + + @Nonnull private final com.google.bigtable.admin.v2.Backup proto; + @Nonnull private final String id; + @Nonnull private final String instanceId; + + @InternalApi + public static Backup fromProto(@Nonnull com.google.bigtable.admin.v2.Backup proto) { + return new Backup(proto); + } + + private Backup(@Nonnull com.google.bigtable.admin.v2.Backup proto) { + Preconditions.checkNotNull(proto); + Preconditions.checkArgument(!proto.getName().isEmpty(), "Name must be set"); + Preconditions.checkArgument(!proto.getSourceTable().isEmpty(), "Source table must be set"); + + BackupName name = BackupName.parse(proto.getName()); + this.id = name.getBackup(); + this.instanceId = name.getInstance(); + this.proto = proto; + } + + /** Get the ID of this backup. */ + public String getId() { + return id; + } + + /** Get the source table ID from which the backup is created. */ + public String getSourceTableId() { + return NameUtil.extractTableIdFromTableName(proto.getSourceTable()); + } + + /** Get the instance ID where this backup is located. */ + public String getInstanceId() { + return instanceId; + } + + /** Get the expire time of this backup. */ + public Instant getExpireTime() { + return Instant.ofEpochMilli(Timestamps.toMillis(proto.getExpireTime())); + } + + /** Get the start time when this backup is taken. */ + public @Nullable Instant getStartTime() { + if (proto.hasStartTime()) { + return Instant.ofEpochMilli(Timestamps.toMillis(proto.getStartTime())); + } + return null; + } + + /** Get the end time when the creation of this backup has completed. */ + public @Nullable Instant getEndTime() { + if (proto.hasEndTime()) { + return Instant.ofEpochMilli(Timestamps.toMillis(proto.getEndTime())); + } + return null; + } + + /** Get the size of this backup. */ + public long getSizeBytes() { + return proto.getSizeBytes(); + } + + /** Get the state of this backup. */ + public State getState() { + return State.fromProto(proto.getState()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Backup backup = (Backup) o; + return Objects.equal(proto, backup.proto); + } + + @Override + public int hashCode() { + return Objects.hashCode(proto); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequest.java new file mode 100644 index 0000000000..1a27546c8d --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 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; + +/** Fluent wrapper for {@link com.google.bigtable.admin.v2.CreateBackupRequest} */ +public final class CreateBackupRequest { + private final com.google.bigtable.admin.v2.CreateBackupRequest.Builder requestBuilder = + com.google.bigtable.admin.v2.CreateBackupRequest.newBuilder(); + private final String clusterId; + private String sourceTableId; + + public static CreateBackupRequest of(String clusterId, String backupId) { + CreateBackupRequest request = new CreateBackupRequest(clusterId, backupId); + return request; + } + + private CreateBackupRequest(String clusterId, String backupId) { + Preconditions.checkNotNull(clusterId); + Preconditions.checkNotNull(backupId); + + requestBuilder.setBackupId(backupId); + this.clusterId = clusterId; + this.sourceTableId = null; + } + + public CreateBackupRequest setSourceTableId(String sourceTableId) { + Preconditions.checkNotNull(sourceTableId); + this.sourceTableId = sourceTableId; + return this; + } + + public CreateBackupRequest setExpireTime(Instant expireTime) { + Preconditions.checkNotNull(expireTime); + requestBuilder + .getBackupBuilder() + .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; + } + CreateBackupRequest that = (CreateBackupRequest) o; + return Objects.equal(requestBuilder.getBackupId(), that.requestBuilder.getBackupId()) + && Objects.equal(clusterId, that.clusterId) + && Objects.equal(sourceTableId, that.sourceTableId); + } + + @Override + public int hashCode() { + return Objects.hashCode(requestBuilder.getBackupId(), clusterId, sourceTableId); + } + + @InternalApi + public com.google.bigtable.admin.v2.CreateBackupRequest toProto( + @Nonnull String projectId, @Nonnull String instanceId) { + Preconditions.checkNotNull(projectId); + Preconditions.checkNotNull(instanceId); + + requestBuilder + .getBackupBuilder() + .setSourceTable(NameUtil.formatTableName(projectId, instanceId, sourceTableId)); + return requestBuilder + .setParent(NameUtil.formatClusterName(projectId, instanceId, clusterId)) + .build(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/OptimizeRestoredTableOperationToken.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/OptimizeRestoredTableOperationToken.java new file mode 100644 index 0000000000..d38f82e4f1 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/OptimizeRestoredTableOperationToken.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 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.common.base.Preconditions; + +/** + * OptimizeRestoredTableOperationToken is a wrapper for the name of OptimizeRestoredTable operation. + */ +public class OptimizeRestoredTableOperationToken { + private final String operationName; + + @InternalApi + public static OptimizeRestoredTableOperationToken of(String operationName) { + return new OptimizeRestoredTableOperationToken(operationName); + } + + private OptimizeRestoredTableOperationToken(String operationName) { + Preconditions.checkNotNull(operationName); + Preconditions.checkState(!operationName.isEmpty()); + this.operationName = operationName; + } + + @InternalApi + public String getOperationName() { + return operationName; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequest.java new file mode 100644 index 0000000000..fa47ba582e --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 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 javax.annotation.Nonnull; + +/** Fluent wrapper for {@link com.google.bigtable.admin.v2.RestoreTableRequest} */ +public final class RestoreTableRequest { + private final com.google.bigtable.admin.v2.RestoreTableRequest.Builder requestBuilder = + com.google.bigtable.admin.v2.RestoreTableRequest.newBuilder(); + private final String backupId; + private final String clusterId; + + public static RestoreTableRequest of(String clusterId, String backupId) { + RestoreTableRequest request = new RestoreTableRequest(clusterId, backupId); + return request; + } + + private RestoreTableRequest(String clusterId, String backupId) { + Preconditions.checkNotNull(clusterId); + Preconditions.checkNotNull(backupId); + this.backupId = backupId; + this.clusterId = clusterId; + } + + public RestoreTableRequest setTableId(String tableId) { + Preconditions.checkNotNull(tableId); + requestBuilder.setTableId(tableId); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RestoreTableRequest that = (RestoreTableRequest) o; + return Objects.equal(requestBuilder.getTableId(), that.requestBuilder.getTableId()) + && Objects.equal(clusterId, that.clusterId) + && Objects.equal(backupId, that.backupId); + } + + @Override + public int hashCode() { + return Objects.hashCode(requestBuilder.getTableId(), clusterId, backupId); + } + + @InternalApi + public com.google.bigtable.admin.v2.RestoreTableRequest toProto( + @Nonnull String projectId, @Nonnull String instanceId) { + Preconditions.checkNotNull(projectId); + Preconditions.checkNotNull(instanceId); + + return requestBuilder + .setParent(NameUtil.formatInstanceName(projectId, instanceId)) + .setBackup(NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId)) + .build(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoredTableResult.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoredTableResult.java new file mode 100644 index 0000000000..e31d8c4b4a --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/RestoredTableResult.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 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.common.base.Strings; +import javax.annotation.Nullable; + +/** + * A RestoredTableResult holds the restored table object and the {@link + * OptimizeRestoredTableOperationToken} object (if any). + */ +public class RestoredTableResult { + + private final Table table; + private final OptimizeRestoredTableOperationToken optimizeRestoredTableOperationToken; + + @InternalApi + public RestoredTableResult( + Table restoredTable, @Nullable String optimizeRestoredTableOperationName) { + this.table = restoredTable; + this.optimizeRestoredTableOperationToken = + Strings.isNullOrEmpty(optimizeRestoredTableOperationName) + ? null + : OptimizeRestoredTableOperationToken.of(optimizeRestoredTableOperationName); + } + + public Table getTable() { + return table; + } + + /** + * OptimizeRestoredTable operation may not be started when the table was restored from a backup + * stored in HDD clusters. + */ + @Nullable + public OptimizeRestoredTableOperationToken getOptimizeRestoredTableOperationToken() { + return this.optimizeRestoredTableOperationToken; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequest.java new file mode 100644 index 0000000000..9f8aa6a799 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2020 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.FieldMask; +import com.google.protobuf.util.Timestamps; +import javax.annotation.Nonnull; +import org.threeten.bp.Instant; + +/** Fluent wrapper for {@link com.google.bigtable.admin.v2.UpdateBackupRequest} */ +public final class UpdateBackupRequest { + private final com.google.bigtable.admin.v2.UpdateBackupRequest.Builder requestBuilder = + com.google.bigtable.admin.v2.UpdateBackupRequest.newBuilder(); + private final String backupId; + private final String clusterId; + + public static UpdateBackupRequest of(String clusterId, String backupId) { + UpdateBackupRequest request = new UpdateBackupRequest(clusterId, backupId); + return request; + } + + private UpdateBackupRequest(String clusterId, String backupId) { + Preconditions.checkNotNull(clusterId); + Preconditions.checkNotNull(backupId); + this.backupId = backupId; + this.clusterId = clusterId; + } + + public UpdateBackupRequest setExpireTime(Instant expireTime) { + Preconditions.checkNotNull(expireTime); + requestBuilder + .getBackupBuilder() + .setExpireTime(Timestamps.fromMillis(expireTime.toEpochMilli())); + requestBuilder.setUpdateMask(FieldMask.newBuilder().addPaths("expire_time")); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UpdateBackupRequest that = (UpdateBackupRequest) o; + return Objects.equal( + requestBuilder.getBackupBuilder().getExpireTime(), + that.requestBuilder.getBackupBuilder().getExpireTime()) + && Objects.equal(requestBuilder.getUpdateMask(), that.requestBuilder.getUpdateMask()) + && Objects.equal(clusterId, that.clusterId) + && Objects.equal(backupId, that.backupId); + } + + @Override + public int hashCode() { + return Objects.hashCode( + requestBuilder.getBackupBuilder().getExpireTime(), + requestBuilder.getUpdateMask(), + backupId); + } + + @InternalApi + public com.google.bigtable.admin.v2.UpdateBackupRequest toProto( + @Nonnull String projectId, @Nonnull String instanceId) { + Preconditions.checkNotNull(projectId); + Preconditions.checkNotNull(instanceId); + + requestBuilder + .getBackupBuilder() + .setName(NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId)); + return requestBuilder.build(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/EnhancedBigtableTableAdminStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/EnhancedBigtableTableAdminStub.java index bf1913a36a..0a6e8efec3 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/EnhancedBigtableTableAdminStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/EnhancedBigtableTableAdminStub.java @@ -15,12 +15,29 @@ */ package com.google.cloud.bigtable.admin.v2.stub; +import com.google.api.core.ApiFunction; import com.google.api.core.InternalApi; +import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcCallableFactory; +import com.google.api.gax.grpc.ProtoOperationTransformers.MetadataTransformer; +import com.google.api.gax.grpc.ProtoOperationTransformers.ResponseTransformer; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.OperationCallSettings; +import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.gax.rpc.UnaryCallable; +import com.google.bigtable.admin.v2.OptimizeRestoredTableMetadata; import com.google.bigtable.admin.v2.TableName; +import com.google.longrunning.Operation; +import com.google.protobuf.Empty; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.Marshaller; +import io.grpc.MethodDescriptor.MethodType; import java.io.IOException; +import java.io.InputStream; import org.threeten.bp.Duration; /** @@ -36,6 +53,8 @@ public class EnhancedBigtableTableAdminStub extends GrpcBigtableTableAdminStub { private final ClientContext clientContext; private final AwaitReplicationCallable awaitReplicationCallable; + private final OperationCallable + optimizeRestoredTableOperationBaseCallable; public static EnhancedBigtableTableAdminStub createEnhanced( BigtableTableAdminStubSettings settings) throws IOException { @@ -49,6 +68,8 @@ private EnhancedBigtableTableAdminStub( this.settings = settings; this.clientContext = clientContext; this.awaitReplicationCallable = createAwaitReplicationCallable(); + this.optimizeRestoredTableOperationBaseCallable = + createOptimizeRestoredTableOperationBaseCallable(); } private AwaitReplicationCallable createAwaitReplicationCallable() { @@ -78,7 +99,103 @@ private AwaitReplicationCallable createAwaitReplicationCallable() { pollingSettings); } + // Plug into gax operation infrastructure + // gax assumes that all operations are started immediately and doesn't provide support for child + // operations + // this method wraps a fake method "OptimizeTable" in an OperationCallable. This should not be + // exposed to + // end users, but will be used for its resumeOperation functionality via a wrapper + private OperationCallable + createOptimizeRestoredTableOperationBaseCallable() { + // Fake initial callable settings. Since this is child operation, it doesn't have an initial + // callable but gax doesn't have support for child operation, so this creates a fake method + // descriptor that will never be used. + GrpcCallSettings unusedInitialCallSettings = + GrpcCallSettings.create( + MethodDescriptor. newBuilder() + .setType(MethodType.UNARY) + .setFullMethodName( + "google.bigtable.admin.v2.BigtableTableAdmin/OptimizeRestoredTable") + .setRequestMarshaller( + new Marshaller () { + @Override + public InputStream stream(Void value) { + throw new UnsupportedOperationException("not used"); + } + + @Override + public Void parse(InputStream stream) { + throw new UnsupportedOperationException("not used"); + } + }) + .setResponseMarshaller( + new Marshaller () { + @Override + public InputStream stream(Operation value) { + throw new UnsupportedOperationException("not used"); + } + + @Override + public Operation parse(InputStream stream) { + throw new UnsupportedOperationException("not used"); + } + }) + .build()); + + // helpers to extract the underlying protos from the operation + final MetadataTransformer protoMetadataTransformer = + MetadataTransformer.create(OptimizeRestoredTableMetadata.class); + + final ResponseTransformer protoResponseTransformer = + ResponseTransformer.create(com.google.protobuf.Empty.class); + + // TODO(igorbernstein2): expose polling settings + OperationCallSettings operationCallSettings = + OperationCallSettings. newBuilder() + // Since this is used for a child operation, the initial call settings will not be used + .setInitialCallSettings( + UnaryCallSettings. newUnaryCallSettingsBuilder() + .setSimpleTimeoutNoRetries(Duration.ZERO) + .build()) + // Configure the extractors to wrap the protos + .setMetadataTransformer( + new ApiFunction () { + @Override + public OptimizeRestoredTableMetadata apply(OperationSnapshot input) { + return protoMetadataTransformer.apply(input); + } + }) + .setResponseTransformer( + new ApiFunction () { + @Override + public Empty apply(OperationSnapshot input) { + return protoResponseTransformer.apply(input); + } + }) + .setPollingAlgorithm( + OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(500L)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofMillis(5000L)) + .setInitialRpcTimeout(Duration.ZERO) // ignored + .setRpcTimeoutMultiplier(1.0) // ignored + .setMaxRpcTimeout(Duration.ZERO) // ignored + .setTotalTimeout(Duration.ofMillis(600000L)) + .build())) + .build(); + + // Create the callable + return GrpcCallableFactory.createOperationCallable( + unusedInitialCallSettings, operationCallSettings, clientContext, getOperationsStub()); + } + public UnaryCallable awaitReplicationCallable() { return awaitReplicationCallable; } + + public OperationCallable + awaitOptimizeRestoredTableCallable() { + return optimizeRestoredTableOperationBaseCallable; + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java index 358d2990c5..2cda42b3ea 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java @@ -20,29 +20,55 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.gax.grpc.GrpcStatusCode; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationFutures; +import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.NotFoundException; +import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.gax.rpc.testing.FakeOperationSnapshot; +import com.google.bigtable.admin.v2.Backup.State; +import com.google.bigtable.admin.v2.BackupInfo; import com.google.bigtable.admin.v2.ColumnFamily; +import com.google.bigtable.admin.v2.CreateBackupMetadata; +import com.google.bigtable.admin.v2.DeleteBackupRequest; import com.google.bigtable.admin.v2.DeleteTableRequest; import com.google.bigtable.admin.v2.DropRowRangeRequest; import com.google.bigtable.admin.v2.GcRule; +import com.google.bigtable.admin.v2.GetBackupRequest; import com.google.bigtable.admin.v2.GetTableRequest; +import com.google.bigtable.admin.v2.ListBackupsRequest; import com.google.bigtable.admin.v2.ListTablesRequest; import com.google.bigtable.admin.v2.ModifyColumnFamiliesRequest.Modification; +import com.google.bigtable.admin.v2.RestoreSourceType; +import com.google.bigtable.admin.v2.RestoreTableMetadata; import com.google.bigtable.admin.v2.Table.View; import com.google.bigtable.admin.v2.TableName; +import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListBackupsPage; +import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListBackupsPagedResponse; import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListTablesPage; 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.CreateBackupRequest; import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest; import com.google.cloud.bigtable.admin.v2.models.ModifyColumnFamiliesRequest; +import com.google.cloud.bigtable.admin.v2.models.RestoreTableRequest; +import com.google.cloud.bigtable.admin.v2.models.RestoredTableResult; import com.google.cloud.bigtable.admin.v2.models.Table; +import com.google.cloud.bigtable.admin.v2.models.UpdateBackupRequest; import com.google.cloud.bigtable.admin.v2.stub.EnhancedBigtableTableAdminStub; import com.google.common.collect.Lists; +import com.google.longrunning.Operation; +import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.Empty; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Timestamps; import io.grpc.Status; +import io.grpc.Status.Code; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; @@ -53,6 +79,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.threeten.bp.Instant; @RunWith(MockitoJUnitRunner.class) public class BigtableTableAdminClientTest { @@ -60,6 +87,8 @@ public class BigtableTableAdminClientTest { private static final String PROJECT_ID = "my-project"; private static final String INSTANCE_ID = "my-instance"; private static final String TABLE_ID = "my-table"; + private static final String CLUSTER_ID = "my-cluster"; + private static final String BACKUP_ID = "my-backup"; private static final String PROJECT_NAME = NameUtil.formatProjectName(PROJECT_ID); private static final String INSTANCE_NAME = NameUtil.formatInstanceName(PROJECT_ID, INSTANCE_ID); @@ -89,6 +118,40 @@ public class BigtableTableAdminClientTest { @Mock private UnaryCallable mockDropRowRangeCallable; @Mock private UnaryCallable mockAwaitReplicationCallable; + @Mock + private UnaryCallable + mockCreateBackupCallable; + + @Mock + private OperationCallable< + com.google.bigtable.admin.v2.CreateBackupRequest, + com.google.bigtable.admin.v2.Backup, + CreateBackupMetadata> + mockCreateBackupOperationCallable; + + @Mock + private UnaryCallable + mockGetBackupCallable; + + @Mock + private UnaryCallable< + com.google.bigtable.admin.v2.UpdateBackupRequest, com.google.bigtable.admin.v2.Backup> + mockUpdateBackupCallable; + + @Mock private UnaryCallable mockListBackupCallable; + @Mock private UnaryCallable mockDeleteBackupCallable; + + @Mock + private UnaryCallable + mockRestoreTableCallable; + + @Mock + private OperationCallable< + com.google.bigtable.admin.v2.RestoreTableRequest, + com.google.bigtable.admin.v2.Table, + RestoreTableMetadata> + mockRestoreTableOperationCallable; + @Before public void setUp() { adminClient = BigtableTableAdminClient.create(PROJECT_ID, INSTANCE_ID, mockStub); @@ -100,6 +163,16 @@ public void setUp() { Mockito.when(mockStub.listTablesPagedCallable()).thenReturn(mockListTableCallable); Mockito.when(mockStub.dropRowRangeCallable()).thenReturn(mockDropRowRangeCallable); Mockito.when(mockStub.awaitReplicationCallable()).thenReturn(mockAwaitReplicationCallable); + Mockito.when(mockStub.createBackupOperationCallable()) + .thenReturn(mockCreateBackupOperationCallable); + Mockito.when(mockStub.createBackupCallable()).thenReturn(mockCreateBackupCallable); + Mockito.when(mockStub.getBackupCallable()).thenReturn(mockGetBackupCallable); + Mockito.when(mockStub.listBackupsPagedCallable()).thenReturn(mockListBackupCallable); + Mockito.when(mockStub.updateBackupCallable()).thenReturn(mockUpdateBackupCallable); + Mockito.when(mockStub.deleteBackupCallable()).thenReturn(mockDeleteBackupCallable); + Mockito.when(mockStub.restoreTableCallable()).thenReturn(mockRestoreTableCallable); + Mockito.when(mockStub.restoreTableOperationCallable()) + .thenReturn(mockRestoreTableOperationCallable); } @Test @@ -336,4 +409,253 @@ public void testExistsFalse() { // Verify assertThat(found).isFalse(); } + + @Test + public void testCreateBackup() { + // Setup + String backupName = NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID); + Timestamp startTime = Timestamp.newBuilder().setSeconds(123).build(); + Timestamp endTime = Timestamp.newBuilder().setSeconds(456).build(); + Timestamp expireTime = Timestamp.newBuilder().setSeconds(789).build(); + long sizeBytes = 123456789; + CreateBackupRequest req = + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setSourceTableId(TABLE_ID); + mockOperationResult( + mockCreateBackupOperationCallable, + req.toProto(PROJECT_ID, INSTANCE_ID), + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName(backupName) + .setSourceTable(TABLE_NAME) + .setStartTime(startTime) + .setEndTime(endTime) + .setExpireTime(expireTime) + .setSizeBytes(sizeBytes) + .build(), + CreateBackupMetadata.newBuilder() + .setName(backupName) + .setStartTime(startTime) + .setEndTime(endTime) + .setSourceTable(TABLE_NAME) + .build()); + // Execute + Backup actualResult = adminClient.createBackup(req); + + // Verify + assertThat(actualResult.getId()).isEqualTo(BACKUP_ID); + assertThat(actualResult.getSourceTableId()).isEqualTo(TABLE_ID); + assertThat(actualResult.getStartTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(startTime))); + assertThat(actualResult.getEndTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(endTime))); + assertThat(actualResult.getExpireTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(expireTime))); + assertThat(actualResult.getSizeBytes()).isEqualTo(sizeBytes); + } + + @Test + public void testGetBackup() { + // Setup + Timestamp expireTime = Timestamp.newBuilder().setSeconds(123456789).build(); + Timestamp startTime = Timestamp.newBuilder().setSeconds(1234).build(); + Timestamp endTime = Timestamp.newBuilder().setSeconds(5678).build(); + com.google.bigtable.admin.v2.Backup.State state = State.CREATING; + long sizeBytes = 12345L; + GetBackupRequest testRequest = + GetBackupRequest.newBuilder() + .setName(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .build(); + Mockito.when(mockGetBackupCallable.futureCall(testRequest)) + .thenReturn( + ApiFutures.immediateFuture( + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName( + NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .setSourceTable(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setExpireTime(expireTime) + .setStartTime(startTime) + .setEndTime(endTime) + .setSizeBytes(sizeBytes) + .setState(state) + .build())); + + // Execute + Backup actualResult = adminClient.getBackup(CLUSTER_ID, BACKUP_ID); + + // Verify + assertThat(actualResult.getId()).isEqualTo(BACKUP_ID); + assertThat(actualResult.getSourceTableId()).isEqualTo(TABLE_ID); + assertThat(actualResult.getExpireTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(expireTime))); + assertThat(actualResult.getStartTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(startTime))); + assertThat(actualResult.getEndTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(endTime))); + assertThat(actualResult.getSizeBytes()).isEqualTo(sizeBytes); + assertThat(actualResult.getState()).isEqualTo(Backup.State.fromProto(state)); + } + + @Test + public void testUpdateBackup() { + // Setup + Timestamp expireTime = Timestamp.newBuilder().setSeconds(123456789).build(); + long sizeBytes = 12345L; + UpdateBackupRequest req = UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID); + Mockito.when(mockUpdateBackupCallable.futureCall(req.toProto(PROJECT_ID, INSTANCE_ID))) + .thenReturn( + ApiFutures.immediateFuture( + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName( + NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .setSourceTable(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setExpireTime(expireTime) + .setSizeBytes(sizeBytes) + .build())); + + // Execute + Backup actualResult = adminClient.updateBackup(req); + + // Verify + assertThat(actualResult.getId()).isEqualTo(BACKUP_ID); + assertThat(actualResult.getSourceTableId()).isEqualTo(TABLE_ID); + assertThat(actualResult.getExpireTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(expireTime))); + assertThat(actualResult.getSizeBytes()).isEqualTo(sizeBytes); + } + + @Test + public void testRestoreTable() throws ExecutionException, InterruptedException { + // Setup + Timestamp startTime = Timestamp.newBuilder().setSeconds(1234).build(); + Timestamp endTime = Timestamp.newBuilder().setSeconds(5678).build(); + String operationName = "my-operation"; + RestoreTableRequest req = RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID); + Mockito.when(mockRestoreTableCallable.futureCall(req.toProto(PROJECT_ID, INSTANCE_ID))) + .thenReturn( + ApiFutures.immediateFuture( + Operation.newBuilder() + .setMetadata( + Any.pack( + RestoreTableMetadata.newBuilder() + .setName( + NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setOptimizeTableOperationName(operationName) + .setSourceType(RestoreSourceType.BACKUP) + .setBackupInfo( + BackupInfo.newBuilder() + .setBackup(BACKUP_ID) + .setSourceTable( + NameUtil.formatTableName( + PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setStartTime(startTime) + .setEndTime(endTime) + .build()) + .build())) + .build())); + mockOperationResult( + mockRestoreTableOperationCallable, + req.toProto(PROJECT_ID, INSTANCE_ID), + com.google.bigtable.admin.v2.Table.newBuilder().setName(TABLE_NAME).build(), + RestoreTableMetadata.newBuilder() + .setName(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setOptimizeTableOperationName(operationName) + .setSourceType(RestoreSourceType.BACKUP) + .setBackupInfo( + BackupInfo.newBuilder() + .setBackup(BACKUP_ID) + .setSourceTable(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setStartTime(startTime) + .setEndTime(endTime) + .build()) + .build()); + // Execute + RestoredTableResult actualResult = adminClient.restoreTable(req); + + // Verify + assertThat(actualResult.getTable().getId()).isEqualTo(TABLE_ID); + } + + @Test + public void testDeleteBackup() { + // Setup + DeleteBackupRequest testRequest = + DeleteBackupRequest.newBuilder() + .setName(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .build(); + Mockito.when(mockDeleteBackupCallable.futureCall(testRequest)) + .thenReturn(ApiFutures.immediateFuture(Empty.getDefaultInstance())); + + // Execute + adminClient.deleteBackup(CLUSTER_ID, BACKUP_ID); + + // Verify + Mockito.verify(mockDeleteBackupCallable, Mockito.times(1)).futureCall(testRequest); + } + + @Test + public void testListBackups() { + // Setup + com.google.bigtable.admin.v2.ListBackupsRequest testRequest = + com.google.bigtable.admin.v2.ListBackupsRequest.newBuilder() + .setParent(NameUtil.formatClusterName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID)) + .build(); + + // 3 Backups spread across 2 pages + List expectedProtos = Lists.newArrayList(); + for (int i = 0; i < 3; i++) { + expectedProtos.add( + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName( + NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID + i)) + .build()); + } + + // 2 on the first page + ListBackupsPage page0 = Mockito.mock(ListBackupsPage.class); + Mockito.when(page0.getValues()).thenReturn(expectedProtos.subList(0, 2)); + Mockito.when(page0.getNextPageToken()).thenReturn("next-page"); + Mockito.when(page0.hasNextPage()).thenReturn(true); + + // 1 on the last page + ListBackupsPage page1 = Mockito.mock(ListBackupsPage.class); + Mockito.when(page1.getValues()).thenReturn(expectedProtos.subList(2, 3)); + + // Link page0 to page1 + Mockito.when(page0.getNextPageAsync()).thenReturn(ApiFutures.immediateFuture(page1)); + + // Link page to the response + ListBackupsPagedResponse response0 = Mockito.mock(ListBackupsPagedResponse.class); + Mockito.when(response0.getPage()).thenReturn(page0); + + Mockito.when(mockListBackupCallable.futureCall(testRequest)) + .thenReturn(ApiFutures.immediateFuture(response0)); + + // Execute + List actualResults = adminClient.listBackups(CLUSTER_ID); + + // Verify + List expectedResults = Lists.newArrayList(); + for (com.google.bigtable.admin.v2.Backup expectedProto : expectedProtos) { + expectedResults.add(NameUtil.extractBackupIdFromBackupName(expectedProto.getName())); + } + + assertThat(actualResults).containsExactlyElementsIn(expectedResults); + } + + private void mockOperationResult( + OperationCallable callable, + ReqT request, + RespT response, + MetaT metadata) { + OperationSnapshot operationSnapshot = + FakeOperationSnapshot.newBuilder() + .setDone(true) + .setErrorCode(GrpcStatusCode.of(Code.OK)) + .setName("fake-name") + .setResponse(response) + .setMetadata(metadata) + .build(); + OperationFuture operationFuture = + OperationFutures.immediateOperationFuture(operationSnapshot); + Mockito.when(callable.futureCall(request)).thenReturn(operationFuture); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java new file mode 100644 index 0000000000..a452a2bc55 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/internal/NameUtilTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 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.internal; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NameUtilTest { + @Rule public ExpectedException exception = ExpectedException.none(); + + @Test + public void extractBackupIdFromBackupNameTest() { + String testBackupName = + "projects/my-project/instances/my-instance/clusters/my-cluster/backups/my-backup"; + assertThat(NameUtil.extractBackupIdFromBackupName(testBackupName)).isEqualTo("my-backup"); + + exception.expect(IllegalArgumentException.class); + NameUtil.extractBackupIdFromBackupName("bad-format"); + } + + @Test + public void formatBackupNameTest() { + String testBackupName = + "projects/my-project/instances/my-instance/clusters/my-cluster/backups/my-backup"; + + assertThat(NameUtil.formatBackupName("my-project", "my-instance", "my-cluster", "my-backup")) + .isEqualTo(testBackupName); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java new file mode 100644 index 0000000000..1e1bc84648 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java @@ -0,0 +1,358 @@ +/* + * Copyright 2020 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.it; + +import static com.google.common.truth.Truth.assertWithMessage; +import static io.grpc.Status.Code.NOT_FOUND; +import static org.junit.Assert.fail; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; +import com.google.cloud.bigtable.admin.v2.BigtableTableAdminSettings; +import com.google.cloud.bigtable.admin.v2.models.Backup; +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.RestoreTableRequest; +import com.google.cloud.bigtable.admin.v2.models.RestoredTableResult; +import com.google.cloud.bigtable.admin.v2.models.Table; +import com.google.cloud.bigtable.admin.v2.models.UpdateBackupRequest; +import com.google.cloud.bigtable.data.v2.BigtableDataClient; +import com.google.cloud.bigtable.data.v2.BigtableDataSettings; +import com.google.cloud.bigtable.data.v2.models.RowMutation; +import com.google.common.base.Joiner; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import com.google.protobuf.Timestamp; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; +import org.junit.*; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; + +public class BigtableBackupIT { + private static final Logger LOGGER = Logger.getLogger(BigtableBackupIT.class.getName()); + + private static final String PROJECT_PROPERTY_NAME = "bigtable.project"; + private static final String INSTANCE_PROPERTY_NAME = "bigtable.instance"; + private static final String CLUSTER_PROPERTY_NAME = "bigtable.cluster"; + private static final String ADMIN_ENDPOINT_PROPERTY_NAME = "bigtable.adminendpoint"; + private static final String DATA_ENDPOINT_PROPERTY_NAME = "bigtable.dataendpoint"; + private static final String TABLE_SIZE_PROPERTY_NAME = "bigtable.tablesizekb"; + private static final int[] BACKOFF_DURATION = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}; + + private static final String TEST_TABLE_SUFFIX = "test-table-for-backup-it"; + private static final String TEST_BACKUP_SUFFIX = "test-backup-for-backup-it"; + + private static final int DAYS_IN_SECONDS = 24 * 60 * 60; + + private static BigtableTableAdminClient tableAdmin; + private static BigtableDataClient dataClient; + + private static String targetProject; + private static String targetInstance; + private static String targetCluster; + private static Table testTable; + private static String prefix; + + @BeforeClass + public static void createClient() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + List missingProperties = Lists.newArrayList(); + + targetProject = System.getProperty(PROJECT_PROPERTY_NAME); + if (targetProject == null) { + missingProperties.add(PROJECT_PROPERTY_NAME); + } + + targetInstance = System.getProperty(INSTANCE_PROPERTY_NAME); + if (targetInstance == null) { + missingProperties.add(INSTANCE_PROPERTY_NAME); + } + + targetCluster = System.getProperty(CLUSTER_PROPERTY_NAME); + if (targetCluster == null) { + missingProperties.add(CLUSTER_PROPERTY_NAME); + } + + String adminApiEndpoint = System.getProperty(ADMIN_ENDPOINT_PROPERTY_NAME); + if (adminApiEndpoint == null) { + adminApiEndpoint = "bigtableadmin.googleapis.com:443"; + } + + int tableSize = MoreObjects.firstNonNull(Integer.getInteger(TABLE_SIZE_PROPERTY_NAME), 1); + if (!missingProperties.isEmpty()) { + LOGGER.warning("Missing properties: " + Joiner.on(",").join(missingProperties)); + return; + } + + // Setup a prefix to avoid collisions between concurrent test runs + prefix = String.format("020%d", System.currentTimeMillis()); + + BigtableTableAdminSettings.Builder settings = + BigtableTableAdminSettings.newBuilder() + .setInstanceId(targetInstance) + .setProjectId(targetProject); + settings.stubSettings().setEndpoint(adminApiEndpoint); + tableAdmin = BigtableTableAdminClient.create(settings.build()); + + testTable = + tableAdmin.createTable( + CreateTableRequest.of(generateId(TEST_TABLE_SUFFIX)).addFamily("cf1")); + + // Populate test data. + if (tableSize > 0) { + String dataApiEndpoint = System.getProperty(DATA_ENDPOINT_PROPERTY_NAME); + if (dataApiEndpoint == null) { + dataApiEndpoint = "bigtable.googleapis.com:443"; + } + BigtableDataSettings.Builder dataSettings = + BigtableDataSettings.newBuilder() + .setInstanceId(targetInstance) + .setProjectId(targetProject); + dataSettings.stubSettings().setEndpoint(dataApiEndpoint); + dataClient = BigtableDataClient.create(dataSettings.build()); + byte[] rowBytes = new byte[1024]; + Random random = new Random(); + random.nextBytes(rowBytes); + + List > futures = Lists.newArrayList(); + for (int i = 0; i < tableSize; i++) { + ApiFuture future = + dataClient.mutateRowAsync( + RowMutation.create(testTable.getId(), "test-row-" + i) + .setCell("cf1", "", rowBytes.toString())); + futures.add(future); + } + ApiFutures.allAsList(futures).get(3, TimeUnit.MINUTES); + } + + // Cleanup old backups and tables, under normal circumstances this will do nothing + String stalePrefix = + String.format("020%d", System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2)); + for (String backupId : tableAdmin.listBackups(targetCluster)) { + if (backupId.endsWith(TEST_BACKUP_SUFFIX) && stalePrefix.compareTo(backupId) > 0) { + LOGGER.info("Deleting stale backup: " + backupId); + tableAdmin.deleteBackup(targetCluster, backupId); + } + } + for (String tableId : tableAdmin.listTables()) { + if (tableId.endsWith("TEST_TABLE_SUFFIX") && stalePrefix.compareTo(tableId) > 0) { + LOGGER.info("Deleting stale backup: " + tableId); + tableAdmin.deleteTable(tableId); + } + } + } + + @AfterClass + public static void closeClient() { + if (testTable != null) { + try { + tableAdmin.deleteTable(testTable.getId()); + } catch (Exception e) { + // Ignore. + } + } + + if (tableAdmin != null) { + tableAdmin.close(); + } + + if (dataClient != null) { + dataClient.close(); + } + } + + @Before + public void setup() { + if (tableAdmin == null) { + throw new AssumptionViolatedException( + "Required properties are not set, skipping integration tests."); + } + } + + @Test + public void createAndGetBackupTest() throws InterruptedException { + Instant expireTime = Instant.now().plus(Duration.ofDays(15)); + String backupId = generateId(TEST_BACKUP_SUFFIX); + CreateBackupRequest request = + CreateBackupRequest.of(targetCluster, backupId) + .setSourceTableId(testTable.getId()) + .setExpireTime(expireTime); + try { + Backup response = tableAdmin.createBackup(request); + assertWithMessage("Got wrong backup Id in CreateBackup") + .that(response.getId()) + .isEqualTo(backupId); + assertWithMessage("Got wrong source table name in CreateBackup") + .that(response.getSourceTableId()) + .isEqualTo(testTable.getId()); + assertWithMessage("Got wrong expire time in CreateBackup") + .that(response.getExpireTime()) + .isEqualTo(expireTime); + + Backup result = tableAdmin.getBackup(targetCluster, backupId); + assertWithMessage("Got wrong backup Id in GetBackup API") + .that(result.getId()) + .isEqualTo(backupId); + assertWithMessage("Got wrong source table name in GetBackup API") + .that(result.getSourceTableId()) + .isEqualTo(testTable.getId()); + assertWithMessage("Got wrong expire time in GetBackup API") + .that(result.getExpireTime()) + .isEqualTo(expireTime); + assertWithMessage("Got empty start time in GetBackup API") + .that(result.getStartTime()) + .isNotEqualTo(Timestamp.getDefaultInstance()); + assertWithMessage("Got wrong size bytes in GetBackup API") + .that(result.getSizeBytes()) + .isEqualTo(0L); + assertWithMessage("Got wrong state in GetBackup API") + .that(result.getState()) + .isAnyOf(Backup.State.CREATING, Backup.State.READY); + + } finally { + tableAdmin.deleteBackup(targetCluster, backupId); + } + } + + @Test + public void listBackupTest() throws InterruptedException { + String backupId1 = generateId("list-1-" + TEST_BACKUP_SUFFIX); + String backupId2 = generateId("list-2-" + TEST_BACKUP_SUFFIX); + + try { + createBackupAndWait(backupId1); + createBackupAndWait(backupId2); + + List response = tableAdmin.listBackups(targetCluster); + // Concurrent tests running may cause flakiness. Use containsAtLeast instead of + // containsExactly. + assertWithMessage("Incorrect backup name") + .that(response) + .containsAtLeast(backupId1, backupId2); + } finally { + tableAdmin.deleteBackup(targetCluster, backupId1); + tableAdmin.deleteBackup(targetCluster, backupId2); + } + } + + @Test + public void updateBackupTest() throws InterruptedException { + String backupId = generateId("update-" + TEST_BACKUP_SUFFIX); + createBackupAndWait(backupId); + + Instant expireTime = Instant.now().plus(Duration.ofDays(20)); + UpdateBackupRequest req = + UpdateBackupRequest.of(targetCluster, backupId).setExpireTime(expireTime); + try { + Backup backup = tableAdmin.updateBackup(req); + assertWithMessage("Incorrect expire time").that(backup.getExpireTime()).isEqualTo(expireTime); + } finally { + tableAdmin.deleteBackup(targetCluster, backupId); + } + } + + @Test + public void deleteBackupTest() throws InterruptedException { + String backupId = generateId("delete-" + TEST_BACKUP_SUFFIX); + + createBackupAndWait(backupId); + tableAdmin.deleteBackup(targetCluster, backupId); + + try { + for (int i = 0; i < BACKOFF_DURATION.length; i++) { + tableAdmin.getBackup(targetCluster, backupId); + + LOGGER.info("Wait for " + BACKOFF_DURATION[i] + " seconds for deleting backup " + backupId); + Thread.sleep(BACKOFF_DURATION[i] * 1000); + } + fail("backup was not deleted."); + } catch (ApiException ex) { + assertWithMessage("Incorrect exception type") + .that(ex.getCause()) + .isInstanceOf(StatusRuntimeException.class); + assertWithMessage("Incorrect error message") + .that(((StatusRuntimeException) ex.getCause()).getStatus().getCode()) + .isEqualTo(NOT_FOUND); + } + } + + @Test + public void restoreTableTest() throws InterruptedException, ExecutionException { + String backupId = generateId("restore-" + TEST_BACKUP_SUFFIX); + String tableId = generateId("restored-table"); + createBackupAndWait(backupId); + + // Wait 2 minutes so that the RestoreTable API will trigger an optimize restored + // table operation. + Thread.sleep(120 * 1000); + + try { + RestoreTableRequest req = RestoreTableRequest.of(targetCluster, backupId).setTableId(tableId); + RestoredTableResult result = tableAdmin.restoreTable(req); + assertWithMessage("Incorrect restored table id") + .that(result.getTable().getId()) + .isEqualTo(tableId); + + // The assertion might be missing if the test is running against a HDD cluster or an + // optimization is not necessary. + assertWithMessage("Empty OptimizeRestoredTable token") + .that(result.getOptimizeRestoredTableOperationToken()) + .isNotNull(); + tableAdmin.awaitOptimizeRestoredTable(result.getOptimizeRestoredTableOperationToken()); + tableAdmin.getTable(tableId); + } finally { + tableAdmin.deleteBackup(targetCluster, backupId); + tableAdmin.deleteTable(tableId); + } + } + + private CreateBackupRequest createBackupRequest(String backupName) { + return CreateBackupRequest.of(targetCluster, backupName) + .setSourceTableId(testTable.getId()) + .setExpireTime(Instant.now().plus(Duration.ofDays(15))); + } + + private static String generateId(String name) { + return prefix + "-" + name; + } + + private void createBackupAndWait(String backupId) throws InterruptedException { + tableAdmin.createBackup(createBackupRequest(backupId)); + for (int i = 0; i < BACKOFF_DURATION.length; i++) { + try { + Backup backup = tableAdmin.getBackup(targetCluster, backupId); + if (backup.getState() == Backup.State.READY) { + return; + } + } catch (ApiException ex) { + LOGGER.info("Wait for " + BACKOFF_DURATION[i] + " seconds for creating backup " + backupId); + } + + Thread.sleep(BACKOFF_DURATION[i] * 1000); + } + + fail("Creating Backup Timeout"); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/BackupTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/BackupTest.java new file mode 100644 index 0000000000..be32058e23 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/BackupTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020 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 static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Lists; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Timestamps; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Instant; + +@RunWith(JUnit4.class) +public class BackupTest { + @Test + public void testBackupStateEnumUpToDate() { + List validProtoValues = + Lists.newArrayList(com.google.bigtable.admin.v2.Backup.State.values()); + + List validModelValues = Lists.newArrayList(Backup.State.values()); + + List actualModelValues = Lists.newArrayList(); + + for (com.google.bigtable.admin.v2.Backup.State protoValue : validProtoValues) { + Backup.State modelValue = Backup.State.fromProto(protoValue); + actualModelValues.add(modelValue); + } + + assertThat(actualModelValues).containsExactlyElementsIn(validModelValues); + } + + @Test + public void testFromProto() { + Timestamp expireTime = Timestamp.newBuilder().setSeconds(1234).build(); + Timestamp startTime = Timestamp.newBuilder().setSeconds(1234).build(); + Timestamp endTime = Timestamp.newBuilder().setSeconds(1234).build(); + com.google.bigtable.admin.v2.Backup proto = + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName("projects/my-project/instances/instance1/clusters/cluster1/backups/backup1") + .setSourceTable("projects/my-project/instances/instance1/tables/table1") + .setExpireTime(expireTime) + .setStartTime(startTime) + .setEndTime(endTime) + .setSizeBytes(123456) + .setState(com.google.bigtable.admin.v2.Backup.State.READY) + .build(); + + Backup result = Backup.fromProto(proto); + + assertThat(result.getId()).isEqualTo("backup1"); + assertThat(result.getSourceTableId()).isEqualTo("table1"); + assertThat(result.getExpireTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(expireTime))); + assertThat(result.getStartTime()) + .isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(startTime))); + assertThat(result.getEndTime()).isEqualTo(Instant.ofEpochMilli(Timestamps.toMillis(endTime))); + assertThat(result.getSizeBytes()).isEqualTo(123456); + assertThat(result.getState()).isEqualTo(Backup.State.READY); + } + + @Test + public void testRequiresName() { + com.google.bigtable.admin.v2.Backup proto = + com.google.bigtable.admin.v2.Backup.newBuilder() + .setSourceTable("projects/my-project/instances/instance1/tables/table1") + .setExpireTime(Timestamp.newBuilder().setSeconds(1234).build()) + .setStartTime(Timestamp.newBuilder().setSeconds(123).build()) + .setEndTime(Timestamp.newBuilder().setSeconds(456).build()) + .setSizeBytes(123456) + .setState(com.google.bigtable.admin.v2.Backup.State.READY) + .build(); + + Exception actualException = null; + + try { + Backup.fromProto(proto); + } catch (Exception e) { + actualException = e; + } + + assertThat(actualException).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testRequiresSourceTable() { + com.google.bigtable.admin.v2.Backup proto = + com.google.bigtable.admin.v2.Backup.newBuilder() + .setName("projects/my-project/instances/instance1/clusters/cluster1/backups/backup1") + .setExpireTime(Timestamp.newBuilder().setSeconds(1234).build()) + .setStartTime(Timestamp.newBuilder().setSeconds(123).build()) + .setEndTime(Timestamp.newBuilder().setSeconds(456).build()) + .setSizeBytes(123456) + .setState(com.google.bigtable.admin.v2.Backup.State.READY) + .build(); + + Exception actualException = null; + + try { + Backup.fromProto(proto); + } catch (Exception e) { + actualException = e; + } + + assertThat(actualException).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequestTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequestTest.java new file mode 100644 index 0000000000..f4a1e12f65 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateBackupRequestTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 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 static com.google.common.truth.Truth.assertThat; + +import com.google.bigtable.admin.v2.Backup; +import com.google.cloud.bigtable.admin.v2.internal.NameUtil; +import com.google.protobuf.util.Timestamps; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; + +@RunWith(JUnit4.class) +public class CreateBackupRequestTest { + + private static final String TABLE_ID = "my-table"; + private static final String BACKUP_ID = "my-backup"; + private static final String PROJECT_ID = "my-project"; + private static final String INSTANCE_ID = "my-instance"; + private static final String CLUSTER_ID = "my-cluster"; + private static final Instant EXPIRE_TIME = Instant.now().plus(Duration.ofDays(15)); + + @Test + public void testToProto() { + CreateBackupRequest request = + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId(TABLE_ID) + .setExpireTime(EXPIRE_TIME); + + com.google.bigtable.admin.v2.CreateBackupRequest requestProto = + com.google.bigtable.admin.v2.CreateBackupRequest.newBuilder() + .setBackupId(BACKUP_ID) + .setBackup( + Backup.newBuilder() + .setSourceTable(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID)) + .setExpireTime(Timestamps.fromMillis(EXPIRE_TIME.toEpochMilli())) + .build()) + .setParent(NameUtil.formatClusterName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID)) + .build(); + assertThat(request.toProto(PROJECT_ID, INSTANCE_ID)).isEqualTo(requestProto); + } + + @Test + public void testEquality() { + CreateBackupRequest request = + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId(TABLE_ID) + .setExpireTime(EXPIRE_TIME); + + assertThat(request) + .isEqualTo( + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId(TABLE_ID) + .setExpireTime(EXPIRE_TIME)); + + assertThat(request) + .isNotEqualTo( + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId("another-table") + .setExpireTime(EXPIRE_TIME)); + } + + @Test + public void testHashCode() { + CreateBackupRequest request = + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId(TABLE_ID) + .setExpireTime(EXPIRE_TIME); + + assertThat(request.hashCode()) + .isEqualTo( + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId(TABLE_ID) + .setExpireTime(EXPIRE_TIME) + .hashCode()); + + assertThat(request.hashCode()) + .isNotEqualTo( + CreateBackupRequest.of(CLUSTER_ID, BACKUP_ID) + .setSourceTableId("another-table") + .setExpireTime(EXPIRE_TIME) + .hashCode()); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequestTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequestTest.java new file mode 100644 index 0000000000..3ed165042c --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/RestoreTableRequestTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 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 static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigtable.admin.v2.internal.NameUtil; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RestoreTableRequestTest { + + private static final String TABLE_ID = "my-table"; + private static final String BACKUP_ID = "my-backup"; + private static final String PROJECT_ID = "my-project"; + private static final String INSTANCE_ID = "my-instance"; + private static final String CLUSTER_ID = "my-cluster"; + + @Test + public void testToProto() { + RestoreTableRequest request = + RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID); + + com.google.bigtable.admin.v2.RestoreTableRequest requestProto = + com.google.bigtable.admin.v2.RestoreTableRequest.newBuilder() + .setParent(NameUtil.formatInstanceName(PROJECT_ID, INSTANCE_ID)) + .setBackup(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .setTableId(TABLE_ID) + .build(); + assertThat(request.toProto(PROJECT_ID, INSTANCE_ID)).isEqualTo(requestProto); + } + + @Test + public void testEquality() { + RestoreTableRequest request = + RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID); + + assertThat(request) + .isEqualTo(RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID)); + assertThat(request) + .isNotEqualTo(RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId("another-table")); + } + + @Test + public void testHashCode() { + RestoreTableRequest request = + RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID); + assertThat(request.hashCode()) + .isEqualTo(RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId(TABLE_ID).hashCode()); + assertThat(request.hashCode()) + .isNotEqualTo( + RestoreTableRequest.of(CLUSTER_ID, BACKUP_ID).setTableId("another-table").hashCode()); + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequestTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequestTest.java new file mode 100644 index 0000000000..c8d34833f3 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateBackupRequestTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 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 static com.google.common.truth.Truth.assertThat; + +import com.google.bigtable.admin.v2.Backup; +import com.google.cloud.bigtable.admin.v2.internal.NameUtil; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.Timestamps; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; + +@RunWith(JUnit4.class) +public class UpdateBackupRequestTest { + + private static final String TABLE_ID = "my-table"; + private static final String BACKUP_ID = "my-backup"; + private static final String PROJECT_ID = "my-project"; + private static final String INSTANCE_ID = "my-instance"; + private static final String CLUSTER_ID = "my-cluster"; + private static final Instant EXPIRE_TIME = Instant.now().plus(Duration.ofDays(15)); + private static final Instant EXPIRE_TIME_2 = Instant.now().plus(Duration.ofDays(20)); + + @Test + public void testToProto() { + UpdateBackupRequest request = + UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME); + + com.google.bigtable.admin.v2.UpdateBackupRequest requestProto = + com.google.bigtable.admin.v2.UpdateBackupRequest.newBuilder() + .setBackup( + Backup.newBuilder() + .setName( + NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .setExpireTime(Timestamps.fromMillis(EXPIRE_TIME.toEpochMilli())) + .build()) + .setUpdateMask(FieldMask.newBuilder().addPaths("expire_time").build()) + .build(); + assertThat(request.toProto(PROJECT_ID, INSTANCE_ID)).isEqualTo(requestProto); + } + + @Test + public void testEquality() { + UpdateBackupRequest request = + UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME); + assertThat(request) + .isEqualTo(UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME)); + assertThat(request) + .isNotEqualTo(UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME_2)); + } + + @Test + public void testHashCode() { + UpdateBackupRequest request = + UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME); + assertThat(request.hashCode()) + .isEqualTo( + UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME).hashCode()); + assertThat(request.hashCode()) + .isNotEqualTo( + UpdateBackupRequest.of(CLUSTER_ID, BACKUP_ID).setExpireTime(EXPIRE_TIME_2).hashCode()); + } +} diff --git a/grpc-google-cloud-bigtable-admin-v2/pom.xml b/grpc-google-cloud-bigtable-admin-v2/pom.xml index 3ba6a3d503..6b8e663b9a 100644 --- a/grpc-google-cloud-bigtable-admin-v2/pom.xml +++ b/grpc-google-cloud-bigtable-admin-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-bigtable-admin-v2 -1.13.1 +1.14.0 grpc-google-cloud-bigtable-admin-v2 GRPC library for grpc-google-cloud-bigtable-admin-v2 com.google.cloud google-cloud-bigtable-parent -1.13.1 +1.14.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom -1.13.1 +1.14.0 pom import diff --git a/grpc-google-cloud-bigtable-v2/pom.xml b/grpc-google-cloud-bigtable-v2/pom.xml index 5f7d1b27f9..3574b668cf 100644 --- a/grpc-google-cloud-bigtable-v2/pom.xml +++ b/grpc-google-cloud-bigtable-v2/pom.xml @@ -4,13 +4,13 @@ com.google.cloud google-cloud-bigtable-bom -1.13.1 +1.14.0 pom import 4.0.0 com.google.api.grpc grpc-google-cloud-bigtable-v2 -1.13.1 +1.14.0 grpc-google-cloud-bigtable-v2 GRPC library for grpc-google-cloud-bigtable-v2 com.google.cloud google-cloud-bigtable-parent -1.13.1 +1.14.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom -1.13.1 +1.14.0 pom import diff --git a/pom.xml b/pom.xml index 0a38236547..9d0b57aa45 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-bigtable-bom -1.13.1 +1.14.0 pom import google-cloud-bigtable-parent pom -1.13.1 +1.14.0 Google Cloud Bigtable Parent https://github.com/googleapis/java-bigtable diff --git a/proto-google-cloud-bigtable-admin-v2/pom.xml b/proto-google-cloud-bigtable-admin-v2/pom.xml index ffba7f4ca1..778237ef87 100644 --- a/proto-google-cloud-bigtable-admin-v2/pom.xml +++ b/proto-google-cloud-bigtable-admin-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-bigtable-admin-v2 -1.13.1 +1.14.0 proto-google-cloud-bigtable-admin-v2 PROTO library for proto-google-cloud-bigtable-admin-v2 com.google.cloud google-cloud-bigtable-parent -1.13.1 +1.14.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom -1.13.1 +1.14.0 pom import diff --git a/proto-google-cloud-bigtable-v2/pom.xml b/proto-google-cloud-bigtable-v2/pom.xml index 2e8ed76fbd..7f8628551c 100644 --- a/proto-google-cloud-bigtable-v2/pom.xml +++ b/proto-google-cloud-bigtable-v2/pom.xml @@ -4,13 +4,13 @@ com.google.cloud google-cloud-bigtable-bom -1.13.1 +1.14.0 pom import 4.0.0 com.google.api.grpc proto-google-cloud-bigtable-v2 -1.13.1 +1.14.0 proto-google-cloud-bigtable-v2 PROTO library for proto-google-cloud-bigtable-v2 com.google.cloud google-cloud-bigtable-parent -1.13.1 +1.14.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom -1.13.1 +1.14.0 pom import diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 84ec83aedc..b22bcd33e6 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-bigtable-bom -1.13.1 +1.14.0 pom import diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index bf65fea31e..f5aa3a83e6 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-bigtable -1.13.0 +1.13.1 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 7c8015591b..f894c41f15 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -30,7 +30,7 @@ com.google.cloud google-cloud-bigtable -1.13.1 +1.14.0 diff --git a/versions.txt b/versions.txt index 18ca691cbc..60222c86f4 100644 --- a/versions.txt +++ b/versions.txt @@ -1,9 +1,9 @@ # Format: # module:released-version:current-version -google-cloud-bigtable:1.13.1:1.13.1 -grpc-google-cloud-bigtable-admin-v2:1.13.1:1.13.1 -grpc-google-cloud-bigtable-v2:1.13.1:1.13.1 -proto-google-cloud-bigtable-admin-v2:1.13.1:1.13.1 -proto-google-cloud-bigtable-v2:1.13.1:1.13.1 -google-cloud-bigtable-emulator:0.122.1:0.122.1 +google-cloud-bigtable:1.14.0:1.14.0 +grpc-google-cloud-bigtable-admin-v2:1.14.0:1.14.0 +grpc-google-cloud-bigtable-v2:1.14.0:1.14.0 +proto-google-cloud-bigtable-admin-v2:1.14.0:1.14.0 +proto-google-cloud-bigtable-v2:1.14.0:1.14.0 +google-cloud-bigtable-emulator:0.123.0:0.123.0 com.google.cloud libraries-bom -8.0.0 +8.1.0 pom import