Merge branch 'master' of github.com:jclouds/jclouds into 1.5.x

* 'master' of github.com:jclouds/jclouds:
  minor volume test-related changes
  better exception message on key not found
  switched to buildView
  master is not accessible via getComputer
  Nova VolumeClient: adjusting attachment method names after review
  Issue 907: initial jenkins api
  Nova VolumeClient: improving javadocs
  Nova VolumeClient: improving javadocs
  Nova VolumeClient: improving javadocs
  Adding remaining VolumeClientExpectTest methods
  Adding CreateVolume and CreateSnapshot options and improving live tests accordingly
  Adding Volumes extension - first stage includes get/list volumes and list attachments
This commit is contained in:
Adrian Cole 2012-04-24 12:40:45 -07:00
commit b6597630db
58 changed files with 4058 additions and 28 deletions

View File

@ -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.KeyPairAsyncClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; 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.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.ExtensionAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient;
@ -120,4 +121,10 @@ public interface NovaAsyncClient {
Optional<SimpleTenantUsageAsyncClient> getSimpleTenantUsageExtensionForZone( Optional<SimpleTenantUsageAsyncClient> getSimpleTenantUsageExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Volume features.
*/
@Delegate
Optional<VolumeAsyncClient> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
} }

View File

@ -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.KeyPairClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; 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.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.ExtensionClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorClient; 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.ImageClient;
@ -122,4 +123,12 @@ public interface NovaClient {
Optional<SimpleTenantUsageClient> getSimpleTenantUsageExtensionForZone( Optional<SimpleTenantUsageClient> getSimpleTenantUsageExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Volume features.
*/
@Delegate
Optional<VolumeClient> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
} }

View File

@ -73,6 +73,7 @@ public class NovaRestClientModule extends RestClientModule<NovaClient, NovaAsync
.put(KeyPairClient.class, KeyPairAsyncClient.class) .put(KeyPairClient.class, KeyPairAsyncClient.class)
.put(HostAdministrationClient.class, HostAdministrationAsyncClient.class) .put(HostAdministrationClient.class, HostAdministrationAsyncClient.class)
.put(SimpleTenantUsageClient.class, SimpleTenantUsageAsyncClient.class) .put(SimpleTenantUsageClient.class, SimpleTenantUsageAsyncClient.class)
.put(VolumeClient.class, VolumeAsyncClient.class)
.build(); .build();
public NovaRestClientModule() { public NovaRestClientModule() {

View File

@ -0,0 +1,324 @@
/**
* 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<T extends Builder<T>> {
protected abstract T self();
private String id;
private Status status;
private int size;
private String zone;
private Date created;
private Set<VolumeAttachment> attachments = Sets.newLinkedHashSet();
private String volumeType;
private String snapshotId;
private String name;
private String description;
private Map<String, String> 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<VolumeAttachment> 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<String, String> 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();
}
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<ConcreteBuilder> {
@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<VolumeAttachment> 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<String, String> 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(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<VolumeAttachment> getAttachments() {
return Collections.unmodifiableSet(this.attachments);
}
/**
* @return the type of this volume
*/
@Nullable
public String getVolumeType() {
return this.volumeType;
}
@Nullable
public String getSnapshotId() {
return this.snapshotId;
}
/**
* @return the name of this volume - as displayed in the openstack console
*/
@Nullable
public String getName() {
return this.name;
}
/**
* @return the description of this volume - as displayed in the openstack console
*/
@Nullable
public String getDescription() {
return this.description;
}
@Nullable
public Map<String, String> getMetadata() {
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, zone);
}
@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.zone, that.zone);
}
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();
}
}

View File

@ -0,0 +1,168 @@
/**
* 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 org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
/**
* An Openstack Nova Volume Attachment (describes how Volumes are attached to Servers)
*/
public class VolumeAttachment {
public static Builder<?> builder() {
return new ConcreteBuilder();
}
public Builder<?> toBuilder() {
return new ConcreteBuilder().fromAttachment(this);
}
public static abstract class Builder<T extends Builder<T>> {
protected abstract T self();
private String id;
private String volumeId;
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();
}
public VolumeAttachment build() {
return new VolumeAttachment(this);
}
public T fromAttachment(VolumeAttachment in) {
return this
.id(in.getId())
.volumeId(in.getVolumeId())
.serverId(in.getServerId())
.device(in.getDevice())
;
}
}
private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
@Override
protected ConcreteBuilder self() {
return this;
}
}
private final String id;
private final String volumeId;
private final String serverId;
private final String device;
protected VolumeAttachment(Builder<?> builder) {
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())
*/
public String getId() {
return this.id;
}
/**
* @return the id of the volume attached
*/
public String getVolumeId() {
return this.volumeId;
}
/**
* @return the id of the server the volume is attached to
*/
@Nullable
public String getServerId() {
return this.serverId;
}
/**
* @return the device name (e.g. "/dev/vdc")
*/
@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;
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)
&& 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();
}
}

