added read/write features for drives in elastic hosts

This commit is contained in:
Adrian Cole 2010-11-21 22:40:56 +01:00
parent 559a9485ad
commit a2a84535d0
10 changed files with 605 additions and 1 deletions

View File

@ -26,17 +26,23 @@ 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.elastichosts.binders.BindCreateDriveRequestToPlainTextString;
import org.jclouds.elastichosts.binders.BindDriveDataToPlainTextString;
import org.jclouds.elastichosts.binders.BindReadDriveOptionsToPath;
import org.jclouds.elastichosts.domain.CreateDriveRequest;
import org.jclouds.elastichosts.domain.DriveData;
import org.jclouds.elastichosts.domain.DriveInfo;
import org.jclouds.elastichosts.domain.ImageConversionType;
import org.jclouds.elastichosts.functions.KeyValuesDelimitedByBlankLinesToDriveInfo;
import org.jclouds.elastichosts.functions.ListOfKeyValuesDelimitedByBlankLinesToDriveInfoSet;
import org.jclouds.elastichosts.functions.ReturnPayload;
import org.jclouds.elastichosts.functions.SplitNewlines;
import org.jclouds.elastichosts.options.ReadDriveOptions;
import org.jclouds.http.filters.BasicAuthentication;
import org.jclouds.io.Payload;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.RequestFilters;
@ -112,10 +118,66 @@ public interface ElasticHostsAsyncClient {
@BinderParam(BindDriveDataToPlainTextString.class) DriveData createDrive);
/**
* @see ElasticHostsClient#deleteDrive
* @see ElasticHostsClient#destroyDrive
*/
@POST
@Path("/drives/{uuid}/destroy")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> destroyDrive(@PathParam("uuid") String uuid);
/**
* @see ElasticHostsClient#imageDrive(String,String)
*/
@POST
@Path("/drives/{destination}/image/{source}")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> imageDrive(@PathParam("source") String source, @PathParam("destination") String destination);
/**
* @see ElasticHostsClient#imageDrive(String,String,ImageConversionType)
*/
@POST
@Path("/drives/{destination}/image/{source}/{conversion}")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> imageDrive(@PathParam("source") String source, @PathParam("destination") String destination,
@PathParam("conversion") ImageConversionType conversionType);
/**
* @see ElasticHostsClient#readDrive(String)
*/
@GET
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Path("/drives/{uuid}/read")
@ResponseParser(ReturnPayload.class)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<Payload> readDrive(@PathParam("uuid") String uuid);
/**
* @see ElasticHostsClient#readDrive(String,ReadDriveOptions)
*/
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Path("/drives/{uuid}/read")
@ResponseParser(ReturnPayload.class)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<Payload> readDrive(@PathParam("uuid") String uuid,
@BinderParam(BindReadDriveOptionsToPath.class) ReadDriveOptions options);
/**
* @see ElasticHostsClient#writeDrive(String, Payload)
*/
@POST
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/drives/{uuid}/write")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> writeDrive(@PathParam("uuid") String uuid, Payload content);
/**
* @see ElasticHostsClient#writeDrive(String, Payload, long)
*/
@POST
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/drives/{uuid}/write/{offset}")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> writeDrive(@PathParam("uuid") String uuid, Payload content, @PathParam("offset") long offset);
}

View File

@ -26,6 +26,9 @@ import org.jclouds.concurrent.Timeout;
import org.jclouds.elastichosts.domain.CreateDriveRequest;
import org.jclouds.elastichosts.domain.DriveData;
import org.jclouds.elastichosts.domain.DriveInfo;
import org.jclouds.elastichosts.domain.ImageConversionType;
import org.jclouds.elastichosts.options.ReadDriveOptions;
import org.jclouds.io.Payload;
/**
* Provides synchronous access to ElasticHosts.
@ -93,4 +96,61 @@ public interface ElasticHostsClient {
*/
void destroyDrive(String uuid);
/**
* Image a drive from another drive. The actual imaging process is asynchronous, with progress
* reported via drive info.
*
* @param source
* drive to copy from
* @param destination
* drive to copy to
*/
void imageDrive(String source, String destination);
/**
* @see #imageDrive(String, String)
* @param conversionType
* Supports 'gzip' or 'gunzip' conversions.
*/
void imageDrive(String source, String destination, ImageConversionType conversionType);
/**
* Read binary data from a drive
*
* @param uuid
* drive to read
* @return binary content of the drive.
*/
Payload readDrive(String uuid);
/**
* @see #readDrive(String)
* @param options
* controls offset and size of the request
*/
Payload readDrive(String uuid, ReadDriveOptions options);
/**
* Write binary data to a drive
*
* @param uuid
* drive to write
* @param content
* what to write.
* <ul>
* <li>Binary data (Content-Type: application/octet-stream)</li>
* <li>Supports raw data or Content-Encoding: gzip</li>
* <li>Does not support Transfer-Encoding: chunked</li>
* </ul>
*/
void writeDrive(String uuid, Payload content);
/**
* @see ElasticHostsClient#writeDrive(String, Payload)
* @param offset
* the byte offset in the target drive at which to start writing, not an offset in the
* input stream.
*/
void writeDrive(String uuid, Payload content, long offset);
}

