From 4771fafe48e504bd5c808536c5a7382a82b7f350 Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Tue, 3 Apr 2012 16:49:07 -0700 Subject: [PATCH 1/4] create an image from a nova vm instance. --- .../nova/v1_1/features/ServerAsyncClient.java | 17 ++++++ .../nova/v1_1/features/ServerClient.java | 14 +++++ .../ParseImageIdFromLocationHeader.java | 53 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java index 994dd290df..9e475a7d3b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java @@ -33,7 +33,9 @@ import javax.ws.rs.core.MediaType; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.filters.AuthenticateRequest; import org.jclouds.openstack.nova.v1_1.domain.RebootType; +import org.jclouds.openstack.nova.v1_1.domain.Image; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.functions.ParseImageIdFromLocationHeader; import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; import org.jclouds.openstack.nova.v1_1.options.RebuildServerOptions; import org.jclouds.rest.annotations.ExceptionParser; @@ -41,9 +43,11 @@ import org.jclouds.rest.annotations.MapBinder; import org.jclouds.rest.annotations.Payload; import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.annotations.SkipEncoding; import org.jclouds.rest.annotations.Unwrap; +import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; @@ -182,4 +186,17 @@ public interface ServerAsyncClient { @Produces(MediaType.APPLICATION_JSON) @Payload("%7B\"server\":%7B\"name\":\"{name}\"%7D%7D") ListenableFuture renameServer(@PathParam("id") String id, @PayloadParam("name") String newName); + + /** + * @see ServerClient#createImageFromServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Payload("%7B\"createImage\":%7B\"name\":\"{name}\", \"metadata\": %7B%7D%7D%7D") + @ExceptionParser(MapHttp4xxCodesToExceptions.class) + @ResponseParser(ParseImageIdFromLocationHeader.class) + ListenableFuture createImageFromServer(@PathParam("id") String id, @PayloadParam("name") String name); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java index b0723f1568..da2b20ee73 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.jclouds.concurrent.Timeout; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.nova.v1_1.domain.RebootType; +import org.jclouds.openstack.nova.v1_1.domain.Image; import org.jclouds.openstack.nova.v1_1.domain.Server; import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; import org.jclouds.openstack.nova.v1_1.options.RebuildServerOptions; @@ -156,4 +157,17 @@ public interface ServerClient { * The new name for the server */ void renameServer(String id, String newName); + + /** + * Create an image from a server. + * + * @param id + * id of the server + * @param name + * The name of the new image + * + * @return ID of the new / updated image + */ + String createImageFromServer(String id, String name); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java new file mode 100644 index 0000000000..3bf7b82e6d --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java @@ -0,0 +1,53 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.openstack.nova.v1_1.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URI; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import javax.ws.rs.core.HttpHeaders; + +import org.jclouds.openstack.nova.v1_1.domain.Image; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseJson; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMultimap; + + +/** + * This parses {@link Image} from the body of the link in the Location header of the HTTPResponse. + * + * @author Tim Miller + */ +@Singleton +public class ParseImageIdFromLocationHeader implements Function { + + public String apply(HttpResponse response) { + String location = response.getFirstHeaderOrNull(HttpHeaders.LOCATION); + String[] parts = location.split("/"); + return parts[parts.length - 1]; + } +} From 3cab636f299c4ca3cbb8b3524de1bb2dd2d307b7 Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Wed, 4 Apr 2012 11:48:05 -0700 Subject: [PATCH 2/4] remove unused import statements. --- .../openstack/nova/v1_1/features/ServerAsyncClient.java | 1 - .../openstack/nova/v1_1/features/ServerClient.java | 1 - .../v1_1/functions/ParseImageIdFromLocationHeader.java | 8 -------- 3 files changed, 10 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java index 9e475a7d3b..62ef9e4181 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java @@ -33,7 +33,6 @@ import javax.ws.rs.core.MediaType; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.filters.AuthenticateRequest; import org.jclouds.openstack.nova.v1_1.domain.RebootType; -import org.jclouds.openstack.nova.v1_1.domain.Image; import org.jclouds.openstack.nova.v1_1.domain.Server; import org.jclouds.openstack.nova.v1_1.functions.ParseImageIdFromLocationHeader; import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java index da2b20ee73..9835affeb8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java @@ -24,7 +24,6 @@ import java.util.concurrent.TimeUnit; import org.jclouds.concurrent.Timeout; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.nova.v1_1.domain.RebootType; -import org.jclouds.openstack.nova.v1_1.domain.Image; import org.jclouds.openstack.nova.v1_1.domain.Server; import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; import org.jclouds.openstack.nova.v1_1.options.RebuildServerOptions; diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java index 3bf7b82e6d..1bdeb2c8f0 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/ParseImageIdFromLocationHeader.java @@ -18,23 +18,15 @@ */ package org.jclouds.openstack.nova.v1_1.functions; -import static com.google.common.base.Preconditions.checkNotNull; -import java.net.URI; - -import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import javax.ws.rs.core.HttpHeaders; import org.jclouds.openstack.nova.v1_1.domain.Image; -import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; -import org.jclouds.http.functions.ParseJson; import com.google.common.base.Function; -import com.google.common.collect.ImmutableMultimap; /** From c8939ac0da29aa8ba8b45199b855ca41d7c8efbc Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Wed, 4 Apr 2012 16:09:34 -0700 Subject: [PATCH 3/4] add tests for createImage, switch argument order. --- .../nova/v1_1/features/ServerAsyncClient.java | 2 +- .../nova/v1_1/features/ServerClient.java | 6 +- .../v1_1/features/ServerClientExpectTest.java | 66 +++++++++++++++++-- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java index 62ef9e4181..2640f7439e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java @@ -196,6 +196,6 @@ public interface ServerAsyncClient { @Payload("%7B\"createImage\":%7B\"name\":\"{name}\", \"metadata\": %7B%7D%7D%7D") @ExceptionParser(MapHttp4xxCodesToExceptions.class) @ResponseParser(ParseImageIdFromLocationHeader.class) - ListenableFuture createImageFromServer(@PathParam("id") String id, @PayloadParam("name") String name); + ListenableFuture createImageFromServer(@PayloadParam("name") String name, @PathParam("id") String id); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java index 9835affeb8..51003d3340 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java @@ -160,13 +160,13 @@ public interface ServerClient { /** * Create an image from a server. * - * @param id - * id of the server * @param name * The name of the new image + * @param id + * id of the server * * @return ID of the new / updated image */ - String createImageFromServer(String id, String name); + String createImageFromServer(String name, String id); } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java index c59690c0d9..7c0edb65e6 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java @@ -20,6 +20,7 @@ package org.jclouds.openstack.nova.v1_1.features; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; import java.net.URI; @@ -37,7 +38,7 @@ import com.google.common.collect.ImmutableSet; /** * Tests annotation parsing of {@code ServerAsyncClient} - * + * * @author Adrian Cole */ @Test(groups = "unit", testName = "ServerAsyncClientTest") @@ -80,7 +81,7 @@ public class ServerClientExpectTest extends BaseNovaClientExpectTest { assertTrue(clientWhenNoServersExist.getServerClientForZone("az-1.region-a.geo-1").listServers().isEmpty()); } - + public void testCreateServerWhenResponseIs202() throws Exception { HttpRequest createServer = HttpRequest .builder() @@ -93,7 +94,7 @@ public class ServerClientExpectTest extends BaseNovaClientExpectTest { "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\"}}","application/json")) .build(); - + HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") .payload(payloadFromResourceWithContentType("/new_server.json","application/json; charset=UTF-8")).build(); @@ -117,7 +118,7 @@ public class ServerClientExpectTest extends BaseNovaClientExpectTest { "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"security_groups\":[{\"name\":\"group2\"},{\"name\":\"group1\"}]}}","application/json")) .build(); - + HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") .payload(payloadFromResourceWithContentType("/new_server.json","application/json; charset=UTF-8")).build(); @@ -130,4 +131,61 @@ public class ServerClientExpectTest extends BaseNovaClientExpectTest { new ParseCreatedServerTest().expected().toString()); } + public void testCreateImageWhenResponseIs2xx() throws Exception { + String serverId = "123"; + String imageId = "456"; + String imageName = "foo"; + + HttpRequest createImage = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://compute.north.host/v1.1/3456/servers/" + serverId + "/action")) + .headers( + ImmutableMultimap. builder().put("Accept", "application/json") + .put("X-Auth-Token", authToken).build()) + .payload(payloadFromStringWithContentType( + "{\"createImage\":{\"name\":\"" + imageName + "\", \"metadata\": {}}}", "application/json")) + .build(); + + HttpResponse createImageResponse = HttpResponse.builder() + .statusCode(200) + .headers( + ImmutableMultimap. builder() + .put("Location", "https://compute.north.host/v1.1/3456/images/" + imageId).build()).build(); + + NovaClient clientWhenServerExists = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, createImage, createImageResponse); + + assertEquals(clientWhenServerExists.getServerClientForZone("az-1.region-a.geo-1").createImageFromServer(imageName, serverId), + imageId); + } + + public void testCreateImageWhenResponseIs404IsEmpty() throws Exception { + String serverId = "123"; + String imageName = "foo"; + + HttpRequest createImage = HttpRequest + .builder() + .method("POST") + .endpoint(URI.create("https://compute.north.host/v1.1/3456/servers/" + serverId + "/action")) + .headers( + ImmutableMultimap. builder().put("Accept", "application/json") + .put("X-Auth-Token", authToken) + .put("Content-Type", "application/json").build()) + .payload(payloadFromStringWithContentType( + "{\"createImage\":{\"name\": " + imageName + ", \"metadata\": {}}}", "application/json")) + .build(); + + HttpResponse createImageResponse = HttpResponse.builder().statusCode(404).build(); + NovaClient clientWhenServerDoesNotExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, createImage, createImageResponse); + + try { + clientWhenServerDoesNotExist.getServerClientForZone("az-1.region-a.geo-1").createImageFromServer(imageName, serverId); + fail("Expected an exception."); + } catch (Exception e) { + ; + } + } + } From 59aa198fa70897d5ec010d041a1e5f5ee61b04eb Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Wed, 4 Apr 2012 16:27:22 -0700 Subject: [PATCH 4/4] fix (unused) createImage http body in 404 test. --- .../openstack/nova/v1_1/features/ServerClientExpectTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java index 7c0edb65e6..35de130921 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java @@ -173,7 +173,7 @@ public class ServerClientExpectTest extends BaseNovaClientExpectTest { .put("X-Auth-Token", authToken) .put("Content-Type", "application/json").build()) .payload(payloadFromStringWithContentType( - "{\"createImage\":{\"name\": " + imageName + ", \"metadata\": {}}}", "application/json")) + "{\"createImage\":{\"name\":\"" + imageName + "\", \"metadata\": {}}}", "application/json")) .build(); HttpResponse createImageResponse = HttpResponse.builder().statusCode(404).build();