View File

@ -0,0 +1,233 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.openstack.nova.v1_1.domain;
import static com.google.common.base.Preconditions.checkNotNull;
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 VolumeSnapshot {
public static Builder<?> builder() {
return new ConcreteBuilder();
}
public Builder<?> toBuilder() {
return new ConcreteBuilder().fromSnapshot(this);
}
public static abstract class Builder<T extends Builder<T>> {
protected abstract T self();
private String id;
private String volumeId;
private Volume.Status status;
private int size;
private Date created;
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();
}
public VolumeSnapshot build() {
return new VolumeSnapshot(this);
}
public T fromSnapshot(VolumeSnapshot 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<ConcreteBuilder> {
@Override
protected ConcreteBuilder self() {
return this;
}
}
private final String id;
private final String volumeId;
private final Volume.Status 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 VolumeSnapshot(Builder<?> builder) {
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;
this.description = builder.description;
}
/**
* @return the id of this snapshot
*/
public String getId() {
return this.id;
}
/**
* @return the id of the Volume this snapshot was taken from
*/
public String getVolumeId() {
return this.volumeId;
}
/**
* @return the status of this snapshot
*/
public Volume.Status getStatus() {
return this.status;
}
/**
* @return the size in GB of the volume this snapshot was taken from
*/
public int getSize() {
return this.size;
}
/**
* @return the data the snapshot was taken
*/
@Nullable
public Date getCreated() {
return this.created;
}
/**
* @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() {
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;
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)
&& 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();
}
}

View File

@ -0,0 +1,234 @@
/**
* 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.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;
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.
* <p/>
*
* @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<Set<Volume>> 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<Set<Volume>> 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<Volume> getVolume(@PathParam("id") String volumeId);
/**
* Creates a new volume
*
* @return the new Snapshot
*/
@POST
@Path("/os-volumes")
@SelectJson("volume")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@MapBinder(CreateVolumeOptions.class)
ListenableFuture<Volume> createVolume(@PayloadParam("size") int sizeGB, CreateVolumeOptions... options);
/**
* Delete a volume.
*
* @return true if successful
*/
@DELETE
@Path("/os-volumes/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
ListenableFuture<Boolean> 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<Set<VolumeAttachment>> listAttachmentsOnServer(@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<VolumeAttachment> getAttachmentForVolumeOnServer(@PathParam("id") String volumeId,
@PathParam("server_id") String serverId);
/**
* 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)
@Payload("%7B\"volumeAttachment\":%7B\"volumeId\":\"{id}\",\"device\":\"{device}\"%7D%7D")
ListenableFuture<VolumeAttachment> attachVolumeToServerAsDevice(@PayloadParam("id") String volumeId,
@PathParam("server_id") String serverId, @PayloadParam("device") String device);
/**
* 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<Boolean> detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId);
/**
* Returns a summary list of snapshots.
*
* @return the list of snapshots
*/
@GET
@Path("/os-snapshots")
@SelectJson("snapshots")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnEmptySetOnNotFoundOr404.class)
ListenableFuture<Set<VolumeSnapshot>> 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<Set<VolumeSnapshot>> listSnapshotsInDetail();
/**
* Return data about the given snapshot.
*
* @return details of a specific snapshot.
*/
@GET
@Path("/os-snapshots/{id}")
@SelectJson("snapshot")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<VolumeSnapshot> getSnapshot(@PathParam("id") String snapshotId);
/**
* Creates a new Snapshot
*
* @return the new Snapshot
*/
@POST
@Path("/os-snapshots")
@SelectJson("snapshot")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@MapBinder(CreateVolumeSnapshotOptions.class)
ListenableFuture<VolumeSnapshot> createSnapshot(@PayloadParam("volume_id") String volumeId, CreateVolumeSnapshotOptions... options);
/**
* Delete a snapshot.
*
* @return true if successful
*/
@DELETE
@Path("/os-snapshots/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
ListenableFuture<Boolean> deleteSnapshot(@PathParam("id") String snapshotId);
}

View File

@ -0,0 +1,141 @@
/**
* 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.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;
/**
* Provides synchronous access to Volumes.
* <p/>
*
* @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<Volume> listVolumes();
/**
* Returns a detailed list of volumes.
*
* @return the list of volumes.
*/
Set<Volume> 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(int sizeGB, CreateVolumeOptions... options);
/**
* Delete a snapshot.
*
* @return true if successful
*/
Boolean deleteVolume(String volumeId);
/**
* List volume attachments for a given instance.
*
* @return all Floating IPs
*/
Set<VolumeAttachment> listAttachmentsOnServer(String serverId);
/**
* Get a specific attached volume.
*
* @return data about the given volume attachment.
*/
VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId);
/**
* Attach a volume to an instance
*
* @return data about the new volume attachment
*/
VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device);
/**
* Detach a Volume from an instance.
*
* @return true if successful
*/
Boolean detachVolumeFromServer(String server_id, String volumeId);
/**
* Returns a summary list of snapshots.
*
* @return the list of snapshots
*/
Set<VolumeSnapshot> listSnapshots();
/**
* Returns a summary list of snapshots.
*
* @return the list of snapshots
*/
Set<VolumeSnapshot> listSnapshotsInDetail();
/**
* Return data about the given snapshot.
*
* @return details of a specific snapshot.
*/
VolumeSnapshot getSnapshot(String snapshotId);
/**
* Creates a new Snapshot
*
* @return the new Snapshot
*/
VolumeSnapshot createSnapshot(String volumeId, CreateVolumeSnapshotOptions... options);
/**
* Delete a snapshot.
*
* @return true if successful
*/
Boolean deleteSnapshot(String snapshotId);
}

