From d8b465ec2e9d6d8af27bb7d505e24d46eb5035ab Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sat, 3 Mar 2012 11:33:27 -0800 Subject: [PATCH] Issue 851:add save/delete image support to go grid --- .../gogrid/domain/ServerImageState.java | 1 + .../gogrid/handlers/GoGridErrorHandler.java | 1 + .../gogrid/options/SaveImageOptions.java | 48 +++++++++ .../gogrid/services/GridImageAsyncClient.java | 33 +++++-- .../gogrid/services/GridImageClient.java | 30 +++++- .../gogrid/options/SaveImageOptionsTest.java | 99 +++++++++++++++++++ .../services/GridImageAsyncClientTest.java | 55 ++++++++++- .../services/GridImageClientLiveTest.java | 77 ++++++++++++++- 8 files changed, 330 insertions(+), 14 deletions(-) create mode 100644 providers/gogrid/src/main/java/org/jclouds/gogrid/options/SaveImageOptions.java create mode 100644 providers/gogrid/src/test/java/org/jclouds/gogrid/options/SaveImageOptionsTest.java diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/domain/ServerImageState.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/domain/ServerImageState.java index 9111aceb82..4e569eca33 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/domain/ServerImageState.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/domain/ServerImageState.java @@ -27,6 +27,7 @@ public enum ServerImageState { AVAILABLE("Available"), SAVING("Saving"), + TRASH("Trash"), UNRECOGNIZED("Unknown"); String type; diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/handlers/GoGridErrorHandler.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/handlers/GoGridErrorHandler.java index 8ef4ec993c..e0acddcd20 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/handlers/GoGridErrorHandler.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/handlers/GoGridErrorHandler.java @@ -60,6 +60,7 @@ public class GoGridErrorHandler implements HttpErrorHandler { exception = new ResourceNotFoundException(Iterables.get(errors, 0).getMessage(), exception); break; } + break; case 403: exception = new AuthorizationException(exception.getMessage(), exception); break; diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/options/SaveImageOptions.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/options/SaveImageOptions.java new file mode 100644 index 0000000000..28908670cb --- /dev/null +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/options/SaveImageOptions.java @@ -0,0 +1,48 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.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); + } + } +} diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageAsyncClient.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageAsyncClient.java index 4acf8e0686..71bb63f126 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageAsyncClient.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageAsyncClient.java @@ -19,10 +19,12 @@ package org.jclouds.gogrid.services; 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_FRIENDLY_NAME_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.SERVER_ID_OR_NAME_KEY; 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.ParseOptionsFromJsonResponse; import org.jclouds.gogrid.options.GetImageListOptions; +import org.jclouds.gogrid.options.SaveImageOptions; import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -68,8 +73,7 @@ public interface GridImageAsyncClient { @GET @ResponseParser(ParseImageListFromJsonResponse.class) @Path("/grid/image/get") - ListenableFuture> getImagesById( - @BinderParam(BindIdsToQueryParams.class) Long... ids); + ListenableFuture> getImagesById(@BinderParam(BindIdsToQueryParams.class) Long... ids); /** * @see GridImageClient#getImagesByName @@ -77,8 +81,7 @@ public interface GridImageAsyncClient { @GET @ResponseParser(ParseImageListFromJsonResponse.class) @Path("/grid/image/get") - ListenableFuture> getImagesByName( - @BinderParam(BindNamesToQueryParams.class) String... names); + ListenableFuture> getImagesByName(@BinderParam(BindNamesToQueryParams.class) String... names); /** * @see GridImageClient#editImageDescription @@ -87,7 +90,7 @@ public interface GridImageAsyncClient { @ResponseParser(ParseImageFromJsonResponse.class) @Path("/grid/image/edit") ListenableFuture editImageDescription(@QueryParam(IMAGE_KEY) String idOrName, - @QueryParam(IMAGE_DESCRIPTION_KEY) String newDescription); + @QueryParam(IMAGE_DESCRIPTION_KEY) String newDescription); /** * @see GridImageClient#editImageFriendlyName @@ -96,7 +99,7 @@ public interface GridImageAsyncClient { @ResponseParser(ParseImageFromJsonResponse.class) @Path("/grid/image/edit") ListenableFuture editImageFriendlyName(@QueryParam(IMAGE_KEY) String idOrName, - @QueryParam(IMAGE_FRIENDLY_NAME_KEY) String newFriendlyName); + @QueryParam(IMAGE_FRIENDLY_NAME_KEY) String newFriendlyName); /** * @see GridImageClient#getDatacenters @@ -106,4 +109,22 @@ public interface GridImageAsyncClient { @Path("/common/lookup/list") @QueryParams(keys = LOOKUP_LIST_KEY, values = "datacenter") ListenableFuture> getDatacenters(); + + /** + * @see GridImageClient#deleteById(Long) + */ + @GET + @ResponseParser(ParseImageFromJsonResponse.class) + @Path("/grid/image/delete") + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture deleteById(@QueryParam(ID_KEY) long id); + + /** + * @see GridImageClient#saveImageFromServer + */ + @GET + @ResponseParser(ParseImageFromJsonResponse.class) + @Path("/grid/image/save") + ListenableFuture saveImageFromServer(@QueryParam(IMAGE_FRIENDLY_NAME_KEY) String friendlyName, + @QueryParam(SERVER_ID_OR_NAME_KEY) String idOrName, SaveImageOptions... options); } diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageClient.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageClient.java index fd0777eac3..df690ec960 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageClient.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/services/GridImageClient.java @@ -25,15 +25,37 @@ import org.jclouds.concurrent.Timeout; import org.jclouds.gogrid.domain.Option; import org.jclouds.gogrid.domain.ServerImage; import org.jclouds.gogrid.options.GetImageListOptions; +import org.jclouds.gogrid.options.SaveImageOptions; /** * Manages the server images * - * @see + * @see * @author Oleksiy Yarmula */ @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) 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. @@ -85,9 +107,9 @@ public interface GridImageClient { ServerImage editImageFriendlyName(String idOrName, String newFriendlyName); /** - * Retrieves the list of supported Datacenters to save images in. The objects will have - * datacenter ID, name and description. In most cases, id or name will be used for - * {@link #getImageList}. + * Retrieves the list of supported Datacenters to save images in. The objects + * will have datacenter ID, name and description. In most cases, id or name + * will be used for {@link #getImageList}. * * @return supported datacenters */ diff --git a/providers/gogrid/src/test/java/org/jclouds/gogrid/options/SaveImageOptionsTest.java b/providers/gogrid/src/test/java/org/jclouds/gogrid/options/SaveImageOptionsTest.java new file mode 100644 index 0000000000..fd38ff5b7a --- /dev/null +++ b/providers/gogrid/src/test/java/org/jclouds/gogrid/options/SaveImageOptionsTest.java @@ -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); + } + +} diff --git a/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageAsyncClientTest.java b/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageAsyncClientTest.java index 8770ae0d39..0999997979 100644 --- a/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageAsyncClientTest.java +++ b/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageAsyncClientTest.java @@ -26,7 +26,9 @@ import org.jclouds.gogrid.domain.ServerImageType; import org.jclouds.gogrid.functions.ParseImageFromJsonResponse; import org.jclouds.gogrid.functions.ParseImageListFromJsonResponse; import org.jclouds.gogrid.options.GetImageListOptions; +import org.jclouds.gogrid.options.SaveImageOptions; import org.jclouds.http.HttpRequest; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.testng.annotations.Test; @@ -38,7 +40,8 @@ import com.google.inject.TypeLiteral; * * @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") public class GridImageAsyncClientTest extends BaseGoGridAsyncClientTest { @@ -139,6 +142,56 @@ public class GridImageAsyncClientTest extends BaseGoGridAsyncClientTest> createTypeLiteral() { return new TypeLiteral>() { diff --git a/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageClientLiveTest.java b/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageClientLiveTest.java index 9620b49710..812ae5c4bb 100644 --- a/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageClientLiveTest.java +++ b/providers/gogrid/src/test/java/org/jclouds/gogrid/services/GridImageClientLiveTest.java @@ -19,20 +19,34 @@ package org.jclouds.gogrid.services; 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.concurrent.TimeUnit; 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.ServerImageState; +import org.jclouds.gogrid.options.SaveImageOptions; +import org.jclouds.gogrid.predicates.ServerLatestJobCompleted; +import org.jclouds.predicates.RetryablePredicate; import org.testng.annotations.Test; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; /** * * @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") public class GridImageClientLiveTest extends BaseGoGridClientLiveTest { @@ -43,8 +57,8 @@ public class GridImageClientLiveTest extends BaseGoGridClientLiveTest { assert image.getId() >= 0 : image; checkImage(image); - ServerImage query = Iterables.getOnlyElement(restContext.getApi().getImageServices().getImagesById( - image.getId())); + ServerImage query = Iterables.getOnlyElement(restContext.getApi().getImageServices() + .getImagesById(image.getId())); assertEquals(query.getId(), image.getId()); checkImage(query); @@ -69,4 +83,61 @@ public class GridImageClientLiveTest extends BaseGoGridClientLiveTest { if (image.getUpdatedTime() == null) Logger.getAnonymousLogger().warning("image " + image.getId() + " is missing the updatedon field"); } + + @Test + public void testSaveServerToImage() throws IOException { + RetryablePredicate serverLatestJobCompleted = new RetryablePredicate( + 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 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(new Predicate() { + + @Override + public boolean apply(ServerImage input) { + return Iterables.getOnlyElement(restContext + .getApi() + .getImageServices().getImagesById(input.getId())).getState() == state; + } + }, 300, 1, TimeUnit.SECONDS).apply(image)); + } }