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..26f62eded8 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
@@ -48,6 +48,10 @@ public class BaseListOptions extends BaseHttpRequestOptions {
* Indicates where to begin listing. The list will only include objects that occur after the
* offset. This is convenient for pagination: To get the next page of results use the last result
* number of the current page + current page offset as the offset.
+ *
+ * This isn't supported by newer openstack API implementations
+ *
+ * @see #marker(String) for the new mechanism to set the page offset
*/
public BaseListOptions startAt(long offset) {
checkState(offset >= 0, "offset must be >= 0");
@@ -55,6 +59,19 @@ public class BaseListOptions extends BaseHttpRequestOptions {
return this;
}
+ /**
+ * The marker parameter is the ID of the last item in the previous list
+ * (i.e. return the page of items after the marker).
+ *
+ * This is only supported by newer openstack API implementations
+ *
+ * @see #startAt for the old mechanism to set the page offset
+ */
+ public BaseListOptions marker(String marker) {
+ queryParameters.put("marker", checkNotNull(marker, "marker"));
+ return this;
+ }
+
/**
* To reduce load on the service, list operations will return a maximum of 1,000 items at a time.
* To navigate the collection, the parameters limit and offset can be set in the URI
@@ -81,7 +98,15 @@ public class BaseListOptions extends BaseHttpRequestOptions {
}
/**
- * @see BaseListOptions#maxResults(long)
+ * @see BaseListOptions#marker
+ */
+ public static BaseListOptions marker(String marker) {
+ BaseListOptions options = new BaseListOptions();
+ return options.marker(marker);
+ }
+
+ /**
+ * @see BaseListOptions#maxResults
*/
public static BaseListOptions maxResults(int maxKeys) {
BaseListOptions options = new BaseListOptions();
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..112e2b140a
--- /dev/null
+++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/CreateImageOptions.java
@@ -0,0 +1,126 @@
+/**
+ * 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;
+
+/**
+ * @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..b095a1cd88
--- /dev/null
+++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/ListImageOptions.java
@@ -0,0 +1,240 @@
+/**
+ * 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 java.util.Date;
+
+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;
+import org.jclouds.openstack.options.BaseListOptions;
+
+/**
+ * @author Adam Lowe
+ * @see
+ */
+public class ListImageOptions extends BaseListOptions {
+ /**
+ * 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 BaseListOptions#maxResults
+ */
+ public static ListImageOptions maxResults(int limit) {
+ return ListImageOptions.class.cast(new ListImageOptions().maxResults(limit));
+ }
+
+ /**
+ * @see BaseListOptions#marker
+ */
+ public static ListImageOptions marker(String marker) {
+ return ListImageOptions.class.cast(new ListImageOptions().marker(marker));
+ }
+
+ /**
+ * @see BaseListOptions#changesSince
+ */
+ public static ListImageOptions changesSince(Date ifModifiedSince) {
+ return ListImageOptions.class.cast(new BaseListOptions().changesSince(ifModifiedSince));
+ }
+ }
+}
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..834ae9a7f3
--- /dev/null
+++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/options/UpdateImageOptions.java
@@ -0,0 +1,233 @@
+/**
+ * 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;
+
+/**
+ * @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..62bf4d7e6c 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,34 @@
*/
package org.jclouds.openstack.glance.v1_0.features;
-import static org.testng.Assert.assertEquals;
+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 static org.testng.Assert.fail;
+
+import java.io.File;
import java.util.Set;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.FilePayload;
+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.base.Optional;
+import com.google.common.collect.Iterables;
+
/**
* @author Adrian Cole
+ * @author Adam Lowe
*/
@Test(groups = "live", testName = "ImageClientLiveTest")
public class ImageClientLiveTest extends BaseGlanceClientLiveTest {
@@ -37,7 +54,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.maxResults(100));
assert null != response;
for (Image image : response) {
checkImage(image);
@@ -49,8 +66,6 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest {
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;
}
@Test
@@ -69,7 +84,8 @@ public class ImageClientLiveTest extends BaseGlanceClientLiveTest {
}
private void checkImageDetails(ImageDetails image) {
- //TODO
+ checkImage(image);
+ // TODO
}
private void checkImageDetailsEqual(ImageDetails image, ImageDetails newDetails) {
@@ -78,4 +94,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.maxResults(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.maxResults(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