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