From 01de74236b064191cd1a05e2d2e5087ef86a4b85 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Mon, 23 Apr 2012 15:32:17 +0100 Subject: [PATCH 01/12] Adding Volumes extension - first stage includes get/list volumes and list attachments --- .../openstack/nova/v1_1/NovaAsyncClient.java | 7 + .../openstack/nova/v1_1/NovaClient.java | 9 + .../v1_1/config/NovaRestClientModule.java | 1 + .../nova/v1_1/domain/Attachment.java | 160 +++++++++ .../openstack/nova/v1_1/domain/Snapshot.java | 220 ++++++++++++ .../openstack/nova/v1_1/domain/Volume.java | 316 ++++++++++++++++++ .../v1_1/extensions/VolumeAsyncClient.java | 224 +++++++++++++ .../nova/v1_1/extensions/VolumeClient.java | 139 ++++++++ ...paceEqualsAnyNamespaceInExtensionsSet.java | 2 + .../extensions/VolumeClientExpectTest.java | 129 +++++++ .../v1_1/extensions/VolumeClientLiveTest.java | 128 +++++++ .../src/test/resources/attachment_list.json | 1 + .../src/test/resources/volume_details.json | 1 + .../src/test/resources/volume_list.json | 1 + .../test/resources/volume_list_detail.json | 1 + 15 files changed, 1339 insertions(+) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java create mode 100644 apis/openstack-nova/src/test/resources/attachment_list.json create mode 100644 apis/openstack-nova/src/test/resources/volume_details.json create mode 100644 apis/openstack-nova/src/test/resources/volume_list.json create mode 100644 apis/openstack-nova/src/test/resources/volume_list_detail.json diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index da9d6507b5..9f5d32b035 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -28,6 +28,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageAsyncClient; +import org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; @@ -120,4 +121,10 @@ public interface NovaAsyncClient { Optional getSimpleTenantUsageExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides asynchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index d33e0ab91b..37dc81e228 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -30,6 +30,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageClient; +import org.jclouds.openstack.nova.v1_1.extensions.VolumeClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionClient; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; @@ -122,4 +123,12 @@ public interface NovaClient { Optional getSimpleTenantUsageExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index fdb3b039a8..8a9a6e20bc 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -73,6 +73,7 @@ public class NovaRestClientModule extends RestClientModule builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromAttachment(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String volumeId; + private String serverId; + private String device; + + public T id(String id) { + this.id = id; + return self(); + } + + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + public T serverId(String serverId) { + this.serverId = serverId; + return self(); + } + + public T device(String device) { + this.device = device; + return self(); + } + + public Attachment build() { + return new Attachment(this); + } + + public T fromAttachment(Attachment in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .serverId(in.getServerId()) + .device(in.getDevice()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final String volumeId; + private final String serverId; + private final String device; + + protected Attachment(Builder builder) { + this.id = builder.id; + this.volumeId = builder.volumeId; + this.serverId = builder.serverId; + this.device = builder.device; + } + + /** + */ + @Nullable + public String getId() { + return this.id; + } + + /** + */ + @Nullable + public String getVolumeId() { + return this.volumeId; + } + + /** + */ + @Nullable + public String getServerId() { + return this.serverId; + } + + /** + */ + @Nullable + public String getDevice() { + return this.device; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, serverId, device); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Attachment that = Attachment.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.serverId, that.serverId) + && Objects.equal(this.device, that.device) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("volumeId", volumeId) + .add("serverId", serverId) + .add("device", device) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java new file mode 100644 index 0000000000..d2db66289c --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java @@ -0,0 +1,220 @@ +/** + * 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.nova.v1_1.domain; + +import java.util.Date; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.gson.annotations.SerializedName; + +/** + * An Openstack Nova Volume Snapshot + */ +public class Snapshot { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromSnapshot(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String volumeId; + private String status; + private int size; + private Date created; + private String name; + private String description; + + public T id(String id) { + this.id = id; + return self(); + } + + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + public T status(String status) { + this.status = status; + return self(); + } + + public T size(int size) { + this.size = size; + return self(); + } + + public T created(Date created) { + this.created = created; + return self(); + } + + public T name(String name) { + this.name = name; + return self(); + } + + public T description(String description) { + this.description = description; + return self(); + } + + public Snapshot build() { + return new Snapshot(this); + } + + public T fromSnapshot(Snapshot in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .status(in.getStatus()) + .size(in.getSize()) + .created(in.getCreated()) + .name(in.getName()) + .description(in.getDescription()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final String volumeId; + private final String status; + private final int size; + @SerializedName(value="createdAt") + private final Date created; + @SerializedName(value="displayName") + private final String name; + @SerializedName(value="displayDescription") + private final String description; + + protected Snapshot(Builder builder) { + this.id = builder.id; + this.volumeId = builder.volumeId; + this.status = builder.status; + this.size = builder.size; + this.created = builder.created; + this.name = builder.name; + this.description = builder.description; + } + + /** + */ + @Nullable + public String getId() { + return this.id; + } + + /** + */ + @Nullable + public String getVolumeId() { + return this.volumeId; + } + + /** + */ + @Nullable + public String getStatus() { + return this.status; + } + + /** + */ + @Nullable + public int getSize() { + return this.size; + } + + /** + */ + @Nullable + public Date getCreated() { + return this.created; + } + + /** + */ + @Nullable + public String getName() { + return this.name; + } + + /** + */ + @Nullable + public String getDescription() { + return this.description; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, status, size, created, name, description); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Snapshot that = Snapshot.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.created, that.created) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("volumeId", volumeId) + .add("status", status) + .add("size", size) + .add("created", created) + .add("name", name) + .add("description", description) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java new file mode 100644 index 0000000000..7b179a1b7a --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -0,0 +1,316 @@ +/** + * 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.nova.v1_1.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * An Openstack Nova Volume + */ +public class Volume { + + public static enum Status { + CREATING, AVAILABLE, IN_USE, DELETING, ERROR, UNRECOGNIZED; + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + @Override + public String toString() { + return value(); + } + + public static Status fromValue(String status) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(status, "status"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVolume(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private Status status; + private int size; + private String zone; + private Date created; + private Set attachments = Sets.newLinkedHashSet(); + private String volumeType; + private String snapshotId; + private String name; + private String description; + private Map metadata = Maps.newHashMap(); + + public T id(String id) { + this.id = id; + return self(); + } + + public T status(Status status) { + this.status = status; + return self(); + } + + public T size(int size) { + this.size = size; + return self(); + } + + public T zone(String zone) { + this.zone = zone; + return self(); + } + + public T created(Date created) { + this.created = created; + return self(); + } + + public T attachments(Set attachments) { + this.attachments = attachments; + return self(); + } + + public T volumeType(String volumeType) { + this.volumeType = volumeType; + return self(); + } + + public T snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return self(); + } + + public T metadata(Map metadata) { + this.metadata = metadata; + return self(); + } + + public T name(String name) { + this.name = name; + return self(); + } + + public T description(String description) { + this.description = description; + return self(); + } + + public Volume build() { + return new Volume(this); + } + + public T fromVolume(Volume in) { + return this + .id(in.getId()) + .status(in.getStatus()) + .size(in.getSize()) + .zone(in.getZone()) + .created(in.getCreated()) + .attachments(in.getAttachments()) + .volumeType(in.getVolumeType()) + .snapshotId(in.getSnapshotId()) + .metadata(in.getMetadata()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final Status status; + private final int size; + @SerializedName(value="availabilityZone") + private final String zone; + @SerializedName(value="createdAt") + private final Date created; + private final Set attachments; + private final String volumeType; + private final String snapshotId; + @SerializedName(value="displayName") + private final String name; + @SerializedName(value="displayDescription") + private final String description; + private final Map metadata; + + protected Volume(Builder builder) { + this.id = builder.id; + this.status = builder.status; + this.size = builder.size; + this.zone = builder.zone; + this.created = builder.created; + this.attachments = ImmutableSet.copyOf(checkNotNull(builder.attachments, "attachments")); + this.volumeType = builder.volumeType; + this.snapshotId = builder.snapshotId; + this.name = builder.name; + this.description = builder.description; + this.metadata = ImmutableMap.copyOf(builder.metadata); + } + + /** + */ + public String getId() { + return this.id; + } + + /** + */ + public Status getStatus() { + return this.status; + } + + /** + */ + public int getSize() { + return this.size; + } + + /** + */ + public String getZone() { + return this.zone; + } + + /** + */ + public Date getCreated() { + return this.created; + } + + /** + */ + @Nullable + public Set getAttachments() { + return Collections.unmodifiableSet(this.attachments); + } + + /** + */ + @Nullable + public String getVolumeType() { + return this.volumeType; + } + + /** + */ + @Nullable + public String getSnapshotId() { + return this.snapshotId; + } + + /** + */ + @Nullable + public String getName() { + return this.name; + } + + /** + */ + @Nullable + public String getDescription() { + return this.description; + } + + /** + */ + @Nullable + public Map getMetadata() { + return Collections.unmodifiableMap(this.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Volume that = Volume.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.zone, that.zone) + && Objects.equal(this.created, that.created) + && Objects.equal(this.attachments, that.attachments) + && Objects.equal(this.volumeType, that.volumeType) + && Objects.equal(this.snapshotId, that.snapshotId) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description) + && Objects.equal(this.metadata, that.metadata) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("status", status) + .add("size", size) + .add("zone", zone) + .add("created", created) + .add("attachments", attachments) + .add("volumeType", volumeType) + .add("snapshotId", snapshotId) + .add("name", name) + .add("description", description) + .add("metadata", metadata) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java new file mode 100644 index 0000000000..b8b1275542 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java @@ -0,0 +1,224 @@ +/** + * 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.nova.v1_1.extensions; + +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.filters.AuthenticateRequest; +import org.jclouds.openstack.nova.v1_1.domain.Attachment; +import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +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; + +/** + * Provides synchronous access to Volumes. + *

+ * + * @see org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VolumeAsyncClient { + /** + * Returns a summary list of volumes. + * + * @return the list of volumes + */ + @GET + @Path("/os-volumes") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listVolumes(); + + /** + * Returns a detailed list of volumes. + * + * @return the list of volumes. + */ + @GET + @Path("/os-volumes/detail") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listVolumesInDetail(); + + /** + * Return data about the given volume. + * + * @return details of a specific volume. + */ + @GET + @Path("/os-volumes/{id}") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getVolume(@PathParam("id") String volumeId); + + /** + * Creates a new volume + * + * @return the new Snapshot + */ + @POST + @Path("/os-volumes/") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture createVolume(@PayloadParam("volume") Volume createVolume); + + /** + * Delete a volume. + * + * @return true if successful + */ + @DELETE + @Path("/os-volumes/{id}") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture deleteVolume(@PathParam("id") String volumeId); + + /** + * List volume attachments for a given instance. + * + * @return all Floating IPs + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments") + @SelectJson("volumeAttachments") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + + /** + * Get a specific attached volume. + * + * @return data about the given volume attachment. + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @SelectJson("volumeAttachment") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + + /** + * Attach a volume to an instance + * + * @return the new Attachment + */ + @POST + @Path("/servers/{server_id}/os-volume_attachments/") + @SelectJson("volumeAttachment") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture attachVolume(@PathParam("server_id") String serverId, @PayloadParam("attachment") Attachment attachVolume); + + /** + * Detach a Volume from an instance. + * + * @return true if successful + */ + @DELETE + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture detachVolume(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + @GET + @Path("/os-snapshots") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listSnapshots(); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + @GET + @Path("/os-snapshots/detail") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listSnapshotsInDetail(); + + /** + * Return data about the given snapshot. + * + * @return details of a specific snapshot. + */ + @GET + @Path("/os-snapshots/{id}") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getSnapshot(@PathParam("id") String snapshotId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + @GET + @Path("/os-snapshots/detail") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture createSnapshot(@PayloadParam("snapshot") Snapshot createSnapshot); + + /** + * Delete a snapshot. + * + * @return true if successful + */ + @DELETE + @Path("/os-snapshots/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture deleteSnapshot(@PathParam("id") String snapshotId); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java new file mode 100644 index 0000000000..2eea3ddbfa --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java @@ -0,0 +1,139 @@ +/** + * 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.nova.v1_1.extensions; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.nova.v1_1.domain.Attachment; +import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; + +/** + * Provides synchronous access to Volumes. + *

+ * + * @see org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VolumeClient { + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listVolumes(); + + /** + * Returns a detailed list of volumes. + * + * @return the list of volumes. + */ + Set listVolumesInDetail(); + + /** + * Return data about the given volume. + * + * @return details of a specific snapshot. + */ + Volume getVolume(String volumeId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + Volume createVolume(Volume createVolume); + + /** + * Delete a snapshot. + * + * @return + */ + Boolean deleteVolume(String volumeId); + + /** + * List volume attachments for a given instance. + * + * @return all Floating IPs + */ + Set listAttachments(String serverId); + + /** + * Get a specific attached volume. + * + * @return data about the given volume attachment. + */ + Attachment getAttachment(String serverId, String volumeId); + + /** + * Attach a volume to an instance + * + * @return a new Volume + */ + Attachment attachVolume(String serverId, Attachment attachVolume); + + /** + * Detach a Volume from an instance. + * + * @return + */ + Boolean detachVolume(String server_id, String volumeId); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listSnapshots(); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listSnapshotsInDetail(); + + /** + * Return data about the given snapshot. + * + * @return details of a specific snapshot. + */ + Snapshot getSnapshot(String snapshotId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + Snapshot createSnapshot(Snapshot createSnapshot); + + /** + * Delete a snapshot. + * + * @return + */ + Boolean deleteSnapshot(String snapshotId); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java index 5db7bb9d46..85406b5e4c 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java @@ -66,6 +66,8 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio URI.create("http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1")) .put(URI.create(ExtensionNamespaces.HOSTS), URI.create("http://docs.openstack.org/compute/ext/hosts/api/v1.1")) + .put(URI.create(ExtensionNamespaces.VOLUMES), + URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1")) .build(); @Inject diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java new file mode 100644 index 0000000000..1f1633deab --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -0,0 +1,129 @@ +/** + * 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.nova.v1_1.extensions; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; +import java.util.Set; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.nova.v1_1.domain.Attachment; +import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests VolumeClient guice wiring and parsing + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "VolumeClientExpectTest") +public class VolumeClientExpectTest extends BaseNovaClientExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListVolumes() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumes(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + + public void testListVolumesInDetail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_detail.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumesInDetail(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + public void testGetVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Volume volume = client.getVolume("1"); + assertEquals(volume, testVolume()); + // double-check equals() + assertEquals(volume.getStatus(), Volume.Status.IN_USE); + assertEquals(volume.getDescription(), "This is a test volume"); + assertEquals(volume.getZone(), "nova"); + assertEquals(volume.getName(), "test"); + assertEquals(volume.getStatus(), Volume.Status.IN_USE); + assertEquals(Iterables.getOnlyElement(volume.getAttachments()), testAttachment()); + } + + public void testListAttachments() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set attachments = client.listAttachments("instance-1"); + assertEquals(attachments, ImmutableSet.of(testAttachment())); + // double-check equals() + Attachment attachment = Iterables.getOnlyElement(attachments); + assertEquals(attachment.getDevice(), "/dev/vdc"); + assertEquals(attachment.getServerId(), "b4785058-cb80-491b-baa3-e4ee6546450e"); + assertEquals(attachment.getId(), "1"); + assertEquals(attachment.getVolumeId(), "1"); + } + + protected Volume testVolume() { + return Volume.builder().status(Volume.Status.IN_USE).description("This is a test volume").zone("nova").name("test") + .attachments(ImmutableSet.of(testAttachment())).size(1).id("1").created(dateService.iso8601SecondsDateParse("2012-04-23 12:16:45")).build(); + } + + protected Attachment testAttachment() { + return Attachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java new file mode 100644 index 0000000000..a254df0f85 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -0,0 +1,128 @@ +/** + * 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.nova.v1_1.extensions; + +import static org.testng.Assert.*; + +import java.util.Set; + +import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.nova.v1_1.domain.Attachment; +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.SimpleTenantUsage; +import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of SimpleTenantUsageClient + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) +public class VolumeClientLiveTest extends BaseNovaClientLiveTest { + + private VolumeClient client; + private String zone; + + @BeforeMethod(alwaysRun = true) + public void setUpClient() { + zone = Iterables.getFirst(novaContext.getApi().getConfiguredZones(), "nova"); + Optional optClient = novaContext.getApi().getVolumeExtensionForZone(zone); + if (optClient.isPresent()) { + client = optClient.get(); + } + } + + public void testListVolumes() throws Exception { + if (client != null) { + Set volumes = client.listVolumes(); + assertNotNull(volumes); + for (Volume vol : volumes) { + Volume details = client.getVolume(vol.getId()); + assertNotNull(details); + } + } + } + + public void testListVolumesInDetail() throws Exception { + if (client != null) { + Set volumes = client.listVolumesInDetail(); + assertNotNull(volumes); + for (Volume vol : volumes) { + Volume details = client.getVolume(vol.getId()); + assertNotNull(details); + assertEquals(details, vol); + assertNotNull(details.getId()); + assertNotNull(details.getStatus()); + assertTrue(details.getSize() > -1); + assertNotNull(details.getCreated()); + } + } + } + + public void testListSnapshots() throws Exception { + if (client != null) { + Set snapshots = client.listSnapshots(); + assertNotNull(snapshots); + for (Snapshot snap : snapshots) { + Snapshot details = client.getSnapshot(snap.getId()); + assertNotNull(details); + assertNotNull(details.getId()); + } + } + } + + public void testListSnapshotsInDetail() throws Exception { + if (client != null) { + Set snapshots = client.listSnapshotsInDetail(); + assertNotNull(snapshots); + for (Snapshot snap : snapshots) { + Snapshot details = client.getSnapshot(snap.getId()); + assertNotNull(details); + assertEquals(details, snap); + } + } + } + + public void testListAttachments() throws Exception { + if (client != null) { + Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); + if (!servers.isEmpty()) { + String serverId = Iterables.getFirst(servers, null).getId(); + Set attachments = client.listAttachments(serverId); + assertNotNull(attachments); + assertTrue(attachments.size() > 0); + for (Attachment att : attachments) { + Attachment details = client.getAttachment(serverId, att.getId()); + assertNotNull(details); + assertNotNull(details.getId()); + assertNotNull(details.getServerId()); + assertNotNull(details.getVolumeId()); + } + } + } + } +} diff --git a/apis/openstack-nova/src/test/resources/attachment_list.json b/apis/openstack-nova/src/test/resources/attachment_list.json new file mode 100644 index 0000000000..b40de0bd46 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/attachment_list.json @@ -0,0 +1 @@ +{"volumeAttachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_details.json b/apis/openstack-nova/src/test/resources/volume_details.json new file mode 100644 index 0000000000..8dd11dc4e6 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_details.json @@ -0,0 +1 @@ +{"volume": {"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_list.json b/apis/openstack-nova/src/test/resources/volume_list.json new file mode 100644 index 0000000000..d2179e799d --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_list.json @@ -0,0 +1 @@ +{"volumes": [{"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_list_detail.json b/apis/openstack-nova/src/test/resources/volume_list_detail.json new file mode 100644 index 0000000000..d2179e799d --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_list_detail.json @@ -0,0 +1 @@ +{"volumes": [{"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}]} \ No newline at end of file From 58d4de4bbe4dc4b5fc940a473683345365ce4c53 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 14:36:16 +0100 Subject: [PATCH 02/12] Adding CreateVolume and CreateSnapshot options and improving live tests accordingly --- .../openstack/nova/v1_1/domain/Volume.java | 8 +- ...{Attachment.java => VolumeAttachment.java} | 12 +- .../{Snapshot.java => VolumeSnapshot.java} | 20 +- .../v1_1/extensions/VolumeAsyncClient.java | 46 ++-- .../nova/v1_1/extensions/VolumeClient.java | 30 +-- .../v1_1/options/CreateVolumeOptions.java | 224 ++++++++++++++++++ .../options/CreateVolumeSnapshotOptions.java | 140 +++++++++++ .../v1_1/extensions/VolumeClientLiveTest.java | 196 ++++++++++++--- 8 files changed, 597 insertions(+), 79 deletions(-) rename apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/{Attachment.java => VolumeAttachment.java} (92%) rename apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/{Snapshot.java => VolumeSnapshot.java} (92%) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java index 7b179a1b7a..8a77bdbb8e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -77,7 +77,7 @@ public class Volume { private int size; private String zone; private Date created; - private Set attachments = Sets.newLinkedHashSet(); + private Set attachments = Sets.newLinkedHashSet(); private String volumeType; private String snapshotId; private String name; @@ -109,7 +109,7 @@ public class Volume { return self(); } - public T attachments(Set attachments) { + public T attachments(Set attachments) { this.attachments = attachments; return self(); } @@ -173,7 +173,7 @@ public class Volume { private final String zone; @SerializedName(value="createdAt") private final Date created; - private final Set attachments; + private final Set attachments; private final String volumeType; private final String snapshotId; @SerializedName(value="displayName") @@ -229,7 +229,7 @@ public class Volume { /** */ @Nullable - public Set getAttachments() { + public Set getAttachments() { return Collections.unmodifiableSet(this.attachments); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java similarity index 92% rename from apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java rename to apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index a9388b201d..ef93d9d18b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -26,7 +26,7 @@ import com.google.common.base.Objects.ToStringHelper; /** * An Openstack Nova Volume Attachment */ -public class Attachment { +public class VolumeAttachment { public static Builder builder() { return new ConcreteBuilder(); @@ -64,11 +64,11 @@ public class Attachment { return self(); } - public Attachment build() { - return new Attachment(this); + public VolumeAttachment build() { + return new VolumeAttachment(this); } - public T fromAttachment(Attachment in) { + public T fromAttachment(VolumeAttachment in) { return this .id(in.getId()) .volumeId(in.getVolumeId()) @@ -91,7 +91,7 @@ public class Attachment { private final String serverId; private final String device; - protected Attachment(Builder builder) { + protected VolumeAttachment(Builder builder) { this.id = builder.id; this.volumeId = builder.volumeId; this.serverId = builder.serverId; @@ -135,7 +135,7 @@ public class Attachment { public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - Attachment that = Attachment.class.cast(obj); + VolumeAttachment that = VolumeAttachment.class.cast(obj); return Objects.equal(this.id, that.id) && Objects.equal(this.volumeId, that.volumeId) && Objects.equal(this.serverId, that.serverId) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java similarity index 92% rename from apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java rename to apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index d2db66289c..fa7ca86cb8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -29,7 +29,7 @@ import com.google.gson.annotations.SerializedName; /** * An Openstack Nova Volume Snapshot */ -public class Snapshot { +public class VolumeSnapshot { public static Builder builder() { return new ConcreteBuilder(); @@ -44,7 +44,7 @@ public class Snapshot { private String id; private String volumeId; - private String status; + private Volume.Status status; private int size; private Date created; private String name; @@ -60,7 +60,7 @@ public class Snapshot { return self(); } - public T status(String status) { + public T status(Volume.Status status) { this.status = status; return self(); } @@ -85,11 +85,11 @@ public class Snapshot { return self(); } - public Snapshot build() { - return new Snapshot(this); + public VolumeSnapshot build() { + return new VolumeSnapshot(this); } - public T fromSnapshot(Snapshot in) { + public T fromSnapshot(VolumeSnapshot in) { return this .id(in.getId()) .volumeId(in.getVolumeId()) @@ -112,7 +112,7 @@ public class Snapshot { private final String id; private final String volumeId; - private final String status; + private final Volume.Status status; private final int size; @SerializedName(value="createdAt") private final Date created; @@ -121,7 +121,7 @@ public class Snapshot { @SerializedName(value="displayDescription") private final String description; - protected Snapshot(Builder builder) { + protected VolumeSnapshot(Builder builder) { this.id = builder.id; this.volumeId = builder.volumeId; this.status = builder.status; @@ -148,7 +148,7 @@ public class Snapshot { /** */ @Nullable - public String getStatus() { + public Volume.Status getStatus() { return this.status; } @@ -189,7 +189,7 @@ public class Snapshot { public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - Snapshot that = Snapshot.class.cast(obj); + VolumeSnapshot that = VolumeSnapshot.class.cast(obj); return Objects.equal(this.id, that.id) && Objects.equal(this.volumeId, that.volumeId) && Objects.equal(this.status, that.status) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java index b8b1275542..fbc7465e0b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java @@ -30,12 +30,16 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.jclouds.openstack.filters.AuthenticateRequest; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; import org.jclouds.openstack.services.Extension; import org.jclouds.openstack.services.ServiceType; import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.Payload; import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.SelectJson; @@ -99,10 +103,12 @@ public interface VolumeAsyncClient { * @return the new Snapshot */ @POST - @Path("/os-volumes/") + @Path("/os-volumes") @SelectJson("volume") @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture createVolume(@PayloadParam("volume") Volume createVolume); + @Produces(MediaType.APPLICATION_JSON) + @MapBinder(CreateVolumeOptions.class) + ListenableFuture createVolume(@PayloadParam("size") int sizeGB, CreateVolumeOptions... options); /** * Delete a volume. @@ -111,7 +117,6 @@ public interface VolumeAsyncClient { */ @DELETE @Path("/os-volumes/{id}") - @SelectJson("volumes") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnFalseOnNotFoundOr404.class) ListenableFuture deleteVolume(@PathParam("id") String volumeId); @@ -126,7 +131,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachments") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + ListenableFuture> listAttachments(@PathParam("server_id") String serverId); /** * Get a specific attached volume. @@ -138,7 +143,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachment") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); /** * Attach a volume to an instance @@ -146,11 +151,14 @@ public interface VolumeAsyncClient { * @return the new Attachment */ @POST - @Path("/servers/{server_id}/os-volume_attachments/") + @Path("/servers/{server_id}/os-volume_attachments") @SelectJson("volumeAttachment") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture attachVolume(@PathParam("server_id") String serverId, @PayloadParam("attachment") Attachment attachVolume); + @Payload("%7B\"volumeAttachment\":%7B\"volumeId\":\"{id}\",\"device\":\"{device}\"%7D%7D") + ListenableFuture attachVolume(@PathParam("server_id") String serverId, + @PayloadParam("id") String volumeId, + @PayloadParam("device") String device); /** * Detach a Volume from an instance. @@ -170,10 +178,10 @@ public interface VolumeAsyncClient { */ @GET @Path("/os-snapshots") - @SelectJson("volumes") + @SelectJson("snapshots") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listSnapshots(); + ListenableFuture> listSnapshots(); /** * Returns a summary list of snapshots. @@ -185,7 +193,7 @@ public interface VolumeAsyncClient { @SelectJson("snapshots") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listSnapshotsInDetail(); + ListenableFuture> listSnapshotsInDetail(); /** * Return data about the given snapshot. @@ -194,21 +202,23 @@ public interface VolumeAsyncClient { */ @GET @Path("/os-snapshots/{id}") - @SelectJson("snapshots") + @SelectJson("snapshot") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getSnapshot(@PathParam("id") String snapshotId); + ListenableFuture getSnapshot(@PathParam("id") String snapshotId); /** * Creates a new Snapshot * * @return the new Snapshot */ - @GET - @Path("/os-snapshots/detail") - @SelectJson("snapshots") + @POST + @Path("/os-snapshots") + @SelectJson("snapshot") + @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture createSnapshot(@PayloadParam("snapshot") Snapshot createSnapshot); + @MapBinder(CreateVolumeSnapshotOptions.class) + ListenableFuture createSnapshot(@PayloadParam("volume_id") String volumeId, CreateVolumeSnapshotOptions... options); /** * Delete a snapshot. diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java index 2eea3ddbfa..fc26aad4cf 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java @@ -22,9 +22,11 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.jclouds.concurrent.Timeout; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; import org.jclouds.openstack.services.Extension; import org.jclouds.openstack.services.ServiceType; @@ -64,12 +66,12 @@ public interface VolumeClient { * * @return the new Snapshot */ - Volume createVolume(Volume createVolume); + Volume createVolume(int sizeGB, CreateVolumeOptions... options); /** * Delete a snapshot. * - * @return + * @return true if successful */ Boolean deleteVolume(String volumeId); @@ -78,26 +80,26 @@ public interface VolumeClient { * * @return all Floating IPs */ - Set listAttachments(String serverId); + Set listAttachments(String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. */ - Attachment getAttachment(String serverId, String volumeId); + VolumeAttachment getAttachment(String serverId, String volumeId); /** * Attach a volume to an instance * - * @return a new Volume + * @return data about the new volume attachment */ - Attachment attachVolume(String serverId, Attachment attachVolume); + VolumeAttachment attachVolume(String serverId, String volumeId, String device); /** * Detach a Volume from an instance. * - * @return + * @return true if successful */ Boolean detachVolume(String server_id, String volumeId); @@ -106,33 +108,33 @@ public interface VolumeClient { * * @return the list of snapshots */ - Set listSnapshots(); + Set listSnapshots(); /** * Returns a summary list of snapshots. * * @return the list of snapshots */ - Set listSnapshotsInDetail(); + Set listSnapshotsInDetail(); /** * Return data about the given snapshot. * * @return details of a specific snapshot. */ - Snapshot getSnapshot(String snapshotId); + VolumeSnapshot getSnapshot(String snapshotId); /** * Creates a new Snapshot * * @return the new Snapshot */ - Snapshot createSnapshot(Snapshot createSnapshot); + VolumeSnapshot createSnapshot(String volumeId, CreateVolumeSnapshotOptions... options); /** * Delete a snapshot. * - * @return + * @return true if successful */ Boolean deleteSnapshot(String snapshotId); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java new file mode 100644 index 0000000000..e92f3566db --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java @@ -0,0 +1,224 @@ +/** + * 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.nova.v1_1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.*; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.encryption.internal.Base64; +import org.jclouds.http.HttpRequest; +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; +import org.jclouds.util.Preconditions2; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * @author Adam Lowe + */ +public class CreateVolumeOptions implements MapBinder { + public static final CreateVolumeOptions NONE = new CreateVolumeOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + private String name; + private String description; + private String volumeType; + private String availabilityZone; + private String snapshotId; + private Map metadata = ImmutableMap.of(); + + @Override + public R bindToRequest(R request, Map postParams) { + Map image = Maps.newHashMap(); + image.putAll(postParams); + if (name != null) + image.put("display_name", name); + if (description != null) + image.put("display_description", description); + if (!metadata.isEmpty()) + image.put("metadata", metadata); + return jsonBinder.bindToRequest(request, ImmutableMap.of("volume", image)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateVolume is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateVolumeOptions)) return false; + final CreateVolumeOptions other = CreateVolumeOptions.class.cast(object); + return equal(volumeType, other.volumeType) && equal(availabilityZone, other.availabilityZone) && equal(snapshotId, other.snapshotId) + && equal(name, other.name) && equal(description, other.description) && equal(metadata, other.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(volumeType, availabilityZone, snapshotId, name, description, metadata); + } + + protected ToStringHelper string() { + return toStringHelper("").add("volumeType", volumeType).add("availabilityZone", availabilityZone) + .add("snapshotId", snapshotId).add("name", name).add("description", description).add("metadata", metadata); + } + + @Override + public String toString() { + return string().toString(); + } + + /** + * Custom cloud server metadata can also be supplied at launch time. This + * metadata is stored in the API system where it is retrievable by querying + * the API for server status. The maximum size of the metadata key and value + * is each 255 bytes and the maximum number of key-value pairs that can be + * supplied per volume is 5. + */ + public CreateVolumeOptions metadata(Map metadata) { + checkNotNull(metadata, "metadata"); + checkArgument(metadata.size() <= 5, + "you cannot have more then 5 metadata values. You specified: " + metadata.size()); + for (Entry entry : metadata.entrySet()) { + checkArgument( + entry.getKey().getBytes().length < 255, + String.format("maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", + entry.getKey(), entry.getKey().getBytes().length)); + checkArgument(entry.getKey().getBytes().length < 255, String.format( + "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", + entry.getKey(), entry.getValue(), entry.getValue().getBytes().length)); + } + this.metadata = ImmutableMap.copyOf(metadata); + return this; + } + + public CreateVolumeOptions name(String name) { + this.name = name; + return this; + } + + public CreateVolumeOptions description(String description) { + this.description = description; + return this; + } + + public CreateVolumeOptions volumeType(String volumeType) { + this.volumeType = volumeType; + return this; + } + + public CreateVolumeOptions availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + public CreateVolumeOptions snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return this; + } + + public String getVolumeType() { + return volumeType; + } + + public String getAvailabilityZone() { + return availabilityZone; + } + + public String getSnapshotId() { + return snapshotId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Map getMetadata() { + return metadata; + } + + public static class Builder { + /** + * @see CreateVolumeOptions#getName() + */ + public static CreateVolumeOptions name(String name) { + return new CreateVolumeOptions().name(name); + } + /** + * @see CreateVolumeOptions#getDescription() + */ + public static CreateVolumeOptions description(String description) { + return new CreateVolumeOptions().description(description); + } + + /** + * @see CreateVolumeOptions#getVolumeType() + */ + public static CreateVolumeOptions volumeType(String volumeType) { + return new CreateVolumeOptions().volumeType(volumeType); + } + + /** + * @see CreateVolumeOptions#getAvailabilityZone() + */ + public static CreateVolumeOptions availabilityZone(String availabilityZone) { + return new CreateVolumeOptions().availabilityZone(availabilityZone); + } + + /** + * @see CreateVolumeOptions#getSnapshotId() + */ + public static CreateVolumeOptions snapshotId(String snapshotId) { + return new CreateVolumeOptions().snapshotId(snapshotId); + } + + /** + * @see CreateVolumeOptions#getMetadata() + */ + public static CreateVolumeOptions metadata(Map metadata) { + return new CreateVolumeOptions().metadata(metadata); + } + } + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java new file mode 100644 index 0000000000..df9b628970 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java @@ -0,0 +1,140 @@ +/** + * 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.nova.v1_1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; + +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * @author Adam Lowe + */ +public class CreateVolumeSnapshotOptions implements MapBinder { + public static final CreateVolumeSnapshotOptions NONE = new CreateVolumeSnapshotOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + private String name; + private String description; + private boolean force = false; + + @Override + public R bindToRequest(R request, Map postParams) { + Map data = Maps.newHashMap(postParams); + if (name != null) + data.put("display_name", name); + if (description != null) + data.put("display_description", description); + if (force) + data.put("force", "true"); + return jsonBinder.bindToRequest(request, ImmutableMap.of("snapshot", data)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateSnapshot is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateVolumeSnapshotOptions)) return false; + final CreateVolumeSnapshotOptions other = CreateVolumeSnapshotOptions.class.cast(object); + return equal(name, other.name) && equal(description, other.description); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, description); + } + + protected ToStringHelper string() { + return toStringHelper("").add("name", name).add("description", description); + } + + @Override + public String toString() { + return string().toString(); + } + + public CreateVolumeSnapshotOptions name(String name) { + this.name = name; + return this; + } + + public CreateVolumeSnapshotOptions description(String description) { + this.description = description; + return this; + } + + public CreateVolumeSnapshotOptions force() { + this.force = true; + return this; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean isForce() { + return force; + } + + public static class Builder { + /** + * @see CreateVolumeSnapshotOptions#getName() + */ + public static CreateVolumeSnapshotOptions name(String name) { + return new CreateVolumeSnapshotOptions().name(name); + } + /** + * @see CreateVolumeSnapshotOptions#getDescription() + */ + public static CreateVolumeSnapshotOptions description(String description) { + return new CreateVolumeSnapshotOptions().description(description); + } + + /** + * @see CreateVolumeSnapshotOptions#isForce() + */ + public static CreateVolumeSnapshotOptions force() { + return new CreateVolumeSnapshotOptions().force(); + } + } + +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java index a254df0f85..522aaa3215 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -23,22 +23,27 @@ import static org.testng.Assert.*; import java.util.Set; import org.jclouds.openstack.domain.Resource; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.SimpleTenantUsage; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; +import org.jclouds.predicates.RetryablePredicate; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; +import com.google.common.base.Objects; import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; /** - * Tests behavior of SimpleTenantUsageClient + * Tests behavior of VolumeClient * + * Cannot create snapshots whilst a volume is attached, hence single threaded + * * @author Adam Lowe */ @Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) @@ -47,8 +52,13 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { private VolumeClient client; private String zone; - @BeforeMethod(alwaysRun = true) - public void setUpClient() { + private Volume testVolume; + private VolumeSnapshot testSnapshot; + + @BeforeGroups(groups = { "integration", "live" }) + @Override + public void setupContext() { + super.setupContext(); zone = Iterables.getFirst(novaContext.getApi().getConfiguredZones(), "nova"); Optional optClient = novaContext.getApi().getVolumeExtensionForZone(zone); if (optClient.isPresent()) { @@ -56,72 +66,204 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { } } + @AfterGroups(groups = "live", alwaysRun = true) + @Override + protected void tearDown() { + if (client != null) { + if (testSnapshot != null) { + final String snapshotId = testSnapshot.getId(); + assertTrue(client.deleteSnapshot(snapshotId)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getSnapshot(snapshotId) == null; + } + }, 30 * 1000L).apply(client)); + } + if (testVolume != null) { + final String volumeId = testVolume.getId(); + assertTrue(client.deleteVolume(volumeId)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getVolume(volumeId) == null; + } + }, 180 * 1000L).apply(client)); + } + } + super.tearDown(); + } + + public void testCreateVolume() throws Exception { + testVolume = client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume").availabilityZone(zone)); + for (int i=0; i<100 && testVolume.getStatus() == Volume.Status.CREATING; i++) { + Thread.sleep(100); + testVolume = client.getVolume(testVolume.getId()); + } + assertEquals(testVolume.getStatus(), Volume.Status.AVAILABLE); + } + + @Test(dependsOnMethods = "testCreateVolume") public void testListVolumes() throws Exception { if (client != null) { Set volumes = client.listVolumes(); assertNotNull(volumes); + boolean foundIt = false; for (Volume vol : volumes) { Volume details = client.getVolume(vol.getId()); assertNotNull(details); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } } + assertTrue(foundIt, "Failed to find the volume we created in listVolumes() response"); } } + @Test(dependsOnMethods = "testCreateVolume") public void testListVolumesInDetail() throws Exception { if (client != null) { Set volumes = client.listVolumesInDetail(); assertNotNull(volumes); + assertTrue(volumes.contains(testVolume)); + boolean foundIt = false; for (Volume vol : volumes) { Volume details = client.getVolume(vol.getId()); assertNotNull(details); - assertEquals(details, vol); assertNotNull(details.getId()); - assertNotNull(details.getStatus()); - assertTrue(details.getSize() > -1); assertNotNull(details.getCreated()); + assertTrue(details.getSize() > -1); + + assertEquals(details.getId(), vol.getId()); + assertEquals(details.getSize(), vol.getSize()); + assertEquals(details.getName(), vol.getName()); + assertEquals(details.getDescription(), vol.getDescription()); + assertEquals(details.getCreated(), vol.getCreated()); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } } + assertTrue(foundIt, "Failed to find the volume we previously created in listVolumesInDetail() response"); } } + @Test(dependsOnMethods = "testCreateVolume") + public void testCreateSnapshot() throws Exception { + if (client != null) { + testSnapshot = client.createSnapshot(testVolume.getId(), CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + assertNotNull(testSnapshot); + assertNotNull(testSnapshot.getId()); + final String snapshotId = testSnapshot.getId(); + assertNotNull(testSnapshot.getStatus()); + assertTrue(testSnapshot.getSize() > -1); + assertNotNull(testSnapshot.getCreated()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getSnapshot(snapshotId).getStatus() == Volume.Status.AVAILABLE; + } + }, 30 * 1000L).apply(client)); + } + } + + @Test(dependsOnMethods = "testCreateSnapshot") public void testListSnapshots() throws Exception { if (client != null) { - Set snapshots = client.listSnapshots(); + Set snapshots = client.listSnapshots(); assertNotNull(snapshots); - for (Snapshot snap : snapshots) { - Snapshot details = client.getSnapshot(snap.getId()); + boolean foundIt = false; + for (VolumeSnapshot snap : snapshots) { + VolumeSnapshot details = client.getSnapshot(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + } assertNotNull(details); - assertNotNull(details.getId()); + assertEquals(details.getId(), snap.getId()); + assertEquals(details.getVolumeId(), snap.getVolumeId()); } + assertTrue(foundIt, "Failed to find the snapshot we previously created in listSnapshots() response"); } } + @Test(dependsOnMethods = "testCreateSnapshot") public void testListSnapshotsInDetail() throws Exception { if (client != null) { - Set snapshots = client.listSnapshotsInDetail(); + Set snapshots = client.listSnapshotsInDetail(); assertNotNull(snapshots); - for (Snapshot snap : snapshots) { - Snapshot details = client.getSnapshot(snap.getId()); - assertNotNull(details); - assertEquals(details, snap); + boolean foundIt = false; + for (VolumeSnapshot snap : snapshots) { + VolumeSnapshot details = client.getSnapshot(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + assertSame(details, testSnapshot); + } + assertSame(details, snap); } + + assertTrue(foundIt, "Failed to find the snapshot we created in listSnapshotsInDetail() response"); } } + + private void assertSame(VolumeSnapshot a, VolumeSnapshot b) { + assertNotNull(a); + assertNotNull(b); + assertEquals(a.getId(), b.getId()); + assertEquals(a.getDescription(), b.getDescription()); + assertEquals(a.getName(), b.getName()); + assertEquals(a.getVolumeId(), b.getVolumeId()); + } - public void testListAttachments() throws Exception { + @Test(enabled=false, dependsOnMethods = "testCreateVolume") // disabled as it alters an existing server + public void testAttachments() throws Exception { if (client != null) { Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); if (!servers.isEmpty()) { - String serverId = Iterables.getFirst(servers, null).getId(); - Set attachments = client.listAttachments(serverId); + final String serverId = Iterables.getFirst(servers, null).getId(); + Set attachments = client.listAttachments(serverId); assertNotNull(attachments); - assertTrue(attachments.size() > 0); - for (Attachment att : attachments) { - Attachment details = client.getAttachment(serverId, att.getId()); + final int before = attachments.size(); + + VolumeAttachment testAttachment = client.attachVolume(serverId, testVolume.getId(), "/dev/vdf"); + assertNotNull(testAttachment.getId()); + assertEquals(testAttachment.getVolumeId(), testVolume.getId()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.listAttachments(serverId).size() == before+1; + } + }, 60 * 1000L).apply(client)); + + attachments = client.listAttachments(serverId); + assertNotNull(attachments); + assertEquals(attachments.size(), before+1); + + assertEquals(client.getVolume(testVolume.getId()).getStatus(), Volume.Status.IN_USE); + + boolean foundIt = false; + for (VolumeAttachment att : attachments) { + VolumeAttachment details = client.getAttachment(serverId, att.getId()); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getServerId()); assertNotNull(details.getVolumeId()); + if (Objects.equal(details.getVolumeId(), testVolume.getId())) { + foundIt = true; + assertEquals(details.getDevice(), "/dev/vdf"); + assertEquals(details.getServerId(), serverId); + } } + + assertTrue(foundIt, "Failed to find the attachment we created in listAttachments() response"); + + client.detachVolume(serverId, testVolume.getId()); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.listAttachments(serverId).size() == before; + } + }, 60 * 1000L).apply(client)); } } } From 0e9153017c45f834d7588ce59fc0d7c13dd7c4d6 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:17:07 +0100 Subject: [PATCH 03/12] Adding remaining VolumeClientExpectTest methods --- .../extensions/VolumeClientExpectTest.java | 373 ++++++++++++++++-- .../v1_1/internal/BaseNovaExpectTest.java | 12 + .../test/resources/attachment_details.json | 1 + .../src/test/resources/snapshot_details.json | 1 + .../src/test/resources/snapshot_list.json | 1 + .../test/resources/snapshot_list_detail.json | 1 + 6 files changed, 367 insertions(+), 22 deletions(-) create mode 100644 apis/openstack-nova/src/test/resources/attachment_details.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_details.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_list.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_list_detail.json diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java index 1f1633deab..4f70e81496 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -18,7 +18,7 @@ */ package org.jclouds.openstack.nova.v1_1.extensions; -import static org.testng.Assert.assertEquals; +import static org.testng.Assert.*; import java.net.URI; import java.util.Set; @@ -27,14 +27,16 @@ import javax.ws.rs.core.MediaType; import org.jclouds.date.DateService; import org.jclouds.date.internal.SimpleDateFormatDateService; -import org.jclouds.http.HttpRequest; -import org.jclouds.http.HttpResponse; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -52,38 +54,93 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Set volumes = client.listVolumes(); assertEquals(volumes, ImmutableSet.of(testVolume())); } + public void testListVolumesFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumes(); + assertTrue(volumes.isEmpty()); + } public void testListVolumesInDetail() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_detail.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_list_detail.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Set volumes = client.listVolumesInDetail(); assertEquals(volumes, ImmutableSet.of(testVolume())); } + public void testListVolumesInDetailFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumesInDetail(); + assertTrue(volumes.isEmpty()); + } + + public void testCreateVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"volume\":{\"display_name\":\"jclouds-test-volume\",\"display_description\":\"description of test volume\",\"size\":\"1\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Volume volume = client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume")); + assertEquals(volume, testVolume()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testCreateVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .endpoint(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"volume\":{\"display_name\":\"jclouds-test-volume\",\"display_description\":\"description of test volume\",\"size\":\"1\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(404).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume")); + } + public void testGetVolume() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_details.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Volume volume = client.getVolume("1"); @@ -97,33 +154,305 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { assertEquals(Iterables.getOnlyElement(volume.getAttachments()), testAttachment()); } + public void testGetVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getVolume("1")); + } + + public void testDeleteVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.deleteVolume("1")); + } + + public void testDeleteVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.deleteVolume("1")); + } + public void testListAttachments() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_list.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - Set attachments = client.listAttachments("instance-1"); + Set attachments = client.listAttachments("instance-1"); assertEquals(attachments, ImmutableSet.of(testAttachment())); - // double-check equals() - Attachment attachment = Iterables.getOnlyElement(attachments); + // double-check individual fields + VolumeAttachment attachment = Iterables.getOnlyElement(attachments); assertEquals(attachment.getDevice(), "/dev/vdc"); assertEquals(attachment.getServerId(), "b4785058-cb80-491b-baa3-e4ee6546450e"); assertEquals(attachment.getId(), "1"); assertEquals(attachment.getVolumeId(), "1"); } + + @Test(expectedExceptions = AuthorizationException.class) + public void testListAttachmentsFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-2/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.listAttachments("instance-2"); + } + + public void testGetAttachment() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment attachment = client.getAttachment("instance-1", "1"); + assertEquals(attachment, testAttachment()); + } + + public void testGetAttachmentFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getAttachment("instance-1", "1")); + } + + public void testAttachVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment result = client.attachVolume("instance-1", "1", "/dev/vdc"); + assertEquals(result, testAttachment()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testAttachVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.attachVolume("instance-1", "1", "/dev/vdc"); + } + + public void testDetachVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.detachVolume("instance-1", "1")); + } + + public void testDetachVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.detachVolume("instance-1", "1")); + } + + public void testListSnapshots() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshots(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + } + + public void testListSnapshotsFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshots(); + assertTrue(snapshots.isEmpty()); + } + + public void testGetSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeSnapshot snapshot = client.getSnapshot("1"); + assertEquals(snapshot, testSnapshot()); + } + + public void testGetSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getSnapshot("1")); + } + + public void testListSnapshotsInDetail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_list_detail.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshotsInDetail(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + + // double-check individual fields + VolumeSnapshot snappy = Iterables.getOnlyElement(snapshots); + assertEquals(snappy.getId(), "7"); + assertEquals(snappy.getVolumeId(), "9"); + assertEquals(snappy.getStatus(), Volume.Status.AVAILABLE); + assertEquals(snappy.getDescription(), "jclouds live test snapshot"); + assertEquals(snappy.getName(), "jclouds-live-test"); + assertEquals(snappy.getSize(), 1); + } + + public void testListSnapshotsInDetailFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshotsInDetail(); + assertTrue(snapshots.isEmpty()); + } + + public void testCreateSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"snapshot\":{\"display_name\":\"jclouds-live-test\",\"volume_id\":\"13\",\"display_description\":\"jclouds live test snapshot\",\"force\":\"true\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeSnapshot snapshot = client.createSnapshot("13", CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + assertEquals(snapshot, testSnapshot()); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testCreateSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"snapshot\":{\"display_name\":\"jclouds-live-test\",\"volume_id\":\"13\",\"display_description\":\"jclouds live test snapshot\",\"force\":\"true\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.createSnapshot("13", CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + } + + public void testDeleteSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.deleteSnapshot("1")); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testDeleteSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.deleteSnapshot("1"); + } protected Volume testVolume() { return Volume.builder().status(Volume.Status.IN_USE).description("This is a test volume").zone("nova").name("test") .attachments(ImmutableSet.of(testAttachment())).size(1).id("1").created(dateService.iso8601SecondsDateParse("2012-04-23 12:16:45")).build(); } - protected Attachment testAttachment() { - return Attachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); + protected VolumeAttachment testAttachment() { + return VolumeAttachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); } + protected VolumeSnapshot testSnapshot() { + return VolumeSnapshot.builder().id("7").volumeId("9").description("jclouds live test snapshot").status(Volume.Status.AVAILABLE) + .name("jclouds-live-test").size(1).created(dateService.iso8601SecondsDateParse("2012-04-24 13:34:42")).build(); + } } \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java index 336f507ba7..8815279eb4 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java @@ -20,6 +20,8 @@ package org.jclouds.openstack.nova.v1_1.internal; import java.net.URI; +import javax.ws.rs.core.MediaType; + import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture; @@ -68,4 +70,14 @@ public class BaseNovaExpectTest extends BaseRestClientExpectTest { unmatchedExtensionsOfNovaResponse = HttpResponse.builder().statusCode(200) .payload(payloadFromResource("/extension_list.json")).build(); } + + protected HttpRequest.Builder standardRequestBuilder(URI endpoint) { + return HttpRequest.builder().method("GET") + .headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint); + } + + protected HttpResponse.Builder standardResponseBuilder(int status) { + return HttpResponse.builder().statusCode(status); + } } diff --git a/apis/openstack-nova/src/test/resources/attachment_details.json b/apis/openstack-nova/src/test/resources/attachment_details.json new file mode 100644 index 0000000000..c436406780 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/attachment_details.json @@ -0,0 +1 @@ +{"volumeAttachment": {"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_details.json b/apis/openstack-nova/src/test/resources/snapshot_details.json new file mode 100644 index 0000000000..375127b705 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_details.json @@ -0,0 +1 @@ +{"snapshot": {"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_list.json b/apis/openstack-nova/src/test/resources/snapshot_list.json new file mode 100644 index 0000000000..03980b1697 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_list.json @@ -0,0 +1 @@ +{"snapshots": [{"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_list_detail.json b/apis/openstack-nova/src/test/resources/snapshot_list_detail.json new file mode 100644 index 0000000000..03980b1697 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_list_detail.json @@ -0,0 +1 @@ +{"snapshots": [{"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}]} \ No newline at end of file From 01c6a48786e4d58e6a08407f649515e508141f13 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:33:00 +0100 Subject: [PATCH 04/12] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/domain/Volume.java | 14 ++++++++++--- .../nova/v1_1/domain/VolumeAttachment.java | 14 ++++++++----- .../nova/v1_1/domain/VolumeSnapshot.java | 20 ++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java index 8a77bdbb8e..299e6d9c22 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -193,40 +193,46 @@ public class Volume { this.snapshotId = builder.snapshotId; this.name = builder.name; this.description = builder.description; - this.metadata = ImmutableMap.copyOf(builder.metadata); + this.metadata = ImmutableMap.copyOf(checkNotNull(builder.metadata, "metadata")); } /** + * @return the id of this volume */ public String getId() { return this.id; } /** + * @return the status of this volume */ public Status getStatus() { return this.status; } /** + * @return the size in GB of this volume */ public int getSize() { return this.size; } /** + * @return the availabilityZone containing this volume */ public String getZone() { return this.zone; } /** + * @return the time this volume was created */ public Date getCreated() { return this.created; } /** + * @return the set of attachments (to Servers) */ @Nullable public Set getAttachments() { @@ -234,6 +240,7 @@ public class Volume { } /** + * @return the type of this volume */ @Nullable public String getVolumeType() { @@ -241,6 +248,7 @@ public class Volume { } /** + * @return the snapshot id this volume is associated with. */ @Nullable public String getSnapshotId() { @@ -248,6 +256,7 @@ public class Volume { } /** + * @return the name of this volume - as displayed in the openstack console */ @Nullable public String getName() { @@ -255,14 +264,13 @@ public class Volume { } /** + * @return the description of this volume - as displayed in the openstack console */ @Nullable public String getDescription() { return this.description; } - /** - */ @Nullable public Map getMetadata() { return Collections.unmodifiableMap(this.metadata); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index ef93d9d18b..b6a231d7d1 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -18,13 +18,15 @@ */ package org.jclouds.openstack.nova.v1_1.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import org.jclouds.javax.annotation.Nullable; import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; /** - * An Openstack Nova Volume Attachment + * An Openstack Nova Volume Attachment (describes how Volumes are attached to Servers) */ public class VolumeAttachment { @@ -92,27 +94,28 @@ public class VolumeAttachment { private final String device; protected VolumeAttachment(Builder builder) { - this.id = builder.id; - this.volumeId = builder.volumeId; + this.id = checkNotNull(builder.id, "id"); + this.volumeId = checkNotNull(builder.volumeId, "volumeId"); this.serverId = builder.serverId; this.device = builder.device; } /** + * @return the attachment id (typically the same as #getVolumeId()) */ - @Nullable public String getId() { return this.id; } /** + * @return the id of the volume attached */ - @Nullable public String getVolumeId() { return this.volumeId; } /** + * @return the id of the server the volume is attached to */ @Nullable public String getServerId() { @@ -120,6 +123,7 @@ public class VolumeAttachment { } /** + * @return the device name (e.g. "/dev/vdc") */ @Nullable public String getDevice() { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index fa7ca86cb8..b20d47c467 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -18,6 +18,8 @@ */ package org.jclouds.openstack.nova.v1_1.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Date; import org.jclouds.javax.annotation.Nullable; @@ -122,9 +124,9 @@ public class VolumeSnapshot { private final String description; protected VolumeSnapshot(Builder builder) { - this.id = builder.id; - this.volumeId = builder.volumeId; - this.status = builder.status; + this.id = checkNotNull(builder.id, "id"); + this.volumeId = checkNotNull(builder.volumeId, "volumeId"); + this.status = checkNotNull(builder.status, "status"); this.size = builder.size; this.created = builder.created; this.name = builder.name; @@ -132,34 +134,35 @@ public class VolumeSnapshot { } /** + * @return the id of this snapshot */ - @Nullable public String getId() { return this.id; } /** + * @return the id of the Volume this snapshot was taken from */ - @Nullable public String getVolumeId() { return this.volumeId; } /** + * @return the status of this snapshot */ - @Nullable public Volume.Status getStatus() { return this.status; } /** + * @return the size in GB of the volume this snapshot was taken from */ - @Nullable public int getSize() { return this.size; } /** + * @return the data the snapshot was taken */ @Nullable public Date getCreated() { @@ -167,13 +170,16 @@ public class VolumeSnapshot { } /** + * @return the name of this snapshot - as displayed in the openstack console */ @Nullable public String getName() { return this.name; } + /** + * @return the description of this snapshot - as displayed in the openstack console */ @Nullable public String getDescription() { From 1dfcc6cde5334ba490f09b8f30191e15601ff657 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:39:33 +0100 Subject: [PATCH 05/12] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/domain/Volume.java | 18 +++++++++++++----- .../nova/v1_1/domain/VolumeAttachment.java | 4 ++++ .../nova/v1_1/domain/VolumeSnapshot.java | 7 +++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java index 299e6d9c22..ab7017ae26 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -84,56 +84,67 @@ public class Volume { private String description; private Map metadata = Maps.newHashMap(); + /** @see Volume#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see Volume#getStatus() */ public T status(Status status) { this.status = status; return self(); } + /** @see Volume#getSize() */ public T size(int size) { this.size = size; return self(); } + /** @see Volume#getZone() */ public T zone(String zone) { this.zone = zone; return self(); } + /** @see Volume#getCreated() */ public T created(Date created) { this.created = created; return self(); } + /** @see Volume#getAttachments() */ public T attachments(Set attachments) { this.attachments = attachments; return self(); } - + + /** @see Volume#getVolumeType() */ public T volumeType(String volumeType) { this.volumeType = volumeType; return self(); } + /** @see Volume#getSnapshotId() */ public T snapshotId(String snapshotId) { this.snapshotId = snapshotId; return self(); } - + + /** @see Volume#getMetadata() */ public T metadata(Map metadata) { this.metadata = metadata; return self(); } + /** @see Volume#getName() */ public T name(String name) { this.name = name; return self(); } + /** @see Volume#getDescription() */ public T description(String description) { this.description = description; return self(); @@ -247,9 +258,6 @@ public class Volume { return this.volumeType; } - /** - * @return the snapshot id this volume is associated with. - */ @Nullable public String getSnapshotId() { return this.snapshotId; diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index b6a231d7d1..bc33a0ba63 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -46,21 +46,25 @@ public class VolumeAttachment { private String serverId; private String device; + /** @see VolumeAttachment#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see VolumeAttachment#getVolumeId() */ public T volumeId(String volumeId) { this.volumeId = volumeId; return self(); } + /** @see VolumeAttachment#getServerId() */ public T serverId(String serverId) { this.serverId = serverId; return self(); } + /** @see VolumeAttachment#getDevice() */ public T device(String device) { this.device = device; return self(); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index b20d47c467..d79d3d3231 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -52,36 +52,43 @@ public class VolumeSnapshot { private String name; private String description; + /** @see VolumeSnapshot#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see VolumeSnapshot#getVolumeId() */ public T volumeId(String volumeId) { this.volumeId = volumeId; return self(); } + /** @see VolumeSnapshot#getStatus() */ public T status(Volume.Status status) { this.status = status; return self(); } + /** @see VolumeSnapshot#getSize() */ public T size(int size) { this.size = size; return self(); } + /** @see VolumeSnapshot#getCreated() */ public T created(Date created) { this.created = created; return self(); } + /** @see VolumeSnapshot#getName() */ public T name(String name) { this.name = name; return self(); } + /** @see VolumeSnapshot#getDescription() */ public T description(String description) { this.description = description; return self(); From ff1428e6f7bb99a5222b61ea5e1e7b59ffde2ccf Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:41:54 +0100 Subject: [PATCH 06/12] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/extensions/VolumeClientLiveTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java index 522aaa3215..b4b61ed103 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -42,8 +42,6 @@ import com.google.common.collect.Iterables; /** * Tests behavior of VolumeClient * - * Cannot create snapshots whilst a volume is attached, hence single threaded - * * @author Adam Lowe */ @Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) From 96b9e8c8b0c0e463d5475533ffa1e7e6e3e7504b Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 24 Apr 2012 10:54:03 -0700 Subject: [PATCH 07/12] Issue 907: initial jenkins api --- labs/jenkins/pom.xml | 123 +++++++++++++ .../jenkins/v1/JenkinsApiMetadata.java | 97 ++++++++++ .../jenkins/v1/JenkinsAsyncClient.java | 41 +++++ .../org/jclouds/jenkins/v1/JenkinsClient.java | 44 +++++ .../jenkins/v1/config/JenkinsProperties.java | 28 +++ .../v1/config/JenkinsRestClientModule.java | 59 ++++++ .../jclouds/jenkins/v1/domain/Computer.java | 134 ++++++++++++++ .../jenkins/v1/domain/ComputerView.java | 168 ++++++++++++++++++ .../v1/features/ComputerAsyncClient.java | 64 +++++++ .../jenkins/v1/features/ComputerClient.java | 48 +++++ .../BasicAuthenticationUnlessAnonymous.java | 56 ++++++ .../v1/handlers/JenkinsErrorHandler.java | 66 +++++++ .../services/org.jclouds.apis.ApiMetadata | 1 + .../jenkins/v1/JenkinsApiMetadataTest.java | 37 ++++ .../jenkins/v1/JenkinsErrorHandlerTest.java | 98 ++++++++++ .../v1/features/ComputerClientExpectTest.java | 77 ++++++++ .../v1/features/ComputerClientLiveTest.java | 50 ++++++ ...thenticationUnlessAnonymousExpectTest.java | 62 +++++++ .../BaseJenkinsAsyncClientExpectTest.java | 39 ++++ .../internal/BaseJenkinsClientExpectTest.java | 30 ++++ .../internal/BaseJenkinsClientLiveTest.java | 64 +++++++ .../v1/internal/BaseJenkinsExpectTest.java | 32 ++++ .../jenkins/v1/parse/ParseComputerTest.java | 49 +++++ .../v1/parse/ParseComputerViewTest.java | 64 +++++++ labs/jenkins/src/test/resources/computer.json | 37 ++++ .../src/test/resources/computerview.json | 114 ++++++++++++ labs/jenkins/src/test/resources/logback.xml | 38 ++++ labs/pom.xml | 1 + 28 files changed, 1721 insertions(+) create mode 100644 labs/jenkins/pom.xml create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsApiMetadata.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsProperties.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsRestClientModule.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Computer.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/ComputerView.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymous.java create mode 100644 labs/jenkins/src/main/java/org/jclouds/jenkins/v1/handlers/JenkinsErrorHandler.java create mode 100644 labs/jenkins/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsApiMetadataTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsErrorHandlerTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientExpectTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymousExpectTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsAsyncClientExpectTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientExpectTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientLiveTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsExpectTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerTest.java create mode 100644 labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerViewTest.java create mode 100644 labs/jenkins/src/test/resources/computer.json create mode 100644 labs/jenkins/src/test/resources/computerview.json create mode 100644 labs/jenkins/src/test/resources/logback.xml diff --git a/labs/jenkins/pom.xml b/labs/jenkins/pom.xml new file mode 100644 index 0000000000..8c06216b8a --- /dev/null +++ b/labs/jenkins/pom.xml @@ -0,0 +1,123 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.5.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + jenkins + jcloud jenkins api + jclouds components to access an implementation of Jenkins + bundle + + + http://localhost:8080 + 1.0 + 1.460 + ANONYMOUS + ANONYMOUS + + + + + org.jclouds + jclouds-core + ${project.version} + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds.driver + jclouds-slf4j + ${project.version} + test + + + ch.qos.logback + logback-classic + 1.0.0 + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.jenkins.endpoint} + ${test.jenkins.api-version} + ${test.jenkins.build-version} + ${test.jenkins.identity} + ${test.jenkins.credential} + + + + + + + + + + + + + + org.apache.felix + maven-bundle-plugin + + + ${project.artifactId} + org.jclouds.jenkins.v1*;version="${project.version}" + + org.jclouds.rest.internal;version="${project.version}", + org.jclouds*;version="${project.version}", + * + + + + + + + + diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsApiMetadata.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsApiMetadata.java new file mode 100644 index 0000000000..d84743ed22 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsApiMetadata.java @@ -0,0 +1,97 @@ +/** + * 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.jenkins.v1; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.jenkins.v1.config.JenkinsRestClientModule; +import org.jclouds.rest.RestContext; +import org.jclouds.rest.internal.BaseRestApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.google.inject.Module; + +/** + * Implementation of {@link ApiMetadata} for Jenkins 1.0 API + * + * @author Adrian Cole + */ +public class JenkinsApiMetadata extends BaseRestApiMetadata { + + public static final String ANONYMOUS_IDENTITY = "ANONYMOUS"; + + /** The serialVersionUID */ + private static final long serialVersionUID = 6725672099385580694L; + + public static final TypeToken> CONTEXT_TOKEN = new TypeToken>() { + private static final long serialVersionUID = -5070937833892503232L; + }; + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public JenkinsApiMetadata() { + this(new Builder()); + } + + protected JenkinsApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseRestApiMetadata.defaultProperties(); + return properties; + } + + public static class Builder extends BaseRestApiMetadata.Builder { + + protected Builder() { + super(JenkinsClient.class, JenkinsAsyncClient.class); + id("jenkins") + .name("Jenkins API") + .identityName("Username (or " + ANONYMOUS_IDENTITY + " if anonymous)") + .defaultIdentity(ANONYMOUS_IDENTITY) + .credentialName("Password") + .defaultCredential(ANONYMOUS_IDENTITY) + .documentation(URI.create("https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API")) + .version("1.0") + .defaultEndpoint("http://localhost:8080") + .defaultProperties(JenkinsApiMetadata.defaultProperties()) + .defaultModules(ImmutableSet.>of(JenkinsRestClientModule.class)); + } + + @Override + public JenkinsApiMetadata build() { + return new JenkinsApiMetadata(this); + } + + @Override + public Builder fromApiMetadata(ApiMetadata in) { + super.fromApiMetadata(in); + return this; + } + + } + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java new file mode 100644 index 0000000000..aac4675d44 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java @@ -0,0 +1,41 @@ +/** + * 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.jenkins.v1; + +import org.jclouds.jenkins.v1.features.ComputerAsyncClient; +import org.jclouds.rest.annotations.Delegate; + +/** + * Provides asynchronous access to Jenkins via their REST API. + *

+ * + * @see JenkinsClient + * @see api doc + * @author Adrian Cole + */ +public interface JenkinsAsyncClient { + + + /** + * Provides asynchronous access to Computer features. + */ + @Delegate + ComputerAsyncClient getComputerClient(); + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java new file mode 100644 index 0000000000..b0653af12b --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java @@ -0,0 +1,44 @@ +/** + * 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.jenkins.v1; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.jenkins.v1.features.ComputerClient; +import org.jclouds.rest.annotations.Delegate; + +/** + * Provides synchronous access to Jenkins. + *

+ * + * @see JenkinsAsyncClient + * @see api doc + * @author Adrian Cole + */ +@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS) +public interface JenkinsClient { + + /** + * Provides synchronous access to Computer features. + */ + @Delegate + ComputerClient getComputerClient(); + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsProperties.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsProperties.java new file mode 100644 index 0000000000..473db99e68 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsProperties.java @@ -0,0 +1,28 @@ +/** + * 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.jenkins.v1.config; + +/** + * Configuration properties and constants used in Jenkins connections. + * + * @author Adrian Cole + */ +public class JenkinsProperties { + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsRestClientModule.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsRestClientModule.java new file mode 100644 index 0000000000..b0e11aa31c --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/config/JenkinsRestClientModule.java @@ -0,0 +1,59 @@ +/** + * 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.jenkins.v1.config; + +import java.util.Map; + +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; +import org.jclouds.jenkins.v1.JenkinsAsyncClient; +import org.jclouds.jenkins.v1.JenkinsClient; +import org.jclouds.jenkins.v1.features.ComputerAsyncClient; +import org.jclouds.jenkins.v1.features.ComputerClient; +import org.jclouds.jenkins.v1.handlers.JenkinsErrorHandler; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.rest.config.RestClientModule; + +import com.google.common.collect.ImmutableMap; + +/** + * Configures the Jenkins connection. + * + * @author Adrian Cole + */ +@ConfiguresRestClient +public class JenkinsRestClientModule extends RestClientModule { + + public static final Map, Class> DELEGATE_MAP = ImmutableMap., Class> builder() + .put(ComputerClient.class, ComputerAsyncClient.class) + .build(); + + public JenkinsRestClientModule() { + super(DELEGATE_MAP); + } + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(JenkinsErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(JenkinsErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(JenkinsErrorHandler.class); + } +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Computer.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Computer.java new file mode 100644 index 0000000000..065adde340 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Computer.java @@ -0,0 +1,134 @@ +package org.jclouds.jenkins.v1.domain; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * @author Adrian Cole + * @see api + * doc + */ +public class Computer implements Comparable { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromComputerMetadata(this); + } + + public static class Builder { + protected String displayName; + protected boolean idle; + protected boolean offline; + + /** + * @see Computer#getDisplayName() + */ + public Builder displayName(String displayName) { + this.displayName = checkNotNull(displayName, "displayName"); + return this; + } + + /** + * @see Computer#isIdle() + */ + public Builder idle(boolean idle) { + this.idle = idle; + return this; + } + + /** + * @see Computer#isOffline() + */ + public Builder offline(boolean offline) { + this.offline = offline; + return this; + } + + public Computer build() { + return new Computer(displayName, idle, offline); + } + + public Builder fromComputerMetadata(Computer from) { + return displayName(from.getDisplayName()).idle(from.isIdle()).offline(from.isOffline()); + } + } + + protected final String displayName; + protected final boolean idle; + protected final boolean offline; + + public Computer(String displayName, boolean idle, boolean offline) { + this.displayName = checkNotNull(displayName, "displayName"); + this.idle = idle; + this.offline = offline; + } + + /** + * + * @return the displayName of the computer + */ + public String getDisplayName() { + return displayName; + } + + /** + * + * @return the number of objects in the computer + */ + public boolean isIdle() { + return idle; + } + + /** + * @return the total offline stored in this computer + */ + public boolean isOffline() { + return offline; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Computer) { + final Computer other = Computer.class.cast(object); + return equal(getDisplayName(), other.getDisplayName()) && equal(isIdle(), other.isIdle()) + && equal(isOffline(), other.isOffline()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getDisplayName(), isIdle(), isOffline()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper("").add("displayName", getDisplayName()).add("idle", isIdle()).add("offline", isOffline()); + } + + @Override + public int compareTo(Computer that) { + if (that == null) + return 1; + if (this == that) + return 0; + return this.getDisplayName().compareTo(that.getDisplayName()); + } + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/ComputerView.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/ComputerView.java new file mode 100644 index 0000000000..3c505f7c27 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/ComputerView.java @@ -0,0 +1,168 @@ +package org.jclouds.jenkins.v1.domain; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableSet; +import com.google.gson.annotations.SerializedName; + +/** + * @author Adrian Cole + * @see api + * doc + */ +public class ComputerView implements Comparable { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromComputerMetadata(this); + } + + public static class Builder { + protected String displayName; + protected int busyExecutors; + protected int totalExecutors; + protected Set computers = ImmutableSet.of(); + + /** + * @see ComputerView#getDisplayName() + */ + public Builder displayName(String displayName) { + this.displayName = checkNotNull(displayName, "displayName"); + return this; + } + + /** + * @see ComputerView#getBusyExecutors() + */ + public Builder busyExecutors(int busyExecutors) { + this.busyExecutors = busyExecutors; + return this; + } + + /** + * @see ComputerView#getTotalExecutors() + */ + public Builder totalExecutors(int totalExecutors) { + this.totalExecutors = totalExecutors; + return this; + } + + /** + * @see ComputerView#getLinks() + */ + public Builder computers(Computer... computers) { + return computers(ImmutableSet.copyOf(checkNotNull(computers, "computers"))); + } + + /** + * @see ComputerView#getLinks() + */ + public Builder computers(Set computers) { + this.computers = ImmutableSet.copyOf(checkNotNull(computers, "computers")); + return this; + } + + public ComputerView build() { + return new ComputerView(displayName, busyExecutors, totalExecutors, computers); + } + + public Builder fromComputerMetadata(ComputerView from) { + return displayName(from.getDisplayName()).busyExecutors(from.getBusyExecutors()).totalExecutors(from.getTotalExecutors()).computers(from.getComputers()); + } + } + + protected final String displayName; + protected final int busyExecutors; + protected final int totalExecutors; + @SerializedName("computer") + protected final Set computers; + + public ComputerView(String displayName, int busyExecutors, int totalExecutors, Set computers) { + this.displayName = checkNotNull(displayName, "displayName"); + this.busyExecutors = busyExecutors; + this.totalExecutors = totalExecutors; + this.computers = ImmutableSet.copyOf(checkNotNull(computers, "computers")); + } + + /** + * + * @return the displayName of the computer + */ + public String getDisplayName() { + return displayName; + } + + /** + * + * @return the number of objects in the computer + */ + public int getBusyExecutors() { + return busyExecutors; + } + + /** + * @return the total totalExecutors stored in this computer + */ + public int getTotalExecutors() { + return totalExecutors; + } + + /** + * @return the computers in this set + */ + //TODO: create type adapter for gson that understands ForwardingSet so that we can implement the Set interface + public Set getComputers() { + return computers; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof ComputerView) { + final ComputerView other = ComputerView.class.cast(object); + return equal(getDisplayName(), other.getDisplayName()) && equal(getBusyExecutors(), other.getBusyExecutors()) + && equal(getTotalExecutors(), other.getTotalExecutors()) && equal(getComputers(), other.getComputers()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getDisplayName(), getBusyExecutors(), getTotalExecutors(), getComputers()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper("").add("displayName", getDisplayName()).add("busyExecutors", getBusyExecutors()).add( + "totalExecutors", getTotalExecutors()).add("computers", getComputers()); + } + + @Override + public int compareTo(ComputerView that) { + if (that == null) + return 1; + if (this == that) + return 0; + return this.getDisplayName().compareTo(that.getDisplayName()); + } + + + +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java new file mode 100644 index 0000000000..0ef8154e58 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java @@ -0,0 +1,64 @@ +/** + * 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.jenkins.v1.features; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; + +import org.jclouds.jenkins.v1.domain.Computer; +import org.jclouds.jenkins.v1.domain.ComputerView; +import org.jclouds.jenkins.v1.filters.BasicAuthenticationUnlessAnonymous; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Computer Services + * + * @see ComputerClient + * @author Adrian Cole + * @see api doc + */ +@RequestFilters(BasicAuthenticationUnlessAnonymous.class) +public interface ComputerAsyncClient { + + /** + * @see ComputerClient#getComputerView + */ + @GET + @Path("/computer/api/json") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture getComputerView(); + + /** + * @see ComputerClient#getComputer + */ + @GET + @Path("/computer/{displayName}/api/json") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getComputer(@PathParam("displayName") String displayName); +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java new file mode 100644 index 0000000000..1ab93add18 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java @@ -0,0 +1,48 @@ +/** + * 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.jenkins.v1.features; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.jenkins.v1.domain.Computer; +import org.jclouds.jenkins.v1.domain.ComputerView; + +/** + * Computer Services + * + * @see ComputerAsyncClient + * @author Adrian Cole + * @see api doc + */ +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface ComputerClient { + + /** + * @return overview of all configured computers + */ + ComputerView getComputerView(); + + /** + * + * @param displayName display name of the computer + * @return computer or null if not found + */ + Computer getComputer(String displayName); +} diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymous.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymous.java new file mode 100644 index 0000000000..de8183d370 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymous.java @@ -0,0 +1,56 @@ +/** + * 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.jenkins.v1.filters; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.filters.BasicAuthentication; +import org.jclouds.jenkins.v1.JenkinsApiMetadata; +import org.jclouds.rest.annotations.Identity; + +import com.google.common.base.Optional; + +/** + * @author Adrian Cole + * + */ +@Singleton +public class BasicAuthenticationUnlessAnonymous implements HttpRequestFilter { + + private final Optional auth; + + @Inject + public BasicAuthenticationUnlessAnonymous(@Identity String user, BasicAuthentication auth) { + this.auth = JenkinsApiMetadata.ANONYMOUS_IDENTITY.equals(checkNotNull(user, "user")) ? Optional + . absent() : Optional.of(checkNotNull(auth, "auth")); + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + if (auth.isPresent()) + return auth.get().filter(request); + return request; + } +} \ No newline at end of file diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/handlers/JenkinsErrorHandler.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/handlers/JenkinsErrorHandler.java new file mode 100644 index 0000000000..3fa8b80ce0 --- /dev/null +++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/handlers/JenkinsErrorHandler.java @@ -0,0 +1,66 @@ +/** + * 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.jenkins.v1.handlers; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; + +import javax.inject.Singleton; + +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; + +/** + * This will parse and set an appropriate exception on the command object. + * + * @author Adrian Cole + * + */ +// TODO: is there error spec someplace? let's type errors, etc. +@Singleton +public class JenkinsErrorHandler implements HttpErrorHandler { + + public void handleError(HttpCommand command, HttpResponse response) { + // it is important to always read fully and close streams + byte[] data = closeClientButKeepContentStream(response); + String message = data != null ? new String(data) : null; + + Exception exception = message != null ? new HttpResponseException(command, response, message) + : new HttpResponseException(command, response); + message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), + response.getStatusLine()); + switch (response.getStatusCode()) { + case 400: + break; + case 401: + case 403: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + } + command.setException(exception); + } +} diff --git a/labs/jenkins/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/labs/jenkins/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000000..c80a7f32a8 --- /dev/null +++ b/labs/jenkins/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.jenkins.v1.JenkinsApiMetadata diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsApiMetadataTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsApiMetadataTest.java new file mode 100644 index 0000000000..707c42ea37 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsApiMetadataTest.java @@ -0,0 +1,37 @@ +/** + * 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.jenkins.v1; + +import org.jclouds.View; +import org.jclouds.apis.internal.BaseApiMetadataTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "JenkinsApiMetadataTest") +public class JenkinsApiMetadataTest extends BaseApiMetadataTest { + public JenkinsApiMetadataTest() { + super(new JenkinsApiMetadata(), ImmutableSet.> of()); + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsErrorHandlerTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsErrorHandlerTest.java new file mode 100644 index 0000000000..bb26d01e84 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsErrorHandlerTest.java @@ -0,0 +1,98 @@ +/** + * 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.jenkins.v1; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.verify; + +import java.net.URI; + +import org.easymock.IArgumentMatcher; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.io.Payloads; +import org.jclouds.jenkins.v1.handlers.JenkinsErrorHandler; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "JenkinsErrorHandlerTest") +public class JenkinsErrorHandlerTest { + + @Test + public void test404WithHTMLDoesntBustParserAndMakesResourceNotFoundException() { + assertCodeMakes("GET", URI + .create("http://ci.jruby.org/computer/master/api/json"), + 404, "Not Found", "", ResourceNotFoundException.class); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content, + Class expected) { + assertCodeMakes(method, uri, statusCode, message, "text/plain", content, expected); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType, + String content, Class expected) { + + JenkinsErrorHandler function = new JenkinsErrorHandler(); + + HttpCommand command = createMock(HttpCommand.class); + HttpRequest request = new HttpRequest(method, uri); + HttpResponse response = new HttpResponse(statusCode, message, Payloads.newInputStreamPayload(Strings2 + .toInputStream(content))); + response.getPayload().getContentMetadata().setContentType(contentType); + + expect(command.getCurrentRequest()).andReturn(request).atLeastOnce(); + command.setException(classEq(expected)); + + replay(command); + + function.handleError(command, response); + + verify(command); + } + + public static Exception classEq(final Class in) { + reportMatcher(new IArgumentMatcher() { + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("classEq("); + buffer.append(in); + buffer.append(")"); + } + + @Override + public boolean matches(Object arg) { + return arg.getClass() == in; + } + + }); + return null; + } + +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientExpectTest.java new file mode 100644 index 0000000000..47ebdcff41 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientExpectTest.java @@ -0,0 +1,77 @@ +/** + * 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.jenkins.v1.features; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.jenkins.v1.JenkinsClient; +import org.jclouds.jenkins.v1.internal.BaseJenkinsClientExpectTest; +import org.jclouds.jenkins.v1.parse.ParseComputerTest; +import org.jclouds.jenkins.v1.parse.ParseComputerViewTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMultimap; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ComputerClientExpectTest") +public class ComputerClientExpectTest extends BaseJenkinsClientExpectTest { + + public void testGetComputerViewWhenResponseIs2xx() { + HttpRequest getComputerView = HttpRequest + .builder() + .method("GET") + .endpoint(URI.create("http://localhost:8080/computer/api/json")) + .headers( + ImmutableMultimap. builder().put("Accept", "application/json") + .put("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==").build()).build(); + + HttpResponse getComputerViewResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/computerview.json")).build(); + + JenkinsClient clientWhenServersExist = requestSendsResponse(getComputerView, getComputerViewResponse); + + assertEquals(clientWhenServersExist.getComputerClient().getComputerView().toString(), + new ParseComputerViewTest().expected().toString()); + } + + public void testGetComputerWhenResponseIs2xx() { + HttpRequest getComputer = HttpRequest + .builder() + .method("GET") + .endpoint(URI.create("http://localhost:8080/computer/Ruboto/api/json")) + .headers( + ImmutableMultimap. builder().put("Accept", "application/json") + .put("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==").build()).build(); + + HttpResponse getComputerResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/computer.json")).build(); + + JenkinsClient clientWhenServersExist = requestSendsResponse(getComputer, getComputerResponse); + + assertEquals(clientWhenServersExist.getComputerClient().getComputer("Ruboto").toString(), + new ParseComputerTest().expected().toString()); + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java new file mode 100644 index 0000000000..44c30c652b --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java @@ -0,0 +1,50 @@ +/** + * 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.jenkins.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import org.jclouds.jenkins.v1.domain.Computer; +import org.jclouds.jenkins.v1.domain.ComputerView; +import org.jclouds.jenkins.v1.internal.BaseJenkinsClientLiveTest; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "live", testName = "ComputerClientLiveTest") +public class ComputerClientLiveTest extends BaseJenkinsClientLiveTest { + + public void testGetComputerView(){ + ComputerView view = getClient().getComputerView(); + assertNotNull(view); + assertNotNull(view.getDisplayName()); + for (Computer computerFromView : view.getComputers()) { + assertNotNull(computerFromView.getDisplayName()); + Computer computerFromGetRequest = getClient().getComputer(computerFromView.getDisplayName()); + assertEquals(computerFromGetRequest, computerFromView); + } + } + + private ComputerClient getClient() { + return context.getApi().getComputerClient(); + } +} \ No newline at end of file diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymousExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymousExpectTest.java new file mode 100644 index 0000000000..f281b7920f --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/filters/BasicAuthenticationUnlessAnonymousExpectTest.java @@ -0,0 +1,62 @@ +/** + * 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.jenkins.v1.filters; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.jenkins.v1.JenkinsApiMetadata; +import org.jclouds.jenkins.v1.JenkinsClient; +import org.jclouds.jenkins.v1.internal.BaseJenkinsClientExpectTest; +import org.jclouds.jenkins.v1.parse.ParseComputerViewTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMultimap; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "BasicAuthenticationUnlessAnonymousExpectTest") +public class BasicAuthenticationUnlessAnonymousExpectTest extends BaseJenkinsClientExpectTest { + + public BasicAuthenticationUnlessAnonymousExpectTest(){ + identity = JenkinsApiMetadata.ANONYMOUS_IDENTITY; + } + + public void testWhenIdentityIsAnonymousNoAuthorizationHeader() { + HttpRequest getComputerView = HttpRequest + .builder() + .method("GET") + .endpoint(URI.create("http://localhost:8080/computer/api/json")) + .headers( + ImmutableMultimap. builder().put("Accept", "application/json").build()).build(); + + HttpResponse getComputerViewResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/computerview.json")).build(); + + JenkinsClient clientWhenServersExist = requestSendsResponse(getComputerView, getComputerViewResponse); + + assertEquals(clientWhenServersExist.getComputerClient().getComputerView().toString(), + new ParseComputerViewTest().expected().toString()); + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsAsyncClientExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsAsyncClientExpectTest.java new file mode 100644 index 0000000000..fd8f4489b4 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsAsyncClientExpectTest.java @@ -0,0 +1,39 @@ +/** + * 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.jenkins.v1.internal; + +import java.util.Properties; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.jenkins.v1.JenkinsAsyncClient; + +import com.google.common.base.Function; +import com.google.inject.Module; + +/** + * Base class for writing KeyStone Rest Client Expect tests + * + * @author Adrian Cole + */ +public class BaseJenkinsAsyncClientExpectTest extends BaseJenkinsExpectTest { + public JenkinsAsyncClient createClient(Function fn, Module module, Properties props) { + return createInjector(fn, module, props).getInstance(JenkinsAsyncClient.class); + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientExpectTest.java new file mode 100644 index 0000000000..9b7fb63f03 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientExpectTest.java @@ -0,0 +1,30 @@ +/** + * 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.jenkins.v1.internal; + +import org.jclouds.jenkins.v1.JenkinsClient; + +/** + * Base class for writing Jenkins Expect tests + * + * @author Adrian Cole + */ +public class BaseJenkinsClientExpectTest extends BaseJenkinsExpectTest { + +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientLiveTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientLiveTest.java new file mode 100644 index 0000000000..e934c87467 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsClientLiveTest.java @@ -0,0 +1,64 @@ +/** + * 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.jenkins.v1.internal; + +import org.jclouds.apis.BaseContextLiveTest; +import org.jclouds.jenkins.v1.JenkinsApiMetadata; +import org.jclouds.jenkins.v1.JenkinsAsyncClient; +import org.jclouds.jenkins.v1.JenkinsClient; +import org.jclouds.rest.RestContext; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +/** + * Tests behavior of {@code JenkinsClient} + * + * @author Adrian Cole + */ +@Test(groups = "live") +public class BaseJenkinsClientLiveTest extends BaseContextLiveTest> { + + public BaseJenkinsClientLiveTest() { + provider = "jenkins"; + } + + protected RestContext jenkinsContext; + + @BeforeGroups(groups = { "integration", "live" }) + @Override + public void setupContext() { + super.setupContext(); + jenkinsContext = context; + } + + @AfterGroups(groups = "live") + protected void tearDown() { + if (jenkinsContext != null) + jenkinsContext.close(); + } + + @Override + protected TypeToken> contextType() { + return JenkinsApiMetadata.CONTEXT_TOKEN; + } + +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsExpectTest.java new file mode 100644 index 0000000000..b12fcac29a --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/internal/BaseJenkinsExpectTest.java @@ -0,0 +1,32 @@ +/** + * 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.jenkins.v1.internal; + +import org.jclouds.rest.internal.BaseRestClientExpectTest; + +/** + * Base class for writing Jenkins Expect tests + * + * @author Adrian Cole + */ +public class BaseJenkinsExpectTest extends BaseRestClientExpectTest { + public BaseJenkinsExpectTest() { + provider = "jenkins"; + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerTest.java new file mode 100644 index 0000000000..05c4e6a859 --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerTest.java @@ -0,0 +1,49 @@ +/** + * 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.jenkins.v1.parse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +import org.jclouds.jenkins.v1.domain.Computer; +import org.jclouds.json.BaseItemParserTest; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ParseComputerTest") +public class ParseComputerTest extends BaseItemParserTest { + + @Override + public String resource() { + return "/computer.json"; + } + + @Override + @Consumes(MediaType.APPLICATION_JSON) + public Computer expected() { + return Computer.builder() + .displayName("Ruboto") + .idle(true) + .offline(false) + .build(); + } +} diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerViewTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerViewTest.java new file mode 100644 index 0000000000..c45296610e --- /dev/null +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseComputerViewTest.java @@ -0,0 +1,64 @@ +/** + * 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.jenkins.v1.parse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +import org.jclouds.jenkins.v1.domain.Computer; +import org.jclouds.jenkins.v1.domain.ComputerView; +import org.jclouds.json.BaseItemParserTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ParseComputerViewTest") +public class ParseComputerViewTest extends BaseItemParserTest { + + @Override + public String resource() { + return "/computerview.json"; + } + + @Override + @Consumes(MediaType.APPLICATION_JSON) + public ComputerView expected() { + return ComputerView.builder() + .displayName("nodes") + .totalExecutors(4) + .busyExecutors(0) + .computers(ImmutableSet.builder() + .add(Computer.builder() + .displayName("master") + .idle(true) + .offline(false).build()) + .add(Computer.builder() + .displayName("Ruboto") + .idle(true) + .offline(false).build()) + .add(Computer.builder() + .displayName("winserver2008-x86") + .idle(true) + .offline(false).build()).build()).build(); + } +} diff --git a/labs/jenkins/src/test/resources/computer.json b/labs/jenkins/src/test/resources/computer.json new file mode 100644 index 0000000000..610126b49a --- /dev/null +++ b/labs/jenkins/src/test/resources/computer.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "displayName": "Ruboto", + "executors": [{}], + "icon": "computer.png", + "idle": true, + "jnlpAgent": true, + "launchSupported": false, + "loadStatistics": {}, + "manualLaunchAllowed": true, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 1697591296, + "availableSwapSpace": 5626036224, + "totalPhysicalMemory": 4157317120, + "totalSwapSpace": 6568271872 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 955 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "size": 53646782464 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "size": 53646782464 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": -309 + } + }, + "numExecutors": 1, + "offline": false, + "offlineCause": null, + "oneOffExecutors": [], + "temporarilyOffline": false +} \ No newline at end of file diff --git a/labs/jenkins/src/test/resources/computerview.json b/labs/jenkins/src/test/resources/computerview.json new file mode 100644 index 0000000000..e9b070469e --- /dev/null +++ b/labs/jenkins/src/test/resources/computerview.json @@ -0,0 +1,114 @@ +{ + "busyExecutors": 0, + "computer": [{ + "actions": [], + "displayName": "master", + "executors": [{}, {}], + "icon": "computer.png", + "idle": true, + "jnlpAgent": false, + "launchSupported": true, + "loadStatistics": {}, + "manualLaunchAllowed": true, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 1385115648, + "availableSwapSpace": 32208396288, + "totalPhysicalMemory": 8053207040, + "totalSwapSpace": 32218378240 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 1 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "size": 6235500544 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "size": 79292284928 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 0 + } + }, + "numExecutors": 2, + "offline": false, + "offlineCause": null, + "oneOffExecutors": [], + "temporarilyOffline": false + }, { + "actions": [], + "displayName": "Ruboto", + "executors": [{}], + "icon": "computer.png", + "idle": true, + "jnlpAgent": true, + "launchSupported": false, + "loadStatistics": {}, + "manualLaunchAllowed": true, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 1684832256, + "availableSwapSpace": 5625421824, + "totalPhysicalMemory": 4157317120, + "totalSwapSpace": 6568271872 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 856 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "size": 53648973824 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "size": 53648969728 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": -462 + } + }, + "numExecutors": 1, + "offline": false, + "offlineCause": null, + "oneOffExecutors": [], + "temporarilyOffline": false + }, { + "actions": [], + "displayName": "winserver2008-x86", + "executors": [{}], + "icon": "computer.png", + "idle": true, + "jnlpAgent": true, + "launchSupported": false, + "loadStatistics": {}, + "manualLaunchAllowed": true, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 1117851648, + "availableSwapSpace": 1429299200, + "totalPhysicalMemory": 1781420032, + "totalSwapSpace": 1994350592 + }, + "hudson.node_monitors.ArchitectureMonitor": "Windows Server 2008 (x86)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 1 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "size": 19072663552 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "size": 19072663552 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 71 + } + }, + "numExecutors": 1, + "offline": false, + "offlineCause": null, + "oneOffExecutors": [], + "temporarilyOffline": false + }], + "displayName": "nodes", + "totalExecutors": 4 +} \ No newline at end of file diff --git a/labs/jenkins/src/test/resources/logback.xml b/labs/jenkins/src/test/resources/logback.xml new file mode 100644 index 0000000000..9679b2e03a --- /dev/null +++ b/labs/jenkins/src/test/resources/logback.xml @@ -0,0 +1,38 @@ + + + + target/test-data/jclouds.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-wire.log + + + %d %-5p [%c] [%thread] %m%n + + + + + + + + + + + + + + + + + + + + + + + diff --git a/labs/pom.xml b/labs/pom.xml index b774ad00cb..a6d3e22750 100644 --- a/labs/pom.xml +++ b/labs/pom.xml @@ -42,5 +42,6 @@ dmtf carrenza-vcloud-director openstack-swift + jenkins From 32022723ade9a680dcea066fbfd98721841b8669 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 19:47:54 +0100 Subject: [PATCH 08/12] Nova VolumeClient: adjusting attachment method names after review --- .../nova/v1_1/extensions/VolumeAsyncClient.java | 12 ++++++------ .../nova/v1_1/extensions/VolumeClient.java | 8 ++++---- .../v1_1/extensions/VolumeClientExpectTest.java | 16 ++++++++-------- .../v1_1/extensions/VolumeClientLiveTest.java | 14 +++++++------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java index fbc7465e0b..97584b1d07 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java @@ -131,7 +131,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachments") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + ListenableFuture> listAttachmentsOnServer(@PathParam("server_id") String serverId); /** * Get a specific attached volume. @@ -143,7 +143,8 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachment") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture getAttachmentForVolumeOnServer(@PathParam("id") String volumeId, + @PathParam("server_id") String serverId); /** * Attach a volume to an instance @@ -156,9 +157,8 @@ public interface VolumeAsyncClient { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Payload("%7B\"volumeAttachment\":%7B\"volumeId\":\"{id}\",\"device\":\"{device}\"%7D%7D") - ListenableFuture attachVolume(@PathParam("server_id") String serverId, - @PayloadParam("id") String volumeId, - @PayloadParam("device") String device); + ListenableFuture attachVolumeToServerAsDevice(@PayloadParam("id") String volumeId, + @PathParam("server_id") String serverId, @PayloadParam("device") String device); /** * Detach a Volume from an instance. @@ -169,7 +169,7 @@ public interface VolumeAsyncClient { @Path("/servers/{server_id}/os-volume_attachments/{id}") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnFalseOnNotFoundOr404.class) - ListenableFuture detachVolume(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId); /** * Returns a summary list of snapshots. diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java index fc26aad4cf..3f40523d79 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java @@ -80,28 +80,28 @@ public interface VolumeClient { * * @return all Floating IPs */ - Set listAttachments(String serverId); + Set listAttachmentsOnServer(String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. */ - VolumeAttachment getAttachment(String serverId, String volumeId); + VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId); /** * Attach a volume to an instance * * @return data about the new volume attachment */ - VolumeAttachment attachVolume(String serverId, String volumeId, String device); + VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device); /** * Detach a Volume from an instance. * * @return true if successful */ - Boolean detachVolume(String server_id, String volumeId); + Boolean detachVolumeFromServer(String server_id, String volumeId); /** * Returns a summary list of snapshots. diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java index 4f70e81496..3349f0ac92 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -199,7 +199,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - Set attachments = client.listAttachments("instance-1"); + Set attachments = client.listAttachmentsOnServer("instance-1"); assertEquals(attachments, ImmutableSet.of(testAttachment())); // double-check individual fields VolumeAttachment attachment = Iterables.getOnlyElement(attachments); @@ -219,7 +219,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(401).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - client.listAttachments("instance-2"); + client.listAttachmentsOnServer("instance-2"); } public void testGetAttachment() { @@ -231,7 +231,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - VolumeAttachment attachment = client.getAttachment("instance-1", "1"); + VolumeAttachment attachment = client.getAttachmentForVolumeOnServer("1", "instance-1"); assertEquals(attachment, testAttachment()); } @@ -244,7 +244,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertNull(client.getAttachment("instance-1", "1")); + assertNull(client.getAttachmentForVolumeOnServer("1", "instance-1")); } public void testAttachVolume() { @@ -257,7 +257,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - VolumeAttachment result = client.attachVolume("instance-1", "1", "/dev/vdc"); + VolumeAttachment result = client.attachVolumeToServerAsDevice("1", "instance-1", "/dev/vdc"); assertEquals(result, testAttachment()); } @@ -272,7 +272,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - client.attachVolume("instance-1", "1", "/dev/vdc"); + client.attachVolumeToServerAsDevice("1", "instance-1","/dev/vdc"); } public void testDetachVolume() { @@ -284,7 +284,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertTrue(client.detachVolume("instance-1", "1")); + assertTrue(client.detachVolumeFromServer("1", "instance-1")); } public void testDetachVolumeFail() { @@ -296,7 +296,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertFalse(client.detachVolume("instance-1", "1")); + assertFalse(client.detachVolumeFromServer("1", "instance-1")); } public void testListSnapshots() { diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java index b4b61ed103..bca364837c 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -218,22 +218,22 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); if (!servers.isEmpty()) { final String serverId = Iterables.getFirst(servers, null).getId(); - Set attachments = client.listAttachments(serverId); + Set attachments = client.listAttachmentsOnServer(serverId); assertNotNull(attachments); final int before = attachments.size(); - VolumeAttachment testAttachment = client.attachVolume(serverId, testVolume.getId(), "/dev/vdf"); + VolumeAttachment testAttachment = client.attachVolumeToServerAsDevice(testVolume.getId(), serverId, "/dev/vdf"); assertNotNull(testAttachment.getId()); assertEquals(testAttachment.getVolumeId(), testVolume.getId()); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.listAttachments(serverId).size() == before+1; + return client.listAttachmentsOnServer(serverId).size() == before+1; } }, 60 * 1000L).apply(client)); - attachments = client.listAttachments(serverId); + attachments = client.listAttachmentsOnServer(serverId); assertNotNull(attachments); assertEquals(attachments.size(), before+1); @@ -241,7 +241,7 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { boolean foundIt = false; for (VolumeAttachment att : attachments) { - VolumeAttachment details = client.getAttachment(serverId, att.getId()); + VolumeAttachment details = client.getAttachmentForVolumeOnServer(att.getVolumeId(), serverId); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getServerId()); @@ -255,11 +255,11 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { assertTrue(foundIt, "Failed to find the attachment we created in listAttachments() response"); - client.detachVolume(serverId, testVolume.getId()); + client.detachVolumeFromServer(testVolume.getId(), serverId); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.listAttachments(serverId).size() == before; + return client.listAttachmentsOnServer(serverId).size() == before; } }, 60 * 1000L).apply(client)); } From 559a37ceff1f7d5dc83cd32e377610e410769eeb Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 24 Apr 2012 11:51:20 -0700 Subject: [PATCH 09/12] master is not accessible via getComputer --- .../jclouds/jenkins/v1/features/ComputerClientLiveTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java index 44c30c652b..45d0b3d90c 100644 --- a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java +++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/features/ComputerClientLiveTest.java @@ -39,8 +39,10 @@ public class ComputerClientLiveTest extends BaseJenkinsClientLiveTest { assertNotNull(view.getDisplayName()); for (Computer computerFromView : view.getComputers()) { assertNotNull(computerFromView.getDisplayName()); - Computer computerFromGetRequest = getClient().getComputer(computerFromView.getDisplayName()); - assertEquals(computerFromGetRequest, computerFromView); + if (!"master".equals(computerFromView.getDisplayName())) { + Computer computerFromGetRequest = getClient().getComputer(computerFromView.getDisplayName()); + assertEquals(computerFromGetRequest, computerFromView); + } } } From ba7ba36d2073a2e84550e381d05b7dfae7340ade Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 24 Apr 2012 12:26:37 -0700 Subject: [PATCH 10/12] switched to buildView --- blobstore/src/main/clojure/org/jclouds/blobstore2.clj | 2 +- compute/src/main/clojure/org/jclouds/compute2.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blobstore/src/main/clojure/org/jclouds/blobstore2.clj b/blobstore/src/main/clojure/org/jclouds/blobstore2.clj index 1bb2a87abb..4d69e74dcc 100644 --- a/blobstore/src/main/clojure/org/jclouds/blobstore2.clj +++ b/blobstore/src/main/clojure/org/jclouds/blobstore2.clj @@ -118,7 +118,7 @@ Options can also be specified for extension modules (modules (apply modules (concat ext-modules (opts :extensions)))) (overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) (Properties.) (dissoc opts :extensions))) - (build BlobStoreContext))] + (buildView BlobStoreContext))] (if (some #(= :async %) options) (.getAsyncBlobStore context) (.getBlobStore context))))) diff --git a/compute/src/main/clojure/org/jclouds/compute2.clj b/compute/src/main/clojure/org/jclouds/compute2.clj index 35169afb6a..db3c4a6c89 100644 --- a/compute/src/main/clojure/org/jclouds/compute2.clj +++ b/compute/src/main/clojure/org/jclouds/compute2.clj @@ -90,7 +90,7 @@ Here's an example of creating and running a small linux node in the group webser (modules (apply modules (concat ext-modules (opts :extensions)))) (overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) (Properties.) (dissoc opts :extensions))) - (build ComputeServiceContext) + (buildView ComputeServiceContext) (getComputeService)))) ([#^ComputeServiceContext compute-context] (.getComputeService compute-context))) From c624c88274055b74256f0992ad733f0873d5618e Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 24 Apr 2012 12:27:16 -0700 Subject: [PATCH 11/12] better exception message on key not found --- core/src/main/java/org/jclouds/ContextBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/jclouds/ContextBuilder.java b/core/src/main/java/org/jclouds/ContextBuilder.java index eed4b9c408..9d8074955d 100644 --- a/core/src/main/java/org/jclouds/ContextBuilder.java +++ b/core/src/main/java/org/jclouds/ContextBuilder.java @@ -231,6 +231,8 @@ public class ContextBuilder { try { return find(newArrayList(mutable.getProperty(prov + "." + key), mutable.getProperty("jclouds." + key)), notNull()); + } catch (NoSuchElementException e) { + throw new NoSuchElementException(String.format("property %s.%s not present in properties: %s", prov, key, mutable.keySet())); } finally { mutable.remove(prov + "." + key); mutable.remove("jclouds." + key); From 1f7211d32de88f17c313dba5a4a10f9e55de8c86 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 24 Apr 2012 12:36:11 -0700 Subject: [PATCH 12/12] minor volume test-related changes --- .../openstack/nova/v1_1/domain/Volume.java | 22 +-- .../nova/v1_1/handlers/NovaErrorHandler.java | 3 + .../nova/v1_1/NovaErrorHandlerTest.java | 12 +- .../extensions/FloatingIPClientLiveTest.java | 26 +-- .../v1_1/extensions/VolumeClientLiveTest.java | 151 ++++++++++-------- .../v1_1/internal/BaseNovaClientLiveTest.java | 38 +++++ .../HPCloudComputeVolumeClientLiveTest.java | 34 ++++ 7 files changed, 177 insertions(+), 109 deletions(-) create mode 100644 providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/features/HPCloudComputeVolumeClientLiveTest.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java index ab7017ae26..ad581a650c 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -284,28 +284,20 @@ public class Volume { return Collections.unmodifiableMap(this.metadata); } + // keeping fields short in eq/hashCode so that minor state differences don't affect collection membership @Override public int hashCode() { - return Objects.hashCode(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); + return Objects.hashCode(id, zone); } @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; Volume that = Volume.class.cast(obj); - return Objects.equal(this.id, that.id) - && Objects.equal(this.status, that.status) - && Objects.equal(this.size, that.size) - && Objects.equal(this.zone, that.zone) - && Objects.equal(this.created, that.created) - && Objects.equal(this.attachments, that.attachments) - && Objects.equal(this.volumeType, that.volumeType) - && Objects.equal(this.snapshotId, that.snapshotId) - && Objects.equal(this.name, that.name) - && Objects.equal(this.description, that.description) - && Objects.equal(this.metadata, that.metadata) - ; + return Objects.equal(this.id, that.id) && Objects.equal(this.zone, that.zone); } protected ToStringHelper string() { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java index ef2c150cc1..c7cc8a2e14 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java @@ -67,6 +67,9 @@ public class NovaErrorHandler implements HttpErrorHandler { exception = new ResourceNotFoundException(message, exception); } break; + case 413: + exception = new InsufficientResourcesException(message, exception); + break; } command.setException(exception); } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java index ec82fd4e6a..91dbf2470a 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java @@ -76,7 +76,6 @@ public class NovaErrorHandlerTest { IllegalStateException.class); } - @Test public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() { assertCodeMakes( @@ -87,6 +86,17 @@ public class NovaErrorHandlerTest { "{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot allocate any more addresses\", \"code\": 400}}", InsufficientResourcesException.class); } + + @Test + public void test413MakesInsufficientResourcesException() { + assertCodeMakes( + "POST", + URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/os-volumes"), + 413, + "HTTP/1.1 413 Request Entity Too Large", + "{\"badRequest\": {\"message\": \"Volume quota exceeded. You cannot create a volume of size 1G\", \"code\": 413, \"retryAfter\": 0}}", + InsufficientResourcesException.class); + } @Test public void test404MakesResourceNotFoundException() { diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java index b3262d325b..d2bfb9d91f 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java @@ -28,15 +28,11 @@ import java.util.Set; import org.jclouds.openstack.nova.v1_1.domain.Address; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.Server.Status; -import org.jclouds.openstack.nova.v1_1.features.FlavorClient; -import org.jclouds.openstack.nova.v1_1.features.ImageClient; import org.jclouds.openstack.nova.v1_1.features.ServerClient; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; import org.testng.annotations.Test; import com.google.common.base.Optional; -import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; /** @@ -110,8 +106,7 @@ public class FloatingIPClientLiveTest extends BaseNovaClientLiveTest { continue; FloatingIPClient client = clientOption.get(); ServerClient serverClient = novaContext.getApi().getServerClientForZone(zoneId); - Server server = serverClient.createServer("test", imageIdForZone(zoneId), flavorRefForZone(zoneId)); - blockUntilServerActive(server.getId(), serverClient); + Server server = createServerInZone(zoneId); FloatingIP floatingIP = client.allocate(); assertNotNull(floatingIP); try { @@ -124,25 +119,6 @@ public class FloatingIPClientLiveTest extends BaseNovaClientLiveTest { } } - private String imageIdForZone(String zoneId) { - ImageClient imageClient = novaContext.getApi().getImageClientForZone(zoneId); - return Iterables.getLast(imageClient.listImages()).getId(); - } - - private String flavorRefForZone(String zoneId) { - FlavorClient flavorClient = novaContext.getApi().getFlavorClientForZone(zoneId); - return Iterables.getLast(flavorClient.listFlavors()).getId(); - } - - private void blockUntilServerActive(String serverId, ServerClient client) throws InterruptedException { - Server currentDetails = null; - for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != Status.ACTIVE; currentDetails = client - .getServer(serverId)) { - System.out.printf("blocking on status active%n%s%n", currentDetails); - Thread.sleep(5 * 1000); - } - } - protected static void assertEventually(Runnable assertion) { long start = System.currentTimeMillis(); AssertionError error = null; diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java index bca364837c..05d1e268ba 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -18,17 +18,18 @@ */ package org.jclouds.openstack.nova.v1_1.extensions; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; import java.util.Set; -import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.nova.v1_1.domain.Volume; import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; -import org.jclouds.openstack.nova.v1_1.domain.Volume; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; -import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; import org.jclouds.predicates.RetryablePredicate; import org.testng.annotations.AfterGroups; import org.testng.annotations.BeforeGroups; @@ -41,13 +42,13 @@ import com.google.common.collect.Iterables; /** * Tests behavior of VolumeClient - * + * * @author Adam Lowe */ @Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) public class VolumeClientLiveTest extends BaseNovaClientLiveTest { - private VolumeClient client; + private Optional volumeOption; private String zone; private Volume testVolume; @@ -57,58 +58,61 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { @Override public void setupContext() { super.setupContext(); - zone = Iterables.getFirst(novaContext.getApi().getConfiguredZones(), "nova"); - Optional optClient = novaContext.getApi().getVolumeExtensionForZone(zone); - if (optClient.isPresent()) { - client = optClient.get(); - } + zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); + volumeOption = novaContext.getApi().getVolumeExtensionForZone(zone); } @AfterGroups(groups = "live", alwaysRun = true) @Override - protected void tearDown() { - if (client != null) { + protected void tearDown() { + if (volumeOption.isPresent()) { if (testSnapshot != null) { final String snapshotId = testSnapshot.getId(); - assertTrue(client.deleteSnapshot(snapshotId)); + assertTrue(volumeOption.get().deleteSnapshot(snapshotId)); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.getSnapshot(snapshotId) == null; + return volumeOption.get().getSnapshot(snapshotId) == null; } - }, 30 * 1000L).apply(client)); + }, 30 * 1000L).apply(volumeOption.get())); } if (testVolume != null) { final String volumeId = testVolume.getId(); - assertTrue(client.deleteVolume(volumeId)); + assertTrue(volumeOption.get().deleteVolume(volumeId)); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.getVolume(volumeId) == null; + return volumeOption.get().getVolume(volumeId) == null; } - }, 180 * 1000L).apply(client)); + }, 180 * 1000L).apply(volumeOption.get())); } } super.tearDown(); } - public void testCreateVolume() throws Exception { - testVolume = client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume").availabilityZone(zone)); - for (int i=0; i<100 && testVolume.getStatus() == Volume.Status.CREATING; i++) { - Thread.sleep(100); - testVolume = client.getVolume(testVolume.getId()); + public void testCreateVolume() { + if (volumeOption.isPresent()) { + testVolume = volumeOption.get().createVolume( + 1, + CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume") + .availabilityZone(zone)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return volumeOption.get().getVolume(testVolume.getId()).getStatus() == Volume.Status.AVAILABLE; + } + }, 180 * 1000L).apply(volumeOption.get())); } - assertEquals(testVolume.getStatus(), Volume.Status.AVAILABLE); } @Test(dependsOnMethods = "testCreateVolume") - public void testListVolumes() throws Exception { - if (client != null) { - Set volumes = client.listVolumes(); + public void testListVolumes() { + if (volumeOption.isPresent()) { + Set volumes = volumeOption.get().listVolumes(); assertNotNull(volumes); boolean foundIt = false; for (Volume vol : volumes) { - Volume details = client.getVolume(vol.getId()); + Volume details = volumeOption.get().getVolume(vol.getId()); assertNotNull(details); if (Objects.equal(details.getId(), testVolume.getId())) { foundIt = true; @@ -119,14 +123,14 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { } @Test(dependsOnMethods = "testCreateVolume") - public void testListVolumesInDetail() throws Exception { - if (client != null) { - Set volumes = client.listVolumesInDetail(); + public void testListVolumesInDetail() { + if (volumeOption.isPresent()) { + Set volumes = volumeOption.get().listVolumesInDetail(); assertNotNull(volumes); assertTrue(volumes.contains(testVolume)); boolean foundIt = false; for (Volume vol : volumes) { - Volume details = client.getVolume(vol.getId()); + Volume details = volumeOption.get().getVolume(vol.getId()); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getCreated()); @@ -146,9 +150,12 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { } @Test(dependsOnMethods = "testCreateVolume") - public void testCreateSnapshot() throws Exception { - if (client != null) { - testSnapshot = client.createSnapshot(testVolume.getId(), CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + public void testCreateSnapshot() { + if (volumeOption.isPresent()) { + testSnapshot = volumeOption.get().createSnapshot( + testVolume.getId(), + CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description( + "jclouds live test snapshot").force()); assertNotNull(testSnapshot); assertNotNull(testSnapshot.getId()); final String snapshotId = testSnapshot.getId(); @@ -159,20 +166,20 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.getSnapshot(snapshotId).getStatus() == Volume.Status.AVAILABLE; + return volumeOption.get().getSnapshot(snapshotId).getStatus() == Volume.Status.AVAILABLE; } - }, 30 * 1000L).apply(client)); + }, 30 * 1000L).apply(volumeOption.get())); } } - + @Test(dependsOnMethods = "testCreateSnapshot") - public void testListSnapshots() throws Exception { - if (client != null) { - Set snapshots = client.listSnapshots(); + public void testListSnapshots() { + if (volumeOption.isPresent()) { + Set snapshots = volumeOption.get().listSnapshots(); assertNotNull(snapshots); boolean foundIt = false; for (VolumeSnapshot snap : snapshots) { - VolumeSnapshot details = client.getSnapshot(snap.getId()); + VolumeSnapshot details = volumeOption.get().getSnapshot(snap.getId()); if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { foundIt = true; } @@ -185,24 +192,24 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { } @Test(dependsOnMethods = "testCreateSnapshot") - public void testListSnapshotsInDetail() throws Exception { - if (client != null) { - Set snapshots = client.listSnapshotsInDetail(); + public void testListSnapshotsInDetail() { + if (volumeOption.isPresent()) { + Set snapshots = volumeOption.get().listSnapshotsInDetail(); assertNotNull(snapshots); boolean foundIt = false; for (VolumeSnapshot snap : snapshots) { - VolumeSnapshot details = client.getSnapshot(snap.getId()); + VolumeSnapshot details = volumeOption.get().getSnapshot(snap.getId()); if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { foundIt = true; assertSame(details, testSnapshot); } assertSame(details, snap); } - + assertTrue(foundIt, "Failed to find the snapshot we created in listSnapshotsInDetail() response"); } } - + private void assertSame(VolumeSnapshot a, VolumeSnapshot b) { assertNotNull(a); assertNotNull(b); @@ -212,36 +219,39 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { assertEquals(a.getVolumeId(), b.getVolumeId()); } - @Test(enabled=false, dependsOnMethods = "testCreateVolume") // disabled as it alters an existing server - public void testAttachments() throws Exception { - if (client != null) { - Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); - if (!servers.isEmpty()) { - final String serverId = Iterables.getFirst(servers, null).getId(); - Set attachments = client.listAttachmentsOnServer(serverId); + @Test(dependsOnMethods = "testCreateVolume") + public void testAttachments() { + if (volumeOption.isPresent()) { + String server_id = null; + try { + final String serverId = server_id = createServerInZone(zone).getId(); + + Set attachments = volumeOption.get().listAttachmentsOnServer(serverId); assertNotNull(attachments); final int before = attachments.size(); - VolumeAttachment testAttachment = client.attachVolumeToServerAsDevice(testVolume.getId(), serverId, "/dev/vdf"); + VolumeAttachment testAttachment = volumeOption.get().attachVolumeToServerAsDevice(testVolume.getId(), + serverId, "/dev/vdf"); assertNotNull(testAttachment.getId()); assertEquals(testAttachment.getVolumeId(), testVolume.getId()); - + assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.listAttachmentsOnServer(serverId).size() == before+1; + return volumeOption.get().listAttachmentsOnServer(serverId).size() == before + 1; } - }, 60 * 1000L).apply(client)); + }, 60 * 1000L).apply(volumeOption.get())); - attachments = client.listAttachmentsOnServer(serverId); + attachments = volumeOption.get().listAttachmentsOnServer(serverId); assertNotNull(attachments); - assertEquals(attachments.size(), before+1); - - assertEquals(client.getVolume(testVolume.getId()).getStatus(), Volume.Status.IN_USE); + assertEquals(attachments.size(), before + 1); + + assertEquals(volumeOption.get().getVolume(testVolume.getId()).getStatus(), Volume.Status.IN_USE); boolean foundIt = false; for (VolumeAttachment att : attachments) { - VolumeAttachment details = client.getAttachmentForVolumeOnServer(att.getVolumeId(), serverId); + VolumeAttachment details = volumeOption.get() + .getAttachmentForVolumeOnServer(att.getVolumeId(), serverId); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getServerId()); @@ -255,14 +265,19 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { assertTrue(foundIt, "Failed to find the attachment we created in listAttachments() response"); - client.detachVolumeFromServer(testVolume.getId(), serverId); + volumeOption.get().detachVolumeFromServer(testVolume.getId(), serverId); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.listAttachmentsOnServer(serverId).size() == before; + return volumeOption.get().listAttachmentsOnServer(serverId).size() == before; } - }, 60 * 1000L).apply(client)); + }, 60 * 1000L).apply(volumeOption.get())); + + } finally { + if (server_id != null) + novaContext.getApi().getServerClientForZone(zone).deleteServer(server_id); } + } } } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java index ca8089bf7f..2a37f52f7e 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java @@ -25,11 +25,19 @@ import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; import org.jclouds.openstack.nova.v1_1.NovaAsyncClient; import org.jclouds.openstack.nova.v1_1.NovaClient; import org.jclouds.openstack.nova.v1_1.config.NovaProperties; +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.Server.Status; +import org.jclouds.openstack.nova.v1_1.features.FlavorClient; +import org.jclouds.openstack.nova.v1_1.features.ImageClient; +import org.jclouds.openstack.nova.v1_1.features.ServerClient; import org.jclouds.rest.RestContext; import org.testng.annotations.AfterGroups; import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; + /** * Tests behavior of {@code NovaClient} * @@ -64,5 +72,35 @@ public class BaseNovaClientLiveTest extends BaseComputeServiceContextLiveTest { if (novaContext != null) novaContext.close(); } + + protected Server createServerInZone(String zoneId) { + ServerClient serverClient = novaContext.getApi().getServerClientForZone(zoneId); + Server server = serverClient.createServer("test", imageIdForZone(zoneId), flavorRefForZone(zoneId)); + blockUntilServerActive(server.getId(), serverClient); + return server; + } + + private void blockUntilServerActive(String serverId, ServerClient client) { + Server currentDetails = null; + for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != Status.ACTIVE; currentDetails = client + .getServer(serverId)) { + System.out.printf("blocking on status active%n%s%n", currentDetails); + try { + Thread.sleep(5 * 1000); + } catch (InterruptedException e) { + throw Throwables.propagate(e); + } + } + } + + protected String imageIdForZone(String zoneId) { + ImageClient imageClient = novaContext.getApi().getImageClientForZone(zoneId); + return Iterables.getLast(imageClient.listImages()).getId(); + } + + protected String flavorRefForZone(String zoneId) { + FlavorClient flavorClient = novaContext.getApi().getFlavorClientForZone(zoneId); + return Iterables.getLast(flavorClient.listFlavors()).getId(); + } } diff --git a/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/features/HPCloudComputeVolumeClientLiveTest.java b/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/features/HPCloudComputeVolumeClientLiveTest.java new file mode 100644 index 0000000000..1dc049f1c8 --- /dev/null +++ b/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/features/HPCloudComputeVolumeClientLiveTest.java @@ -0,0 +1,34 @@ +/** + * 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.hpcloud.compute.features; + +import org.jclouds.openstack.nova.v1_1.extensions.VolumeClientLiveTest; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "live", testName = "HPCloudComputeVolumeClientLiveTest") +public class HPCloudComputeVolumeClientLiveTest extends VolumeClientLiveTest { + public HPCloudComputeVolumeClientLiveTest() { + provider = "hpcloud-compute"; + } + +}