View File

@ -0,0 +1,59 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.binders;
import static com.google.common.base.Preconditions.checkArgument;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.core.UriBuilder;
import org.jclouds.elastichosts.options.ReadDriveOptions;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
/**
*
* @author Adrian Cole
*/
@Singleton
public class BindReadDriveOptionsToPath implements Binder {
private final Provider<UriBuilder> uriBuilderProvider;
@Inject
public BindReadDriveOptionsToPath(Provider<UriBuilder> uriBuilderProvider) {
this.uriBuilderProvider = uriBuilderProvider;
}
public void bindToRequest(HttpRequest request, Object payload) {
checkArgument(payload instanceof ReadDriveOptions, "this binder is only valid for ReadDriveOptions!");
ReadDriveOptions options = ReadDriveOptions.class.cast(payload);
if (options.getOffset() != null || options.getSize() != null){
UriBuilder builder = uriBuilderProvider.get().uri(request.getEndpoint());
if (options.getOffset() != null)
builder.path("/"+options.getOffset());
if (options.getSize() != null)
builder.path("/"+options.getSize());
request.setEndpoint(builder.build());
}
}
}

View File

@ -0,0 +1,48 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.domain;
import static com.google.common.base.Preconditions.checkNotNull;
/**
*
* @author Adrian Cole
*/
public enum ImageConversionType {
GZIP, GUNZIP, UNRECOGNIZED;
public String value() {
return name().toLowerCase();
}
@Override
public String toString() {
return value();
}
public static ImageConversionType fromValue(String type) {
try {
return valueOf(checkNotNull(type, "type").toUpperCase());
} catch (IllegalArgumentException e) {
return UNRECOGNIZED;
}
}
}

View File

@ -0,0 +1,38 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.functions;
import javax.inject.Singleton;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payload;
import com.google.common.base.Function;
/**
* @author Adrian Cole
*/
@Singleton
public class ReturnPayload implements Function<HttpResponse, Payload> {
public Payload apply(HttpResponse from) {
return from.getPayload();
}
}

View File

@ -0,0 +1,91 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.options;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Contains options supported for read drive operations. <h2>
* Usage</h2> The recommended way to instantiate a ReadDriveOptions object is to statically import
* ReadDriveOptions.Builder.* and invoke a static creation method followed by an instance mutator
* (if needed):
* <p/>
* <code>
* import static org.jclouds.elastichosts.options.ReadDriveOptions.Builder.*;
*
*
* // this will get the first 1024 bytes starting at offset 2048
* Payload payload = client.readDrive("drive-uuid",offset(2048l).size(1024l));
* <code>
*
* @author Adrian Cole
*
*/
public class ReadDriveOptions {
private Long offset;
private Long size;
/**
* start at the specified offset in bytes
*/
public ReadDriveOptions offset(long offset) {
checkArgument(offset >= 0, "start must be >= 0");
this.offset = offset;
return this;
}
/**
* download the specified size in bytes
*/
public ReadDriveOptions size(long size) {
checkArgument(size >= 0, "start must be >= 0");
this.size = size;
return this;
}
public static class Builder {
/**
* @see ReadDriveOptions#offset
*/
public static ReadDriveOptions offset(long offset) {
ReadDriveOptions options = new ReadDriveOptions();
return options.offset(offset);
}
/**
* @see ReadDriveOptions#size
*/
public static ReadDriveOptions size(long size) {
ReadDriveOptions options = new ReadDriveOptions();
return options.size(size);
}
}
public Long getOffset() {
return offset;
}
public Long getSize() {
return size;
}
}

View File

