From 3c0547c7df6b954717d74c09642a98215b07e186 Mon Sep 17 00:00:00 2001 From: "adrian.f.cole" Date: Sun, 19 Jul 2009 18:54:58 +0000 Subject: [PATCH] Issue 77: more support on cloud servers including password change git-svn-id: http://jclouds.googlecode.com/svn/trunk@1654 3d8758e0-26b5-11de-8745-db77d3ebf521 --- .../jclouds/ssh/jsch/JschSshConnection.java | 4 +- .../cloudservers/CloudServersConnection.java | 129 ++++--- .../binders/ChangeAdminPassBinder.java | 31 ++ .../binders/ChangeServerNameBinder.java | 31 ++ .../cloudservers/domain/BackupSchedule.java | 9 +- .../rackspace/cloudservers/domain/Flavor.java | 2 +- .../rackspace/cloudservers/domain/Image.java | 10 +- .../rackspace/cloudservers/domain/Server.java | 4 +- .../options/CreateServerOptions.java | 14 + .../cloudservers/options/ListOptions.java | 90 +++++ .../CloudServersConnectionLiveTest.java | 158 ++++++--- .../CloudServersConnectionTest.java | 323 +++++++++++++++--- .../binders/ChangeAdminPassBinderTest.java | 83 +++++ .../binders/ChangeServerNameBinderTest.java | 83 +++++ .../cloudservers/options/ListOptionsTest.java | 66 ++++ .../rackspace/RackspaceAuthentication.java | 1 - .../rackspace/options/BaseListOptions.java | 81 +++++ .../options/BaseListOptionsTest.java | 58 ++++ 18 files changed, 1018 insertions(+), 159 deletions(-) create mode 100644 rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinder.java create mode 100644 rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinder.java create mode 100644 rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/ListOptions.java create mode 100644 rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinderTest.java create mode 100644 rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinderTest.java create mode 100644 rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/options/ListOptionsTest.java create mode 100644 rackspace/core/src/main/java/org/jclouds/rackspace/options/BaseListOptions.java create mode 100644 rackspace/core/src/test/java/org/jclouds/rackspace/options/BaseListOptionsTest.java diff --git a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java index 5ab79d46df..87256bb1c7 100644 --- a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java +++ b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java @@ -60,8 +60,8 @@ public class JschSshConnection implements SshConnection { } private void checkConnected() { - checkState(sftp.isConnected(), String.format("%s@%s:%d: SFTP not connected!", username, host - .getHostAddress(), port)); + checkState(sftp != null && sftp.isConnected(), String.format("%s@%s:%d: SFTP not connected!", + username, host.getHostAddress(), port)); } @PostConstruct diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/CloudServersConnection.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/CloudServersConnection.java index 987b2a3412..f381f8e00b 100755 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/CloudServersConnection.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/CloudServersConnection.java @@ -30,10 +30,13 @@ import java.util.concurrent.Future; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import org.jclouds.http.functions.ReturnFalseOn404; +import org.jclouds.rackspace.cloudservers.binders.ChangeAdminPassBinder; +import org.jclouds.rackspace.cloudservers.binders.ChangeServerNameBinder; import org.jclouds.rackspace.cloudservers.domain.Flavor; import org.jclouds.rackspace.cloudservers.domain.Image; import org.jclouds.rackspace.cloudservers.domain.Server; @@ -47,7 +50,9 @@ import org.jclouds.rackspace.cloudservers.functions.ReturnFlavorNotFoundOn404; import org.jclouds.rackspace.cloudservers.functions.ReturnImageNotFoundOn404; import org.jclouds.rackspace.cloudservers.functions.ReturnServerNotFoundOn404; import org.jclouds.rackspace.cloudservers.options.CreateServerOptions; +import org.jclouds.rackspace.cloudservers.options.ListOptions; import org.jclouds.rackspace.filters.AuthenticateRequest; +import org.jclouds.rest.EntityParam; import org.jclouds.rest.ExceptionParser; import org.jclouds.rest.PostBinder; import org.jclouds.rest.PostParam; @@ -73,7 +78,11 @@ public interface CloudServersConnection { * * List all servers (IDs and names only) * - * @see #listServerDetails() + * This operation provides a list of servers associated with your account. Servers that have been + * deleted are not included in this list. + *

