Issue 851:add save/delete image support to go grid

This commit is contained in:
Adrian Cole 2012-03-03 11:33:27 -08:00
parent d5a9162348
commit d8b465ec2e
8 changed files with 330 additions and 14 deletions

View File

@ -27,6 +27,7 @@ public enum ServerImageState {
AVAILABLE("Available"), AVAILABLE("Available"),
SAVING("Saving"), SAVING("Saving"),
TRASH("Trash"),
UNRECOGNIZED("Unknown"); UNRECOGNIZED("Unknown");
String type; String type;

View File

@ -60,6 +60,7 @@ public class GoGridErrorHandler implements HttpErrorHandler {
exception = new ResourceNotFoundException(Iterables.get(errors, 0).getMessage(), exception); exception = new ResourceNotFoundException(Iterables.get(errors, 0).getMessage(), exception);
break; break;
} }
break;
case 403: case 403:
exception = new AuthorizationException(exception.getMessage(), exception); exception = new AuthorizationException(exception.getMessage(), exception);
break; break;

View File

@ -0,0 +1,48 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.gogrid.options;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.gogrid.reference.GoGridQueryParams.DESCRIPTION_KEY;
import org.jclouds.http.options.BaseHttpRequestOptions;
/**
* @author Adrian Cole
*/
public class SaveImageOptions extends BaseHttpRequestOptions {
public SaveImageOptions withDescription(String description) {
checkArgument(description.length() <= 500, "Description cannot be longer than 500 characters");
checkState(!queryParameters.containsKey(DESCRIPTION_KEY), "Can't have duplicate image description");
queryParameters.put(DESCRIPTION_KEY, description);
return this;
}
public static class Builder {
/**
* @see SaveImageOptions#withDescription(String)
*/
public static SaveImageOptions withDescription(String description) {
SaveImageOptions options = new SaveImageOptions();
return options.withDescription(description);
}
}
}

View File

@ -19,10 +19,12 @@
package org.jclouds.gogrid.services; package org.jclouds.gogrid.services;
import static org.jclouds.gogrid.reference.GoGridHeaders.VERSION; import static org.jclouds.gogrid.reference.GoGridHeaders.VERSION;
import static org.jclouds.gogrid.reference.GoGridQueryParams.ID_KEY;
import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_DESCRIPTION_KEY; import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_DESCRIPTION_KEY;
import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_FRIENDLY_NAME_KEY; import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_FRIENDLY_NAME_KEY;
import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_KEY; import static org.jclouds.gogrid.reference.GoGridQueryParams.IMAGE_KEY;
import static org.jclouds.gogrid.reference.GoGridQueryParams.LOOKUP_LIST_KEY; import static org.jclouds.gogrid.reference.GoGridQueryParams.LOOKUP_LIST_KEY;
import static org.jclouds.gogrid.reference.GoGridQueryParams.SERVER_ID_OR_NAME_KEY;
import java.util.Set; import java.util.Set;
@ -40,10 +42,13 @@ import org.jclouds.gogrid.functions.ParseImageFromJsonResponse;
import org.jclouds.gogrid.functions.ParseImageListFromJsonResponse; import org.jclouds.gogrid.functions.ParseImageListFromJsonResponse;
import org.jclouds.gogrid.functions.ParseOptionsFromJsonResponse; import org.jclouds.gogrid.functions.ParseOptionsFromJsonResponse;
import org.jclouds.gogrid.options.GetImageListOptions; import org.jclouds.gogrid.options.GetImageListOptions;
import org.jclouds.gogrid.options.SaveImageOptions;
import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -68,8 +73,7 @@ public interface GridImageAsyncClient {
@GET @GET
@ResponseParser(ParseImageListFromJsonResponse.class) @ResponseParser(ParseImageListFromJsonResponse.class)
@Path("/grid/image/get") @Path("/grid/image/get")
ListenableFuture<Set<ServerImage>> getImagesById( ListenableFuture<Set<ServerImage>> getImagesById(@BinderParam(BindIdsToQueryParams.class) Long... ids);
@BinderParam(BindIdsToQueryParams.class) Long... ids);
/** /**
* @see GridImageClient#getImagesByName * @see GridImageClient#getImagesByName
@ -77,8 +81,7 @@ public interface GridImageAsyncClient {
@GET @GET
@ResponseParser(ParseImageListFromJsonResponse.class) @ResponseParser(ParseImageListFromJsonResponse.class)
@Path("/grid/image/get") @Path("/grid/image/get")
ListenableFuture<Set<ServerImage>> getImagesByName( ListenableFuture<Set<ServerImage>> getImagesByName(@BinderParam(BindNamesToQueryParams.class) String... names);
@BinderParam(BindNamesToQueryParams.class) String... names);
/** /**
* @see GridImageClient#editImageDescription * @see GridImageClient#editImageDescription
@ -87,7 +90,7 @@ public interface GridImageAsyncClient {
@ResponseParser(ParseImageFromJsonResponse.class) @ResponseParser(ParseImageFromJsonResponse.class)
@Path("/grid/image/edit") @Path("/grid/image/edit")
ListenableFuture<ServerImage> editImageDescription(@QueryParam(IMAGE_KEY) String idOrName, ListenableFuture<ServerImage> editImageDescription(@QueryParam(IMAGE_KEY) String idOrName,
@QueryParam(IMAGE_DESCRIPTION_KEY) String newDescription); @QueryParam(IMAGE_DESCRIPTION_KEY) String newDescription);
/** /**
* @see GridImageClient#editImageFriendlyName * @see GridImageClient#editImageFriendlyName
@ -96,7 +99,7 @@ public interface GridImageAsyncClient {
@ResponseParser(ParseImageFromJsonResponse.class) @ResponseParser(ParseImageFromJsonResponse.class)
@Path("/grid/image/edit") @Path("/grid/image/edit")
ListenableFuture<ServerImage> editImageFriendlyName(@QueryParam(IMAGE_KEY) String idOrName, ListenableFuture<ServerImage> editImageFriendlyName(@QueryParam(IMAGE_KEY) String idOrName,
@QueryParam(IMAGE_FRIENDLY_NAME_KEY) String newFriendlyName); @QueryParam(IMAGE_FRIENDLY_NAME_KEY) String newFriendlyName);
/** /**
* @see GridImageClient#getDatacenters * @see GridImageClient#getDatacenters
@ -106,4 +109,22 @@ public interface GridImageAsyncClient {
@Path("/common/lookup/list") @Path("/common/lookup/list")
@QueryParams(keys = LOOKUP_LIST_KEY, values = "datacenter") @QueryParams(keys = LOOKUP_LIST_KEY, values = "datacenter")
ListenableFuture<Set<Option>> getDatacenters(); ListenableFuture<Set<Option>> getDatacenters();
/**
* @see GridImageClient#deleteById(Long)
*/
@GET
@ResponseParser(ParseImageFromJsonResponse.class)
@Path("/grid/image/delete")
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<ServerImage> deleteById(@QueryParam(ID_KEY) long id);
/**
* @see GridImageClient#saveImageFromServer
*/
@GET
@ResponseParser(ParseImageFromJsonResponse.class)
@Path("/grid/image/save")
ListenableFuture<ServerImage> saveImageFromServer(@QueryParam(IMAGE_FRIENDLY_NAME_KEY) String friendlyName,
@QueryParam(SERVER_ID_OR_NAME_KEY) String idOrName, SaveImageOptions... options);
} }