@ -25,14 +25,21 @@ import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.ws.rs.core.MediaType;
import org.jclouds.elastichosts.domain.CreateDriveRequest;
import org.jclouds.elastichosts.domain.DriveData;
import org.jclouds.elastichosts.domain.ImageConversionType;
import org.jclouds.elastichosts.functions.KeyValuesDelimitedByBlankLinesToDriveInfo;
import org.jclouds.elastichosts.functions.ListOfKeyValuesDelimitedByBlankLinesToDriveInfoSet;
import org.jclouds.elastichosts.functions.ReturnPayload;
import org.jclouds.elastichosts.functions.SplitNewlines;
import org.jclouds.elastichosts.options.ReadDriveOptions;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.filters.BasicAuthentication;
import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.RestContextSpec;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
@ -160,6 +167,40 @@ public class ElasticHostsAsyncClientTest extends RestClientTest<ElasticHostsAsyn
}
public void testImageDrive() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("imageDrive", String.class, String.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100", "200");
assertRequestLineEquals(httpRequest, "POST https://api.elastichosts.com/drives/200/image/100 HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: text/plain\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testImageDriveWithConversion() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("imageDrive", String.class, String.class,
ImageConversionType.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100", "200",
ImageConversionType.GUNZIP);
assertRequestLineEquals(httpRequest, "POST https://api.elastichosts.com/drives/200/image/100/gunzip HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: text/plain\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testDestroyDrive() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("destroyDrive", String.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "uuid");
@ -176,6 +217,68 @@ public class ElasticHostsAsyncClientTest extends RestClientTest<ElasticHostsAsyn
}
public void testReadDrive() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("readDrive", String.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100");
assertRequestLineEquals(httpRequest, "GET https://api.elastichosts.com/drives/100/read HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/octet-stream\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ReturnPayload.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testReadDriveOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("readDrive", String.class, ReadDriveOptions.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100",
new ReadDriveOptions().offset(1024).size(2048));
assertRequestLineEquals(httpRequest, "POST https://api.elastichosts.com/drives/100/read/1024/2048 HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/octet-stream\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ReturnPayload.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testWriteDrive() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("writeDrive", String.class, Payload.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100",
Payloads.newStringPayload("foo"));
assertRequestLineEquals(httpRequest, "POST https://api.elastichosts.com/drives/100/write HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: text/plain\n");
assertPayloadEquals(httpRequest, "", MediaType.APPLICATION_OCTET_STREAM, false);
assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testWriteDriveOffset() throws SecurityException, NoSuchMethodException, IOException {
Method method = ElasticHostsAsyncClient.class.getMethod("writeDrive", String.class, Payload.class, long.class);
GeneratedHttpRequest<ElasticHostsAsyncClient> httpRequest = processor.createRequest(method, "100",
Payloads.newStringPayload("foo"), 2048);
assertRequestLineEquals(httpRequest, "POST https://api.elastichosts.com/drives/100/write/2048 HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: text/plain\n");
assertPayloadEquals(httpRequest, "", MediaType.APPLICATION_OCTET_STREAM, false);
assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
checkFilters(httpRequest);
}
@Override
protected void checkFilters(HttpRequest request) {
assertEquals(request.getFilters().size(), 1);

View File

@ -149,6 +149,15 @@ public class ElasticHostsClientLiveTest {
}
public void testWeCanReadAndWriteToDrive() {
// TODO put a bunch of bytes and then read them back.
}
public void testWeCopyADriveContentsViaGzip() {
// TODO gzip source to destination, then gunzip back to source and assert equiv
}
@Test(dependsOnMethods = "testCreate")
public void testSetDriveData() throws Exception {

View File

@ -0,0 +1,51 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.binders;
import static org.testng.Assert.assertEquals;
import java.net.URI;
import org.jclouds.elastichosts.options.ReadDriveOptions;
import org.jclouds.http.HttpRequest;
import org.jclouds.logging.config.NullLoggingModule;
import org.jclouds.rest.BaseRestClientTest.MockModule;
import org.jclouds.rest.config.RestModule;
import org.testng.annotations.Test;
import com.google.inject.Guice;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public class BindReadDriveOptionsToPathTest {
private static final BindReadDriveOptionsToPath FN = Guice.createInjector(new RestModule(), new MockModule(),
new NullLoggingModule()).getInstance(BindReadDriveOptionsToPath.class);
public void testSimple() {
HttpRequest request = new HttpRequest("POST", URI.create("https://drives/read"));
FN.bindToRequest(request, new ReadDriveOptions().offset(1024l).size(2048l));
assertEquals(request.getEndpoint().getPath(), "/read/1024/2048");
}
}

View File

@ -0,0 +1,83 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.elastichosts.options;
import static org.jclouds.elastichosts.options.ReadDriveOptions.Builder.offset;
import static org.jclouds.elastichosts.options.ReadDriveOptions.Builder.size;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import org.testng.annotations.Test;
/**
* Tests possible uses of ReadDriveOptions and ReadDriveOptions.Builder.*
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class ReadDriveOptionsTest {
@Test
public void testNullOffset() {
ReadDriveOptions options = new ReadDriveOptions();
assertNull(options.getOffset());
}
@Test
public void testOffset() {
ReadDriveOptions options = new ReadDriveOptions().offset(1024);
assertEquals(options.getOffset(), new Long(1024));
}
@Test
public void testOffsetStatic() {
ReadDriveOptions options = offset(1024);
assertEquals(options.getOffset(), new Long(1024));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testOffsetNegative() {
offset(-1);
}
@Test
public void testNullSize() {
ReadDriveOptions options = new ReadDriveOptions();
assertNull(options.getSize());
}
@Test
public void testSize() {
ReadDriveOptions options = new ReadDriveOptions().size(1024);
assertEquals(options.getSize(), new Long(1024));
}
@Test
public void testSizeStatic() {
ReadDriveOptions options = size(1024);
assertEquals(options.getSize(), new Long(1024));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSizeNegative() {
size(-1);
}
}