diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Address.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Address.java index b30293fb6d48..e1e70def80d5 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Address.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Address.java @@ -172,8 +172,8 @@ public final int hashCode() { return Objects.hash(super.hashCode(), options); } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); this.compute = options.service(); } diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java index 815b5ebe49e6..6d033688d8c2 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java @@ -582,7 +582,7 @@ class DiskTypeFilter extends ListFilter { private static final long serialVersionUID = 4847837203592234453L; - DiskTypeFilter(DiskTypeField field, ComparisonOperator operator, Object value) { + private DiskTypeFilter(DiskTypeField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -630,7 +630,7 @@ class MachineTypeFilter extends ListFilter { private static final long serialVersionUID = 7346062041571853235L; - MachineTypeFilter(MachineTypeField field, ComparisonOperator operator, Object value) { + private MachineTypeFilter(MachineTypeField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -678,7 +678,7 @@ class RegionFilter extends ListFilter { private static final long serialVersionUID = 4464892812442567172L; - RegionFilter(RegionField field, ComparisonOperator operator, Object value) { + private RegionFilter(RegionField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -712,7 +712,7 @@ class ZoneFilter extends ListFilter { private static final long serialVersionUID = -3927428278548808737L; - ZoneFilter(ZoneField field, ComparisonOperator operator, Object value) { + private ZoneFilter(ZoneField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -746,7 +746,7 @@ class OperationFilter extends ListFilter { private static final long serialVersionUID = -3202249202748346427L; - OperationFilter(OperationField field, ComparisonOperator operator, Object value) { + private OperationFilter(OperationField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -794,7 +794,7 @@ class AddressFilter extends ListFilter { private static final long serialVersionUID = -227481644259653765L; - AddressFilter(AddressField field, ComparisonOperator operator, Object value) { + private AddressFilter(AddressField field, ComparisonOperator operator, Object value) { super(field.selector(), operator, value); } @@ -821,6 +821,54 @@ public static AddressFilter notEquals(AddressField field, String value) { } } + /** + * Class for filtering snapshot lists. + */ + class SnapshotFilter extends ListFilter { + + private static final long serialVersionUID = 8757711630092406747L; + + private SnapshotFilter(SnapshotField field, ComparisonOperator operator, Object value) { + super(field.selector(), operator, value); + } + + /** + * Returns an equals filter for the given field and string value. For string fields, + * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must + * match the entire field. + * + * @see RE2 + */ + public static SnapshotFilter equals(SnapshotField field, String value) { + return new SnapshotFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value)); + } + + /** + * Returns a not-equals filter for the given field and string value. For string fields, + * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must + * match the entire field. + * + * @see RE2 + */ + public static SnapshotFilter notEquals(SnapshotField field, String value) { + return new SnapshotFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value)); + } + + /** + * Returns an equals filter for the given field and long value. + */ + public static SnapshotFilter equals(SnapshotField field, long value) { + return new SnapshotFilter(checkNotNull(field), ComparisonOperator.EQ, value); + } + + /** + * Returns a not-equals filter for the given field and long value. + */ + public static SnapshotFilter notEquals(SnapshotField field, long value) { + return new SnapshotFilter(checkNotNull(field), ComparisonOperator.NE, value); + } + } + /** * Class for specifying disk type get options. */ @@ -863,6 +911,7 @@ public static DiskTypeListOption filter(DiskTypeFilter filter) { /** * Returns an option to specify the maximum number of disk types returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static DiskTypeListOption pageSize(long pageSize) { return new DiskTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -908,6 +957,7 @@ public static DiskTypeAggregatedListOption filter(DiskTypeFilter filter) { /** * Returns an option to specify the maximum number of disk types returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static DiskTypeAggregatedListOption pageSize(long pageSize) { return new DiskTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -963,6 +1013,7 @@ public static MachineTypeListOption filter(MachineTypeFilter filter) { /** * Returns an option to specify the maximum number of machine types returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static MachineTypeListOption pageSize(long pageSize) { return new MachineTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1008,6 +1059,7 @@ public static MachineTypeAggregatedListOption filter(MachineTypeFilter filter) { /** * Returns an option to specify the maximum number of machine types returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static MachineTypeAggregatedListOption pageSize(long pageSize) { return new MachineTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1063,6 +1115,7 @@ public static RegionListOption filter(RegionFilter filter) { /** * Returns an option to specify the maximum number of regions returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static RegionListOption pageSize(long pageSize) { return new RegionListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1130,6 +1183,7 @@ public static ZoneListOption filter(ZoneFilter filter) { /** * Returns an option to specify the maximum number of zones returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static ZoneListOption pageSize(long pageSize) { return new ZoneListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1219,6 +1273,7 @@ public static OperationListOption filter(OperationFilter filter) { /** * Returns an option to specify the maximum number of operations returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static OperationListOption pageSize(long pageSize) { return new OperationListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1286,6 +1341,7 @@ public static AddressListOption filter(AddressFilter filter) { /** * Returns an option to specify the maximum number of addresses returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static AddressListOption pageSize(long pageSize) { return new AddressListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1331,6 +1387,7 @@ public static AddressAggregatedListOption filter(AddressFilter filter) { /** * Returns an option to specify the maximum number of addresses returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. */ public static AddressAggregatedListOption pageSize(long pageSize) { return new AddressAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); @@ -1344,6 +1401,74 @@ public static AddressAggregatedListOption pageToken(String pageToken) { } } + /** + * Class for specifying snapshot get options. + */ + class SnapshotOption extends Option { + + private static final long serialVersionUID = -3505179459035500945L; + + private SnapshotOption(ComputeRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the snapshot's fields to be returned by the RPC call. If this + * option is not provided, all the snapshot's fields are returned. {@code SnapshotOption.fields} + * can be used to specify only the fields of interest. {@link Snapshot#snapshotId()} is always + * returned, even if not specified. + */ + public static SnapshotOption fields(SnapshotField... fields) { + return new SnapshotOption(ComputeRpc.Option.FIELDS, SnapshotField.selector(fields)); + } + } + + /** + * Class for specifying snapshot list options. + */ + class SnapshotListOption extends Option { + + private static final long serialVersionUID = 8278588147660831257L; + + private SnapshotListOption(ComputeRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify a filter on the snapshots being listed. + */ + public static SnapshotListOption filter(SnapshotFilter filter) { + return new SnapshotListOption(ComputeRpc.Option.FILTER, filter.toPb()); + } + + /** + * Returns an option to specify the maximum number of snapshots returned per page. + * {@code pageSize} must be between 0 and 500 (inclusive). If not specified 500 is used. + */ + public static SnapshotListOption pageSize(long pageSize) { + return new SnapshotListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); + } + + /** + * Returns an option to specify the page token from which to start listing snapshots. + */ + public static SnapshotListOption pageToken(String pageToken) { + return new SnapshotListOption(ComputeRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * Returns an option to specify the snapshot's fields to be returned by the RPC call. If this + * option is not provided, all the snapshot's fields are returned. + * {@code SnapshotListOption.fields} can be used to specify only the fields of interest. + * {@link Snapshot#snapshotId()} is always returned, even if not specified. + */ + public static SnapshotListOption fields(SnapshotField... fields) { + StringBuilder builder = new StringBuilder(); + builder.append("items(").append(SnapshotField.selector(fields)).append("),nextPageToken"); + return new SnapshotListOption(ComputeRpc.Option.FIELDS, builder.toString()); + } + } + /** * Returns the requested disk type or {@code null} if not found. * @@ -1366,7 +1491,7 @@ public static AddressAggregatedListOption pageToken(String pageToken) { Page listDiskTypes(String zone, DiskTypeListOption... options); /** - * Lists all disk types. + * Lists the disk types in all zones. * * @throws ComputeException upon failure */ @@ -1394,7 +1519,7 @@ public static AddressAggregatedListOption pageToken(String pageToken) { Page listMachineTypes(String zone, MachineTypeListOption... options); /** - * Lists all machine types. + * Lists the machine types in all zones. * * @throws ComputeException upon failure */ @@ -1511,7 +1636,7 @@ public static AddressAggregatedListOption pageToken(String pageToken) { Page
listRegionAddresses(String region, AddressListOption... options); /** - * Lists all addresses. + * Lists both global and region addresses. * * @throws ComputeException upon failure */ @@ -1525,4 +1650,53 @@ public static AddressAggregatedListOption pageToken(String pageToken) { * @throws ComputeException upon failure */ Operation delete(AddressId addressId, OperationOption... options); + + /** + * Creates a new snapshot. + * + * @return a zone operation if the create request was issued correctly, {@code null} if + * {@code snapshot.sourceDisk} was not found + * @throws ComputeException upon failure + */ + Operation create(SnapshotInfo snapshot, OperationOption... options); + + /** + * Returns the requested snapshot or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Snapshot getSnapshot(String snapshot, SnapshotOption... options); + + /** + * Lists snapshots. + * + * @throws ComputeException upon failure + */ + Page listSnapshots(SnapshotListOption... options); + + /** + * Deletes the requested snapshot. Keep in mind that deleting a single snapshot might not + * necessarily delete all the data for that snapshot. If any data for the snapshot that is marked + * for deletion is needed for subsequent snapshots, the data will be moved to the next snapshot. + * + * @return a global operation if the request was issued correctly, {@code null} if the snapshot + * was not found + * @throws ComputeException upon failure + * @see + * Deleting a snapshot + */ + Operation deleteSnapshot(SnapshotId snapshot, OperationOption... options); + + /** + * Deletes the requested snapshot. Keep in mind that deleting a single snapshot might not + * necessarily delete all the data for that snapshot. If any data on the snapshot that is marked + * for deletion is needed for subsequent snapshots, the data will be moved to the next snapshot. + * + * @return a global operation if the request was issued correctly, {@code null} if the snapshot + * was not found + * @throws ComputeException upon failure + * @see + * Deleting a snapshot + */ + Operation deleteSnapshot(String snapshot, OperationOption... options); } diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java index 838816a035e0..a348e1e0e253 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java @@ -273,6 +273,25 @@ public Page
nextPage() { } } + private static class SnapshotPageFetcher implements NextPageFetcher { + + private static final long serialVersionUID = 6205774609802216986L; + private final Map requestOptions; + private final ComputeOptions serviceOptions; + + SnapshotPageFetcher(ComputeOptions serviceOptions, String cursor, + Map optionMap) { + this.requestOptions = + PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap); + this.serviceOptions = serviceOptions; + } + + @Override + public Page nextPage() { + return listSnapshots(serviceOptions, requestOptions); + } + } + private final ComputeRpc computeRpc; ComputeImpl(ComputeOptions options) { @@ -881,8 +900,8 @@ public Address apply(com.google.api.services.compute.model.Address address) { return Address.fromPb(serviceOptions.service(), address); } }); - return new PageImpl<>(new AggregatedAddressPageFetcher(serviceOptions, cursor, - optionsMap), cursor, operations); + return new PageImpl<>(new AggregatedAddressPageFetcher(serviceOptions, cursor, optionsMap), + cursor, operations); } catch (RetryHelper.RetryHelperException e) { throw ComputeException.translateAndThrow(e); } @@ -914,6 +933,99 @@ public com.google.api.services.compute.model.Operation call() { } } + @Override + public Operation create(SnapshotInfo snapshot, OperationOption... options) { + final SnapshotInfo completeSnapshot = snapshot.setProjectId(options().projectId()); + final Map optionsMap = optionMap(options); + try { + com.google.api.services.compute.model.Operation answer = + runWithRetries(new Callable() { + @Override + public com.google.api.services.compute.model.Operation call() { + return computeRpc.createSnapshot(completeSnapshot.sourceDisk().zone(), + completeSnapshot.sourceDisk().disk(), completeSnapshot.snapshotId().snapshot(), + completeSnapshot.description(), optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Operation.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Snapshot getSnapshot(final String snapshot, SnapshotOption... options) { + final Map optionsMap = optionMap(options); + try { + com.google.api.services.compute.model.Snapshot answer = + runWithRetries(new Callable() { + @Override + public com.google.api.services.compute.model.Snapshot call() { + return computeRpc.getSnapshot(snapshot, optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Snapshot.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Page listSnapshots(SnapshotListOption... options) { + return listSnapshots(options(), optionMap(options)); + } + + private static Page listSnapshots(final ComputeOptions serviceOptions, + final Map optionsMap) { + try { + ComputeRpc.Tuple> result = + runWithRetries(new Callable>>() { + @Override + public ComputeRpc.Tuple> call() { + return serviceOptions.rpc().listSnapshots(optionsMap); + } + }, serviceOptions.retryParams(), EXCEPTION_HANDLER); + String cursor = result.x(); + Iterable snapshots = Iterables.transform( + result.y() == null ? ImmutableList.of() + : result.y(), + new Function() { + @Override + public Snapshot apply(com.google.api.services.compute.model.Snapshot snapshot) { + return Snapshot.fromPb(serviceOptions.service(), snapshot); + } + }); + return new PageImpl<>(new SnapshotPageFetcher(serviceOptions, cursor, optionsMap), cursor, + snapshots); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Operation deleteSnapshot(SnapshotId snapshot, OperationOption... options) { + return deleteSnapshot(snapshot.snapshot(), options); + } + + @Override + public Operation deleteSnapshot(final String snapshot, OperationOption... options) { + final Map optionsMap = optionMap(options); + try { + com.google.api.services.compute.model.Operation answer = + runWithRetries(new Callable() { + @Override + public com.google.api.services.compute.model.Operation call() { + return computeRpc.deleteSnapshot(snapshot, optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Operation.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + private Map optionMap(Option... options) { Map optionMap = Maps.newEnumMap(ComputeRpc.Option.class); for (Option option : options) { diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java index ce519edc970d..78afc170f671 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java @@ -792,8 +792,8 @@ com.google.api.services.compute.model.Operation toPb() { return operationPb; } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); this.compute = options.service(); } diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Snapshot.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Snapshot.java new file mode 100644 index 000000000000..e93a419d5b4a --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Snapshot.java @@ -0,0 +1,211 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.compute; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.gcloud.compute.Compute.OperationOption; +import com.google.gcloud.compute.Compute.SnapshotOption; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.List; +import java.util.Objects; + +/** + * A Google Compute Engine snapshot. Compute Engine allows you to take snapshots of your persistent + * disk and create new persistent disks from that snapshot. This can be useful for backing up data, + * recreating a persistent disk that might have been lost, or copying a persistent disk. Snapshots + * can be applied across persistent disk types. {@code Snapshot} adds a layer of service-related + * functionality over {@link SnapshotInfo}. Objects of this class are immutable; to get a + * {@code Snapshot} object with the most recent information use {@link #reload}. + * + * @see Use + * persistent disk snapshots + */ +public class Snapshot extends SnapshotInfo { + + private static final long serialVersionUID = -973924811396336695L; + + private final ComputeOptions options; + private transient Compute compute; + + /** + * A builder for {@code Snapshot} objects. + */ + public static class Builder extends SnapshotInfo.Builder { + + private final Compute compute; + private final SnapshotInfo.BuilderImpl infoBuilder; + + Builder(Compute compute, SnapshotId snapshotId, DiskId sourceDisk) { + this.compute = compute; + this.infoBuilder = new SnapshotInfo.BuilderImpl(); + this.infoBuilder.snapshotId(snapshotId); + this.infoBuilder.sourceDisk(sourceDisk); + } + + Builder(Snapshot snapshot) { + this.compute = snapshot.compute; + this.infoBuilder = new SnapshotInfo.BuilderImpl(snapshot); + } + + @Override + Builder id(String id) { + infoBuilder.id(id); + return this; + } + + @Override + Builder creationTimestamp(Long creationTimestamp) { + infoBuilder.creationTimestamp(creationTimestamp); + return this; + } + + @Override + public Builder snapshotId(SnapshotId snapshotId) { + infoBuilder.snapshotId(snapshotId); + return this; + } + + @Override + public Builder description(String description) { + infoBuilder.description(description); + return this; + } + + @Override + Builder status(Status status) { + infoBuilder.status(status); + return this; + } + + @Override + Builder diskSizeGb(Long diskSizeGb) { + infoBuilder.diskSizeGb(diskSizeGb); + return this; + } + + @Override + Builder licenses(List licenses) { + infoBuilder.licenses(licenses); + return this; + } + + @Override + public Builder sourceDisk(DiskId sourceDisk) { + infoBuilder.sourceDisk(sourceDisk); + return this; + } + + @Override + Builder sourceDiskId(String sourceDiskId) { + infoBuilder.sourceDiskId(sourceDiskId); + return this; + } + + @Override + Builder storageBytes(Long storageBytes) { + infoBuilder.storageBytes(storageBytes); + return this; + } + + @Override + Builder storageBytesStatus(StorageBytesStatus storageBytesStatus) { + infoBuilder.storageBytesStatus(storageBytesStatus); + return this; + } + + @Override + public Snapshot build() { + return new Snapshot(compute, infoBuilder); + } + } + + Snapshot(Compute compute, SnapshotInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.compute = checkNotNull(compute); + this.options = compute.options(); + } + + /** + * Checks if this snapshot exists. + * + * @return {@code true} if this snapshot exists, {@code false} otherwise + * @throws ComputeException upon failure + */ + public boolean exists() { + return reload(SnapshotOption.fields()) != null; + } + + /** + * Fetches current snapshot's latest information. Returns {@code null} if the snapshot does not + * exist. + * + * @param options snapshot options + * @return a {@code Snapshot} object with latest information or {@code null} if not found + * @throws ComputeException upon failure + */ + public Snapshot reload(SnapshotOption... options) { + return compute.getSnapshot(snapshotId().snapshot(), options); + } + + /** + * Deletes this snapshot. + * + * @return a global operation if delete request was successfully sent, {@code null} if the + * snapshot was not found + * @throws ComputeException upon failure + */ + public Operation delete(OperationOption... options) { + return compute.deleteSnapshot(snapshotId(), options); + } + + /** + * Returns the snapshot's {@code Compute} object used to issue requests. + */ + public Compute compute() { + return compute; + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public final boolean equals(Object obj) { + return obj instanceof Snapshot + && Objects.equals(toPb(), ((Snapshot) obj).toPb()) + && Objects.equals(options, ((Snapshot) obj).options); + } + + @Override + public final int hashCode() { + return Objects.hash(super.hashCode(), options); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + this.compute = options.service(); + } + + static Snapshot fromPb(Compute compute, + com.google.api.services.compute.model.Snapshot snapshotPb) { + return new Snapshot(compute, new SnapshotInfo.BuilderImpl(snapshotPb)); + } +} diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java index 66152f5464a4..ff0831ece6e9 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java @@ -22,6 +22,7 @@ import com.google.api.services.compute.model.MachineType; import com.google.api.services.compute.model.Operation; import com.google.api.services.compute.model.Region; +import com.google.api.services.compute.model.Snapshot; import com.google.api.services.compute.model.Zone; import com.google.gcloud.compute.ComputeException; @@ -255,8 +256,8 @@ public Y y() { /** * Deletes the requested global address. * - * @return a global operation if request was issued correctly, {@code null} if the address was not - * found + * @return a global operation if the request was issued correctly, {@code null} if the address was + * not found * @throws ComputeException upon failure */ Operation deleteGlobalAddress(String address, Map options); @@ -293,9 +294,44 @@ public Y y() { /** * Deletes the requested region address. * - * @return a region operation if request was issued correctly, {@code null} if the address was not - * found + * @return a region operation if the request was issued correctly, {@code null} if the address was + * not found * @throws ComputeException upon failure or if region is not found */ Operation deleteRegionAddress(String region, String address, Map options); + + /** + * Creates a snapshot for the specified disk. + * + * @return a zone operation if the create request was issued correctly, {@code null} if the disk + * was not found + * @throws ComputeException upon failure + */ + Operation createSnapshot(String zone, String disk, String snapshot, String description, + Map options); + + /** + * Returns the requested snapshot or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Snapshot getSnapshot(String snapshot, Map options); + + /** + * Lists all snapshots. + * + * @throws ComputeException upon failure + */ + Tuple> listSnapshots(Map options); + + /** + * Deletes the requested snapshot. Keep in mind that deleting a single snapshot might not + * necessarily delete all the data for that snapshot. If any data for the snapshot that is marked + * for deletion is needed for subsequent snapshots, the data will be moved to the next snapshot. + * + * @return a global operation if the request was issued correctly, {@code null} if the snapshot + * was not found + * @throws ComputeException upon failure + */ + Operation deleteSnapshot(String snapshot, Map options); } diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java index 78100f1c3c1f..d41a879f5d91 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java @@ -43,6 +43,8 @@ import com.google.api.services.compute.model.OperationList; import com.google.api.services.compute.model.Region; import com.google.api.services.compute.model.RegionList; +import com.google.api.services.compute.model.Snapshot; +import com.google.api.services.compute.model.SnapshotList; import com.google.api.services.compute.model.Zone; import com.google.api.services.compute.model.ZoneList; import com.google.common.collect.ImmutableList; @@ -507,6 +509,61 @@ public Operation deleteRegionAddress(String region, String address, Map options) { + Snapshot snapshotObject = new Snapshot().setName(snapshot).setDescription(description); + try { + return compute.disks() + .createSnapshot(this.options.projectId(), zone, disk, snapshotObject) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Snapshot getSnapshot(String snapshot, Map options) { + try { + return compute.snapshots() + .get(this.options.projectId(), snapshot) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Tuple> listSnapshots(Map options) { + try { + SnapshotList snapshotList = compute.snapshots() + .list(this.options.projectId()) + .setFilter(FILTER.getString(options)) + .setMaxResults(MAX_RESULTS.getLong(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setFields(FIELDS.getString(options)) + .execute(); + Iterable snapshots = snapshotList.getItems(); + return Tuple.of(snapshotList.getNextPageToken(), snapshots); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Operation deleteSnapshot(String snapshot, Map options) { + try { + return compute.snapshots() + .delete(this.options.projectId(), snapshot) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + /** * This method returns {@code null} if the error code of {@code exception} was 404, re-throws the * exception otherwise. diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/AddressTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/AddressTest.java index 230f603271f9..a9404a90f755 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/AddressTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/AddressTest.java @@ -57,8 +57,8 @@ public class AddressTest { private static final AddressInfo.RegionForwardingUsage REGION_FORWARDING_USAGE = new AddressInfo.RegionForwardingUsage(REGION_FORWARDING_RULES); - private Compute serviceMockReturnsOptions = createStrictMock(Compute.class); - private ComputeOptions mockOptions = createMock(ComputeOptions.class); + private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); + private final ComputeOptions mockOptions = createMock(ComputeOptions.class); private Compute compute; private Address globalForwardingAddress; private Address instanceAddress; diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java index 808d4ce7d255..9cff65dc7dad 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java @@ -34,6 +34,10 @@ import com.google.common.collect.Iterables; import com.google.gcloud.Page; import com.google.gcloud.RetryParams; +import com.google.gcloud.compute.Compute.AddressAggregatedListOption; +import com.google.gcloud.compute.Compute.AddressFilter; +import com.google.gcloud.compute.Compute.AddressListOption; +import com.google.gcloud.compute.Compute.AddressOption; import com.google.gcloud.compute.Compute.DiskTypeAggregatedListOption; import com.google.gcloud.compute.Compute.DiskTypeFilter; import com.google.gcloud.compute.Compute.DiskTypeListOption; @@ -49,6 +53,9 @@ import com.google.gcloud.compute.Compute.RegionFilter; import com.google.gcloud.compute.Compute.RegionListOption; import com.google.gcloud.compute.Compute.RegionOption; +import com.google.gcloud.compute.Compute.SnapshotFilter; +import com.google.gcloud.compute.Compute.SnapshotListOption; +import com.google.gcloud.compute.Compute.SnapshotOption; import com.google.gcloud.compute.Compute.ZoneFilter; import com.google.gcloud.compute.Compute.ZoneListOption; import com.google.gcloud.compute.Compute.ZoneOption; @@ -180,6 +187,9 @@ public class ComputeImplTest { GlobalAddressId.of("project", "address"); private static final AddressInfo REGION_ADDRESS = AddressInfo.builder(REGION_ADDRESS_ID).build(); private static final AddressInfo GLOBAL_ADDRESS = AddressInfo.builder(GLOBAL_ADDRESS_ID).build(); + private static final DiskId DISK_ID = DiskId.of("project", "zone", "disk"); + private static final SnapshotId SNAPSHOT_ID = SnapshotId.of("project", "snapshot"); + private static final SnapshotInfo SNAPSHOT = SnapshotInfo.of(SNAPSHOT_ID, DISK_ID); // Empty ComputeRpc options private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); @@ -294,30 +304,47 @@ public class ComputeImplTest { FILTER, "progress ne 0"); // Address options - private static final Compute.AddressOption ADDRESS_OPTION_FIELDS = - Compute.AddressOption.fields(Compute.AddressField.ID, Compute.AddressField.DESCRIPTION); + private static final AddressOption ADDRESS_OPTION_FIELDS = + AddressOption.fields(Compute.AddressField.ID, Compute.AddressField.DESCRIPTION); // Address list options - private static final Compute.AddressFilter ADDRESS_FILTER = - Compute.AddressFilter.notEquals(Compute.AddressField.REGION, "someRegion"); - private static final Compute.AddressListOption ADDRESS_LIST_PAGE_TOKEN = - Compute.AddressListOption.pageToken("cursor"); - private static final Compute.AddressListOption ADDRESS_LIST_PAGE_SIZE = - Compute.AddressListOption.pageSize(42L); - private static final Compute.AddressListOption ADDRESS_LIST_FILTER = - Compute.AddressListOption.filter(ADDRESS_FILTER); + private static final AddressFilter ADDRESS_FILTER = + AddressFilter.notEquals(Compute.AddressField.REGION, "someRegion"); + private static final AddressListOption ADDRESS_LIST_PAGE_TOKEN = + AddressListOption.pageToken("cursor"); + private static final AddressListOption ADDRESS_LIST_PAGE_SIZE = AddressListOption.pageSize(42L); + private static final AddressListOption ADDRESS_LIST_FILTER = + AddressListOption.filter(ADDRESS_FILTER); private static final Map ADDRESS_LIST_OPTIONS = ImmutableMap.of( PAGE_TOKEN, "cursor", MAX_RESULTS, 42L, FILTER, "region ne someRegion"); // Address aggregated list options - private static final Compute.AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_PAGE_TOKEN = - Compute.AddressAggregatedListOption.pageToken("cursor"); - private static final Compute.AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_PAGE_SIZE = - Compute.AddressAggregatedListOption.pageSize(42L); - private static final Compute.AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_FILTER = - Compute.AddressAggregatedListOption.filter(ADDRESS_FILTER); + private static final AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_PAGE_TOKEN = + AddressAggregatedListOption.pageToken("cursor"); + private static final AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_PAGE_SIZE = + AddressAggregatedListOption.pageSize(42L); + private static final AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_FILTER = + AddressAggregatedListOption.filter(ADDRESS_FILTER); + + // Snapshot options + private static final SnapshotOption SNAPSHOT_OPTION_FIELDS = + SnapshotOption.fields(Compute.SnapshotField.ID, Compute.SnapshotField.DESCRIPTION); + + // Snapshot list options + private static final SnapshotFilter SNAPSHOT_FILTER = + SnapshotFilter.equals(Compute.SnapshotField.DISK_SIZE_GB, 500L); + private static final SnapshotListOption SNAPSHOT_LIST_PAGE_TOKEN = + SnapshotListOption.pageToken("cursor"); + private static final SnapshotListOption SNAPSHOT_LIST_MAX_RESULTS = + SnapshotListOption.pageSize(42L); + private static final SnapshotListOption SNAPSHOT_LIST_FILTER = + SnapshotListOption.filter(SNAPSHOT_FILTER); + private static final Map SNAPSHOT_LIST_OPTIONS = ImmutableMap.of( + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "diskSizeGb eq 500"); private static final Function OPERATION_TO_PB_FUNCTION = new Function> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.createSnapshot(eq(DISK_ID.zone()), eq(DISK_ID.disk()), + eq(SNAPSHOT_ID.snapshot()), EasyMock.isNull(), capture(capturedOptions))) + .andReturn(zoneOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.create(SNAPSHOT, OPERATION_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(zoneOperation, operation); + } + + @Test + public void testGetSnapshot() { + EasyMock.expect(computeRpcMock.getSnapshot(SNAPSHOT_ID.snapshot(), EMPTY_RPC_OPTIONS)) + .andReturn(SNAPSHOT.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Snapshot snapshot = compute.getSnapshot(SNAPSHOT_ID.snapshot()); + assertEquals(new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT)), snapshot); + } + + @Test + public void testGetSnapshot_Null() { + EasyMock.expect(computeRpcMock.getSnapshot(SNAPSHOT_ID.snapshot(), EMPTY_RPC_OPTIONS)) + .andReturn(null); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertNull(compute.getSnapshot(SNAPSHOT_ID.snapshot())); + } + + @Test + public void testGetSnapshotWithSelectedFields() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.getSnapshot(eq(SNAPSHOT_ID.snapshot()), + capture(capturedOptions))).andReturn(SNAPSHOT.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Snapshot snapshot = compute.getSnapshot(SNAPSHOT_ID.snapshot(), SNAPSHOT_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(SNAPSHOT_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT)), snapshot); + } + + @Test + public void testDeleteSnapshot_Operation() { + EasyMock.expect(computeRpcMock.deleteSnapshot(SNAPSHOT_ID.snapshot(), EMPTY_RPC_OPTIONS)) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertEquals(globalOperation, compute.deleteSnapshot(SNAPSHOT_ID.snapshot())); + } + + @Test + public void testDeleteSnapshotWithSelectedFields_Operation() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.deleteSnapshot(eq(SNAPSHOT_ID.snapshot()), + capture(capturedOptions))).andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.deleteSnapshot(SNAPSHOT_ID, OPERATION_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(globalOperation, operation); + } + + @Test + public void testDeleteSnapshot_Null() { + EasyMock.expect(computeRpcMock.deleteSnapshot(SNAPSHOT_ID.snapshot(), EMPTY_RPC_OPTIONS)) + .andReturn(null); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertNull(compute.deleteSnapshot(SNAPSHOT_ID)); + } + + @Test + public void testListSnapshots() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList snapshotList = ImmutableList.of( + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT)), + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(snapshotList, SnapshotInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listSnapshots(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listSnapshots(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(snapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); + } + + @Test + public void testListEmptySnapshots() { + compute = options.service(); + ImmutableList snapshots = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, snapshots); + EasyMock.expect(computeRpcMock.listSnapshots(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listSnapshots(); + assertNull(page.nextPageCursor()); + assertArrayEquals(snapshots.toArray(), Iterables.toArray(page.values(), Snapshot.class)); + } + + @Test + public void testListSnapshotsWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList snapshotList = ImmutableList.of( + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT)), + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(snapshotList, SnapshotInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listSnapshots(SNAPSHOT_LIST_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listSnapshots(SNAPSHOT_LIST_MAX_RESULTS, SNAPSHOT_LIST_PAGE_TOKEN, + SNAPSHOT_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(snapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); + } + @Test public void testRetryableException() { EasyMock.expect( diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java index 9f08491a1632..62899071ce32 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java @@ -76,8 +76,8 @@ public class OperationTest { private static final RegionOperationId REGION_OPERATION_ID = RegionOperationId.of("project", "region", "op"); - private Compute serviceMockReturnsOptions = createStrictMock(Compute.class); - private ComputeOptions mockOptions = createMock(ComputeOptions.class); + private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); + private final ComputeOptions mockOptions = createMock(ComputeOptions.class); private Compute compute; private Operation globalOperation; private Operation regionOperation; diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java index 8b5956cef100..ab15ca5dc6a9 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java @@ -150,6 +150,8 @@ public class SerializationTest { private static final DiskId DISK_ID = DiskId.of("project", "zone", "disk"); private static final SnapshotId SNAPSHOT_ID = SnapshotId.of("project", "snapshot"); private static final SnapshotInfo SNAPSHOT_INFO = SnapshotInfo.of(SNAPSHOT_ID, DISK_ID); + private static final Snapshot SNAPSHOT = + new Snapshot.Builder(COMPUTE, SNAPSHOT_ID, DISK_ID).build(); private static final Compute.DiskTypeOption DISK_TYPE_OPTION = Compute.DiskTypeOption.fields(); private static final Compute.DiskTypeFilter DISK_TYPE_FILTER = @@ -189,6 +191,11 @@ public class SerializationTest { Compute.AddressListOption.filter(ADDRESS_FILTER); private static final Compute.AddressAggregatedListOption ADDRESS_AGGREGATED_LIST_OPTION = Compute.AddressAggregatedListOption.filter(ADDRESS_FILTER); + private static final Compute.SnapshotOption SNAPSHOT_OPTION = Compute.SnapshotOption.fields(); + private static final Compute.SnapshotFilter SNAPSHOT_FILTER = + Compute.SnapshotFilter.equals(Compute.SnapshotField.SELF_LINK, "selfLink"); + private static final Compute.SnapshotListOption SNAPSHOT_LIST_OPTION = + Compute.SnapshotListOption.filter(SNAPSHOT_FILTER); @Test public void testServiceOptions() throws Exception { @@ -215,13 +222,14 @@ public void testModelAndRequests() throws Exception { REGION_OPERATION_ID, ZONE_OPERATION_ID, GLOBAL_OPERATION, REGION_OPERATION, ZONE_OPERATION, INSTANCE_ID, REGION_FORWARDING_RULE_ID, GLOBAL_FORWARDING_RULE_ID, GLOBAL_ADDRESS_ID, REGION_ADDRESS_ID, INSTANCE_USAGE, GLOBAL_FORWARDING_USAGE, REGION_FORWARDING_USAGE, - ADDRESS_INFO, ADDRESS, DISK_ID, SNAPSHOT_ID, SNAPSHOT_INFO, DISK_TYPE_OPTION, + ADDRESS_INFO, ADDRESS, DISK_ID, SNAPSHOT_ID, SNAPSHOT_INFO, SNAPSHOT, DISK_TYPE_OPTION, DISK_TYPE_FILTER, DISK_TYPE_LIST_OPTION, DISK_TYPE_AGGREGATED_LIST_OPTION, MACHINE_TYPE_OPTION, MACHINE_TYPE_FILTER, MACHINE_TYPE_LIST_OPTION, MACHINE_TYPE_AGGREGATED_LIST_OPTION, REGION_OPTION, REGION_FILTER, REGION_LIST_OPTION, ZONE_OPTION, ZONE_FILTER, ZONE_LIST_OPTION, LICENSE_OPTION, OPERATION_OPTION, OPERATION_FILTER, OPERATION_LIST_OPTION, ADDRESS_OPTION, ADDRESS_FILTER, - ADDRESS_LIST_OPTION, ADDRESS_AGGREGATED_LIST_OPTION}; + ADDRESS_LIST_OPTION, ADDRESS_AGGREGATED_LIST_OPTION, SNAPSHOT_OPTION, SNAPSHOT_FILTER, + SNAPSHOT_LIST_OPTION}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SnapshotTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SnapshotTest.java new file mode 100644 index 000000000000..ac02669d7d5c --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SnapshotTest.java @@ -0,0 +1,253 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.compute; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; + +import org.junit.Test; + +import java.util.List; + +public class SnapshotTest { + + private static final String ID = "42"; + private static final DiskId SOURCE_DISK = DiskId.of("project", "zone", "disk"); + private static final Long CREATION_TIMESTAMP = 1453293540000L; + private static final String DESCRIPTION = "description"; + private static final List LICENSES = ImmutableList.of( + LicenseId.of("project", "license1"), LicenseId.of("project", "license2")); + private static final SnapshotId SNAPSHOT_ID = SnapshotId.of("project", "snapshot"); + private static final SnapshotInfo.Status STATUS = SnapshotInfo.Status.CREATING; + private static final Long DISK_SIZE_GB = 42L; + private static final String SOURCE_DISK_ID = "diskId"; + private static final Long STORAGE_BYTES = 24L; + private static final SnapshotInfo.StorageBytesStatus STORAGE_BYTES_STATUS = + SnapshotInfo.StorageBytesStatus.UP_TO_DATE; + + private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); + private final ComputeOptions mockOptions = createMock(ComputeOptions.class); + private Compute compute; + private Snapshot snapshot; + private Snapshot expectedSnapshot; + + private void initializeExpectedSnapshot(int optionsCalls) { + expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + expectedSnapshot = new Snapshot.Builder(serviceMockReturnsOptions, SNAPSHOT_ID, SOURCE_DISK) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .description(DESCRIPTION) + .status(STATUS) + .diskSizeGb(DISK_SIZE_GB) + .licenses(LICENSES) + .sourceDiskId(SOURCE_DISK_ID) + .storageBytes(STORAGE_BYTES) + .storageBytesStatus(STORAGE_BYTES_STATUS) + .build(); + compute = createStrictMock(Compute.class); + } + + private void initializeSnapshot() { + snapshot = new Snapshot.Builder(compute, SNAPSHOT_ID, SOURCE_DISK) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .description(DESCRIPTION) + .status(STATUS) + .diskSizeGb(DISK_SIZE_GB) + .licenses(LICENSES) + .sourceDiskId(SOURCE_DISK_ID) + .storageBytes(STORAGE_BYTES) + .storageBytesStatus(STORAGE_BYTES_STATUS) + .build(); + } + + @Test + public void testToBuilder() { + initializeExpectedSnapshot(8); + compareSnapshot(expectedSnapshot, expectedSnapshot.toBuilder().build()); + Snapshot newSnapshot = expectedSnapshot.toBuilder().description("newDescription").build(); + assertEquals("newDescription", newSnapshot.description()); + newSnapshot = newSnapshot.toBuilder().description("description").build(); + compareSnapshot(expectedSnapshot, newSnapshot); + } + + @Test + public void testToBuilderIncomplete() { + initializeExpectedSnapshot(5); + SnapshotInfo snapshotInfo = SnapshotInfo.of(SNAPSHOT_ID, SOURCE_DISK); + Snapshot snapshot = + new Snapshot(serviceMockReturnsOptions, new SnapshotInfo.BuilderImpl(snapshotInfo)); + compareSnapshot(snapshot, snapshot.toBuilder().build()); + } + + @Test + public void testBuilder() { + initializeExpectedSnapshot(2); + assertEquals(ID, expectedSnapshot.id()); + assertEquals(SNAPSHOT_ID, expectedSnapshot.snapshotId()); + assertEquals(CREATION_TIMESTAMP, expectedSnapshot.creationTimestamp()); + assertEquals(DESCRIPTION, expectedSnapshot.description()); + assertEquals(STATUS, expectedSnapshot.status()); + assertEquals(DISK_SIZE_GB, expectedSnapshot.diskSizeGb()); + assertEquals(LICENSES, expectedSnapshot.licenses()); + assertEquals(SOURCE_DISK, expectedSnapshot.sourceDisk()); + assertEquals(SOURCE_DISK_ID, expectedSnapshot.sourceDiskId()); + assertEquals(STORAGE_BYTES, expectedSnapshot.storageBytes()); + assertEquals(STORAGE_BYTES_STATUS, expectedSnapshot.storageBytesStatus()); + assertSame(serviceMockReturnsOptions, expectedSnapshot.compute()); + SnapshotId otherSnapshotId = SnapshotId.of("otherSnapshot"); + DiskId otherSourceDisk = DiskId.of("zone", "otherDisk"); + Snapshot snapshot = new Snapshot.Builder(serviceMockReturnsOptions, SNAPSHOT_ID, SOURCE_DISK) + .snapshotId(otherSnapshotId) + .sourceDisk(otherSourceDisk) + .build(); + assertNull(snapshot.id()); + assertEquals(otherSnapshotId, snapshot.snapshotId()); + assertNull(snapshot.creationTimestamp()); + assertNull(snapshot.description()); + assertNull(snapshot.status()); + assertNull(snapshot.diskSizeGb()); + assertNull(snapshot.licenses()); + assertEquals(otherSourceDisk, snapshot.sourceDisk()); + assertNull(snapshot.sourceDiskId()); + assertNull(snapshot.storageBytes()); + assertNull(snapshot.storageBytesStatus()); + assertSame(serviceMockReturnsOptions, snapshot.compute()); + } + + @Test + public void testToAndFromPb() { + initializeExpectedSnapshot(8); + compareSnapshot(expectedSnapshot, + Snapshot.fromPb(serviceMockReturnsOptions, expectedSnapshot.toPb())); + Snapshot snapshot = + new Snapshot.Builder(serviceMockReturnsOptions, SNAPSHOT_ID, SOURCE_DISK).build(); + compareSnapshot(snapshot, Snapshot.fromPb(serviceMockReturnsOptions, snapshot.toPb())); + } + + @Test + public void testDeleteOperation() { + initializeExpectedSnapshot(2); + expect(compute.options()).andReturn(mockOptions); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GlobalOperationId.of("project", "op")) + .build(); + expect(compute.deleteSnapshot(SNAPSHOT_ID)).andReturn(operation); + replay(compute); + initializeSnapshot(); + assertSame(operation, snapshot.delete()); + } + + @Test + public void testDeleteNull() { + initializeExpectedSnapshot(1); + expect(compute.options()).andReturn(mockOptions); + expect(compute.deleteSnapshot(SNAPSHOT_ID)).andReturn(null); + replay(compute); + initializeSnapshot(); + assertNull(snapshot.delete()); + } + + @Test + public void testExists_True() throws Exception { + initializeExpectedSnapshot(1); + Compute.SnapshotOption[] expectedOptions = {Compute.SnapshotOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.getSnapshot(SNAPSHOT_ID.snapshot(), expectedOptions)) + .andReturn(expectedSnapshot); + replay(compute); + initializeSnapshot(); + assertTrue(snapshot.exists()); + verify(compute); + } + + @Test + public void testExists_False() throws Exception { + initializeExpectedSnapshot(1); + Compute.SnapshotOption[] expectedOptions = {Compute.SnapshotOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.getSnapshot(SNAPSHOT_ID.snapshot(), expectedOptions)).andReturn(null); + replay(compute); + initializeSnapshot(); + assertFalse(snapshot.exists()); + verify(compute); + } + + @Test + public void testReload() throws Exception { + initializeExpectedSnapshot(3); + expect(compute.options()).andReturn(mockOptions); + expect(compute.getSnapshot(SNAPSHOT_ID.snapshot())).andReturn(expectedSnapshot); + replay(compute); + initializeSnapshot(); + Snapshot updatedSnapshot = snapshot.reload(); + compareSnapshot(expectedSnapshot, updatedSnapshot); + verify(compute); + } + + @Test + public void testReloadNull() throws Exception { + initializeExpectedSnapshot(1); + expect(compute.options()).andReturn(mockOptions); + expect(compute.getSnapshot(SNAPSHOT_ID.snapshot())).andReturn(null); + replay(compute); + initializeSnapshot(); + assertNull(snapshot.reload()); + verify(compute); + } + + @Test + public void testReloadWithOptions() throws Exception { + initializeExpectedSnapshot(3); + expect(compute.options()).andReturn(mockOptions); + expect(compute.getSnapshot(SNAPSHOT_ID.snapshot(), Compute.SnapshotOption.fields())) + .andReturn(expectedSnapshot); + replay(compute); + initializeSnapshot(); + Snapshot updatedSnapshot = snapshot.reload(Compute.SnapshotOption.fields()); + compareSnapshot(expectedSnapshot, updatedSnapshot); + verify(compute); + } + + public void compareSnapshot(Snapshot expected, Snapshot value) { + assertEquals(expected, value); + assertEquals(expected.compute().options(), value.compute().options()); + assertEquals(expected.id(), value.id()); + assertEquals(expected.snapshotId(), value.snapshotId()); + assertEquals(expected.creationTimestamp(), value.creationTimestamp()); + assertEquals(expected.description(), value.description()); + assertEquals(expected.status(), value.status()); + assertEquals(expected.diskSizeGb(), value.diskSizeGb()); + assertEquals(expected.licenses(), value.licenses()); + assertEquals(expected.sourceDisk(), value.sourceDisk()); + assertEquals(expected.sourceDiskId(), value.sourceDiskId()); + assertEquals(expected.storageBytes(), value.storageBytes()); + assertEquals(expected.storageBytesStatus(), value.storageBytesStatus()); + assertEquals(expected.hashCode(), value.hashCode()); + } +}