diff --git a/common/openstack/src/main/java/org/jclouds/openstack/options/BaseListOptions.java b/common/openstack/src/main/java/org/jclouds/openstack/options/BaseListOptions.java index d8e277e661..fa0aed3632 100644 --- a/common/openstack/src/main/java/org/jclouds/openstack/options/BaseListOptions.java +++ b/common/openstack/src/main/java/org/jclouds/openstack/options/BaseListOptions.java @@ -81,7 +81,7 @@ public class BaseListOptions extends BaseHttpRequestOptions { } /** - * @see BaseListOptions#maxResults(long) + * @see BaseListOptions#maxResults */ public static BaseListOptions maxResults(int maxKeys) { BaseListOptions options = new BaseListOptions(); diff --git a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java index 30478ebd0a..a0268d6b9a 100644 --- a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java +++ b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java @@ -472,8 +472,7 @@ public class RestAnnotationProcessor { } Payload payload = null; - HttpRequestOptions options = findOptionsIn(method, args); - if (options != null) { + for(HttpRequestOptions options : findOptionsIn(method, args)) { injector.injectMembers(options);// TODO test case for (Entry header : options.buildRequestHeaders().entries()) { headers.put(header.getKey(), Strings2.replaceTokens(header.getValue(), tokenValues.entries())); @@ -1051,30 +1050,26 @@ public class RestAnnotationProcessor { } //TODO: change to LoadingCache findOptionsIn(Method method, Object... args) throws ExecutionException { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (int index : methodToIndexesOfOptions.get(method)) { if (args.length >= index + 1) {// accomodate varargs if (args[index] instanceof Object[]) { - Object[] options = (Object[]) args[index]; - if (options.length == 0) { - } else if (options.length == 1) { - if (options[0] instanceof HttpRequestOptions) { - HttpRequestOptions binder = (HttpRequestOptions) options[0]; - injector.injectMembers(binder); - return binder; - } - } else { - if (options[0] instanceof HttpRequestOptions) { - throw new IllegalArgumentException("we currently do not support multiple varargs options in: " - + method.getName()); + for (Object option : (Object[]) args[index]) { + if (option instanceof HttpRequestOptions) { + result.add((HttpRequestOptions) option); } } } else { - return (HttpRequestOptions) args[index]; + for (; index < args.length; index++) { + if (args[index] instanceof HttpRequestOptions) { + result.add((HttpRequestOptions) args[index]); + } + } } } } - return null; + return result.build(); } public Multimap buildHeaders(Collection> tokenValues, Method method, diff --git a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java index 9b18a027f3..cd979810a5 100644 --- a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java +++ b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java @@ -511,6 +511,9 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest { @POST public void varargs(HttpRequestOptions... options); + @POST + public void varargsWithReq(String required, HttpRequestOptions... options); + @POST public void post(HttpRequestOptions options); @@ -530,6 +533,12 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest { assertNonPayloadHeadersEqual(request, ""); assertPayloadEquals(request, "", "application/octet-stream", false); } + + private class TestHttpRequestOptions extends BaseHttpRequestOptions { + TestHttpRequestOptions payload(String payload) { this.payload = payload; return this; } + TestHttpRequestOptions headerParams(Multimap headers) { this.headers.putAll(headers); return this; } + TestHttpRequestOptions queryParams(Multimap params) { this.queryParameters.putAll(params); return this; } + } public void testHttpRequestOptionsPayloadParam() throws SecurityException, NoSuchMethodException, IOException { Method method = TestPayloadParamVarargs.class.getMethod("post", Payload.class); @@ -541,48 +550,51 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest { public void testHttpRequestWithOnlyContentType() throws SecurityException, NoSuchMethodException, IOException { Method method = TestPayloadParamVarargs.class.getMethod("post", HttpRequestOptions.class); - verifyTestPostOptions(method); - } - - public void testPayloadParamVarargs() throws SecurityException, NoSuchMethodException, IOException { - Method method = TestPayloadParamVarargs.class.getMethod("varargs", Array.newInstance(HttpRequestOptions.class, 0) - .getClass()); - verifyTestPostOptions(method); - } - - private void verifyTestPostOptions(Method method) throws IOException { - HttpRequest request = factory(TestPayloadParamVarargs.class).createRequest(method, new HttpRequestOptions() { - - public Multimap buildMatrixParameters() { - return LinkedHashMultimap.create(); - } - - public String buildPathSuffix() { - return null; - } - - public Multimap buildQueryParameters() { - return LinkedHashMultimap.create(); - } - - public Multimap buildFormParameters() { - return LinkedHashMultimap.create(); - } - - public Multimap buildRequestHeaders() { - return LinkedHashMultimap.create(); - } - - public String buildStringPayload() { - return "fooya"; - } - - }); + HttpRequest request = factory(TestPayloadParamVarargs.class).createRequest(method, new TestHttpRequestOptions().payload("fooya")); assertRequestLineEquals(request, "POST http://localhost:9999 HTTP/1.1"); assertNonPayloadHeadersEqual(request, ""); assertPayloadEquals(request, "fooya", "application/unknown", false); } + public void testHeaderAndQueryVarargs() throws SecurityException, NoSuchMethodException, IOException { + Method method = TestPayloadParamVarargs.class.getMethod("varargs", Array.newInstance(HttpRequestOptions.class, 0) + .getClass()); + HttpRequest request = factory(TestPayloadParamVarargs.class).createRequest(method, + new TestHttpRequestOptions().payload("fooya"), + new TestHttpRequestOptions().headerParams(ImmutableMultimap.of("X-header-1", "fooya")), + new TestHttpRequestOptions().queryParams(ImmutableMultimap.of("key", "value"))); + assertRequestLineEquals(request, "POST http://localhost:9999?key=value HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "X-header-1: fooya\n"); + assertPayloadEquals(request, "fooya", "application/unknown", false); + } + + public void testHeaderAndQueryVarargsPlusReq() throws SecurityException, NoSuchMethodException, IOException { + Method method = TestPayloadParamVarargs.class.getMethod("varargsWithReq", String.class, Array.newInstance(HttpRequestOptions.class, 0) + .getClass()); + HttpRequest request = factory(TestPayloadParamVarargs.class).createRequest(method, "required param", + new Object[]{ new TestHttpRequestOptions().payload("fooya"), + new TestHttpRequestOptions().headerParams(ImmutableMultimap.of("X-header-1", "fooya")), + new TestHttpRequestOptions().queryParams(ImmutableMultimap.of("key", "value"))}); + assertRequestLineEquals(request, "POST http://localhost:9999?key=value HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "X-header-1: fooya\n"); + assertPayloadEquals(request, "fooya", "application/unknown", false); + } + + public void testDuplicateHeaderAndQueryVarargs() throws SecurityException, NoSuchMethodException, IOException { + Method method = TestPayloadParamVarargs.class.getMethod("varargs", Array.newInstance(HttpRequestOptions.class, 0) + .getClass()); + HttpRequest request = factory(TestPayloadParamVarargs.class).createRequest(method, + new TestHttpRequestOptions().queryParams(ImmutableMultimap.of("key", "value")), + new TestHttpRequestOptions().payload("fooya"), + new TestHttpRequestOptions().headerParams(ImmutableMultimap.of("X-header-1", "fooya")), + new TestHttpRequestOptions().queryParams(ImmutableMultimap.of("key", "anothervalue")), + new TestHttpRequestOptions().headerParams(ImmutableMultimap.of("X-header-1", "fooya again!")), + new TestHttpRequestOptions().payload("last_payload_wins!")); + assertRequestLineEquals(request, "POST http://localhost:9999?key=value&key=anothervalue HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "X-header-1: fooya\nX-header-1: fooya again!\n"); + assertPayloadEquals(request, "last_payload_wins!", "application/unknown", false); + } + public class TestCustomMethod { @FOO public void foo() { diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/ImageDetails.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/ImageDetails.java index 6504b68fe8..343143c5d6 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/ImageDetails.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/ImageDetails.java @@ -246,6 +246,10 @@ public class ImageDetails extends Image { return this.location; } + public Optional getOwner() { + return owner; + } + public Date getUpdatedAt() { return this.updatedAt; } diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/StoreType.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/StoreType.java new file mode 100644 index 0000000000..3b28ff137b --- /dev/null +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/domain/StoreType.java @@ -0,0 +1,57 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.domain; + +/** + * Backing store types for glance images + * + * @author Adam Lowe + * @see + */ +public enum StoreType { + /** + * Filesystem store + */ + FILE, + /** + * S3 store + */ + S3, + /** + * Openstack swift store + */ + SWIFT, + /** + * RADOS (Reliable Autonomic Distributed Object Store) Block Device store + */ + RBD, + /** + * HTTP (read-only) store + */ + HTTP; + + public String value() { + return name().toLowerCase().replace("_", "+"); + } + + @Override + public String toString() { + return value(); + } +} \ No newline at end of file diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageAsyncClient.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageAsyncClient.java index 6d4d140a64..da64dfceb6 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageAsyncClient.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageAsyncClient.java @@ -21,23 +21,24 @@ package org.jclouds.openstack.glance.v1_0.features; import java.io.InputStream; import java.util.Set; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.HEAD; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; +import javax.ws.rs.*; import javax.ws.rs.core.MediaType; +import org.jclouds.io.Payload; import org.jclouds.openstack.filters.AuthenticateRequest; import org.jclouds.openstack.glance.v1_0.domain.Image; import org.jclouds.openstack.glance.v1_0.domain.ImageDetails; import org.jclouds.openstack.glance.v1_0.functions.ParseImageDetailsFromHeaders; +import org.jclouds.openstack.glance.v1_0.options.CreateImageOptions; +import org.jclouds.openstack.glance.v1_0.options.ListImageOptions; +import org.jclouds.openstack.glance.v1_0.options.UpdateImageOptions; import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.annotations.SkipEncoding; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -47,6 +48,7 @@ import com.google.common.util.concurrent.ListenableFuture; * * @see ImageClient * @author Adrian Cole + * @author Adam Lowe * @see api doc * @see api src */ @@ -62,7 +64,7 @@ public interface ImageAsyncClient { @Consumes(MediaType.APPLICATION_JSON) @Path("/images") @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> list(); + ListenableFuture> list(ListImageOptions... options); /** * @see ImageClient#listInDetail @@ -72,7 +74,7 @@ public interface ImageAsyncClient { @Consumes(MediaType.APPLICATION_JSON) @Path("/images/detail") @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listInDetail(); + ListenableFuture> listInDetail(ListImageOptions... options); /** * @see ImageClient#show @@ -91,9 +93,49 @@ public interface ImageAsyncClient { @ExceptionParser(ReturnNullOnNotFoundOr404.class) ListenableFuture getAsStream(@PathParam("id") String id); -// POST /images -- Store image data and return metadata about the -// newly-stored image -// PUT /images/ -- Update image metadata and/or upload image -// data for a previously-reserved image -// DELETE /images/ -- Delete the image with id + /** + * @see ImageClient#create + */ + @POST + @Path("/images") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @SelectJson("image") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture create(@HeaderParam("x-image-meta-name") String name, Payload payload, CreateImageOptions... options); + + /** + * @see ImageClient#reserve + */ + @POST + @Path("/images") + @SelectJson("image") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture reserve(@HeaderParam("x-image-meta-name") String name, CreateImageOptions... options); + + /** + * @see ImageClient#upload + */ + @PUT + @Path("/images/{id}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @SelectJson("image") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture upload(@PathParam("id") String id, Payload imageData, UpdateImageOptions... options); + + /** + * @see ImageClient#update + */ + @PUT + @Path("/images/{id}") + @SelectJson("image") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture update(@PathParam("id") String id, UpdateImageOptions... options); + + /** + * @see ImageClient#delete + */ + @DELETE + @Path("/images/{id}") + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture delete(@PathParam("id") String id); } diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageClient.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageClient.java index 4e50c938ba..3e318e3fa9 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageClient.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/features/ImageClient.java @@ -23,15 +23,21 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.jclouds.concurrent.Timeout; +import org.jclouds.io.Payload; import org.jclouds.javax.annotation.Nullable; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; import org.jclouds.openstack.glance.v1_0.domain.Image; import org.jclouds.openstack.glance.v1_0.domain.ImageDetails; +import org.jclouds.openstack.glance.v1_0.options.CreateImageOptions; +import org.jclouds.openstack.glance.v1_0.options.UpdateImageOptions; +import org.jclouds.openstack.glance.v1_0.options.ListImageOptions; /** * Image Services - * - * @see ImageAsyncClient + * * @author Adrian Cole + * @author Adam Lowe + * @see ImageAsyncClient * @see api doc */ @Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) @@ -39,13 +45,13 @@ public interface ImageClient { /** * Returns a set of brief metadata about images */ - Set list(); - + Set list(ListImageOptions... options); + /** * Returns a set of detailed metadata about images */ - Set listInDetail(); - + Set listInDetail(ListImageOptions... options); + /** * Return metadata about an image with id */ @@ -53,14 +59,51 @@ public interface ImageClient { ImageDetails show(String id); /** - * Return image data for image with id + * Return image data for image with id */ @Nullable InputStream getAsStream(String id); - -// POST /images -- Store image data and return metadata about the -// newly-stored image -// PUT /images/ -- Update image metadata and/or upload image -// data for a previously-reserved image -// DELETE /images/ -- Delete the image with id + + /** + * Create a new image + * + * @return detailed metadata about the newly stored image + */ + ImageDetails create(String name, Payload imageData, CreateImageOptions... options); + + /** + * Reserve a new image to be uploaded later + * + * @return detailed metadata about the newly stored image + * @see #upload + */ + ImageDetails reserve(String name, CreateImageOptions... options); + + /** + * Adjust the metadata stored for an existing image + * + * @return detailed metadata about the updated image + */ + ImageDetails update(String id, UpdateImageOptions... options); + + /** + * Upload image data for a previously-reserved image + *

+ * If an image was previously reserved, and thus is in the queued state, then image data can be added using this method. + * If the image already as data associated with it (e.g. not in the queued state), then you will receive a 409 + * Conflict exception. + * + * @param imageData the new image to upload + * @param options can be used to adjust the metadata stored for the image in the same call + * @return detailed metadata about the updated image + * @see #reserve + */ + ImageDetails upload(String id, Payload imageData, UpdateImageOptions... options); + + /** + * Delete the image with the specified id + * + * @return true if successful + */ + Boolean delete(String id); } diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/ParseImageDetailsFromHeaders.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/ParseImageDetailsFromHeaders.java index 4edb1915b4..7c8ecbe890 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/ParseImageDetailsFromHeaders.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/ParseImageDetailsFromHeaders.java @@ -18,16 +18,19 @@ */ package org.jclouds.openstack.glance.v1_0.functions; +import static org.jclouds.openstack.glance.v1_0.options.ImageField.*; + import javax.inject.Inject; import org.jclouds.date.DateService; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; -import org.jclouds.openstack.glance.v1_0.domain.Image; import org.jclouds.openstack.glance.v1_0.domain.ImageDetails; +import org.jclouds.openstack.glance.v1_0.domain.Image.Status; import com.google.common.base.Function; +import com.google.common.base.Optional; /** * This parses {@link ImageDetails} from HTTP headers. @@ -44,23 +47,27 @@ public class ParseImageDetailsFromHeaders implements Function builder = ImageDetails.builder() - .id(from.getFirstHeaderOrNull("X-Image-Meta-Id")) - .name(from.getFirstHeaderOrNull("X-Image-Meta-Name")) - .checksum(from.getFirstHeaderOrNull("X-Image-Meta-Checksum")) - .containerFormat(ContainerFormat.fromValue(from.getFirstHeaderOrNull("X-Image-Meta-Container_format"))) - .diskFormat(DiskFormat.fromValue(from.getFirstHeaderOrNull("X-Image-Meta-Disk_format"))) - .size(Long.parseLong(from.getFirstHeaderOrNull("X-Image-Meta-Size"))) - .minDisk(Long.parseLong(from.getFirstHeaderOrNull("X-Image-Meta-Min_disk"))) - .minRam(Long.parseLong(from.getFirstHeaderOrNull("X-Image-Meta-Min_ram"))) - .isPublic(Boolean.parseBoolean(from.getFirstHeaderOrNull("X-Image-Meta-Is_public"))) - .createdAt(dateService.iso8601SecondsDateParse(from.getFirstHeaderOrNull("X-Image-Meta-Created_at"))) - .updatedAt(dateService.iso8601SecondsDateParse(from.getFirstHeaderOrNull("X-Image-Meta-Updated_at"))) - .owner(from.getFirstHeaderOrNull("X-Image-Meta-Owner")) - .status(Image.Status.fromValue(from.getFirstHeaderOrNull("X-Image-Meta-Status"))); - - String deletedAt = from.getFirstHeaderOrNull("X-Image-Meta-Deleted_at"); - if (deletedAt != null) - builder.deletedAt(dateService.iso8601SecondsDateParse(deletedAt)); + .id(from.getFirstHeaderOrNull(ID.asHeader())) + .name(from.getFirstHeaderOrNull(NAME.asHeader())) + .checksum(Optional.fromNullable(from.getFirstHeaderOrNull(CHECKSUM.asHeader()))) + .minDisk(Long.parseLong(from.getFirstHeaderOrNull(MIN_DISK.asHeader()))) + .minRam(Long.parseLong(from.getFirstHeaderOrNull(MIN_RAM.asHeader()))) + .isPublic(Boolean.parseBoolean(from.getFirstHeaderOrNull(IS_PUBLIC.asHeader()))) + .createdAt(dateService.iso8601SecondsDateParse(from.getFirstHeaderOrNull(CREATED_AT.asHeader()))) + .updatedAt(dateService.iso8601SecondsDateParse(from.getFirstHeaderOrNull(UPDATED_AT.asHeader()))) + .owner(Optional.fromNullable(from.getFirstHeaderOrNull(OWNER.asHeader()))) + .location(Optional.fromNullable(from.getFirstHeaderOrNull(LOCATION.asHeader()))) + .status(Status.fromValue(from.getFirstHeaderOrNull(STATUS.asHeader()))); + + String containerFormat = from.getFirstHeaderOrNull(CONTAINER_FORMAT.asHeader()); + String diskFormat = from.getFirstHeaderOrNull(DISK_FORMAT.asHeader()); + String deletedAt = from.getFirstHeaderOrNull(DELETED_AT.asHeader()); + String size = from.getFirstHeaderOrNull(SIZE.asHeader()); + + if (containerFormat != null) builder.containerFormat(ContainerFormat.fromValue(containerFormat)); + if (diskFormat != null) builder.diskFormat(DiskFormat.fromValue(diskFormat)); + if (deletedAt != null) builder.deletedAt(dateService.iso8601SecondsDateParse(deletedAt)); + if (size != null) builder.size(Long.parseLong(size)); return builder.build(); } diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/CreateImageOptions.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/CreateImageOptions.java new file mode 100644 index 0000000000..119b469990 --- /dev/null +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/CreateImageOptions.java @@ -0,0 +1,138 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.options; + +import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; +import org.jclouds.openstack.glance.v1_0.domain.StoreType; + +/** + * + *

Usage The recommended way to instantiate a CreateImageOptions object is to statically import + * CreateImageOptions.Builder.* and invoke a static creation method for each option as needed: + *

+ * + * import static org.jclouds.openstack.glance.v1_0.options.CreateImageOptions.Builder.* + * + * + * // this will create an image with the name "imageName", minimum required disk of 10GB, etc. + * details = client.create("imageName", minDisk(10), isPublic(true), property("mykey", "somevalue")); + * + + * @author Adam Lowe + * @see + */ +public class CreateImageOptions extends UpdateImageOptions { + + /** + * When present, Glance will use the supplied identifier for the image instead of generating one. If the identifier + * already exists in that Glance node, then a 409 Conflict will be returned by Glance. The value of the header must + * be a uuid in hexadecimal string notation (i.e. 71c675ab-d94f-49cd-a114-e12490b328d9). + */ + public CreateImageOptions id(String id) { + headers.put(ImageField.ID.asHeader(), id); + return this; + } + + public static class Builder { + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#id + */ + public static CreateImageOptions id(String id) { + return new CreateImageOptions().id(id); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#storeType + */ + public static CreateImageOptions storeType(StoreType storeType) { + return CreateImageOptions.class.cast(new CreateImageOptions().storeType(storeType)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#diskFormat + */ + public static CreateImageOptions diskFormat(DiskFormat diskFormat) { + return CreateImageOptions.class.cast(new CreateImageOptions().diskFormat(diskFormat)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#containerFormat + */ + public static CreateImageOptions containerFormat(ContainerFormat containerFormat) { + return CreateImageOptions.class.cast(new CreateImageOptions().containerFormat(containerFormat)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#size + */ + public static CreateImageOptions size(long size) { + return CreateImageOptions.class.cast(new CreateImageOptions().size(size)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#checksum + */ + public static CreateImageOptions checksum(String checksum) { + return CreateImageOptions.class.cast(new CreateImageOptions().checksum(checksum)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#isPublic + */ + public static CreateImageOptions isPublic(boolean isPublic) { + return CreateImageOptions.class.cast(new CreateImageOptions().isPublic(isPublic)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#isProtected + */ + public static CreateImageOptions isProtected(boolean isProtected) { + return CreateImageOptions.class.cast(new CreateImageOptions().isProtected(isProtected)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#minRam + */ + public static CreateImageOptions minRam(long ram) { + return CreateImageOptions.class.cast(new CreateImageOptions().minRam(ram)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#minDisk + */ + public static CreateImageOptions minDisk(long disk) { + return CreateImageOptions.class.cast(new CreateImageOptions().minDisk(disk)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#owner + */ + public static CreateImageOptions owner(String owner) { + return CreateImageOptions.class.cast(new CreateImageOptions().owner(owner)); + } + + /** + * @see org.jclouds.openstack.glance.v1_0.options.CreateImageOptions#property + */ + public static CreateImageOptions property(String key, String value) { + return CreateImageOptions.class.cast(new CreateImageOptions().property(key, value)); + } + } +} diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ImageField.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ImageField.java new file mode 100644 index 0000000000..fd67d2c355 --- /dev/null +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ImageField.java @@ -0,0 +1,45 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.options; + +import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; + +import org.jclouds.util.Strings2; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Strings; + +/** + * Fields used in Glance options + */ +public enum ImageField { + ID, NAME, CHECKSUM, MIN_DISK, MIN_RAM, IS_PUBLIC, PROTECTED, CREATED_AT, UPDATED_AT, DELETED_AT, + OWNER, LOCATION, STATUS, DISK_FORMAT, CONTAINER_FORMAT, SIZE, SIZE_MIN, SIZE_MAX, STORE, PROPERTY; + + public static final String HEADER_PREFIX = "X-Image-Meta-"; + + public String asParam() { + return name().toLowerCase(); + } + + public String asHeader() { + return HEADER_PREFIX + name().charAt(0) + name().substring(1).toLowerCase(); + } +} diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ListImageOptions.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ListImageOptions.java new file mode 100644 index 0000000000..b81c07c9bc --- /dev/null +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ListImageOptions.java @@ -0,0 +1,265 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.options; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.jclouds.openstack.glance.v1_0.options.ImageField.*; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; +import org.jclouds.openstack.glance.v1_0.domain.Image.Status; + +/** + *

Usage The recommended way to instantiate a ListImageOptions object is to statically import + * ListImageOptions.Builder.* and invoke a static creation method for each option as needed: + *

+ * + * import static org.jclouds.openstack.glance.v1_0.options.ListImageOptions.Builder.* + * + * + * // this will list the first 10 images with the name "name", minimum required disk of 5GB. + * list = client.list(name("newName"), limit(10), minDisk(5)); + * + * + * @author Adam Lowe + * @see + */ +public class ListImageOptions extends BaseHttpRequestOptions { + public static final ListImageOptions NONE = new ListImageOptions(); + + /** + * Given a string value x, return object names greater in value than the specified marker. + */ + public ListImageOptions marker(String marker) { + queryParameters.put("marker", checkNotNull(marker, "marker")); + return this; + } + + /** + * For an integer value n, limits the number of results to n values. + */ + public ListImageOptions limit(int limit) { + checkState(limit >= 0, "limit must be >= 0"); + checkState(limit <= 10000, "limit must be <= 10000"); + queryParameters.put("limit", Integer.toString(limit)); + return this; + } + + /** + * Return only those images having a matching name attribute + */ + public ListImageOptions name(String name) { + queryParameters.put(NAME.asParam(), name); + return this; + } + + /** + * Return only those images that have the requested status + */ + public ListImageOptions status(Status status) { + queryParameters.put(STATUS.asParam(), status.toString()); + return this; + } + + /** + * Return only those images having a matching container format + */ + public ListImageOptions containerFormat(ContainerFormat containerFormat) { + queryParameters.put(CONTAINER_FORMAT.asParam(), containerFormat.toString()); + return this; + } + + /** + * Return only those images having a matching disk format + */ + public ListImageOptions diskFormat(DiskFormat diskFormat) { + queryParameters.put(DISK_FORMAT.asParam(), diskFormat.toString()); + return this; + } + + /** + * Return only those images having a matching min ram size + */ + public ListImageOptions minRam(long ram) { + queryParameters.put(MIN_RAM.asParam(), Long.toString(ram)); + return this; + } + + /** + * Return only those images having a matching min disk size + */ + public ListImageOptions minDisk(long disk) { + queryParameters.put(MIN_DISK.asParam(), Long.toString(disk)); + return this; + } + + /** + * Return those images that have a size attribute greater than or equal to size + */ + public ListImageOptions minSize(long size) { + queryParameters.put(SIZE_MIN.asParam(), Long.toString(size)); + return this; + } + + /** + * Return those images that have a size attribute less than or equal to size + */ + public ListImageOptions maxSize(long size) { + queryParameters.put(SIZE_MAX.asParam(), Long.toString(size)); + return this; + } + + /** + * Return only public images or only private images + */ + public ListImageOptions isPublic(boolean isPublic) { + queryParameters.put(IS_PUBLIC.asParam(), Boolean.toString(isPublic)); + return this; + } + + /** + * Filter to only protected or unprotected images + */ + public ListImageOptions isProtected(boolean isProtected) { + queryParameters.put(PROTECTED.asParam(), Boolean.toString(isProtected)); + return this; + } + + /** + * Results will be ordered by the specified image attribute. + */ + public ListImageOptions sortBy(ImageField key) { + queryParameters.put("sort_key", key.asParam()); + return this; + } + + /** + * Ascending sort order (smallest first). + *

+ * NOTE: default behavior is to sort descending (largest first) + */ + public ListImageOptions sortAscending() { + queryParameters.put("sort_dir", "asc"); + return this; + } + + public static class Builder { + /** + * @see ListImageOptions#name + */ + public static ListImageOptions name(String name) { + return new ListImageOptions().name(name); + } + + /** + * @see ListImageOptions#diskFormat + */ + public static ListImageOptions diskFormat(DiskFormat diskFormat) { + return new ListImageOptions().diskFormat(diskFormat); + } + + /** + * @see ListImageOptions#containerFormat + */ + public static ListImageOptions containerFormat(ContainerFormat containerFormat) { + return new ListImageOptions().containerFormat(containerFormat); + } + + /** + * @see ListImageOptions#minRam + */ + public static ListImageOptions minRam(long size) { + return new ListImageOptions().minRam(size); + } + + + /** + * @see ListImageOptions#minDisk + */ + public static ListImageOptions minDisk(long size) { + return new ListImageOptions().minDisk(size); + } + + /** + * @see ListImageOptions#minSize + */ + public static ListImageOptions minSize(long size) { + return new ListImageOptions().minSize(size); + } + + /** + * @see ListImageOptions#maxSize + */ + public static ListImageOptions maxSize(long size) { + return new ListImageOptions().maxSize(size); + } + + /** + * @see ListImageOptions#sortBy + */ + public static ListImageOptions status(Status status) { + return new ListImageOptions().status(status); + } + + /** + * @see ListImageOptions#sortBy + */ + public static ListImageOptions sortBy(ImageField sortKey) { + return new ListImageOptions().sortBy(sortKey); + } + + /** + * @see ListImageOptions#sortAscending + */ + public static ListImageOptions sortAscending() { + return new ListImageOptions().sortAscending(); + } + + /** + * @see ListImageOptions#isPublic + */ + public static ListImageOptions isPublic(boolean isPublic) { + return ListImageOptions.class.cast(new ListImageOptions().isPublic(isPublic)); + } + + /** + * @see ListImageOptions#isProtected + */ + public static ListImageOptions isProtected(boolean isProtected) { + return ListImageOptions.class.cast(new ListImageOptions().isProtected(isProtected)); + } + + /** + * @see ListImageOptions#limit + */ + public static ListImageOptions limit(int limit) { + return new ListImageOptions().limit(limit); + } + + /** + * @see ListImageOptions#marker + */ + public static ListImageOptions marker(String marker) { + ListImageOptions options = new ListImageOptions(); + return options.marker(marker); + } + } +} diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/UpdateImageOptions.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/UpdateImageOptions.java new file mode 100644 index 0000000000..da0e4e9fb8 --- /dev/null +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/UpdateImageOptions.java @@ -0,0 +1,243 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.options; + +import static org.jclouds.openstack.glance.v1_0.options.ImageField.*; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; +import org.jclouds.openstack.glance.v1_0.domain.StoreType; + +/** + *

Usage The recommended way to instantiate a UpdateImageOptions object is to statically import + * UpdateImageOptions.Builder.* and invoke a static creation method for each option as needed: + *

+ * + * import static org.jclouds.openstack.glance.v1_0.options.UpdateImageOptions.Builder.* + * + * + * // this will adjust the image with id 'id' the name "newName", minimum required disk of 5GB, etc. + * details = client.update(id, name("newName"), minDisk(5), isPublic(true), property("mykey", "somevalue")); + * + * @author Adam Lowe + * @see + */ +public class UpdateImageOptions extends BaseHttpRequestOptions { + + /** + * Adjust the name of the image + */ + public UpdateImageOptions name(String name) { + headers.put(NAME.asHeader(), name); + return this; + } + + /** + * When present, Glance will attempt to store the disk image data in the backing store indicated by the value of the + * header. If the Glance node does not support the backing store, Glance will return a 400 Bad Request. + */ + public UpdateImageOptions storeType(StoreType storeType) { + headers.put(STORE.asHeader(), storeType.toString()); + return this; + } + + public UpdateImageOptions diskFormat(DiskFormat diskFormat) { + headers.put(DISK_FORMAT.asHeader(), diskFormat.toString()); + return this; + } + + public UpdateImageOptions containerFormat(ContainerFormat containerFormat) { + headers.put(CONTAINER_FORMAT.asHeader(), containerFormat.toString()); + return this; + } + + /** + * When present, Glance assumes that the expected size of the request body will be the value of this header. If the + * length in bytes of the request body does not match the value of this header, Glance will return a 400 Bad Request. + */ + public UpdateImageOptions size(long size) { + headers.put(SIZE.asHeader(), Long.toString(size)); + return this; + } + + /** + * MD5 checksum of the image + *

+ * When present, Glance will verify the checksum generated from the backend store when storing your image against + * this value and return a 400 Bad Request if the values do not match. + */ + public UpdateImageOptions checksum(String checksum) { + headers.put(CHECKSUM.asHeader(), checksum); + return this; + } + + public UpdateImageOptions location(String location) { + headers.put(LOCATION.asHeader(), location); + return this; + } + + /** + * Mark the image as public, meaning that any user may view its metadata and may read the disk image + * from Glance. + */ + public UpdateImageOptions isPublic(boolean isPublic) { + headers.put(IS_PUBLIC.asHeader(), Boolean.toString(isPublic)); + return this; + } + + /** + * Mark the image as protected - if set to true the image cannot be deleted till it is unset. + */ + public UpdateImageOptions isProtected(boolean isProtected) { + headers.put(PROTECTED.asHeader(), Boolean.toString(isProtected)); + return this; + } + + /** + * The expected minimum ram required in megabytes to run this image on a server (default 0). + */ + public UpdateImageOptions minRam(long ram) { + headers.put(MIN_RAM.asHeader(), Long.toString(ram)); + return this; + } + + /** + * The expected minimum disk required in gigabytes to run this image on a server (default 0). + */ + public UpdateImageOptions minDisk(long disk) { + headers.put(MIN_DISK.asHeader(), Long.toString(disk)); + return this; + } + + /** + * Glance normally sets the owner of an image to be the tenant or user (depending on the “owner_is_tenant” + * configuration option) of the authenticated user issuing the request. However, if the authenticated user has the + * Admin role, this default may be overridden by setting this header to null or to a string identifying the owner of + * the image. + */ + public UpdateImageOptions owner(String owner) { + headers.put(OWNER.asHeader(), owner); + return this; + } + + /** + * Custom, free-form image properties stored with the image. + */ + public UpdateImageOptions property(String key, String value) { + if (!key.toLowerCase().startsWith(PROPERTY.asHeader() + "-")) { + key = PROPERTY.asHeader() + "-" + key; + } + headers.put(key, value); + return this; + } + + public static class Builder { + /** + * @see UpdateImageOptions#name + */ + public static UpdateImageOptions name(String name) { + return new UpdateImageOptions().name(name); + } + + /** + * @see UpdateImageOptions#storeType + */ + public static UpdateImageOptions storeType(StoreType storeType) { + return new UpdateImageOptions().storeType(storeType); + } + + /** + * @see UpdateImageOptions#diskFormat + */ + public static UpdateImageOptions diskFormat(DiskFormat diskFormat) { + return new UpdateImageOptions().diskFormat(diskFormat); + } + + /** + * @see UpdateImageOptions#containerFormat + */ + public static UpdateImageOptions containerFormat(ContainerFormat containerFormat) { + return new UpdateImageOptions().containerFormat(containerFormat); + } + + /** + * @see UpdateImageOptions#size + */ + public static UpdateImageOptions size(long size) { + return new UpdateImageOptions().size(size); + } + + /** + * @see UpdateImageOptions#checksum + */ + public static UpdateImageOptions checksum(String checksum) { + return new UpdateImageOptions().checksum(checksum); + } + + /** + * @see UpdateImageOptions#location + */ + public static UpdateImageOptions location(String location) { + return new UpdateImageOptions().location(location); + } + + /** + * @see UpdateImageOptions#isPublic + */ + public static UpdateImageOptions isPublic(boolean isPublic) { + return new UpdateImageOptions().isPublic(isPublic); + } + + /** + * @see UpdateImageOptions#isProtected + */ + public static UpdateImageOptions isProtected(boolean isProtected) { + return new UpdateImageOptions().isProtected(isProtected); + } + + /** + * @see UpdateImageOptions#minRam + */ + public static UpdateImageOptions minRam(long ram) { + return new UpdateImageOptions().minRam(ram); + } + + /** + * @see UpdateImageOptions#minDisk + */ + public static UpdateImageOptions minDisk(long disk) { + return new UpdateImageOptions().minDisk(disk); + } + + /** + * @see UpdateImageOptions#owner + */ + public static UpdateImageOptions owner(String owner) { + return new UpdateImageOptions().owner(owner); + } + + /** + * @see UpdateImageOptions#property + */ + public static UpdateImageOptions property(String key, String value) { + return new UpdateImageOptions().property(key, value); + } + } +} diff --git a/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientExpectTest.java b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientExpectTest.java index 4796b15fad..415803bd5c 100644 --- a/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientExpectTest.java +++ b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientExpectTest.java @@ -19,19 +19,28 @@ package org.jclouds.openstack.glance.v1_0.features; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.net.URI; +import javax.ws.rs.core.MediaType; + import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.io.Payloads; +import org.jclouds.io.payloads.StringPayload; import org.jclouds.openstack.glance.v1_0.GlanceClient; import org.jclouds.openstack.glance.v1_0.functions.ParseImageDetailsFromHeadersTest; import org.jclouds.openstack.glance.v1_0.internal.BaseGlanceClientExpectTest; +import org.jclouds.openstack.glance.v1_0.options.UpdateImageOptions; +import org.jclouds.openstack.glance.v1_0.parse.ParseImageDetailsTest; import org.jclouds.openstack.glance.v1_0.parse.ParseImagesInDetailTest; import org.jclouds.openstack.glance.v1_0.parse.ParseImagesTest; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; import org.jclouds.util.Strings2; import org.testng.annotations.Test; @@ -40,6 +49,7 @@ import com.google.common.collect.ImmutableSet; /** * @author Adrian Cole + * @author Adam Lowe */ @Test(groups = "unit", testName = "ImageClientExpectTest") public class ImageClientExpectTest extends BaseGlanceClientExpectTest { @@ -198,5 +208,273 @@ public class ImageClientExpectTest extends BaseGlanceClientExpectTest { assertNull(clientWhenNoExist.getImageClientForRegion("az-1.region-a.geo-1").getAsStream("fcc451d0-f6e4-4824-ad8f-70ec12326d07")); } + + public void testCreateWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images")) + .headers( + ImmutableMultimap.builder() + .put("x-image-meta-name", "test").put("Accept", MediaType.APPLICATION_JSON).put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType("somedata", MediaType.APPLICATION_OCTET_STREAM)) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image.json")).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, createResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertEquals(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").create("test", new StringPayload("somedata")), + new ParseImageDetailsTest().expected()); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testCreateWhenResponseIs4xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images")) + .headers( + ImmutableMultimap.builder() + .put("x-image-meta-name", "test").put("Accept", MediaType.APPLICATION_JSON).put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType("somedata", MediaType.APPLICATION_OCTET_STREAM)) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(401) + .payload(payloadFromResource("/image.json")).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, createResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").create("test", new StringPayload("somedata")); + } + + public void testReserveWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images")) + .headers( + ImmutableMultimap.builder() + .put("x-image-meta-name", "test").put("Accept", MediaType.APPLICATION_JSON).put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image.json")).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, createResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertEquals(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").reserve("test"), new ParseImageDetailsTest().expected()); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testReserveWhenResponseIs4xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images")) + .headers( + ImmutableMultimap.builder() + .put("x-image-meta-name", "test").put("Accept", MediaType.APPLICATION_JSON).put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(401) + .payload(payloadFromResource("/image.json")).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, createResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").reserve("test"); + } + public void testUpdateMetadataWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("PUT") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("Accept", MediaType.APPLICATION_JSON) + .put("X-Image-Meta-Name", "newname") + .put("X-Image-Meta-Is_public", "true") + .put("X-Image-Meta-Protected", "true") + .put("X-Image-Meta-Checksum", "XXXX") + .put("X-Image-Meta-Location", "somewhere") + .put("X-Image-Meta-Min_disk", "10") + .put("X-Image-Meta-Min_ram", "2048") + .put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse updateResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image.json")).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, updateResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertEquals(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1") + .update("fcc451d0-f6e4-4824-ad8f-70ec12326d07", + UpdateImageOptions.Builder.name("newname"), + UpdateImageOptions.Builder.isPublic(true), + UpdateImageOptions.Builder.isProtected(true), + UpdateImageOptions.Builder.checksum("XXXX"), + UpdateImageOptions.Builder.location("somewhere"), + UpdateImageOptions.Builder.minDisk(10), + UpdateImageOptions.Builder.minRam(2048)), + new ParseImageDetailsTest().expected()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testUpdateMetadataWhenResponseIs4xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("PUT") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("Accept", MediaType.APPLICATION_JSON) + .put("X-Image-Meta-Name", "newname") + .put("X-Image-Meta-Is_public", "true") + .put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse updateResponse = HttpResponse.builder().statusCode(404).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, updateResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1") + .update("fcc451d0-f6e4-4824-ad8f-70ec12326d07", + UpdateImageOptions.Builder.name("newname"), + UpdateImageOptions.Builder.isPublic(true)); + } + + public void testUpdateImageWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("PUT") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("Accept", MediaType.APPLICATION_JSON).put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType("somenewdata", MediaType.APPLICATION_OCTET_STREAM)) + .build(); + + HttpResponse updateResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image.json")).build(); + + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, updateResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertEquals(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").upload("fcc451d0-f6e4-4824-ad8f-70ec12326d07", + new StringPayload("somenewdata")), new ParseImageDetailsTest().expected()); + } + + public void testUpdateNameAndImageWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("PUT") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("Accept", MediaType.APPLICATION_JSON) + .put("X-Image-Meta-Name", "anothernewname") + .put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType("somenewdata", MediaType.APPLICATION_OCTET_STREAM)) + .build(); + + HttpResponse updateResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image.json")).build(); + + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, updateResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertEquals(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").upload("fcc451d0-f6e4-4824-ad8f-70ec12326d07", + new StringPayload("somenewdata"), UpdateImageOptions.Builder.name("anothernewname")), new ParseImageDetailsTest().expected()); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testUpdateNameAndImageWhenResponseIs4xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("PUT") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("Accept", MediaType.APPLICATION_JSON) + .put("X-Image-Meta-Name", "anothernewname") + .put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType("somenewdata", MediaType.APPLICATION_OCTET_STREAM)) + .build(); + + HttpResponse updateResponse = HttpResponse.builder().statusCode(403).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, updateResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").upload("fcc451d0-f6e4-4824-ad8f-70ec12326d07", + new StringPayload("somenewdata"), UpdateImageOptions.Builder.name("anothernewname")); + } + + public void testDeleteWhenResponseIs2xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("DELETE") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse getResponse = HttpResponse.builder().statusCode(200).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, getResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertTrue(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").delete("fcc451d0-f6e4-4824-ad8f-70ec12326d07")); + } + + public void testDeleteWhenResponseIs4xx() throws Exception { + HttpRequest get = HttpRequest + .builder() + .method("DELETE") + .endpoint(URI.create("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07")) + .headers( + ImmutableMultimap.builder() + .put("X-Auth-Token", authToken).build()) + .build(); + + HttpResponse getResponse = HttpResponse.builder().statusCode(404).build(); + + GlanceClient clientWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, get, getResponse); + + assertEquals(clientWhenExist.getConfiguredRegions(), ImmutableSet.of("az-1.region-a.geo-1")); + + assertFalse(clientWhenExist.getImageClientForRegion("az-1.region-a.geo-1").delete("fcc451d0-f6e4-4824-ad8f-70ec12326d07")); + } } diff --git a/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientLiveTest.java b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientLiveTest.java index 88f03d3115..76d835e706 100644 --- a/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientLiveTest.java +++ b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageClientLiveTest.java @@ -18,17 +18,29 @@ */ package org.jclouds.openstack.glance.v1_0.features; + +import static org.jclouds.openstack.glance.v1_0.options.CreateImageOptions.Builder.containerFormat; +import static org.jclouds.openstack.glance.v1_0.options.CreateImageOptions.Builder.diskFormat; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.util.Set; +import org.jclouds.io.payloads.StringPayload; +import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; import org.jclouds.openstack.glance.v1_0.domain.Image; import org.jclouds.openstack.glance.v1_0.domain.ImageDetails; import org.jclouds.openstack.glance.v1_0.internal.BaseGlanceClientLiveTest; +import org.jclouds.openstack.glance.v1_0.options.ListImageOptions; +import org.jclouds.openstack.glance.v1_0.options.UpdateImageOptions; import org.testng.annotations.Test; +import com.google.common.collect.Iterables; + /** * @author Adrian Cole + * @author Adam Lowe */ @Test(groups = "live", testName = "ImageClientLiveTest") public class ImageClientLiveTest extends BaseGlanceClientLiveTest { @@ -37,7 +49,7 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest { public void testList() throws Exception { for (String zoneId : glanceContext.getApi().getConfiguredRegions()) { ImageClient client = glanceContext.getApi().getImageClientForRegion(zoneId); - Set response = client.list(); + Set response = client.list(ListImageOptions.Builder.limit(100)); assert null != response; for (Image image : response) { checkImage(image); @@ -47,10 +59,8 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest { private void checkImage(Image image) { assert image.getId() != null : image; - assert image.getSize().isPresent() : image; - assert image.getChecksum().isPresent() : image; - assert image.getContainerFormat().isPresent() : image; - assert image.getContainerFormat().isPresent() : image; + assert image.getName() != null : image; + assert image.getLinks() != null : image; } @Test @@ -69,7 +79,9 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest { } private void checkImageDetails(ImageDetails image) { - //TODO + checkImage(image); + assertTrue(image.getMinDisk() >= 0); + assertTrue(image.getMinRam() >= 0); } private void checkImageDetailsEqual(ImageDetails image, ImageDetails newDetails) { @@ -78,4 +90,54 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest { assertEquals(newDetails.getLinks(), image.getLinks()); } + @Test + public void testCreateUpdateAndDeleteImage() { + StringPayload imageData = new StringPayload("This isn't really an image!"); + for (String zoneId : glanceContext.getApi().getConfiguredRegions()) { + ImageClient client = glanceContext.getApi().getImageClientForRegion(zoneId); + ImageDetails details = client.create("jclouds-live-test", imageData, diskFormat(DiskFormat.RAW), containerFormat(ContainerFormat.BARE)); + assertEquals(details.getName(), "jclouds-live-test"); + assertEquals(details.getSize().get().longValue(), imageData.getRawContent().length()); + + details = client.update(details.getId(), UpdateImageOptions.Builder.name("jclouds-live-test2"), UpdateImageOptions.Builder.minDisk(10)); + assertEquals(details.getName(), "jclouds-live-test2"); + assertEquals(details.getMinDisk(), 10); + + Image fromListing = Iterables.getOnlyElement(client.list(ListImageOptions.Builder.name("jclouds-live-test2"), ListImageOptions.Builder.limit(2), ListImageOptions.Builder.containerFormat(ContainerFormat.BARE))); + assertEquals(fromListing.getId(), details.getId()); + assertEquals(fromListing.getSize(), details.getSize()); + + assertEquals(Iterables.getOnlyElement(client.listInDetail(ListImageOptions.Builder.name("jclouds-live-test2"))), details); + + assertTrue(client.delete(details.getId())); + + assertTrue(client.list(ListImageOptions.Builder.name("jclouds-live-test2")).isEmpty()); + } + } + + @Test + public void testReserveUploadAndDeleteImage() { + StringPayload imageData = new StringPayload("This isn't an image!"); + for (String zoneId : glanceContext.getApi().getConfiguredRegions()) { + ImageClient client = glanceContext.getApi().getImageClientForRegion(zoneId); + ImageDetails details = client.reserve("jclouds-live-res-test", diskFormat(DiskFormat.RAW), containerFormat(ContainerFormat.BARE)); + assertEquals(details.getName(), "jclouds-live-res-test"); + + details = client.upload(details.getId(), imageData, UpdateImageOptions.Builder.name("jclouds-live-res-test2"), UpdateImageOptions.Builder.minDisk(10)); + assertEquals(details.getName(), "jclouds-live-res-test2"); + assertEquals(details.getSize().get().longValue(), imageData.getRawContent().length()); + assertEquals(details.getMinDisk(), 10); + + Image fromListing = Iterables.getOnlyElement(client.list(ListImageOptions.Builder.name("jclouds-live-res-test2"), ListImageOptions.Builder.limit(2), ListImageOptions.Builder.containerFormat(ContainerFormat.BARE))); + assertEquals(fromListing.getId(), details.getId()); + assertEquals(fromListing.getSize(), details.getSize()); + + assertEquals(Iterables.getOnlyElement(client.listInDetail(ListImageOptions.Builder.name("jclouds-live-res-test2"))), details); + + assertTrue(client.delete(details.getId())); + + assertTrue(client.list(ListImageOptions.Builder.name("jclouds-live-res-test2")).isEmpty()); + } + } + } diff --git a/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/parse/ParseImageDetailsTest.java b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/parse/ParseImageDetailsTest.java new file mode 100644 index 0000000000..68bc45b8d1 --- /dev/null +++ b/labs/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/parse/ParseImageDetailsTest.java @@ -0,0 +1,65 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.openstack.glance.v1_0.parse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.json.BaseItemParserTest; +import org.jclouds.openstack.glance.v1_0.domain.ContainerFormat; +import org.jclouds.openstack.glance.v1_0.domain.DiskFormat; +import org.jclouds.openstack.glance.v1_0.domain.Image; +import org.jclouds.openstack.glance.v1_0.domain.ImageDetails; +import org.jclouds.rest.annotations.SelectJson; +import org.testng.annotations.Test; + +/** + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "ParseImageDetailTest") +public class ParseImageDetailsTest extends BaseItemParserTest { + + @Override + public String resource() { + return "/image.json"; + } + + @Override + @SelectJson("image") + @Consumes(MediaType.APPLICATION_JSON) + public ImageDetails expected() { + return ImageDetails + .builder() + .id("02fa0378-f305-43cf-8058-8572fe1da795") + .name("jclouds-live-test") + .containerFormat(ContainerFormat.BARE) + .diskFormat(DiskFormat.RAW) + .checksum("6ae4e0fdc3c108a1bfe10ef5e436f4f4") + .size(27) + .status(Image.Status.ACTIVE) + .owner("68a7c7abb7bf45ada1536dfa28ec2115") + .isPublic(false) + .createdAt(new SimpleDateFormatDateService().iso8601SecondsDateParse("2012-05-31T10:13:47")) + .updatedAt(new SimpleDateFormatDateService().iso8601SecondsDateParse("2012-05-31T10:13:47")) + .build(); + } + +} diff --git a/labs/openstack-glance/src/test/resources/image.json b/labs/openstack-glance/src/test/resources/image.json new file mode 100644 index 0000000000..0e47c7865c --- /dev/null +++ b/labs/openstack-glance/src/test/resources/image.json @@ -0,0 +1 @@ +{"image": {"status": "active", "name": "jclouds-live-test", "deleted": false, "container_format": "bare", "created_at": "2012-05-31T10:13:47", "disk_format": "raw", "updated_at": "2012-05-31T10:13:47", "properties": {}, "min_disk": 0, "protected": false, "id": "02fa0378-f305-43cf-8058-8572fe1da795", "checksum": "6ae4e0fdc3c108a1bfe10ef5e436f4f4", "owner": "68a7c7abb7bf45ada1536dfa28ec2115", "is_public": false, "deleted_at": null, "min_ram": 0, "size": 27}} \ No newline at end of file