View File

@ -66,6 +66,8 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
URI.create("http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1")) URI.create("http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1"))
.put(URI.create(ExtensionNamespaces.HOSTS), .put(URI.create(ExtensionNamespaces.HOSTS),
URI.create("http://docs.openstack.org/compute/ext/hosts/api/v1.1")) 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(); .build();
@Inject @Inject

View File

@ -67,6 +67,9 @@ public class NovaErrorHandler implements HttpErrorHandler {
exception = new ResourceNotFoundException(message, exception); exception = new ResourceNotFoundException(message, exception);
} }
break; break;
case 413:
exception = new InsufficientResourcesException(message, exception);
break;
} }
command.setException(exception); command.setException(exception);
} }

View File

@ -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<String, String> metadata = ImmutableMap.of();
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
Map<String, Object> 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 extends HttpRequest> 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<String, String> metadata) {
checkNotNull(metadata, "metadata");
checkArgument(metadata.size() <= 5,
"you cannot have more then 5 metadata values. You specified: " + metadata.size());
for (Entry<String, String> 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<String, String> 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<String, String> metadata) {
return new CreateVolumeOptions().metadata(metadata);
}
}
}

View File

@ -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 extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
Map<String, String> 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 extends HttpRequest> 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();
}
}
}

View File

@ -76,7 +76,6 @@ public class NovaErrorHandlerTest {
IllegalStateException.class); IllegalStateException.class);
} }
@Test @Test
public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() { public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() {
assertCodeMakes( assertCodeMakes(
@ -88,6 +87,17 @@ public class NovaErrorHandlerTest {
InsufficientResourcesException.class); 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 @Test
public void test404MakesResourceNotFoundException() { public void test404MakesResourceNotFoundException() {
assertCodeMakes("GET", URI.create("https://api.openstack.nova.com/foo"), 404, "", "Not Found", assertCodeMakes("GET", URI.create("https://api.openstack.nova.com/foo"), 404, "", "Not Found",

View File

@ -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.Address;
import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; 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;
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.features.ServerClient;
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
/** /**
@ -110,8 +106,7 @@ public class FloatingIPClientLiveTest extends BaseNovaClientLiveTest {
continue; continue;
FloatingIPClient client = clientOption.get(); FloatingIPClient client = clientOption.get();
ServerClient serverClient = novaContext.getApi().getServerClientForZone(zoneId); ServerClient serverClient = novaContext.getApi().getServerClientForZone(zoneId);
Server server = serverClient.createServer("test", imageIdForZone(zoneId), flavorRefForZone(zoneId)); Server server = createServerInZone(zoneId);
blockUntilServerActive(server.getId(), serverClient);
FloatingIP floatingIP = client.allocate(); FloatingIP floatingIP = client.allocate();
assertNotNull(floatingIP); assertNotNull(floatingIP);
try { 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) { protected static void assertEventually(Runnable assertion) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
AssertionError error = null; AssertionError error = null;

View File

@ -0,0 +1,458 @@
/**
* 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.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.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.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,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(200).payload(payloadFromResource("/volume_list.json")).build()
).getVolumeExtensionForZone("az-1.region-a.geo-1").get();
Set<Volume> 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<Volume> 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,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(200).payload(payloadFromResource("/volume_list_detail.json")).build()
).getVolumeExtensionForZone("az-1.region-a.geo-1").get();
Set<Volume> 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<Volume> 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,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(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 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,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(200).payload(payloadFromResource("/attachment_list.json")).build()
).getVolumeExtensionForZone("az-1.region-a.geo-1").get();
Set<VolumeAttachment> attachments = client.listAttachmentsOnServer("instance-1");
assertEquals(attachments, ImmutableSet.of(testAttachment()));
// 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.listAttachmentsOnServer("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.getAttachmentForVolumeOnServer("1", "instance-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.getAttachmentForVolumeOnServer("1", "instance-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.attachVolumeToServerAsDevice("1", "instance-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.attachVolumeToServerAsDevice("1", "instance-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.detachVolumeFromServer("1", "instance-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.detachVolumeFromServer("1", "instance-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<VolumeSnapshot> 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<VolumeSnapshot> 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<VolumeSnapshot> 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<VolumeSnapshot> 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 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();
}
}

View File

@ -0,0 +1,283 @@
/**
* 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 static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.Set;
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.BaseNovaClientLiveTest;
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;
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 VolumeClient
*
* @author Adam Lowe
*/
@Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true)
public class VolumeClientLiveTest extends BaseNovaClientLiveTest {
private Optional<VolumeClient> volumeOption;
private String zone;
private Volume testVolume;
private VolumeSnapshot testSnapshot;
@BeforeGroups(groups = { "integration", "live" })
@Override
public void setupContext() {
super.setupContext();
zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova");
volumeOption = novaContext.getApi().getVolumeExtensionForZone(zone);
}
@AfterGroups(groups = "live", alwaysRun = true)
@Override
protected void tearDown() {
if (volumeOption.isPresent()) {
if (testSnapshot != null) {
final String snapshotId = testSnapshot.getId();
assertTrue(volumeOption.get().deleteSnapshot(snapshotId));
assertTrue(new RetryablePredicate<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().getSnapshot(snapshotId) == null;
}
}, 30 * 1000L).apply(volumeOption.get()));
}
if (testVolume != null) {
final String volumeId = testVolume.getId();
assertTrue(volumeOption.get().deleteVolume(volumeId));
assertTrue(new RetryablePredicate<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().getVolume(volumeId) == null;
}
}, 180 * 1000L).apply(volumeOption.get()));
}
}
super.tearDown();
}
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<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().getVolume(testVolume.getId()).getStatus() == Volume.Status.AVAILABLE;
}
}, 180 * 1000L).apply(volumeOption.get()));
}
}
@Test(dependsOnMethods = "testCreateVolume")
public void testListVolumes() {
if (volumeOption.isPresent()) {
Set<Volume> volumes = volumeOption.get().listVolumes();
assertNotNull(volumes);
boolean foundIt = false;
for (Volume vol : volumes) {
Volume details = volumeOption.get().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() {
if (volumeOption.isPresent()) {
Set<Volume> volumes = volumeOption.get().listVolumesInDetail();
assertNotNull(volumes);
assertTrue(volumes.contains(testVolume));
boolean foundIt = false;
for (Volume vol : volumes) {
Volume details = volumeOption.get().getVolume(vol.getId());
assertNotNull(details);
assertNotNull(details.getId());
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() {
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();
assertNotNull(testSnapshot.getStatus());
assertTrue(testSnapshot.getSize() > -1);
assertNotNull(testSnapshot.getCreated());
assertTrue(new RetryablePredicate<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().getSnapshot(snapshotId).getStatus() == Volume.Status.AVAILABLE;
}
}, 30 * 1000L).apply(volumeOption.get()));
}
}
@Test(dependsOnMethods = "testCreateSnapshot")
public void testListSnapshots() {
if (volumeOption.isPresent()) {
Set<VolumeSnapshot> snapshots = volumeOption.get().listSnapshots();
assertNotNull(snapshots);
boolean foundIt = false;
for (VolumeSnapshot snap : snapshots) {
VolumeSnapshot details = volumeOption.get().getSnapshot(snap.getId());
if (Objects.equal(snap.getVolumeId(), testVolume.getId())) {
foundIt = true;
}
assertNotNull(details);
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() {
if (volumeOption.isPresent()) {
Set<VolumeSnapshot> snapshots = volumeOption.get().listSnapshotsInDetail();
assertNotNull(snapshots);
boolean foundIt = false;
for (VolumeSnapshot snap : snapshots) {
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);
assertEquals(a.getId(), b.getId());
assertEquals(a.getDescription(), b.getDescription());
assertEquals(a.getName(), b.getName());
assertEquals(a.getVolumeId(), b.getVolumeId());
}
@Test(dependsOnMethods = "testCreateVolume")
public void testAttachments() {
if (volumeOption.isPresent()) {
String server_id = null;
try {
final String serverId = server_id = createServerInZone(zone).getId();
Set<VolumeAttachment> attachments = volumeOption.get().listAttachmentsOnServer(serverId);
assertNotNull(attachments);
final int before = attachments.size();
VolumeAttachment testAttachment = volumeOption.get().attachVolumeToServerAsDevice(testVolume.getId(),
serverId, "/dev/vdf");
assertNotNull(testAttachment.getId());
assertEquals(testAttachment.getVolumeId(), testVolume.getId());
assertTrue(new RetryablePredicate<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().listAttachmentsOnServer(serverId).size() == before + 1;
}
}, 60 * 1000L).apply(volumeOption.get()));
attachments = volumeOption.get().listAttachmentsOnServer(serverId);
assertNotNull(attachments);
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 = volumeOption.get()
.getAttachmentForVolumeOnServer(att.getVolumeId(), serverId);
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");
volumeOption.get().detachVolumeFromServer(testVolume.getId(), serverId);
assertTrue(new RetryablePredicate<VolumeClient>(new Predicate<VolumeClient>() {
@Override
public boolean apply(VolumeClient volumeClient) {
return volumeOption.get().listAttachmentsOnServer(serverId).size() == before;
}
}, 60 * 1000L).apply(volumeOption.get()));
} finally {
if (server_id != null)
novaContext.getApi().getServerClientForZone(zone).deleteServer(server_id);
}
}
}
}

View File

@ -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.NovaAsyncClient;
import org.jclouds.openstack.nova.v1_1.NovaClient; import org.jclouds.openstack.nova.v1_1.NovaClient;
import org.jclouds.openstack.nova.v1_1.config.NovaProperties; 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.jclouds.rest.RestContext;
import org.testng.annotations.AfterGroups; import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups; import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
/** /**
* Tests behavior of {@code NovaClient} * Tests behavior of {@code NovaClient}
* *
@ -65,4 +73,34 @@ public class BaseNovaClientLiveTest extends BaseComputeServiceContextLiveTest {
novaContext.close(); 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();
}
} }

View File

@ -20,6 +20,8 @@ package org.jclouds.openstack.nova.v1_1.internal;
import java.net.URI; import java.net.URI;
import javax.ws.rs.core.MediaType;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture; import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture;
@ -68,4 +70,14 @@ public class BaseNovaExpectTest<T> extends BaseRestClientExpectTest<T> {
unmatchedExtensionsOfNovaResponse = HttpResponse.builder().statusCode(200) unmatchedExtensionsOfNovaResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/extension_list.json")).build(); .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);
}
} }

View File

@ -0,0 +1 @@
{"volumeAttachment": {"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}}

View File

@ -0,0 +1 @@
{"volumeAttachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}]}

View File

@ -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}}

View File

@ -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}]}

View File

@ -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}]}

View File

@ -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": {}}}

View File

@ -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": {}}]}

View File

@ -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": {}}]}

View File

@ -118,7 +118,7 @@ Options can also be specified for extension modules
(modules (apply modules (concat ext-modules (opts :extensions)))) (modules (apply modules (concat ext-modules (opts :extensions))))
(overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) (overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1)
(Properties.) (dissoc opts :extensions))) (Properties.) (dissoc opts :extensions)))
(build BlobStoreContext))] (buildView BlobStoreContext))]
(if (some #(= :async %) options) (if (some #(= :async %) options)
(.getAsyncBlobStore context) (.getAsyncBlobStore context)
(.getBlobStore context))))) (.getBlobStore context)))))

View File

@ -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)))) (modules (apply modules (concat ext-modules (opts :extensions))))
(overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) (overrides (reduce #(do (.put %1 (name (first %2)) (second %2)) %1)
(Properties.) (dissoc opts :extensions))) (Properties.) (dissoc opts :extensions)))
(build ComputeServiceContext) (buildView ComputeServiceContext)
(getComputeService)))) (getComputeService))))
([#^ComputeServiceContext compute-context] ([#^ComputeServiceContext compute-context]
(.getComputeService compute-context))) (.getComputeService compute-context)))

View File

@ -231,6 +231,8 @@ public class ContextBuilder {
try { try {
return find(newArrayList(mutable.getProperty(prov + "." + key), mutable.getProperty("jclouds." + key)), return find(newArrayList(mutable.getProperty(prov + "." + key), mutable.getProperty("jclouds." + key)),
notNull()); notNull());
} catch (NoSuchElementException e) {
throw new NoSuchElementException(String.format("property %s.%s not present in properties: %s", prov, key, mutable.keySet()));
} finally { } finally {
mutable.remove(prov + "." + key); mutable.remove(prov + "." + key);
mutable.remove("jclouds." + key); mutable.remove("jclouds." + key);

123
labs/jenkins/pom.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-project</artifactId>
<version>1.5.0-SNAPSHOT</version>
<relativePath>../../project/pom.xml</relativePath>
</parent>
<groupId>org.jclouds.labs</groupId>
<artifactId>jenkins</artifactId>
<name>jcloud jenkins api</name>
<description>jclouds components to access an implementation of Jenkins</description>
<packaging>bundle</packaging>
<properties>
<test.jenkins.endpoint>http://localhost:8080</test.jenkins.endpoint>
<test.jenkins.api-version>1.0</test.jenkins.api-version>
<test.jenkins.build-version>1.460</test.jenkins.build-version>
<test.jenkins.identity>ANONYMOUS</test.jenkins.identity>
<test.jenkins.credential>ANONYMOUS</test.jenkins.credential>
</properties>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-slf4j</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>live</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>integration</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<test.jenkins.endpoint>${test.jenkins.endpoint}</test.jenkins.endpoint>
<test.jenkins.api-version>${test.jenkins.api-version}</test.jenkins.api-version>
<test.jenkins.build-version>${test.jenkins.build-version}</test.jenkins.build-version>
<test.jenkins.identity>${test.jenkins.identity}</test.jenkins.identity>
<test.jenkins.credential>${test.jenkins.credential}</test.jenkins.credential>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>org.jclouds.jenkins.v1*;version="${project.version}"</Export-Package>
<Import-Package>
org.jclouds.rest.internal;version="${project.version}",
org.jclouds*;version="${project.version}",
*
</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<RestContext<JenkinsClient, JenkinsAsyncClient>> CONTEXT_TOKEN = new TypeToken<RestContext<JenkinsClient, JenkinsAsyncClient>>() {
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.<Class<? extends Module>>of(JenkinsRestClientModule.class));
}
@Override
public JenkinsApiMetadata build() {
return new JenkinsApiMetadata(this);
}
@Override
public Builder fromApiMetadata(ApiMetadata in) {
super.fromApiMetadata(in);
return this;
}
}
}

View File

@ -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.
* <p/>
*
* @see JenkinsClient
* @see <a href="https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API">api doc</a>
* @author Adrian Cole
*/
public interface JenkinsAsyncClient {
/**
* Provides asynchronous access to Computer features.
*/
@Delegate
ComputerAsyncClient getComputerClient();
}

View File

@ -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.
* <p/>
*
* @see JenkinsAsyncClient
* @see <a href="https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API">api doc</a>
* @author Adrian Cole
*/
@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS)
public interface JenkinsClient {
/**
* Provides synchronous access to Computer features.
*/
@Delegate
ComputerClient getComputerClient();
}

View File

@ -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 {
}

View File

@ -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<JenkinsClient, JenkinsAsyncClient> {
public static final Map<Class<?>, Class<?>> DELEGATE_MAP = ImmutableMap.<Class<?>, 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);
}
}