View File

@ -25,15 +25,37 @@ import org.jclouds.concurrent.Timeout;
import org.jclouds.gogrid.domain.Option; import org.jclouds.gogrid.domain.Option;
import org.jclouds.gogrid.domain.ServerImage; import org.jclouds.gogrid.domain.ServerImage;
import org.jclouds.gogrid.options.GetImageListOptions; import org.jclouds.gogrid.options.GetImageListOptions;
import org.jclouds.gogrid.options.SaveImageOptions;
/** /**
* Manages the server images * Manages the server images
* *
* @see <a href="http://wiki.gogrid.com/wiki/index.php/API#Server_Image_Methods"/> * @see <a
* href="http://wiki.gogrid.com/wiki/index.php/API#Server_Image_Methods"/>
* @author Oleksiy Yarmula * @author Oleksiy Yarmula
*/ */
@Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS)
public interface GridImageClient { public interface GridImageClient {
/**
* Deletes an existing image
*
* @param id
* id of the existing image
*/
ServerImage deleteById(long id);
/**
* This call will save a private (visible to only you) server image to your
* library of available images. The image will be saved from an existing
* server.
*
* @param idOrName
* id or name of the existing server
* @param friendlyName
* friendly name of the image
* @return saved server image
*/
ServerImage saveImageFromServer(String friendlyName, String idOrName, SaveImageOptions... options);
/** /**
* Returns all server images. * Returns all server images.
@ -85,9 +107,9 @@ public interface GridImageClient {
ServerImage editImageFriendlyName(String idOrName, String newFriendlyName); ServerImage editImageFriendlyName(String idOrName, String newFriendlyName);
/** /**
* Retrieves the list of supported Datacenters to save images in. The objects will have * Retrieves the list of supported Datacenters to save images in. The objects
* datacenter ID, name and description. In most cases, id or name will be used for * will have datacenter ID, name and description. In most cases, id or name
* {@link #getImageList}. * will be used for {@link #getImageList}.
* *
* @return supported datacenters * @return supported datacenters
*/ */

View File

@ -0,0 +1,99 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.gogrid.options;
import static org.jclouds.gogrid.options.SaveImageOptions.Builder.withDescription;
import static org.testng.Assert.assertEquals;
import java.util.Collections;
import org.jclouds.http.options.HttpRequestOptions;
import org.testng.annotations.Test;
/**
* Tests possible uses of SaveImageOptions and SaveImageOptions.Builder.*
*
* @author Adrian Cole
*/
//NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
@Test(groups = "unit", testName = "SaveImageOptionsTest")
public class SaveImageOptionsTest {
@Test
public void testAssignability() {
assert HttpRequestOptions.class.isAssignableFrom(SaveImageOptions.class);
assert !String.class.isAssignableFrom(SaveImageOptions.class);
}
@Test
public void testWithDescription() {
SaveImageOptions options = new SaveImageOptions();
options.withDescription("test");
assertEquals(options.buildQueryParameters().get("description"), Collections
.singletonList("test"));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testWith501LengthDescription() {
SaveImageOptions options = new SaveImageOptions();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1 * 501; i++)
builder.append('a');
String description = builder.toString();
options.withDescription(description);
}
@Test
public void testWith500LengthDescription() {
SaveImageOptions options = new SaveImageOptions();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1 * 500; i++)
builder.append('a');
String description = builder.toString();
options.withDescription(description);
assertEquals(options.buildQueryParameters().get("description"), Collections
.singletonList(description));
}
@Test
public void testNullWithDescription() {
SaveImageOptions options = new SaveImageOptions();
assertEquals(options.buildQueryParameters().get("description"), Collections.EMPTY_LIST);
}
@Test
public void testWithDescriptionStatic() {
SaveImageOptions options = withDescription("test");
assertEquals(options.buildQueryParameters().get("description"), Collections
.singletonList("test"));
}
@Test(expectedExceptions = NullPointerException.class)
public void testWithDescriptionNPE() {
withDescription(null);
}
}

View File

@ -26,7 +26,9 @@ import org.jclouds.gogrid.domain.ServerImageType;
import org.jclouds.gogrid.functions.ParseImageFromJsonResponse; import org.jclouds.gogrid.functions.ParseImageFromJsonResponse;
import org.jclouds.gogrid.functions.ParseImageListFromJsonResponse; import org.jclouds.gogrid.functions.ParseImageListFromJsonResponse;
import org.jclouds.gogrid.options.GetImageListOptions; import org.jclouds.gogrid.options.GetImageListOptions;
import org.jclouds.gogrid.options.SaveImageOptions;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -38,7 +40,8 @@ import com.google.inject.TypeLiteral;
* *
* @author Oleksiy Yarmula * @author Oleksiy Yarmula
*/ */
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire // NOTE:without testName, this will not call @Before* and fail w/NPE during
// surefire
@Test(groups = "unit", testName = "GridImageAsyncClientTest") @Test(groups = "unit", testName = "GridImageAsyncClientTest")
public class GridImageAsyncClientTest extends BaseGoGridAsyncClientTest<GridImageAsyncClient> { public class GridImageAsyncClientTest extends BaseGoGridAsyncClientTest<GridImageAsyncClient> {
@ -139,6 +142,56 @@ public class GridImageAsyncClientTest extends BaseGoGridAsyncClientTest<GridImag
assertPayloadEquals(httpRequest, null, null, false); assertPayloadEquals(httpRequest, null, null, false);
} }
@Test
public void testDeleteById() throws NoSuchMethodException, IOException {
Method method = GridImageAsyncClient.class.getMethod("deleteById", long.class);
HttpRequest httpRequest = processor.createRequest(method, 11l);
assertRequestLineEquals(httpRequest, "GET https://api.gogrid.com/api/grid/image/delete?v=1.5&id=11 HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ParseImageFromJsonResponse.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
}
@Test
public void testSaveImageFromServerNoOptions() throws NoSuchMethodException, IOException {
Method method = GridImageAsyncClient.class.getMethod("saveImageFromServer", String.class, String.class,
SaveImageOptions[].class);
HttpRequest httpRequest = processor.createRequest(method, "friendly", "serverName");
assertRequestLineEquals(httpRequest,
"GET https://api.gogrid.com/api/grid/image/save?v=1.5&friendlyName=friendly&server=serverName HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ParseImageFromJsonResponse.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
}
@Test
public void testSaveImageOptions() throws NoSuchMethodException, IOException {
Method method = GridImageAsyncClient.class.getMethod("saveImageFromServer", String.class, String.class,
SaveImageOptions[].class);
HttpRequest httpRequest = processor.createRequest(method, "friendly", "serverName",
new SaveImageOptions().withDescription("fooy"));
assertRequestLineEquals(
httpRequest,
"GET https://api.gogrid.com/api/grid/image/save?v=1.5&friendlyName=friendly&server=serverName&description=fooy HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, ParseImageFromJsonResponse.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
}
@Override @Override
protected TypeLiteral<RestAnnotationProcessor<GridImageAsyncClient>> createTypeLiteral() { protected TypeLiteral<RestAnnotationProcessor<GridImageAsyncClient>> createTypeLiteral() {
return new TypeLiteral<RestAnnotationProcessor<GridImageAsyncClient>>() { return new TypeLiteral<RestAnnotationProcessor<GridImageAsyncClient>>() {

View File

@ -19,20 +19,34 @@
package org.jclouds.gogrid.services; package org.jclouds.gogrid.services;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.util.Date;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jclouds.gogrid.domain.Ip;
import org.jclouds.gogrid.domain.Server;
import org.jclouds.gogrid.domain.ServerImage; import org.jclouds.gogrid.domain.ServerImage;
import org.jclouds.gogrid.domain.ServerImageState;
import org.jclouds.gogrid.options.SaveImageOptions;
import org.jclouds.gogrid.predicates.ServerLatestJobCompleted;
import org.jclouds.predicates.RetryablePredicate;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire // NOTE:without testName, this will not call @Before* and fail w/NPE during
// surefire
@Test(groups = "unit", testName = "GridImageClientLiveTest") @Test(groups = "unit", testName = "GridImageClientLiveTest")
public class GridImageClientLiveTest extends BaseGoGridClientLiveTest { public class GridImageClientLiveTest extends BaseGoGridClientLiveTest {
@ -43,8 +57,8 @@ public class GridImageClientLiveTest extends BaseGoGridClientLiveTest {
assert image.getId() >= 0 : image; assert image.getId() >= 0 : image;
checkImage(image); checkImage(image);
ServerImage query = Iterables.getOnlyElement(restContext.getApi().getImageServices().getImagesById( ServerImage query = Iterables.getOnlyElement(restContext.getApi().getImageServices()
image.getId())); .getImagesById(image.getId()));
assertEquals(query.getId(), image.getId()); assertEquals(query.getId(), image.getId());
checkImage(query); checkImage(query);
@ -69,4 +83,61 @@ public class GridImageClientLiveTest extends BaseGoGridClientLiveTest {
if (image.getUpdatedTime() == null) if (image.getUpdatedTime() == null)
Logger.getAnonymousLogger().warning("image " + image.getId() + " is missing the updatedon field"); Logger.getAnonymousLogger().warning("image " + image.getId() + " is missing the updatedon field");
} }
@Test
public void testSaveServerToImage() throws IOException {
RetryablePredicate<Server> serverLatestJobCompleted = new RetryablePredicate<Server>(
new ServerLatestJobCompleted(restContext.getApi().getJobServices()), 800, 20, TimeUnit.SECONDS);
final String nameOfServer = "Server" + String.valueOf(new Date().getTime()).substring(6);
ServerImage image = null;
try {
Set<Ip> availableIps = restContext.getApi().getIpServices().getUnassignedIpList();
Ip availableIp = Iterables.getLast(availableIps);
Server createdServer = restContext.getApi().getServerServices()
.addServer(nameOfServer, "5489", "1", availableIp.getIp());
assertNotNull(createdServer);
assert serverLatestJobCompleted.apply(createdServer);
image = restContext
.getApi()
.getImageServices()
.saveImageFromServer("friendlyName", createdServer.getName(),
SaveImageOptions.Builder.withDescription("description"));
assertEquals(image.getFriendlyName(), "friendlyName");
assertEquals(image.getDescription(), "description");
assertFalse(image.isPublic());
assertEventuallyImageStateEquals(image, ServerImageState.AVAILABLE);
restContext.getApi().getImageServices().deleteById(image.getId());
assertEventuallyImageStateEquals(image, ServerImageState.TRASH);
} finally {
if (image != null)
try {
restContext.getApi().getImageServices().deleteById(image.getId());
} catch (Exception e) {
// not failing so that we can ensure server below deletes
e.printStackTrace();
}
// delete the server
restContext.getApi().getServerServices().deleteByName(nameOfServer);
}
}
protected void assertEventuallyImageStateEquals(ServerImage image, final ServerImageState state) {
assertTrue(new RetryablePredicate<ServerImage>(new Predicate<ServerImage>() {
@Override
public boolean apply(ServerImage input) {
return Iterables.getOnlyElement(restContext
.getApi()
.getImageServices().getImagesById(input.getId())).getState() == state;
}
}, 300, 1, TimeUnit.SECONDS).apply(image));
}
} }