From 2812b947035a1aaf1e4f2b53ed1c22f03b3588d9 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 31 Mar 2016 10:02:37 +0200 Subject: [PATCH 1/2] Add functional methods for images and Image class --- .../com/google/gcloud/compute/Compute.java | 164 ++++++++++ .../google/gcloud/compute/ComputeImpl.java | 132 ++++++++ .../java/com/google/gcloud/compute/Image.java | 209 ++++++++++++ .../com/google/gcloud/spi/ComputeRpc.java | 49 ++- .../google/gcloud/spi/DefaultComputeRpc.java | 69 ++++ .../gcloud/compute/ComputeImplTest.java | 304 +++++++++++++++++- .../com/google/gcloud/compute/ImageTest.java | 280 ++++++++++++++++ .../gcloud/compute/SerializationTest.java | 13 +- 8 files changed, 1212 insertions(+), 8 deletions(-) create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java create mode 100644 gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java 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 6d033688d8c2..b433294ecad8 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 @@ -869,6 +869,54 @@ public static SnapshotFilter notEquals(SnapshotField field, long value) { } } + /** + * Class for filtering image lists. + */ + class ImageFilter extends ListFilter { + + private static final long serialVersionUID = -3601427417234098397L; + + private ImageFilter(ImageField 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 ImageFilter equals(ImageField field, String value) { + return new ImageFilter(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 ImageFilter notEquals(ImageField field, String value) { + return new ImageFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value)); + } + + /** + * Returns an equals filter for the given field and long value. + */ + public static ImageFilter equals(ImageField field, long value) { + return new ImageFilter(checkNotNull(field), ComparisonOperator.EQ, value); + } + + /** + * Returns a not-equals filter for the given field and long value. + */ + public static ImageFilter notEquals(ImageField field, long value) { + return new ImageFilter(checkNotNull(field), ComparisonOperator.NE, value); + } + } + /** * Class for specifying disk type get options. */ @@ -1469,6 +1517,74 @@ public static SnapshotListOption fields(SnapshotField... fields) { } } + /** + * Class for specifying image get options. + */ + class ImageOption extends Option { + + private static final long serialVersionUID = -7622190783089299272L; + + private ImageOption(ComputeRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the image's fields to be returned by the RPC call. If this + * option is not provided, all image's fields are returned. {@code ImageOption.fields} can be + * used to specify only the fields of interest. {@link Image#imageId()} and + * {@link Image#configuration()} are always returned, even if not specified. + */ + public static ImageOption fields(ImageField... fields) { + return new ImageOption(ComputeRpc.Option.FIELDS, ImageField.selector(fields)); + } + } + + /** + * Class for specifying image list options. + */ + class ImageListOption extends Option { + + private static final long serialVersionUID = -4927977224287915654L; + + private ImageListOption(ComputeRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify a filter on the images being listed. + */ + public static ImageListOption filter(ImageFilter filter) { + return new ImageListOption(ComputeRpc.Option.FILTER, filter.toPb()); + } + + /** + * Returns an option to specify the maximum number of images returned per page. {@code pageSize} + * must be between 0 and 500 (inclusive). If not specified 500 is used. + */ + public static ImageListOption pageSize(long pageSize) { + return new ImageListOption(ComputeRpc.Option.MAX_RESULTS, pageSize); + } + + /** + * Returns an option to specify the page token from which to start listing images. + */ + public static ImageListOption pageToken(String pageToken) { + return new ImageListOption(ComputeRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * Returns an option to specify the image's fields to be returned by the RPC call. If this + * option is not provided, all image's fields are returned. {@code ImageListOption.fields} can + * be used to specify only the fields of interest. {@link Image#imageId()} and + * {@link Image#configuration()} are always returned, even if not specified. + */ + public static ImageListOption fields(ImageField... fields) { + StringBuilder builder = new StringBuilder(); + builder.append("items(").append(ImageField.selector(fields)).append("),nextPageToken"); + return new ImageListOption(ComputeRpc.Option.FIELDS, builder.toString()); + } + } + /** * Returns the requested disk type or {@code null} if not found. * @@ -1699,4 +1815,52 @@ public static SnapshotListOption fields(SnapshotField... fields) { * Deleting a snapshot */ Operation deleteSnapshot(String snapshot, OperationOption... options); + + /** + * Creates a new image. + * + * @return a global operation for image's creation + * @throws ComputeException upon failure + */ + Operation create(ImageInfo image, OperationOption... options); + + /** + * Returns the requested image or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Image get(ImageId imageId, ImageOption... options); + + /** + * Lists images in the provided project that are available to the current user. + * + * @throws ComputeException upon failure + */ + Page listImages(String project, ImageListOption... options); + + /** + * Lists images in the current project. + * + * @throws ComputeException upon failure + */ + Page listImages(ImageListOption... options); + + /** + * Deletes the requested image. + * + * @return a global operation if the delete request was issued correctly, {@code null} if the + * image was not found + * @throws ComputeException upon failure + */ + Operation delete(ImageId image, OperationOption... options); + + /** + * Deprecates the requested image. + * + * @return a global operation if the deprecation request was issued correctly, {@code null} if the + * image was not found + * @throws ComputeException upon failure + */ + Operation deprecate(ImageId image, DeprecationStatus deprecationStatus, + 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 a348e1e0e253..dda8dbe69bc4 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 @@ -292,6 +292,27 @@ public Page nextPage() { } } + private static class ImagePageFetcher implements NextPageFetcher { + + private static final long serialVersionUID = 6403679803137922023L; + private final Map requestOptions; + private final ComputeOptions serviceOptions; + private final String project; + + ImagePageFetcher(String project, ComputeOptions serviceOptions, String cursor, + Map optionMap) { + this.requestOptions = + PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap); + this.serviceOptions = serviceOptions; + this.project = project; + } + + @Override + public Page nextPage() { + return listImages(project, serviceOptions, requestOptions); + } + } + private final ComputeRpc computeRpc; ComputeImpl(ComputeOptions options) { @@ -1026,6 +1047,117 @@ public com.google.api.services.compute.model.Operation call() { } } + @Override + public Operation create(ImageInfo image, OperationOption... options) { + final ImageInfo completeImage = image.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.createImage(completeImage.toPb(), optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Operation.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Image get(ImageId imageId, ImageOption... options) { + final ImageId completeImageId = imageId.setProjectId(options().projectId()); + final Map optionsMap = optionMap(options); + try { + com.google.api.services.compute.model.Image answer = + runWithRetries(new Callable() { + @Override + public com.google.api.services.compute.model.Image call() { + return computeRpc.getImage(completeImageId.project(), completeImageId.image(), + optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Image.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Page listImages(String project, ImageListOption... options) { + return listImages(project, options(), optionMap(options)); + } + + @Override + public Page listImages(ImageListOption... options) { + return listImages(options().projectId(), options(), optionMap(options)); + } + + private static Page listImages(final String project, final ComputeOptions serviceOptions, + final Map optionsMap) { + try { + ComputeRpc.Tuple> result = + runWithRetries(new Callable>>() { + @Override + public ComputeRpc.Tuple> call() { + return serviceOptions.rpc().listImages(project, optionsMap); + } + }, serviceOptions.retryParams(), EXCEPTION_HANDLER); + String cursor = result.x(); + Iterable images = Iterables.transform( + result.y() == null ? ImmutableList.of() + : result.y(), + new Function() { + @Override + public Image apply(com.google.api.services.compute.model.Image image) { + return Image.fromPb(serviceOptions.service(), image); + } + }); + return new PageImpl<>(new ImagePageFetcher(project, serviceOptions, cursor, optionsMap), + cursor, images); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Operation delete(final ImageId image, 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.deleteImage(image.image(), optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : Operation.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw ComputeException.translateAndThrow(e); + } + } + + @Override + public Operation deprecate(final ImageId image, + final DeprecationStatus deprecationStatus, 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.deprecateImage(image.image(), deprecationStatus.toPb(), 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/Image.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java new file mode 100644 index 000000000000..e42a292f72ab --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java @@ -0,0 +1,209 @@ +/* + * 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.ImageOption; +import com.google.gcloud.compute.Compute.OperationOption; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.List; +import java.util.Objects; + +/** + * A Google Compute Engine Image. An image contains a boot loader, an operating system and a root + * file system that is necessary for starting an instance. Compute Engine offers publicly-available + * images of certain operating systems that you can use, or you can create a custom image. A custom + * image is an image created from one of your virtual machine instances that contains your specific + * instance configurations. To get an {@code Image} object with the most recent information use + * {@link #reload}. {@code Image} adds a layer of service-related functionality + * over {@link ImageInfo}. + * + * @see Images + */ +public class Image extends ImageInfo { + + private static final long serialVersionUID = 4623766590317494020L; + + private final ComputeOptions options; + private transient Compute compute; + + /** + * A builder for {@code Image} objects. + */ + public static class Builder extends ImageInfo.Builder { + + private final Compute compute; + private final ImageInfo.BuilderImpl infoBuilder; + + Builder(Compute compute, ImageId imageId, ImageConfiguration configuration) { + this.compute = compute; + this.infoBuilder = new ImageInfo.BuilderImpl(); + this.infoBuilder.imageId(imageId); + this.infoBuilder.configuration(configuration); + } + + Builder(Image image) { + this.compute = image.compute; + this.infoBuilder = new ImageInfo.BuilderImpl(image); + } + + @Override + Builder id(String id) { + infoBuilder.id(id); + return this; + } + + @Override + Builder creationTimestamp(Long creationTimestamp) { + infoBuilder.creationTimestamp(creationTimestamp); + return this; + } + + @Override + public Builder imageId(ImageId imageId) { + infoBuilder.imageId(imageId); + return this; + } + + @Override + public Builder description(String description) { + infoBuilder.description(description); + return this; + } + + @Override + public Builder configuration(ImageConfiguration configuration) { + infoBuilder.configuration(configuration); + 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 + Builder deprecationStatus(DeprecationStatus deprecationStatus) { + infoBuilder.deprecationStatus(deprecationStatus); + return this; + } + + @Override + public Image build() { + return new Image(compute, infoBuilder); + } + } + + Image(Compute compute, ImageInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.compute = checkNotNull(compute); + this.options = compute.options(); + } + + /** + * Checks if this image exists. + * + * @return {@code true} if this image exists, {@code false} otherwise + * @throws ComputeException upon failure + */ + public boolean exists() { + return reload(ImageOption.fields()) != null; + } + + /** + * Fetches current image' latest information. Returns {@code null} if the image does not exist. + * + * @param options image options + * @return an {@code Image} object with latest information or {@code null} if not found + * @throws ComputeException upon failure + */ + public Image reload(ImageOption... options) { + return compute.get(imageId(), options); + } + + /** + * Deletes this image. + * + * @return a global operation if the delete request was successfully sent, {@code null} if the + * image was not found + * @throws ComputeException upon failure + */ + public Operation delete(OperationOption... options) { + return compute.delete(imageId(), options); + } + + /** + * Deprecates this image. + * + * @return a global operation if the deprecation request was successfully sent, {@code null} if + * the image was not found + * @throws ComputeException upon failure + */ + public Operation deprecate(DeprecationStatus deprecationStatus, + OperationOption... options) { + return compute.deprecate(imageId(), deprecationStatus, options); + } + + /** + * Returns the image'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 Image + && Objects.equals(toPb(), ((Image) obj).toPb()) + && Objects.equals(options, ((Image) 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 Image fromPb(Compute compute, com.google.api.services.compute.model.Image imagePb) { + return new Image(compute, new ImageInfo.BuilderImpl(imagePb)); + } +} 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 ff0831ece6e9..afdf38a27961 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 @@ -17,7 +17,9 @@ package com.google.gcloud.spi; import com.google.api.services.compute.model.Address; +import com.google.api.services.compute.model.DeprecationStatus; import com.google.api.services.compute.model.DiskType; +import com.google.api.services.compute.model.Image; import com.google.api.services.compute.model.License; import com.google.api.services.compute.model.MachineType; import com.google.api.services.compute.model.Operation; @@ -103,7 +105,7 @@ public Y y() { Tuple> listDiskTypes(String zone, Map options); /** - * Lists all disk types. + * Lists disk types. * * @throws ComputeException upon failure */ @@ -124,7 +126,7 @@ public Y y() { Tuple> listMachineTypes(String zone, Map options); /** - * Lists all machine types. + * Lists machine types. * * @throws ComputeException upon failure */ @@ -318,7 +320,7 @@ Operation createSnapshot(String zone, String disk, String snapshot, String descr Snapshot getSnapshot(String snapshot, Map options); /** - * Lists all snapshots. + * Lists snapshots. * * @throws ComputeException upon failure */ @@ -334,4 +336,45 @@ Operation createSnapshot(String zone, String disk, String snapshot, String descr * @throws ComputeException upon failure */ Operation deleteSnapshot(String snapshot, Map options); + + /** + * Creates a new image. + * + * @return a global operation for image's creation + * @throws ComputeException upon failure + */ + Operation createImage(Image image, Map options); + + /** + * Returns the requested image or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Image getImage(String project, String image, Map options); + + /** + * Lists images in the provided project that are available to the current user. + * + * @throws ComputeException upon failure + */ + Tuple> listImages(String project, Map options); + + /** + * Deletes the requested image. + * + * @return a global operation if the delete request was issued correctly, {@code null} if the + * image was not found + * @throws ComputeException upon failure + */ + Operation deleteImage(String image, Map options); + + /** + * Deprecates the requested image. + * + * @return a global operation if the deprecation request was issued correctly, {@code null} if the + * image was not found + * @throws ComputeException upon failure + */ + Operation deprecateImage(String image, DeprecationStatus deprecationStatus, + 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 d41a879f5d91..31b2de22f8af 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 @@ -30,10 +30,13 @@ import com.google.api.services.compute.model.AddressAggregatedList; import com.google.api.services.compute.model.AddressList; import com.google.api.services.compute.model.AddressesScopedList; +import com.google.api.services.compute.model.DeprecationStatus; import com.google.api.services.compute.model.DiskType; import com.google.api.services.compute.model.DiskTypeAggregatedList; import com.google.api.services.compute.model.DiskTypeList; import com.google.api.services.compute.model.DiskTypesScopedList; +import com.google.api.services.compute.model.Image; +import com.google.api.services.compute.model.ImageList; import com.google.api.services.compute.model.License; import com.google.api.services.compute.model.MachineType; import com.google.api.services.compute.model.MachineTypeAggregatedList; @@ -564,6 +567,72 @@ public Operation deleteSnapshot(String snapshot, Map options) { } } + @Override + public Operation createImage(Image image, Map options) { + try { + return compute.images() + .insert(this.options.projectId(), image) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Image getImage(String project, String image, Map options) { + try { + return compute.images() + .get(project, image) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Tuple> listImages(String project, Map options) { + try { + ImageList imageList = compute.images() + .list(project) + .setFilter(FILTER.getString(options)) + .setMaxResults(MAX_RESULTS.getLong(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setFields(FIELDS.getString(options)) + .execute(); + Iterable images = imageList.getItems(); + return Tuple.of(imageList.getNextPageToken(), images); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Operation deleteImage(String image, Map options) { + try { + return compute.images() + .delete(this.options.projectId(), image) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Operation deprecateImage(String image, DeprecationStatus deprecationStatus, + Map options) { + try { + return compute.images() + .deprecate(this.options.projectId(), image, deprecationStatus) + .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/ComputeImplTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java index 9cff65dc7dad..5d66d02853e9 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 @@ -42,6 +42,10 @@ import com.google.gcloud.compute.Compute.DiskTypeFilter; import com.google.gcloud.compute.Compute.DiskTypeListOption; import com.google.gcloud.compute.Compute.DiskTypeOption; +import com.google.gcloud.compute.Compute.ImageField; +import com.google.gcloud.compute.Compute.ImageFilter; +import com.google.gcloud.compute.Compute.ImageListOption; +import com.google.gcloud.compute.Compute.ImageOption; import com.google.gcloud.compute.Compute.LicenseOption; import com.google.gcloud.compute.Compute.MachineTypeAggregatedListOption; import com.google.gcloud.compute.Compute.MachineTypeFilter; @@ -190,6 +194,10 @@ public class ComputeImplTest { 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); + private static final ImageId IMAGE_ID = ImageId.of("project", "snapshot"); + private static final ImageInfo IMAGE = ImageInfo.of(IMAGE_ID, DiskImageConfiguration.of(DISK_ID)); + private static final DeprecationStatus DEPRECATION_STATUS = + DeprecationStatus.builder(DeprecationStatus.Status.DEPRECATED, IMAGE_ID).build(); // Empty ComputeRpc options private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); @@ -337,7 +345,7 @@ public class ComputeImplTest { 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 = + private static final SnapshotListOption SNAPSHOT_LIST_PAGE_SIZE = SnapshotListOption.pageSize(42L); private static final SnapshotListOption SNAPSHOT_LIST_FILTER = SnapshotListOption.filter(SNAPSHOT_FILTER); @@ -346,6 +354,21 @@ public class ComputeImplTest { MAX_RESULTS, 42L, FILTER, "diskSizeGb eq 500"); + // Image options + private static final ImageOption IMAGE_OPTION_FIELDS = + ImageOption.fields(ImageField.ID, ImageField.DESCRIPTION); + + // Image list options + private static final ImageFilter IMAGE_FILTER = + ImageFilter.notEquals(ImageField.DISK_SIZE_GB, 500L); + private static final ImageListOption IMAGE_LIST_PAGE_TOKEN = ImageListOption.pageToken("cursor"); + private static final ImageListOption IMAGE_LIST_PAGE_SIZE = ImageListOption.pageSize(42L); + private static final ImageListOption IMAGE_LIST_FILTER = ImageListOption.filter(IMAGE_FILTER); + private static final Map IMAGE_LIST_OPTIONS = ImmutableMap.of( + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "diskSizeGb ne 500"); + private static final Function OPERATION_TO_PB_FUNCTION = new Function() { @@ -2045,6 +2068,32 @@ public void testListSnapshots() { assertArrayEquals(snapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); } + @Test + public void testListSnapshotsNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList snapshotList = ImmutableList.of( + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT)), + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT))); + ImmutableList nextSnapshotList = ImmutableList.of( + new Snapshot(compute, new SnapshotInfo.BuilderImpl(SNAPSHOT))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(snapshotList, SnapshotInfo.TO_PB_FUNCTION)); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextSnapshotList, SnapshotInfo.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listSnapshots(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listSnapshots(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listSnapshots(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(snapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextSnapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); + } + @Test public void testListEmptySnapshots() { compute = options.service(); @@ -2069,12 +2118,263 @@ public void testListSnapshotsWithOptions() { 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, + Page page = compute.listSnapshots(SNAPSHOT_LIST_PAGE_SIZE, SNAPSHOT_LIST_PAGE_TOKEN, SNAPSHOT_LIST_FILTER); assertEquals(cursor, page.nextPageCursor()); assertArrayEquals(snapshotList.toArray(), Iterables.toArray(page.values(), Snapshot.class)); } + @Test + public void testCreateImage() { + EasyMock.expect(computeRpcMock.createImage(IMAGE.toPb(), EMPTY_RPC_OPTIONS)) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.create(IMAGE); + assertEquals(globalOperation, operation); + } + + @Test + public void testCreateImageWithOptions() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.createImage(eq(IMAGE.toPb()), capture(capturedOptions))) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.create(IMAGE, 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 testGetImage() { + EasyMock.expect( + computeRpcMock.getImage(IMAGE_ID.project(), IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) + .andReturn(IMAGE.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Image image = compute.get(IMAGE_ID); + assertEquals(new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), image); + } + + @Test + public void testGetImage_Null() { + EasyMock.expect( + computeRpcMock.getImage(IMAGE_ID.project(), IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) + .andReturn(null); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertNull(compute.get(IMAGE_ID)); + } + + @Test + public void testGetImageWithSelectedFields() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.getImage(eq(IMAGE_ID.project()), eq(IMAGE_ID.image()), + capture(capturedOptions))).andReturn(IMAGE.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Image image = compute.get(IMAGE_ID, IMAGE_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(IMAGE_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("sourceDisk")); + assertTrue(selector.contains("rawDisk")); + assertTrue(selector.contains("description")); + assertEquals(42, selector.length()); + assertEquals(new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), image); + } + + @Test + public void testDeleteImage_Operation() { + EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertEquals(globalOperation, compute.delete(IMAGE_ID)); + } + + @Test + public void testDeleteImageWithSelectedFields_Operation() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.deleteImage(eq(IMAGE_ID.image()), capture(capturedOptions))) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.delete(IMAGE_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 testDeleteImage_Null() { + EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) + .andReturn(null); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertNull(compute.delete(IMAGE_ID)); + } + + @Test + public void testDeprecateImage_Operation() { + EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.image(), + DEPRECATION_STATUS.toPb(), EMPTY_RPC_OPTIONS)).andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertEquals(globalOperation, compute.deprecate(IMAGE_ID, DEPRECATION_STATUS)); + } + + @Test + public void testDeprecateImageWithSelectedFields_Operation() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.deprecateImage(eq(IMAGE_ID.image()), + eq(DEPRECATION_STATUS.toPb()), capture(capturedOptions))).andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.deprecate(IMAGE_ID, DEPRECATION_STATUS, 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 testDeprecateImage_Null() { + EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.image(), DEPRECATION_STATUS.toPb(), + EMPTY_RPC_OPTIONS)).andReturn(null); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertNull(compute.deprecate(IMAGE_ID, DEPRECATION_STATUS)); + } + + @Test + public void testListImages() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList imageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(imageList, ImageInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listImages(PROJECT, EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(imageList.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListImagesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList imageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + ImmutableList nextImageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(imageList, ImageInfo.TO_PB_FUNCTION)); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextImageList, ImageInfo.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listImages(PROJECT, EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listImages(PROJECT, nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(imageList.toArray(), Iterables.toArray(page.values(), Image.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextImageList.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListImagesForProject() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList imageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(imageList, ImageInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listImages("otherProject", EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages("otherProject"); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(imageList.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListEmptyImages() { + compute = options.service(); + ImmutableList images = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, images); + EasyMock.expect(computeRpcMock.listImages(PROJECT, EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages(); + assertNull(page.nextPageCursor()); + assertArrayEquals(images.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListEmptyImagesForProject() { + compute = options.service(); + ImmutableList images = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, images); + EasyMock.expect(computeRpcMock.listImages("otherProject", EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages("otherProject"); + assertNull(page.nextPageCursor()); + assertArrayEquals(images.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListImagesWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList imageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(imageList, ImageInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listImages(PROJECT, IMAGE_LIST_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages(IMAGE_LIST_PAGE_SIZE, IMAGE_LIST_PAGE_TOKEN, + IMAGE_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(imageList.toArray(), Iterables.toArray(page.values(), Image.class)); + } + + @Test + public void testListImagesForProjectWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList imageList = ImmutableList.of( + new Image(compute, new ImageInfo.BuilderImpl(IMAGE)), + new Image(compute, new ImageInfo.BuilderImpl(IMAGE))); + Tuple> result = + Tuple.of(cursor, Iterables.transform(imageList, ImageInfo.TO_PB_FUNCTION)); + EasyMock.expect(computeRpcMock.listImages("other", IMAGE_LIST_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listImages("other", IMAGE_LIST_PAGE_SIZE, IMAGE_LIST_PAGE_TOKEN, + IMAGE_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(imageList.toArray(), Iterables.toArray(page.values(), Image.class)); + } + @Test public void testRetryableException() { EasyMock.expect( diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java new file mode 100644 index 000000000000..b46deb9bff85 --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java @@ -0,0 +1,280 @@ +/* + * 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 com.google.gcloud.compute.ImageConfiguration.SourceType; + +import org.junit.Test; + +import java.util.List; + +public class ImageTest { + + private static final ImageId IMAGE_ID = ImageId.of("project", "image"); + private static final String ID = "42"; + private static final Long CREATION_TIMESTAMP = 1453293540000L; + private static final String DESCRIPTION = "description"; + private static final ImageInfo.Status STATUS = ImageInfo.Status.READY; + private static final List LICENSES = ImmutableList.of( + LicenseId.of("project", "license1"), LicenseId.of("project", "license2")); + private static final Long DISK_SIZE_GB = 42L; + private static final String STORAGE_SOURCE = "source"; + private static final Long ARCHIVE_SIZE_BYTES = 24L; + private static final String SHA1_CHECKSUM = "checksum"; + private static final DiskId SOURCE_DISK = DiskId.of("project", "zone", "disk"); + private static final String SOURCE_DISK_ID = "diskId"; + private static final SourceType SOURCE_TYPE = SourceType.RAW; + private static final StorageImageConfiguration STORAGE_CONFIGURATION = + StorageImageConfiguration.builder(STORAGE_SOURCE) + .archiveSizeBytes(ARCHIVE_SIZE_BYTES) + .containerType(StorageImageConfiguration.ContainerType.TAR) + .sha1(SHA1_CHECKSUM) + .sourceType(SOURCE_TYPE) + .build(); + private static final DiskImageConfiguration DISK_CONFIGURATION = + DiskImageConfiguration.builder(SOURCE_DISK) + .archiveSizeBytes(ARCHIVE_SIZE_BYTES) + .sourceDiskId(SOURCE_DISK_ID) + .sourceType(SOURCE_TYPE) + .build(); + private static final DeprecationStatus DEPRECATION_STATUS = + DeprecationStatus.of(DeprecationStatus.Status.DELETED, IMAGE_ID); + + private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); + private final ComputeOptions mockOptions = createMock(ComputeOptions.class); + private Compute compute; + private Image image; + private Image diskImage; + private Image storageImage; + + private void initializeExpectedImage(int optionsCalls) { + expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + diskImage = new Image.Builder(serviceMockReturnsOptions, IMAGE_ID, DISK_CONFIGURATION) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .description(DESCRIPTION) + .status(STATUS) + .diskSizeGb(DISK_SIZE_GB) + .licenses(LICENSES) + .deprecationStatus(DEPRECATION_STATUS) + .build(); + storageImage = new Image.Builder(serviceMockReturnsOptions, IMAGE_ID, STORAGE_CONFIGURATION) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .description(DESCRIPTION) + .status(STATUS) + .diskSizeGb(DISK_SIZE_GB) + .licenses(LICENSES) + .deprecationStatus(DEPRECATION_STATUS) + .build(); + compute = createStrictMock(Compute.class); + } + + private void initializeImage() { + image = new Image.Builder(compute, IMAGE_ID, DISK_CONFIGURATION) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .description(DESCRIPTION) + .status(STATUS) + .diskSizeGb(DISK_SIZE_GB) + .licenses(LICENSES) + .deprecationStatus(DEPRECATION_STATUS) + .build(); + } + + @Test + public void testToBuilder() { + initializeExpectedImage(12); + compareImage(diskImage, diskImage.toBuilder().build()); + compareImage(storageImage, storageImage.toBuilder().build()); + Image newImage = diskImage.toBuilder().description("newDescription").build(); + assertEquals("newDescription", newImage.description()); + newImage = newImage.toBuilder().description("description").build(); + compareImage(diskImage, newImage); + } + + @Test + public void testToBuilderIncomplete() { + initializeExpectedImage(6); + ImageInfo imageInfo = ImageInfo.of(IMAGE_ID, DISK_CONFIGURATION); + Image image = + new Image(serviceMockReturnsOptions, new ImageInfo.BuilderImpl(imageInfo)); + compareImage(image, image.toBuilder().build()); + } + + @Test + public void testBuilder() { + initializeExpectedImage(3); + assertEquals(ID, diskImage.id()); + assertEquals(IMAGE_ID, diskImage.imageId()); + assertEquals(CREATION_TIMESTAMP, diskImage.creationTimestamp()); + assertEquals(DESCRIPTION, diskImage.description()); + assertEquals(DISK_CONFIGURATION, diskImage.configuration()); + assertEquals(STATUS, diskImage.status()); + assertEquals(DISK_SIZE_GB, diskImage.diskSizeGb()); + assertEquals(LICENSES, diskImage.licenses()); + assertEquals(DEPRECATION_STATUS, diskImage.deprecationStatus()); + assertSame(serviceMockReturnsOptions, diskImage.compute()); + assertEquals(ID, storageImage.id()); + assertEquals(IMAGE_ID, storageImage.imageId()); + assertEquals(CREATION_TIMESTAMP, storageImage.creationTimestamp()); + assertEquals(DESCRIPTION, storageImage.description()); + assertEquals(STORAGE_CONFIGURATION, storageImage.configuration()); + assertEquals(STATUS, storageImage.status()); + assertEquals(DISK_SIZE_GB, storageImage.diskSizeGb()); + assertEquals(LICENSES, storageImage.licenses()); + assertEquals(DEPRECATION_STATUS, storageImage.deprecationStatus()); + assertSame(serviceMockReturnsOptions, storageImage.compute()); + ImageId imageId = ImageId.of("otherImage"); + Image image = new Image.Builder(serviceMockReturnsOptions, IMAGE_ID, STORAGE_CONFIGURATION) + .imageId(imageId) + .configuration(DISK_CONFIGURATION) + .build(); + assertNull(image.id()); + assertEquals(imageId, image.imageId()); + assertNull(image.creationTimestamp()); + assertNull(image.description()); + assertEquals(DISK_CONFIGURATION, image.configuration()); + assertNull(image.status()); + assertNull(image.diskSizeGb()); + assertNull(image.licenses()); + assertNull(image.deprecationStatus()); + assertSame(serviceMockReturnsOptions, image.compute()); + } + + @Test + public void testToAndFromPb() { + initializeExpectedImage(12); + compareImage(diskImage, + Image.fromPb(serviceMockReturnsOptions, diskImage.toPb())); + compareImage(storageImage, + Image.fromPb(serviceMockReturnsOptions, storageImage.toPb())); + Image image = + new Image.Builder(serviceMockReturnsOptions, IMAGE_ID, DISK_CONFIGURATION).build(); + compareImage(image, Image.fromPb(serviceMockReturnsOptions, image.toPb())); + } + + @Test + public void testDeleteOperation() { + initializeExpectedImage(3); + expect(compute.options()).andReturn(mockOptions); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GlobalOperationId.of("project", "op")) + .build(); + expect(compute.delete(IMAGE_ID)).andReturn(operation); + replay(compute); + initializeImage(); + assertSame(operation, image.delete()); + } + + @Test + public void testDeleteNull() { + initializeExpectedImage(2); + expect(compute.options()).andReturn(mockOptions); + expect(compute.delete(IMAGE_ID)).andReturn(null); + replay(compute); + initializeImage(); + assertNull(image.delete()); + } + + @Test + public void testExists_True() throws Exception { + initializeExpectedImage(2); + Compute.ImageOption[] expectedOptions = {Compute.ImageOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(IMAGE_ID, expectedOptions)).andReturn(diskImage); + replay(compute); + initializeImage(); + assertTrue(image.exists()); + verify(compute); + } + + @Test + public void testExists_False() throws Exception { + initializeExpectedImage(2); + Compute.ImageOption[] expectedOptions = {Compute.ImageOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(IMAGE_ID, expectedOptions)).andReturn(null); + replay(compute); + initializeImage(); + assertFalse(image.exists()); + verify(compute); + } + + @Test + public void testReload() throws Exception { + initializeExpectedImage(5); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(IMAGE_ID)).andReturn(storageImage); + replay(compute); + initializeImage(); + Image updateImage = image.reload(); + compareImage(storageImage, updateImage); + verify(compute); + } + + @Test + public void testReloadNull() throws Exception { + initializeExpectedImage(2); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(IMAGE_ID)).andReturn(null); + replay(compute); + initializeImage(); + assertNull(image.reload()); + verify(compute); + } + + @Test + public void testReloadWithOptions() throws Exception { + initializeExpectedImage(5); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(IMAGE_ID, Compute.ImageOption.fields())).andReturn(storageImage); + replay(compute); + initializeImage(); + Image updateImage = image.reload(Compute.ImageOption.fields()); + compareImage(storageImage, updateImage); + verify(compute); + } + + public void compareImage(Image expected, Image value) { + assertEquals(expected, value); + assertEquals(expected.compute().options(), value.compute().options()); + assertEquals(expected.id(), value.id()); + assertEquals(expected.imageId(), value.imageId()); + assertEquals(expected.creationTimestamp(), value.creationTimestamp()); + assertEquals(expected.description(), value.description()); + assertEquals(expected.configuration(), value.configuration()); + assertEquals(expected.status(), value.status()); + assertEquals(expected.diskSizeGb(), value.diskSizeGb()); + assertEquals(expected.licenses(), value.licenses()); + assertEquals(expected.deprecationStatus(), value.deprecationStatus()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} 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 b533ae9c6ae7..0251dca1fc39 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 @@ -151,9 +151,11 @@ public class SerializationTest { private static final ImageId IMAGE_ID = ImageId.of("project", "image"); private static final DiskImageConfiguration DISK_IMAGE_CONFIGURATION = DiskImageConfiguration.of(DISK_ID); - private static final StorageImageConfiguration RAW_IMAGE_CONFIGURATION = + private static final StorageImageConfiguration STORAGE_IMAGE_CONFIGURATION = StorageImageConfiguration.of("gs:/bucket/file"); private static final ImageInfo IMAGE_INFO = ImageInfo.of(IMAGE_ID, DISK_IMAGE_CONFIGURATION); + private static final Image IMAGE = + new Image.Builder(COMPUTE, IMAGE_ID, DISK_IMAGE_CONFIGURATION).build(); private static final Compute.DiskTypeOption DISK_TYPE_OPTION = Compute.DiskTypeOption.fields(); private static final Compute.DiskTypeFilter DISK_TYPE_FILTER = @@ -198,6 +200,11 @@ public class SerializationTest { Compute.SnapshotFilter.equals(Compute.SnapshotField.SELF_LINK, "selfLink"); private static final Compute.SnapshotListOption SNAPSHOT_LIST_OPTION = Compute.SnapshotListOption.filter(SNAPSHOT_FILTER); + private static final Compute.ImageOption IMAGE_OPTION = Compute.ImageOption.fields(); + private static final Compute.ImageFilter IMAGE_FILTER = + Compute.ImageFilter.equals(Compute.ImageField.SELF_LINK, "selfLink"); + private static final Compute.ImageListOption IMAGE_LIST_OPTION = + Compute.ImageListOption.filter(IMAGE_FILTER); @Test public void testServiceOptions() throws Exception { @@ -225,14 +232,14 @@ public void testModelAndRequests() throws Exception { 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, SNAPSHOT, IMAGE_ID, - DISK_IMAGE_CONFIGURATION, RAW_IMAGE_CONFIGURATION, IMAGE_INFO, DISK_TYPE_OPTION, + DISK_IMAGE_CONFIGURATION, STORAGE_IMAGE_CONFIGURATION, IMAGE_INFO, IMAGE, 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, SNAPSHOT_OPTION, SNAPSHOT_FILTER, - SNAPSHOT_LIST_OPTION}; + SNAPSHOT_LIST_OPTION, IMAGE_OPTION, IMAGE_FILTER, IMAGE_LIST_OPTION}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); From c671888f94f89c4289b8a21a873b4e57083aa5b1 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Fri, 1 Apr 2016 19:12:19 +0200 Subject: [PATCH 2/2] Fix image's delete and deprecate methods, document image's list and update tests --- .../com/google/gcloud/compute/Compute.java | 11 ++++++-- .../google/gcloud/compute/ComputeImpl.java | 11 +++++--- .../java/com/google/gcloud/compute/Image.java | 4 +-- .../com/google/gcloud/spi/ComputeRpc.java | 4 +-- .../google/gcloud/spi/DefaultComputeRpc.java | 8 +++--- .../gcloud/compute/ComputeImplTest.java | 27 +++++++++--------- .../com/google/gcloud/compute/ImageTest.java | 28 ++++++++++++++++++- 7 files changed, 64 insertions(+), 29 deletions(-) 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 b433294ecad8..300d38908a8b 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 @@ -1832,9 +1832,14 @@ public static ImageListOption fields(ImageField... fields) { Image get(ImageId imageId, ImageOption... options); /** - * Lists images in the provided project that are available to the current user. + * Lists images in the provided project that are available to the current user. This method can be + * used to list publicly-available images by providing the respective image project. Examples of + * image projects are: {@code centos-cloud}, {@code coreos-cloud}, {@code debian-cloud}, + * {@code opensuse-cloud}, {@code rhel-cloud}, {@code suse-cloud}, {@code ubuntu-os-cloud} and + * {@code windows-cloud}. Attempting to delete or deprecate a publicly-available image will fail. * * @throws ComputeException upon failure + * @see Operating Systems */ Page listImages(String project, ImageListOption... options); @@ -1850,7 +1855,7 @@ public static ImageListOption fields(ImageField... fields) { * * @return a global operation if the delete request was issued correctly, {@code null} if the * image was not found - * @throws ComputeException upon failure + * @throws ComputeException upon failure or if {@code image} is a publicly-available image */ Operation delete(ImageId image, OperationOption... options); @@ -1859,7 +1864,7 @@ public static ImageListOption fields(ImageField... fields) { * * @return a global operation if the deprecation request was issued correctly, {@code null} if the * image was not found - * @throws ComputeException upon failure + * @throws ComputeException upon failure or if {@code image} is a publicly-available image */ Operation deprecate(ImageId image, DeprecationStatus deprecationStatus, 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 dda8dbe69bc4..1f875ad337f1 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 @@ -1124,14 +1124,15 @@ public Image apply(com.google.api.services.compute.model.Image image) { } @Override - public Operation delete(final ImageId image, OperationOption... options) { + public Operation delete(ImageId image, OperationOption... options) { + final ImageId completeId = image.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.deleteImage(image.image(), optionsMap); + return computeRpc.deleteImage(completeId.project(), completeId.image(), optionsMap); } }, options().retryParams(), EXCEPTION_HANDLER); return answer == null ? null : Operation.fromPb(this, answer); @@ -1141,15 +1142,17 @@ public com.google.api.services.compute.model.Operation call() { } @Override - public Operation deprecate(final ImageId image, + public Operation deprecate(ImageId image, final DeprecationStatus deprecationStatus, OperationOption... options) { + final ImageId completeId = image.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.deprecateImage(image.image(), deprecationStatus.toPb(), optionsMap); + return computeRpc.deprecateImage(completeId.project(), completeId.image(), + deprecationStatus.toPb(), optionsMap); } }, options().retryParams(), EXCEPTION_HANDLER); return answer == null ? null : Operation.fromPb(this, answer); diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java index e42a292f72ab..7815929f3bf3 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java @@ -156,7 +156,7 @@ public Image reload(ImageOption... options) { * * @return a global operation if the delete request was successfully sent, {@code null} if the * image was not found - * @throws ComputeException upon failure + * @throws ComputeException upon failure or if this image is a publicly-available image */ public Operation delete(OperationOption... options) { return compute.delete(imageId(), options); @@ -167,7 +167,7 @@ public Operation delete(OperationOption... options) { * * @return a global operation if the deprecation request was successfully sent, {@code null} if * the image was not found - * @throws ComputeException upon failure + * @throws ComputeException upon failure or if this image is a publicly-available image */ public Operation deprecate(DeprecationStatus deprecationStatus, OperationOption... options) { 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 afdf38a27961..523686283db0 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 @@ -366,7 +366,7 @@ Operation createSnapshot(String zone, String disk, String snapshot, String descr * image was not found * @throws ComputeException upon failure */ - Operation deleteImage(String image, Map options); + Operation deleteImage(String project, String image, Map options); /** * Deprecates the requested image. @@ -375,6 +375,6 @@ Operation createSnapshot(String zone, String disk, String snapshot, String descr * image was not found * @throws ComputeException upon failure */ - Operation deprecateImage(String image, DeprecationStatus deprecationStatus, + Operation deprecateImage(String project, String image, DeprecationStatus deprecationStatus, 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 31b2de22f8af..7d0b77a7a73a 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 @@ -609,10 +609,10 @@ public Tuple> listImages(String project, Map } @Override - public Operation deleteImage(String image, Map options) { + public Operation deleteImage(String project, String image, Map options) { try { return compute.images() - .delete(this.options.projectId(), image) + .delete(project, image) .setFields(FIELDS.getString(options)) .execute(); } catch (IOException ex) { @@ -621,11 +621,11 @@ public Operation deleteImage(String image, Map options) { } @Override - public Operation deprecateImage(String image, DeprecationStatus deprecationStatus, + public Operation deprecateImage(String project, String image, DeprecationStatus deprecationStatus, Map options) { try { return compute.images() - .deprecate(this.options.projectId(), image, deprecationStatus) + .deprecate(project, image, deprecationStatus) .setFields(FIELDS.getString(options)) .execute(); } catch (IOException ex) { 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 5d66d02853e9..ec981a95e0e7 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 @@ -194,7 +194,7 @@ public class ComputeImplTest { 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); - private static final ImageId IMAGE_ID = ImageId.of("project", "snapshot"); + private static final ImageId IMAGE_ID = ImageId.of("project", "image"); private static final ImageInfo IMAGE = ImageInfo.of(IMAGE_ID, DiskImageConfiguration.of(DISK_ID)); private static final DeprecationStatus DEPRECATION_STATUS = DeprecationStatus.builder(DeprecationStatus.Status.DEPRECATED, IMAGE_ID).build(); @@ -2191,8 +2191,8 @@ public void testGetImageWithSelectedFields() { @Test public void testDeleteImage_Operation() { - EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) - .andReturn(globalOperation.toPb()); + EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.project(), IMAGE_ID.image(), + EMPTY_RPC_OPTIONS)).andReturn(globalOperation.toPb()); EasyMock.replay(computeRpcMock); compute = options.service(); assertEquals(globalOperation, compute.delete(IMAGE_ID)); @@ -2201,11 +2201,11 @@ public void testDeleteImage_Operation() { @Test public void testDeleteImageWithSelectedFields_Operation() { Capture> capturedOptions = Capture.newInstance(); - EasyMock.expect(computeRpcMock.deleteImage(eq(IMAGE_ID.image()), capture(capturedOptions))) - .andReturn(globalOperation.toPb()); + EasyMock.expect(computeRpcMock.deleteImage(eq(PROJECT), eq(IMAGE_ID.image()), + capture(capturedOptions))).andReturn(globalOperation.toPb()); EasyMock.replay(computeRpcMock); compute = options.service(); - Operation operation = compute.delete(IMAGE_ID, OPERATION_OPTION_FIELDS); + Operation operation = compute.delete(ImageId.of("image"), OPERATION_OPTION_FIELDS); String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); assertTrue(selector.contains("selfLink")); assertTrue(selector.contains("id")); @@ -2216,8 +2216,8 @@ public void testDeleteImageWithSelectedFields_Operation() { @Test public void testDeleteImage_Null() { - EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.image(), EMPTY_RPC_OPTIONS)) - .andReturn(null); + EasyMock.expect(computeRpcMock.deleteImage(IMAGE_ID.project(), IMAGE_ID.image(), + EMPTY_RPC_OPTIONS)).andReturn(null); EasyMock.replay(computeRpcMock); compute = options.service(); assertNull(compute.delete(IMAGE_ID)); @@ -2225,7 +2225,7 @@ public void testDeleteImage_Null() { @Test public void testDeprecateImage_Operation() { - EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.image(), + EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.project(), IMAGE_ID.image(), DEPRECATION_STATUS.toPb(), EMPTY_RPC_OPTIONS)).andReturn(globalOperation.toPb()); EasyMock.replay(computeRpcMock); compute = options.service(); @@ -2235,11 +2235,12 @@ public void testDeprecateImage_Operation() { @Test public void testDeprecateImageWithSelectedFields_Operation() { Capture> capturedOptions = Capture.newInstance(); - EasyMock.expect(computeRpcMock.deprecateImage(eq(IMAGE_ID.image()), + EasyMock.expect(computeRpcMock.deprecateImage(eq(PROJECT), eq(IMAGE_ID.image()), eq(DEPRECATION_STATUS.toPb()), capture(capturedOptions))).andReturn(globalOperation.toPb()); EasyMock.replay(computeRpcMock); compute = options.service(); - Operation operation = compute.deprecate(IMAGE_ID, DEPRECATION_STATUS, OPERATION_OPTION_FIELDS); + Operation operation = + compute.deprecate(ImageId.of("image"), DEPRECATION_STATUS, OPERATION_OPTION_FIELDS); String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); assertTrue(selector.contains("selfLink")); assertTrue(selector.contains("id")); @@ -2250,8 +2251,8 @@ public void testDeprecateImageWithSelectedFields_Operation() { @Test public void testDeprecateImage_Null() { - EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.image(), DEPRECATION_STATUS.toPb(), - EMPTY_RPC_OPTIONS)).andReturn(null); + EasyMock.expect(computeRpcMock.deprecateImage(IMAGE_ID.project(), IMAGE_ID.image(), + DEPRECATION_STATUS.toPb(), EMPTY_RPC_OPTIONS)).andReturn(null); EasyMock.replay(computeRpcMock); compute = options.service(); assertNull(compute.deprecate(IMAGE_ID, DEPRECATION_STATUS)); diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java index b46deb9bff85..dcf7e2513e07 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ImageTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; +import com.google.gcloud.compute.DeprecationStatus.Status; import com.google.gcloud.compute.ImageConfiguration.SourceType; import org.junit.Test; @@ -64,7 +65,7 @@ public class ImageTest { .sourceType(SOURCE_TYPE) .build(); private static final DeprecationStatus DEPRECATION_STATUS = - DeprecationStatus.of(DeprecationStatus.Status.DELETED, IMAGE_ID); + DeprecationStatus.of(Status.DELETED, IMAGE_ID); private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); private final ComputeOptions mockOptions = createMock(ComputeOptions.class); @@ -263,6 +264,31 @@ public void testReloadWithOptions() throws Exception { verify(compute); } + @Test + public void testDeprecateImage() { + initializeExpectedImage(3); + expect(compute.options()).andReturn(mockOptions); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GlobalOperationId.of("project", "op")) + .build(); + DeprecationStatus status = DeprecationStatus.of(Status.DEPRECATED, IMAGE_ID); + expect(compute.deprecate(IMAGE_ID, status)).andReturn(operation); + replay(compute); + initializeImage(); + assertSame(operation, image.deprecate(status)); + } + + @Test + public void testDeprecateNull() { + initializeExpectedImage(2); + expect(compute.options()).andReturn(mockOptions); + DeprecationStatus status = DeprecationStatus.of(Status.DEPRECATED, IMAGE_ID); + expect(compute.deprecate(IMAGE_ID, status)).andReturn(null); + replay(compute); + initializeImage(); + assertNull(image.deprecate(status)); + } + public void compareImage(Image expected, Image value) { assertEquals(expected, value); assertEquals(expected.compute().options(), value.compute().options());