View File

@ -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 <a
* href="http://ci.jruby.org/computer/api/">api
* doc</a>
*/
public class Computer implements Comparable<Computer> {
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());
}
}

View File

@ -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 <a
* href="http://ci.jruby.org/computer/api/">api
* doc</a>
*/
public class ComputerView implements Comparable<ComputerView> {
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<Computer> 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<Computer> 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<Computer> computers;
public ComputerView(String displayName, int busyExecutors, int totalExecutors, Set<Computer> 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<Computer> 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());
}
}

View File

@ -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 <a href=
* "http://ci.jruby.org/computer/api/"
* >api doc</a>
*/
@RequestFilters(BasicAuthenticationUnlessAnonymous.class)
public interface ComputerAsyncClient {
/**
* @see ComputerClient#getComputerView
*/
@GET
@Path("/computer/api/json")
@Consumes(MediaType.APPLICATION_JSON)
ListenableFuture<ComputerView> getComputerView();
/**
* @see ComputerClient#getComputer
*/
@GET
@Path("/computer/{displayName}/api/json")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<Computer> getComputer(@PathParam("displayName") String displayName);
}

View File

@ -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 <a href= "http://ci.jruby.org/computer/api/" >api doc</a>
*/
@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);
}

View File

@ -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<BasicAuthentication> auth;
@Inject
public BasicAuthenticationUnlessAnonymous(@Identity String user, BasicAuthentication auth) {
this.auth = JenkinsApiMetadata.ANONYMOUS_IDENTITY.equals(checkNotNull(user, "user")) ? Optional
.<BasicAuthentication> absent() : Optional.of(checkNotNull(auth, "auth"));
}
@Override
public HttpRequest filter(HttpRequest request) throws HttpException {
if (auth.isPresent())
return auth.get().filter(request);
return request;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
org.jclouds.jenkins.v1.JenkinsApiMetadata

View File

@ -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.<TypeToken<? extends View>> of());
}
}