+ * in order to retrieve all details, pass the option {@link ListOptions#withDetails() + * withDetails()} */ @GET @ResponseParser(ParseServerListFromGsonResponse.class) @@ -81,24 +90,13 @@ public interface CloudServersConnection { @Path("/servers") // TODO: Error Response Code(s): cloudServersFault (400, 500), serviceUnavailable (503), // unauthorized (401), badRequest (400), overLimit (413) - List listServers(); - - /** - * This operation provides a list of servers associated with your account. Servers that have been - * deleted are not included in this list. - */ - @GET - @ResponseParser(ParseServerListFromGsonResponse.class) - @Query(key = "format", value = "json") - @Path("/servers/detail") - // TODO: Error Response Code(s): cloudServersFault (400, 500), serviceUnavailable (503), - // unauthorized (401), badRequest (400), overLimit (413) - List listServerDetails(); + List listServers(ListOptions... options); /** * * This operation returns details of the specified server. * + * @return {@link Server#NOT_FOUND} if the server is not found * @see Server */ @GET @@ -108,7 +106,7 @@ public interface CloudServersConnection { @Path("/servers/{id}") // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest // (400) - Server getServerDetails(@PathParam("id") int id); + Server getServer(@PathParam("id") int id); /** * @@ -116,7 +114,7 @@ public interface CloudServersConnection { *

* Note: When a server is deleted, all images created from that server are also removed. * - * + * @return false if the server is not found * @see Server */ @DELETE @@ -126,6 +124,25 @@ public interface CloudServersConnection { // (400), itemNotFound (404), buildInProgress (409), overLimit (413) boolean deleteServer(@PathParam("id") int id); + /** + * The reboot function allows for either a soft or hard reboot of a server. With a soft reboot, + * the operating system is signaled to restart, which allows for a graceful shutdown of all + * processes. A hard reboot is the equivalent of power cycling the server. + */ + + /** + * The rebuild function removes all data on the server and replaces it with the specified image. + * Server ID and IP addresses remain the same. + */ + + /** + * The resize function converts an existing server to a different flavor, in essence, scaling the + * server up or down. The original server is saved for a period of time to allow rollback if + * there is a problem. All resizes should be tested and explicitly confirmed, at which time the + * original server is removed. All resizes are automatically confirmed after 24 hours if they are + * not confirmed or reverted. + */ + /** * This operation asynchronously provisions a new server. The progress of this operation depends * on several factors including location of the requested image, network i/o, host load, and the @@ -133,6 +150,9 @@ public interface CloudServersConnection { * which will return a progress attribute (0-100% completion). A password will be randomly * generated for you and returned in the response object. For security reasons, it will not be * returned in subsequent GET calls against a given server ID. + * + * @param options + * - used to specify extra files, metadata, or ip parameters during server creation. */ @POST @ResponseParser(ParseServerFromGsonResponse.class) @@ -142,13 +162,45 @@ public interface CloudServersConnection { // TODO:cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), // badMediaType(415), badRequest (400), serverCapacityUnavailable (503), overLimit (413) Server createServer(@PostParam("name") String name, @PostParam("imageId") int imageId, - @PostParam("flavorId") int flavorId); + @PostParam("flavorId") int flavorId, CreateServerOptions... options); + + /** + * This operation allows you to change the administrative password. + *

+ * Status Transition: ACTIVE - PASSWORD - ACTIVE + * + * @return false if the server is not found + */ + @PUT + @ExceptionParser(ReturnFalseOn404.class) + @Path("/servers/{id}") + // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest + // (400), badMediaType(415), buildInProgress (409), overLimit (413) + boolean changeAdminPass(@PathParam("id") int id, + @EntityParam(ChangeAdminPassBinder.class) String adminPass); + + /** + * This operation allows you to update the name of the server. This operation changes the name of + * the server in the Cloud Servers system and does not change the server host name itself. + *

+ * Status Transition: ACTIVE - PASSWORD - ACTIVE + * + * @return false if the server is not found + */ + @PUT + @ExceptionParser(ReturnFalseOn404.class) + @Path("/servers/{id}") + // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest + // (400), badMediaType(415), buildInProgress (409), overLimit (413) + boolean renameServer(@PathParam("id") int id, + @EntityParam(ChangeServerNameBinder.class) String newName); /** * * List available flavors (IDs and names only) * - * @see #listFlavorDetails() + * in order to retrieve all details, pass the option {@link ListOptions#withDetails() + * withDetails()} */ @GET @ResponseParser(ParseFlavorListFromGsonResponse.class) @@ -156,27 +208,14 @@ public interface CloudServersConnection { @Path("/flavors") // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest // (400) - List listFlavors(); - - /** - * - * List available flavors (all details) - * - * @see Flavor - */ - @GET - @ResponseParser(ParseFlavorListFromGsonResponse.class) - @Query(key = "format", value = "json") - @Path("/flavors/detail") - // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest - // (400) - List listFlavorDetails(); + List listFlavors(ListOptions... options); /** * * List available images (IDs and names only) * - * @see #listImageDetails() + * in order to retrieve all details, pass the option {@link ListOptions#withDetails() + * withDetails()} */ @GET @ResponseParser(ParseImageListFromGsonResponse.class) @@ -184,26 +223,13 @@ public interface CloudServersConnection { @Path("/images") // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest // (400) - List listImages(); - - /** - * - * This operation will list all images visible by the account. - * - * @see Image - */ - @GET - @ResponseParser(ParseImageListFromGsonResponse.class) - @Query(key = "format", value = "json") - @Path("/images/detail") - // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest - // (400) - List listImageDetails(); + List listImages(ListOptions... options); /** * * This operation returns details of the specified image. * + * @return {@link Image#NOT_FOUND} if the image is not found * @see Image */ @GET @@ -213,12 +239,13 @@ public interface CloudServersConnection { @Path("/images/{id}") // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest // (400) - Image getImageDetails(@PathParam("id") int id); + Image getImage(@PathParam("id") int id); /** * * This operation returns details of the specified flavor. * + * @return {@link Flavor#NOT_FOUND} if the flavor is not found * @see Flavor */ @GET @@ -228,6 +255,6 @@ public interface CloudServersConnection { @Path("/flavors/{id}") // TODO: cloudServersFault (400, 500), serviceUnavailable (503), unauthorized (401), badRequest // (400) - Flavor getFlavorDetails(@PathParam("id") int id); + Flavor getFlavor(@PathParam("id") int id); } diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinder.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinder.java new file mode 100644 index 0000000000..a6e5081d39 --- /dev/null +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinder.java @@ -0,0 +1,31 @@ +package org.jclouds.rackspace.cloudservers.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.binders.JsonBinder; + +import com.google.common.collect.ImmutableMap; + +/** + * + * @author Adrian Cole + * + */ +public class ChangeAdminPassBinder extends JsonBinder { + + @Override + public void addEntityToRequest(Map postParams, HttpRequest request) { + throw new IllegalStateException("Change Admin Pass is a PUT operation"); + } + + @Override + public void addEntityToRequest(Object toBind, HttpRequest request) { + checkArgument(toBind instanceof String, "this binder is only valid for Strings!"); + super.addEntityToRequest(ImmutableMap.of("server", ImmutableMap.of("adminPass", checkNotNull( + toBind, "adminPass"))), request); + } +} diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinder.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinder.java new file mode 100644 index 0000000000..93bc8bf95b --- /dev/null +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinder.java @@ -0,0 +1,31 @@ +package org.jclouds.rackspace.cloudservers.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.binders.JsonBinder; + +import com.google.common.collect.ImmutableMap; + +/** + * + * @author Adrian Cole + * + */ +public class ChangeServerNameBinder extends JsonBinder { + + @Override + public void addEntityToRequest(Map postParams, HttpRequest request) { + throw new IllegalStateException("Change Server Name is a PUT operation"); + } + + @Override + public void addEntityToRequest(Object toBind, HttpRequest request) { + checkArgument(toBind instanceof String, "this binder is only valid for Strings!"); + super.addEntityToRequest(ImmutableMap.of("server", ImmutableMap.of("name", checkNotNull( + toBind, "name"))), request); + } +} diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/BackupSchedule.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/BackupSchedule.java index 38ac75ef43..1f8e1ea6ef 100644 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/BackupSchedule.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/BackupSchedule.java @@ -23,12 +23,18 @@ */ package org.jclouds.rackspace.cloudservers.domain; - +/** + * A backup schedule can be defined to create server images at regular intervals (daily and weekly). + * Backup schedules are configurable per server. + * + * @author Adrian Cole + */ public class BackupSchedule { protected DailyBackup daily; protected boolean enabled; protected String weekly; + public DailyBackup getDaily() { return daily; } @@ -40,6 +46,7 @@ public class BackupSchedule { public boolean isEnabled() { return enabled; } + public void setEnabled(boolean value) { this.enabled = value; } diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Flavor.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Flavor.java index 5d0e0c8de9..f879b6b647 100644 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Flavor.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Flavor.java @@ -27,7 +27,7 @@ package org.jclouds.rackspace.cloudservers.domain; /** * * A flavor is an available hardware configuration for a server. Each flavor has a unique - * combination of disk space, memory capacity. + * combination of disk space and memory capacity. * * @author Adrian Cole */ diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Image.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Image.java index 4120561c66..136f463f99 100644 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Image.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Image.java @@ -26,15 +26,17 @@ package org.jclouds.rackspace.cloudservers.domain; import org.joda.time.DateTime; /** - * An image is a collection of files you use to create or rebuild a server. Rackspace provides - * pre-built OS images by default. You may also create custom images. + * An image is a collection of files used to create or rebuild a server. Rackspace provides a number + * of pre-built OS images by default. You may also create custom images from cloud servers you have + * launched. These custom images are useful for backup purposes or for producing ÒgoldÓ server + * images if you plan to deploy a particular server configuration frequently. * * @author Adrian Cole */ public class Image { - public static final Image NOT_FOUND = new Image(-1,"NOT_FOUND"); - + public static final Image NOT_FOUND = new Image(-1, "NOT_FOUND"); + private DateTime created; private int id; private String name; diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Server.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Server.java index 4553d21868..b51c8542e4 100644 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Server.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/domain/Server.java @@ -28,8 +28,8 @@ import java.util.Map; import com.google.inject.internal.Maps; /** - * - * Server. + * A server is a virtual machine instance in the Cloud Servers system. Flavor and image are + * requisite elements when creating a server. * * @author Adrian Cole * @since 4.0 diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/CreateServerOptions.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/CreateServerOptions.java index 63e3dd0f37..ad20608626 100644 --- a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/CreateServerOptions.java +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/CreateServerOptions.java @@ -117,6 +117,12 @@ public class CreateServerOptions extends JsonBinder { } /** + * A shared IP group is a collection of servers that can share IPs with other members of the + * group. Any server in a group can share one or more public IPs with any other server in the + * group. With the exception of the first server in a shared IP group, servers must be launched + * into shared IP groups. A server may only be a member of one shared IP group. + * + *

* Servers in the same shared IP group can share public IPs for various high availability and * load balancing configurations. To launch an HA server, include the optional sharedIpGroupId * element and the server will be launched into that shared IP group. @@ -160,6 +166,14 @@ public class CreateServerOptions extends JsonBinder { } /** + * Public IP addresses can be shared across multiple servers for use in various high availability + * scenarios. When an IP address is shared to another server, the cloud network restrictions are + * modified to allow each server to listen to and respond on that IP address (you may optionally + * specify that the target server network configuration be modified). Shared IP addresses can be + * used with many standard heartbeat facilities (e.g. keepalived) that monitor for failure and + * manage IP failover. + * + *

* If you intend to use a shared IP on the server being created and have no need for a separate * public IP address, you may launch the server into a shared IP group and specify an IP address * from that shared IP group to be used as its public IP. You can accomplish this by specifying diff --git a/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/ListOptions.java b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/ListOptions.java new file mode 100644 index 0000000000..c9d02656bb --- /dev/null +++ b/rackspace/cloudservers/core/src/main/java/org/jclouds/rackspace/cloudservers/options/ListOptions.java @@ -0,0 +1,90 @@ +package org.jclouds.rackspace.cloudservers.options; + +import org.jclouds.rackspace.options.BaseListOptions; +import org.joda.time.DateTime; + +/** + * Options used to control the amount of detail in the request. + * + * @see BaseListOptions + * @see + * @author Adrian Cole + */ +public class ListOptions extends BaseListOptions { + + public static final ListOptions NONE = new ListOptions(); + + /** + * unless used, only the name and id will be returned per row. + * + * @return + */ + public ListOptions withDetails() { + this.pathSuffix = "/detail"; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions changesSince(DateTime ifModifiedSince) { + super.changesSince(ifModifiedSince); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions maxResults(int limit) { + super.maxResults(limit); + return this; + + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions startAt(long offset) { + super.startAt(offset); + return this; + } + + public static class Builder { + + /** + * @see ListOptions#withDetails() + */ + public static ListOptions withDetails() { + ListOptions options = new ListOptions(); + return options.withDetails(); + } + + /** + * @see BaseListOptions#startAt(long) + */ + public static ListOptions startAt(long prefix) { + ListOptions options = new ListOptions(); + return options.startAt(prefix); + } + + /** + * @see BaseListOptions#maxResults(long) + */ + public static ListOptions maxResults(int maxKeys) { + ListOptions options = new ListOptions(); + return options.maxResults(maxKeys); + } + + /** + * @see BaseListOptions#changesSince(DateTime) + */ + public static ListOptions changesSince(DateTime since) { + ListOptions options = new ListOptions(); + return options.changesSince(since); + } + + } +} diff --git a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionLiveTest.java b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionLiveTest.java index de305e9891..11755faa58 100755 --- a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionLiveTest.java +++ b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionLiveTest.java @@ -23,15 +23,22 @@ */ package org.jclouds.rackspace.cloudservers; +import static org.jclouds.rackspace.cloudservers.options.CreateServerOptions.Builder.withFile; +import static org.jclouds.rackspace.cloudservers.options.ListOptions.Builder.withDetails; import static org.jclouds.rackspace.reference.RackspaceConstants.PROPERTY_RACKSPACE_KEY; import static org.jclouds.rackspace.reference.RackspaceConstants.PROPERTY_RACKSPACE_USER; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.SecureRandom; import java.util.List; +import java.util.Map; +import org.jclouds.http.HttpResponseException; import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.rackspace.cloudservers.domain.Flavor; import org.jclouds.rackspace.cloudservers.domain.Image; @@ -43,6 +50,7 @@ import org.jclouds.util.Utils; import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMap; import com.google.inject.Injector; /** @@ -50,13 +58,13 @@ import com.google.inject.Injector; * * @author Adrian Cole */ -@Test(groups = "live", testName = "cloudservers.CloudServersConnectionLiveTest") +@Test(groups = "live", sequential = true, testName = "cloudservers.CloudServersConnectionLiveTest") public class CloudServersConnectionLiveTest { protected static final String sysRackspaceUser = System.getProperty(PROPERTY_RACKSPACE_USER); protected static final String sysRackspaceKey = System.getProperty(PROPERTY_RACKSPACE_KEY); - CloudServersConnection connection; - SshConnection.Factory sshFactory; + protected CloudServersConnection connection; + protected SshConnection.Factory sshFactory; @BeforeGroups(groups = { "live" }) public void setupConnection() { @@ -80,7 +88,7 @@ public class CloudServersConnectionLiveTest { @Test public void testListServersDetail() throws Exception { - List response = connection.listServerDetails(); + List response = connection.listServers(withDetails()); assert null != response; long initialContainerCount = response.size(); assertTrue(initialContainerCount >= 0); @@ -101,7 +109,7 @@ public class CloudServersConnectionLiveTest { @Test public void testListFlavorsDetail() throws Exception { - List response = connection.listFlavorDetails(); + List response = connection.listFlavors(withDetails()); assert null != response; long flavorCount = response.size(); assertTrue(flavorCount >= 0); @@ -128,7 +136,7 @@ public class CloudServersConnectionLiveTest { @Test public void testListImagesDetail() throws Exception { - List response = connection.listImageDetails(); + List response = connection.listImages(withDetails()); assert null != response; long imageCount = response.size(); assertTrue(imageCount >= 0); @@ -144,13 +152,13 @@ public class CloudServersConnectionLiveTest { @Test(enabled = false) // Rackspace Web Hosting issue #118856 - public void testGetImageDetails() throws Exception { - List response = connection.listImageDetails(); + public void testGetImagesDetail() throws Exception { + List response = connection.listImages(withDetails()); assert null != response; long imageCount = response.size(); assertTrue(imageCount >= 0); for (Image image : response) { - Image newDetails = connection.getImageDetails(image.getId()); + Image newDetails = connection.getImage(image.getId()); assertEquals(image, newDetails); } } @@ -158,79 +166,147 @@ public class CloudServersConnectionLiveTest { @Test(enabled = false) // Rackspace Web Hosting issue #118856 public void testGetImageDetailsNotFound() throws Exception { - Image newDetails = connection.getImageDetails(12312987); + Image newDetails = connection.getImage(12312987); assertEquals(Image.NOT_FOUND, newDetails); } @Test - public void testGetFlavorDetails() throws Exception { - List response = connection.listFlavorDetails(); + public void testGetFlavorsDetail() throws Exception { + List response = connection.listFlavors(withDetails()); assert null != response; long flavorCount = response.size(); assertTrue(flavorCount >= 0); for (Flavor flavor : response) { - Flavor newDetails = connection.getFlavorDetails(flavor.getId()); + Flavor newDetails = connection.getFlavor(flavor.getId()); assertEquals(flavor, newDetails); } } public void testGetFlavorDetailsNotFound() throws Exception { - Flavor newDetails = connection.getFlavorDetails(12312987); + Flavor newDetails = connection.getFlavor(12312987); assertEquals(Flavor.NOT_FOUND, newDetails); } public void testGetServerDetailsNotFound() throws Exception { - Server newDetails = connection.getServerDetails(12312987); + Server newDetails = connection.getServer(12312987); assertEquals(Server.NOT_FOUND, newDetails); } - public void testGetServerDetails() throws Exception { - List response = connection.listServerDetails(); + public void testGetServersDetail() throws Exception { + List response = connection.listServers(withDetails()); assert null != response; long serverCount = response.size(); assertTrue(serverCount >= 0); for (Server server : response) { - Server newDetails = connection.getServerDetails(server.getId()); + Server newDetails = connection.getServer(server.getId()); assertEquals(server, newDetails); } } private String serverPrefix = System.getProperty("user.name") + ".cs"; + private int serverId; + private String adminPass; + Map metadata = ImmutableMap.of("jclouds", "rackspace"); @Test(timeOut = 5 * 60 * 1000) public void testCreateServer() throws Exception { - Server newDetails = connection.createServer(serverPrefix + "createserver", 2, 1); - System.err.print(newDetails); - assertNotNull(newDetails.getAdminPass()); - assertNotNull(newDetails.getHostId()); - assertEquals(newDetails.getStatus(), ServerStatus.BUILD); - assert newDetails.getProgress() >= 0 : "newDetails.getProgress()" + newDetails.getProgress(); - assertEquals(new Integer(2), newDetails.getImageId()); - assertEquals(new Integer(1), newDetails.getFlavorId()); - assertNotNull(newDetails.getAddresses()); - assertEquals(newDetails.getAddresses().getPublicAddresses().size(), 1); - assertEquals(newDetails.getAddresses().getPrivateAddresses().size(), 1); + int imageId = 2; + int flavorId = 1; + Server server = null; + while (server == null) { + String serverName = serverPrefix + "createserver" + new SecureRandom().nextInt(); + try { + server = connection.createServer(serverName, imageId, flavorId, withFile( + "/etc/jclouds.txt", "rackspace".getBytes()).withMetadata(metadata)); + } catch (UndeclaredThrowableException e) { + HttpResponseException htpe = (HttpResponseException) e.getCause().getCause(); + if (htpe.getResponse().getStatusCode() == 400) + continue; + throw e; + } + } + assertNotNull(server.getAdminPass()); + serverId = server.getId(); + adminPass = server.getAdminPass(); + assertEquals(server.getStatus(), ServerStatus.BUILD); + blockUntilActive(server); + } - int serverId = newDetails.getId(); - ServerStatus currentStatus = newDetails.getStatus(); - Server currentDetails = newDetails; + private void blockUntilActive(Server server) throws InterruptedException { + ServerStatus currentStatus = server.getStatus(); + Server currentDetails = server; while (currentStatus != ServerStatus.ACTIVE) { Thread.sleep(5 * 1000); - currentDetails = connection.getServerDetails(serverId); + currentDetails = connection.getServer(serverId); System.out.println(currentDetails); currentStatus = currentDetails.getStatus(); } + } - InputStream etcPasswd = sshFactory.create( - newDetails.getAddresses().getPublicAddresses().get(0), 22, "root", - newDetails.getAdminPass()).get("/etc/passwd"); - String etcPasswdContents = Utils.toStringAndClose(etcPasswd); - assert etcPasswdContents.indexOf("root") >= 0 : etcPasswdContents; + @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = "testCreateServer") + public void testServerDetails() throws Exception { + Server server = connection.getServer(serverId); - connection.deleteServer(serverId); + assertNotNull(server.getHostId()); + assertEquals(server.getStatus(), ServerStatus.ACTIVE); + assert server.getProgress() >= 0 : "newDetails.getProgress()" + server.getProgress(); + assertEquals(new Integer(2), server.getImageId()); + assertEquals(new Integer(1), server.getFlavorId()); + assertNotNull(server.getAddresses()); + assertEquals(server.getAddresses().getPublicAddresses().size(), 1); + assertEquals(server.getAddresses().getPrivateAddresses().size(), 1); - currentDetails = connection.getServerDetails(serverId); - assertEquals(ServerStatus.DELETED, currentDetails.getStatus()); + // check metadata + assertEquals(server.getMetadata(), metadata); + checkPassOk(server, adminPass); + } + + /** + * this tests "personality" as the file looked up was sent during server creation + */ + private void checkPassOk(Server newDetails, String pass) throws IOException { + SshConnection connection = sshFactory.create(newDetails.getAddresses().getPublicAddresses() + .get(0), 22, "root", pass); + try { + connection.connect(); + InputStream etcPasswd = connection.get("/etc/jclouds.txt"); + String etcPasswdContents = Utils.toStringAndClose(etcPasswd); + assertEquals("rackspace", etcPasswdContents.trim()); + } finally { + if (connection != null) + connection.disconnect(); + } + } + + @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = "testCreateServer") + public void testRenameServer() throws Exception { + Server server = connection.getServer(serverId); + String oldName = server.getName(); + assertTrue(connection.renameServer(serverId, oldName + "new")); + blockUntilActive(server); + assertEquals(oldName + "new", connection.getServer(serverId).getName()); + } + + @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = "testCreateServer") + public void testChangePassword() throws Exception { + Server server = connection.getServer(serverId); + assertTrue(connection.changeAdminPass(serverId, "elmo")); + blockUntilActive(server); + checkPassOk(connection.getServer(serverId), "elmo"); + this.adminPass = "elmo"; + } + + // TODO test createServer.withSharedIpGroup + // TODO test createServer.withSharedIp + + // must be last! + @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = {"testChangePassword","testRenameServer"}) + void deleteServer() { + if (serverId > 0) { + connection.deleteServer(serverId); + Server server = connection.getServer(serverId); + assertEquals(server, Server.NOT_FOUND); + } } } diff --git a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionTest.java b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionTest.java index a41b23e131..cbeb88f95b 100755 --- a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionTest.java +++ b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/CloudServersConnectionTest.java @@ -23,10 +23,17 @@ */ package org.jclouds.rackspace.cloudservers; -import static org.testng.Assert.assertEquals; +import static org.jclouds.rackspace.cloudservers.options.CreateServerOptions.Builder.withFile; +import static org.jclouds.rackspace.cloudservers.options.CreateServerOptions.Builder.withMetadata; +import static org.jclouds.rackspace.cloudservers.options.CreateServerOptions.Builder.withSharedIpGroup; +import static org.jclouds.rackspace.cloudservers.options.ListOptions.Builder.changesSince; +import static org.jclouds.rackspace.cloudservers.options.ListOptions.Builder.withDetails; +import static org.testng.Assert.*; import java.lang.reflect.Method; +import java.net.InetAddress; import java.net.URI; +import java.net.UnknownHostException; import java.util.Collections; import javax.ws.rs.core.HttpHeaders; @@ -37,6 +44,8 @@ import org.jclouds.concurrent.config.ExecutorServiceModule; import org.jclouds.http.HttpMethod; import org.jclouds.http.HttpRequest; import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; +import org.jclouds.http.functions.ReturnFalseOn404; +import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.rackspace.Authentication; import org.jclouds.rackspace.cloudservers.functions.ParseFlavorFromGsonResponse; import org.jclouds.rackspace.cloudservers.functions.ParseFlavorListFromGsonResponse; @@ -44,11 +53,18 @@ import org.jclouds.rackspace.cloudservers.functions.ParseImageFromGsonResponse; import org.jclouds.rackspace.cloudservers.functions.ParseImageListFromGsonResponse; import org.jclouds.rackspace.cloudservers.functions.ParseServerFromGsonResponse; import org.jclouds.rackspace.cloudservers.functions.ParseServerListFromGsonResponse; +import org.jclouds.rackspace.cloudservers.functions.ReturnFlavorNotFoundOn404; +import org.jclouds.rackspace.cloudservers.functions.ReturnImageNotFoundOn404; +import org.jclouds.rackspace.cloudservers.functions.ReturnServerNotFoundOn404; +import org.jclouds.rackspace.cloudservers.options.CreateServerOptions; +import org.jclouds.rackspace.cloudservers.options.ListOptions; import org.jclouds.rest.JaxrsAnnotationProcessor; import org.jclouds.rest.config.JaxrsModule; +import org.joda.time.DateTime; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMap; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Provides; @@ -63,174 +79,368 @@ public class CloudServersConnectionTest { JaxrsAnnotationProcessor.Factory factory; + private static final Class listOptionsVarargsClass = new ListOptions[] {} + .getClass(); + + private static final Class createServerOptionsVarargsClass = new CreateServerOptions[] {} + .getClass(); + public void testCreateServer() throws SecurityException, NoSuchMethodException { Method method = CloudServersConnection.class.getMethod("createServer", String.class, - int.class, int.class); + int.class, int.class, createServerOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] { "ralphie", 2, 1 }); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { "ralphie", + 2, 1 }); + assertEquals("{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1}}", httpMethod + .getEntity()); + validateCreateServer(method, httpMethod); + + } + + public void testCreateServerWithFile() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("createServer", String.class, + int.class, int.class, createServerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { "ralphie", + 2, 1, new CreateServerOptions[] { withFile("/etc/jclouds", "foo".getBytes()) } }); + assertEquals( + "{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1,\"personality\":[{\"path\":\"/etc/jclouds\",\"contents\":\"Zm9v\"}]}}", + httpMethod.getEntity()); + validateCreateServer(method, httpMethod); + } + + public void testCreateServerWithMetadata() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("createServer", String.class, + int.class, int.class, createServerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { "ralphie", + 2, 1, withMetadata(ImmutableMap.of("foo", "bar")) }); + assertEquals( + "{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1,\"metadata\":{\"foo\":\"bar\"}}}", + httpMethod.getEntity()); + validateCreateServer(method, httpMethod); + } + + public void testCreateServerWithIpGroup() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("createServer", String.class, + int.class, int.class, createServerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { "ralphie", + 2, 1, withSharedIpGroup(2) }); + assertEquals( + "{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1,\"sharedIpGroupId\":2}}", + httpMethod.getEntity()); + validateCreateServer(method, httpMethod); + } + + public void testCreateServerWithIpGroupAndSharedIp() throws SecurityException, + NoSuchMethodException, UnknownHostException { + Method method = CloudServersConnection.class.getMethod("createServer", String.class, + int.class, int.class, createServerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { + "ralphie", + 2, + 1, + withSharedIpGroup(2).withSharedIp( + InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 })) }); + assertEquals( + "{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1,\"sharedIpGroupId\":2,\"addresses\":{\"public\":[\"127.0.0.1\"]}}}", + httpMethod.getEntity()); + validateCreateServer(method, httpMethod); + } + + private void validateCreateServer(Method method, HttpRequest httpMethod) { assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/servers"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.POST); - assertEquals("{\"server\":{\"name\":\"ralphie\",\"imageId\":2,\"flavorId\":1}}", httpMethod - .getEntity()); assertEquals(httpMethod.getHeaders().size(), 2); assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_LENGTH), Collections .singletonList(httpMethod.getEntity().toString().getBytes().length + "")); assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_TYPE), Collections .singletonList(MediaType.APPLICATION_JSON)); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseServerFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + assertNotNull(processor.getPostEntityBinderOrNull(method, new Object[] { "", 1, 2, + new CreateServerOptions[] { CreateServerOptions.Builder.withSharedIpGroup(1) } })); } public void testListServers() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listServers"); + Method method = CloudServersConnection.class + .getMethod("listServers", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] {}); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/servers"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseServerListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + DateTime now = new DateTime(); + + public void testListServersOptions() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class + .getMethod("listServers", listOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { changesSince(now).maxResults(1).startAt(2) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/servers"); + assertEquals(httpMethod.getEndpoint().getQuery(), "format=json" + "&limit=1&changes-since=" + + now.getMillis() / 1000 + "&offset=2"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 0); + assertEquals(processor.createResponseParser(method).getClass(), + ParseServerListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } public void testListServersDetail() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listServerDetails"); + Method method = CloudServersConnection.class + .getMethod("listServers", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { withDetails() }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/servers/detail"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseServerListFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } public void testListFlavors() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listFlavors"); + Method method = CloudServersConnection.class + .getMethod("listFlavors", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] {}); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/flavors"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseFlavorListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + public void testListFlavorsOptions() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class + .getMethod("listFlavors", listOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { changesSince(now).maxResults(1).startAt(2) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/flavors"); + assertEquals(httpMethod.getEndpoint().getQuery(), "format=json" + "&limit=1&changes-since=" + + now.getMillis() / 1000 + "&offset=2"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 0); + assertEquals(processor.createResponseParser(method).getClass(), + ParseFlavorListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } public void testListFlavorsDetail() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listFlavorDetails"); + Method method = CloudServersConnection.class + .getMethod("listFlavors", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { withDetails() }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/flavors/detail"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseFlavorListFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } public void testListImages() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listImages"); + Method method = CloudServersConnection.class.getMethod("listImages", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] {}); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/images"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseImageListFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } public void testListImagesDetail() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("listImageDetails"); + Method method = CloudServersConnection.class.getMethod("listImages", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] {}); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { withDetails() }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/images/detail"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseImageListFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } - public void testGetImageDetails() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("getImageDetails", int.class); + public void testListFlavorsDetailOptions() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class + .getMethod("listFlavors", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] { 2 }); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { withDetails().changesSince(now).maxResults(1).startAt(2) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/flavors/detail"); + assertEquals(httpMethod.getEndpoint().getQuery(), "format=json" + "&limit=1&changes-since=" + + now.getMillis() / 1000 + "&offset=2"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 0); + assertEquals(processor.createResponseParser(method).getClass(), + ParseFlavorListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + + public void testListImagesOptions() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("listImages", listOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { changesSince(now).maxResults(1).startAt(2) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/images"); + assertEquals(httpMethod.getEndpoint().getQuery(), "format=json" + "&limit=1&changes-since=" + + now.getMillis() / 1000 + "&offset=2"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 0); + assertEquals(processor.createResponseParser(method).getClass(), + ParseImageListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + + public void testListImagesDetailOptions() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("listImages", listOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { withDetails().changesSince(now).maxResults(1).startAt(2) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/images/detail"); + assertEquals(httpMethod.getEndpoint().getQuery(), "format=json" + "&limit=1&changes-since=" + + now.getMillis() / 1000 + "&offset=2"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 0); + assertEquals(processor.createResponseParser(method).getClass(), + ParseImageListFromGsonResponse.class); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + + public void testGetImage() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("getImage", int.class); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2 }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/images/2"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseImageFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnImageNotFoundOn404.class); } - public void testGetFlavorDetails() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("getFlavorDetails", int.class); + public void testGetFlavor() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("getFlavor", int.class); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] { 2 }); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2 }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/flavors/2"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseFlavorFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnFlavorNotFoundOn404.class); } - public void testGetServerDetails() throws SecurityException, NoSuchMethodException { - Method method = CloudServersConnection.class.getMethod("getServerDetails", int.class); + public void testGetServer() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("getServer", int.class); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] { 2 }); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2 }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/servers/2"); assertEquals(httpMethod.getEndpoint().getQuery(), "format=json"); assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getHeaders().size(), 0); - assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + assertEquals(processor.createResponseParser(method).getClass(), ParseServerFromGsonResponse.class); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnServerNotFoundOn404.class); } public void testDeleteServer() throws SecurityException, NoSuchMethodException { Method method = CloudServersConnection.class.getMethod("deleteServer", int.class); URI endpoint = URI.create("http://localhost"); - HttpRequest httpMethod = factory.create(CloudServersConnection.class).createRequest(endpoint, - method, new Object[] { 2 }); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2 }); assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getPath(), "/servers/2"); assertEquals(httpMethod.getMethod(), HttpMethod.DELETE); assertEquals(httpMethod.getHeaders().size(), 0); - + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnFalseOn404.class); + assertEquals(processor.createResponseParser(method).getClass(), ReturnTrueIf2xx.class); } + public void testChangeAdminPass() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("changeAdminPass", int.class, + String.class); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2, "foo" }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/servers/2"); + assertEquals(httpMethod.getMethod(), HttpMethod.PUT); + assertEquals(httpMethod.getHeaders().size(), 2); + assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_LENGTH), Collections + .singletonList(httpMethod.getEntity().toString().getBytes().length + "")); + assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_TYPE), Collections + .singletonList(MediaType.APPLICATION_JSON)); + assertEquals("{\"server\":{\"adminPass\":\"foo\"}}", httpMethod.getEntity()); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnFalseOn404.class); + assertEquals(processor.createResponseParser(method).getClass(), ReturnTrueIf2xx.class); + } + + public void testChangeServerName() throws SecurityException, NoSuchMethodException { + Method method = CloudServersConnection.class.getMethod("renameServer", int.class, + String.class); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { 2, "foo" }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/servers/2"); + assertEquals(httpMethod.getMethod(), HttpMethod.PUT); + assertEquals(httpMethod.getHeaders().size(), 2); + assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_LENGTH), Collections + .singletonList(httpMethod.getEntity().toString().getBytes().length + "")); + assertEquals(httpMethod.getHeaders().get(HttpHeaders.CONTENT_TYPE), Collections + .singletonList(MediaType.APPLICATION_JSON)); + assertEquals("{\"server\":{\"name\":\"foo\"}}", httpMethod.getEntity()); + assertEquals(processor.createExceptionParserOrNullIfNotFound(method).getClass(), + ReturnFalseOn404.class); + assertEquals(processor.createResponseParser(method).getClass(), ReturnTrueIf2xx.class); + } + + JaxrsAnnotationProcessor processor; + @BeforeClass void setupFactory() { factory = Guice.createInjector(new AbstractModule() { @@ -248,6 +458,7 @@ public class CloudServersConnectionTest { }, new JaxrsModule(), new ExecutorServiceModule(new WithinThreadExecutorService()), new JavaUrlHttpCommandExecutorServiceModule()).getInstance( JaxrsAnnotationProcessor.Factory.class); + processor = factory.create(CloudServersConnection.class); } } diff --git a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinderTest.java b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinderTest.java new file mode 100644 index 0000000000..dfdf746ab3 --- /dev/null +++ b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeAdminPassBinderTest.java @@ -0,0 +1,83 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.rackspace.cloudservers.binders; + +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.net.URI; + +import org.jclouds.http.HttpMethod; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.config.ParserModule; +import org.jclouds.rackspace.cloudservers.binders.ChangeAdminPassBinder; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests behavior of {@code ChangeAdminPassBinder} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "cloudservers.ChangeAdminPassBinderTest") +public class ChangeAdminPassBinderTest { + + Injector injector = Guice.createInjector(new ParserModule()); + + @Test(expectedExceptions = IllegalStateException.class) + public void testPostIsIncorrect() { + ChangeAdminPassBinder binder = new ChangeAdminPassBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("/")); + binder.addEntityToRequest(ImmutableMap.of("adminPass", "foo"), request); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMustBeString() { + ChangeAdminPassBinder binder = new ChangeAdminPassBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("/")); + binder.addEntityToRequest(new File("foo"), request); + } + + @Test + public void testCorrect() { + ChangeAdminPassBinder binder = new ChangeAdminPassBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.PUT, URI.create("/")); + binder.addEntityToRequest("foo", request); + assertEquals("{\"server\":{\"adminPass\":\"foo\"}}", request.getEntity()); + } + + @Test(expectedExceptions = { NullPointerException.class, IllegalStateException.class }) + public void testNullIsBad() { + ChangeAdminPassBinder binder = new ChangeAdminPassBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.PUT, URI.create("/")); + binder.addEntityToRequest(null, request); + } +} diff --git a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinderTest.java b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinderTest.java new file mode 100644 index 0000000000..a4607dfb21 --- /dev/null +++ b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/binders/ChangeServerNameBinderTest.java @@ -0,0 +1,83 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.rackspace.cloudservers.binders; + +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.net.URI; + +import org.jclouds.http.HttpMethod; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.config.ParserModule; +import org.jclouds.rackspace.cloudservers.binders.ChangeServerNameBinder; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests behavior of {@code ChangeServerNameBinder} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "cloudservers.ChangeServerNameBinderTest") +public class ChangeServerNameBinderTest { + + Injector injector = Guice.createInjector(new ParserModule()); + + @Test(expectedExceptions = IllegalStateException.class) + public void testPostIsIncorrect() { + ChangeServerNameBinder binder = new ChangeServerNameBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("/")); + binder.addEntityToRequest(ImmutableMap.of("name", "foo"), request); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMustBeString() { + ChangeServerNameBinder binder = new ChangeServerNameBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("/")); + binder.addEntityToRequest(new File("foo"), request); + } + + @Test + public void testCorrect() { + ChangeServerNameBinder binder = new ChangeServerNameBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.PUT, URI.create("/")); + binder.addEntityToRequest("foo", request); + assertEquals("{\"server\":{\"name\":\"foo\"}}", request.getEntity()); + } + + @Test(expectedExceptions = { NullPointerException.class, IllegalStateException.class }) + public void testNullIsBad() { + ChangeServerNameBinder binder = new ChangeServerNameBinder(); + injector.injectMembers(binder); + HttpRequest request = new HttpRequest(HttpMethod.PUT, URI.create("/")); + binder.addEntityToRequest(null, request); + } +} diff --git a/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/options/ListOptionsTest.java b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/options/ListOptionsTest.java new file mode 100644 index 0000000000..dcf2428e34 --- /dev/null +++ b/rackspace/cloudservers/core/src/test/java/org/jclouds/rackspace/cloudservers/options/ListOptionsTest.java @@ -0,0 +1,66 @@ +package org.jclouds.rackspace.cloudservers.options; + +import static org.jclouds.rackspace.cloudservers.options.ListOptions.Builder.*; +import static org.testng.Assert.assertEquals; + +import org.joda.time.DateTime; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Tests behavior of {@code ListOptions} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "rackspace.ListOptionsTest") +public class ListOptionsTest { + + public void testWithDetails() { + ListOptions options = new ListOptions().withDetails(); + assertEquals(options.buildPathSuffix(), "/detail"); + } + + public void testWithDetailsStatic() { + ListOptions options = withDetails(); + assertEquals(options.buildPathSuffix(), "/detail"); + } + + public void testChangesSince() { + DateTime ifModifiedSince = new DateTime(); + ListOptions options = new ListOptions().changesSince(ifModifiedSince); + assertEquals(ImmutableList.of(ifModifiedSince.getMillis() / 1000 + ""), options + .buildQueryParameters().get("changes-since")); + } + + public void testStartAt() { + long offset = 1; + ListOptions options = new ListOptions().startAt(offset); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("offset")); + } + + public void testMaxResults() { + int limit = 1; + ListOptions options = new ListOptions().maxResults(limit); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("limit")); + } + + public void testChangesSinceStatic() { + DateTime ifModifiedSince = new DateTime(); + ListOptions options = changesSince(ifModifiedSince); + assertEquals(ImmutableList.of(ifModifiedSince.getMillis() / 1000 + ""), options + .buildQueryParameters().get("changes-since")); + } + + public void testStartAtStatic() { + long offset = 1; + ListOptions options = startAt(offset); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("offset")); + } + + public void testMaxResultsStatic() { + int limit = 1; + ListOptions options = maxResults(limit); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("limit")); + } +} diff --git a/rackspace/core/src/main/java/org/jclouds/rackspace/RackspaceAuthentication.java b/rackspace/core/src/main/java/org/jclouds/rackspace/RackspaceAuthentication.java index dbee025e2a..b893904b9b 100644 --- a/rackspace/core/src/main/java/org/jclouds/rackspace/RackspaceAuthentication.java +++ b/rackspace/core/src/main/java/org/jclouds/rackspace/RackspaceAuthentication.java @@ -38,7 +38,6 @@ import org.jclouds.rest.ResponseParser; *

* * @see - * @see * @author Adrian Cole */ diff --git a/rackspace/core/src/main/java/org/jclouds/rackspace/options/BaseListOptions.java b/rackspace/core/src/main/java/org/jclouds/rackspace/options/BaseListOptions.java new file mode 100644 index 0000000000..a8046071ed --- /dev/null +++ b/rackspace/core/src/main/java/org/jclouds/rackspace/options/BaseListOptions.java @@ -0,0 +1,81 @@ +package org.jclouds.rackspace.options; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.joda.time.DateTime; + +/** + * Options used to control paginated results (aka list commands). + * + * @see + * @author Adrian Cole + */ +public class BaseListOptions extends BaseHttpRequestOptions { + public static final BaseListOptions NONE = new BaseListOptions(); + + /** + * Only return objects changed since this time. + */ + public BaseListOptions changesSince(DateTime ifModifiedSince) { + this.queryParameters.put("changes-since", checkNotNull(ifModifiedSince, "ifModifiedSince") + .getMillis() + / 1000 + ""); + return this; + } + + /** + * Indicates where to begin listing. The list will only include objects that occur after the + * offset. This is convenient for pagination: To get the next page of results use the last result + * number of the current page + current page offset as the offset. + */ + public BaseListOptions startAt(long offset) { + checkState(offset >= 0, "offset must be >= 0"); + queryParameters.put("offset", Long.toString(checkNotNull(offset, "offset"))); + return this; + } + + /** + * To reduce load on the service, list operations will return a maximum of 1,000 items at a time. + * To navigate the collection, the parameters limit and offset can be set in the URI + * (e.g.?limit=0&offset=0). If an offset is given beyond the end of a list an empty list will be + * returned. + *

+ * Note that list operations never return itemNotFound (404) faults. + */ + public BaseListOptions maxResults(int limit) { + checkState(limit >= 0, "limit must be >= 0"); + checkState(limit <= 10000, "limit must be <= 10000"); + queryParameters.put("limit", Integer.toString(limit)); + return this; + } + + public static class Builder { + + /** + * @see BaseListOptions#startAt(long) + */ + public static BaseListOptions startAt(long prefix) { + BaseListOptions options = new BaseListOptions(); + return options.startAt(prefix); + } + + /** + * @see BaseListOptions#maxResults(long) + */ + public static BaseListOptions maxResults(int maxKeys) { + BaseListOptions options = new BaseListOptions(); + return options.maxResults(maxKeys); + } + + /** + * @see BaseListOptions#changesSince(DateTime) + */ + public static BaseListOptions changesSince(DateTime since) { + BaseListOptions options = new BaseListOptions(); + return options.changesSince(since); + } + + } +} diff --git a/rackspace/core/src/test/java/org/jclouds/rackspace/options/BaseListOptionsTest.java b/rackspace/core/src/test/java/org/jclouds/rackspace/options/BaseListOptionsTest.java new file mode 100644 index 0000000000..1ec82c7dc4 --- /dev/null +++ b/rackspace/core/src/test/java/org/jclouds/rackspace/options/BaseListOptionsTest.java @@ -0,0 +1,58 @@ +package org.jclouds.rackspace.options; + +import static org.jclouds.rackspace.options.BaseListOptions.Builder.changesSince; +import static org.jclouds.rackspace.options.BaseListOptions.Builder.maxResults; +import static org.jclouds.rackspace.options.BaseListOptions.Builder.startAt; +import static org.testng.Assert.assertEquals; + +import org.joda.time.DateTime; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Tests behavior of {@code ListOptions} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "rackspace.ListOptionsTest") +public class BaseListOptionsTest { + + public void testChangesSince() { + DateTime ifModifiedSince = new DateTime(); + BaseListOptions options = new BaseListOptions().changesSince(ifModifiedSince); + assertEquals(ImmutableList.of(ifModifiedSince.getMillis() / 1000 + ""), options + .buildQueryParameters().get("changes-since")); + } + + public void testStartAt() { + long offset = 1; + BaseListOptions options = new BaseListOptions().startAt(offset); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("offset")); + } + + public void testMaxResults() { + int limit = 1; + BaseListOptions options = new BaseListOptions().maxResults(limit); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("limit")); + } + + public void testChangesSinceStatic() { + DateTime ifModifiedSince = new DateTime(); + BaseListOptions options = changesSince(ifModifiedSince); + assertEquals(ImmutableList.of(ifModifiedSince.getMillis() / 1000 + ""), options + .buildQueryParameters().get("changes-since")); + } + + public void testStartAtStatic() { + long offset = 1; + BaseListOptions options = startAt(offset); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("offset")); + } + + public void testMaxResultsStatic() { + int limit = 1; + BaseListOptions options = maxResults(limit); + assertEquals(ImmutableList.of("1"), options.buildQueryParameters().get("limit")); + } +}