View File

@ -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", "<html></html>", ResourceNotFoundException.class);
}
private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
Class<? extends Exception> 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<? extends Exception> 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<? extends Exception> 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;
}
}

View File

@ -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.<String, String> 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.<String, String> 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());
}
}

View File

@ -0,0 +1,52 @@
/**
* 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());
if (!"master".equals(computerFromView.getDisplayName())) {
Computer computerFromGetRequest = getClient().getComputer(computerFromView.getDisplayName());
assertEquals(computerFromGetRequest, computerFromView);
}
}
}
private ComputerClient getClient() {
return context.getApi().getComputerClient();
}
}

View File

@ -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.<String, String> 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());
}
}

View File

@ -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<JenkinsAsyncClient> {
public JenkinsAsyncClient createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
return createInjector(fn, module, props).getInstance(JenkinsAsyncClient.class);
}
}

View File

@ -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<JenkinsClient> {
}

View File

@ -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<RestContext<JenkinsClient, JenkinsAsyncClient>> {
public BaseJenkinsClientLiveTest() {
provider = "jenkins";
}
protected RestContext<JenkinsClient, JenkinsAsyncClient> 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<RestContext<JenkinsClient, JenkinsAsyncClient>> contextType() {
return JenkinsApiMetadata.CONTEXT_TOKEN;
}
}

View File

@ -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<T> extends BaseRestClientExpectTest<T> {
public BaseJenkinsExpectTest() {
provider = "jenkins";
}
}

View File

@ -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<Computer> {
@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();
}
}

View File

@ -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<ComputerView> {
@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.<Computer>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();
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<configuration scan="false">
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds-wire.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<root>
<level value="warn" />
</root>
<logger name="org.jclouds">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="jclouds.wire">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<logger name="jclouds.headers">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
</configuration>

View File

@ -42,5 +42,6 @@
<module>dmtf</module> <module>dmtf</module>
<module>carrenza-vcloud-director</module> <module>carrenza-vcloud-director</module>
<module>openstack-swift</module> <module>openstack-swift</module>
<module>jenkins</module>
</modules> </modules>
</project> </project>

View File

@ -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";
}
}