diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java index 727a82bbb5..d9c6dfe28f 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java @@ -43,12 +43,10 @@ import org.jclouds.concurrent.Futures; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.predicates.PredicateWithResult; import org.jclouds.predicates.Retryables; -import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; @@ -61,21 +59,22 @@ public class NovaImageExtension implements ImageExtension { protected Logger logger = Logger.NULL; private final NovaClient novaClient; - private final Function imageInZoneToImage; private final ExecutorService executor; @com.google.inject.Inject(optional = true) @Named("IMAGE_MAX_WAIT") - long maxWait = 3600; + private long maxWait = 3600; @com.google.inject.Inject(optional = true) @Named("IMAGE_WAIT_PERIOD") - long waitPeriod = 1; + private long waitPeriod = 1; + private PredicateWithResult imageReadyPredicate; @Inject - public NovaImageExtension(NovaClient novaClient, Function imageInZoneToImage, - @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads) { + public NovaImageExtension(NovaClient novaClient, + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads, + PredicateWithResult imageReadyPredicate) { this.novaClient = checkNotNull(novaClient); - this.imageInZoneToImage = imageInZoneToImage; this.executor = userThreads; + this.imageReadyPredicate = imageReadyPredicate; } @Override @@ -93,48 +92,21 @@ public class NovaImageExtension implements ImageExtension { checkState(template instanceof CloneImageTemplate, " openstack-nova only supports creating images through cloning."); CloneImageTemplate cloneTemplate = (CloneImageTemplate) template; - final ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId()); + ZoneAndId sourceImageZoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId()); + + String newImageId = novaClient.getServerClientForZone(sourceImageZoneAndId.getZone()).createImageFromServer( + cloneTemplate.getName(), sourceImageZoneAndId.getId()); + + final ZoneAndId targetImageZoneAndId = ZoneAndId.fromZoneAndId(sourceImageZoneAndId.getZone(), newImageId); - final String newImageId = novaClient.getServerClientForZone(zoneAndId.getZone()).createImageFromServer( - cloneTemplate.getName(), zoneAndId.getId()); logger.info(">> Registered new Image %s, waiting for it to become available.", newImageId); return Futures.makeListenable(executor.submit(new Callable() { @Override public Image call() throws Exception { - return Retryables.retryGettingResultOrFailing(new PredicateWithResult() { - - org.jclouds.openstack.nova.v1_1.domain.Image result; - RuntimeException lastFailure; - - @Override - public boolean apply(String input) { - result = checkNotNull(findImage(ZoneAndId.fromZoneAndId(zoneAndId.getZone(), newImageId))); - switch (result.getStatus()) { - case ACTIVE: - logger.info("<< Image %s is available for use.", newImageId); - return true; - case UNKNOWN: - case SAVING: - logger.debug("<< Image %s is not available yet.", newImageId); - return false; - default: - lastFailure = new IllegalStateException("Image was not created: " + newImageId); - throw lastFailure; - } - } - - @Override - public Image getResult() { - return imageInZoneToImage.apply(new ImageInZone(result, zoneAndId.getZone())); - } - - @Override - public Throwable getLastFailure() { - return lastFailure; - } - }, newImageId, maxWait, waitPeriod, TimeUnit.SECONDS, - "Image was not created within the time limit, Giving up! [Limit: " + maxWait + " secs.]"); + return Retryables.retryGettingResultOrFailing(imageReadyPredicate, targetImageZoneAndId, maxWait, + waitPeriod, TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: " + + maxWait + " secs.]"); } }), executor); } @@ -150,7 +122,7 @@ public class NovaImageExtension implements ImageExtension { return true; } - private org.jclouds.openstack.nova.v1_1.domain.Image findImage(final ZoneAndId zoneAndId) { + public static org.jclouds.openstack.nova.v1_1.domain.Image findImage(NovaClient novaClient, final ZoneAndId zoneAndId) { return Iterables.tryFind(novaClient.getImageClientForZone(zoneAndId.getZone()).listImagesInDetail(), new Predicate() { @Override diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java index 2ceaa9db49..ed35f3d96e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java @@ -58,6 +58,7 @@ import org.jclouds.openstack.nova.v1_1.compute.loaders.CreateUniqueKeyPair; import org.jclouds.openstack.nova.v1_1.compute.loaders.FindSecurityGroupOrCreate; import org.jclouds.openstack.nova.v1_1.compute.loaders.LoadFloatingIpsForInstance; import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; +import org.jclouds.openstack.nova.v1_1.compute.predicates.GetImageWhenImageInZoneHasActiveStatusPredicateWithResult; import org.jclouds.openstack.nova.v1_1.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet; import org.jclouds.openstack.nova.v1_1.domain.KeyPair; import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone; @@ -68,6 +69,7 @@ import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v1_1.predicates.FindSecurityGroupWithNameAndReturnTrue; +import org.jclouds.predicates.PredicateWithResult; import org.jclouds.predicates.RetryablePredicate; import com.google.common.base.Function; @@ -141,6 +143,9 @@ public class NovaComputeServiceContextModule extends bind(new TypeLiteral() { }).to(NovaImageExtension.class); + + bind(new TypeLiteral>() { + }).to(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class); } @Override diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.java new file mode 100644 index 0000000000..2ca35ad10d --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.java @@ -0,0 +1,89 @@ +/** + * 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.compute.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.logging.Logger; +import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.compute.NovaImageExtension; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; +import org.jclouds.predicates.PredicateWithResult; + +import com.google.common.base.Function; + +/** + * @author David Alves + */ +public final class GetImageWhenImageInZoneHasActiveStatusPredicateWithResult implements + PredicateWithResult { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private org.jclouds.openstack.nova.v1_1.domain.Image result; + private ZoneAndId resultZoneAndId; + private RuntimeException lastFailure; + private Function imageInZoneToImage; + private NovaClient client; + + @Inject + public GetImageWhenImageInZoneHasActiveStatusPredicateWithResult(Function imageInZoneToImage, + NovaClient client) { + this.imageInZoneToImage = imageInZoneToImage; + this.client = client; + } + + @Override + public boolean apply(ZoneAndId input) { + result = checkNotNull(NovaImageExtension.findImage(client, + ZoneAndId.fromZoneAndId(input.getZone(), input.getId()))); + resultZoneAndId = input; + switch (result.getStatus()) { + case ACTIVE: + logger.info("<< Image %s is available for use.", input.getId()); + return true; + case SAVING: + logger.debug("<< Image %s is not available yet.", input.getId()); + return false; + default: + lastFailure = new IllegalStateException("Image was not created: " + input.getId()); + throw lastFailure; + } + } + + @Override + public Image getResult() { + return imageInZoneToImage.apply(new ImageInZone(result, resultZoneAndId.getZone())); + } + + @Override + public Throwable getLastFailure() { + return lastFailure; + } +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest.java new file mode 100644 index 0000000000..a72be63ce4 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/predicates/GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest.java @@ -0,0 +1,70 @@ +package org.jclouds.openstack.nova.v1_1.compute.predicates; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import java.util.Map; + +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.Image; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaComputeServiceContextExpectTest; +import org.jclouds.predicates.PredicateWithResult; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Injector; + +@Test(groups = "unit", testName = "GetImageWhenImageInZoneHasActiveStatucPredicateWithResultExpectTest") +public class GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest extends + BaseNovaComputeServiceContextExpectTest { + + private final HttpResponse listImagesDetailImageExtensionResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/image_list_detail_imageextension.json")).build(); + + private Map requestResponseMap = ImmutableMap. builder() + .put(keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess) + .put(listImagesDetail, listImagesDetailImageExtensionResponse).build(); + + public void testReturnsFalseOnImageStatusSavingAndTrueOnActive() { + Injector injector = requestsSendResponses(requestResponseMap); + PredicateWithResult predicate = injector + .getInstance(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class); + ZoneAndId zoneAdnId0 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "13"); + ZoneAndId zoneAdnId1 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "12"); + assertTrue(!predicate.apply(zoneAdnId1)); + assertTrue(predicate.apply(zoneAdnId0)); + assertEquals("natty-server-cloudimg-amd64", predicate.getResult().getName()); + } + + public void testFailsOnOtherStatuses() { + Injector injector = requestsSendResponses(requestResponseMap); + PredicateWithResult predicate = injector + .getInstance(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class); + ZoneAndId zoneAdnId0 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "15"); + ZoneAndId zoneAdnId1 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "14"); + ZoneAndId zoneAdnId2 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "11"); + ZoneAndId zoneAdnId3 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "10"); + assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId0)); + assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId1)); + assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId2)); + assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId3)); + } + + private boolean illegalStateExceptionThrown(PredicateWithResult predicate, ZoneAndId zoneAndId) { + try { + predicate.apply(zoneAndId); + } catch (IllegalStateException e) { + return true; + } + return false; + } + + @Override + public Injector apply(ComputeServiceContext input) { + return input.utils().injector(); + } + +} diff --git a/apis/openstack-nova/src/test/resources/image_list_detail_imageextension.json b/apis/openstack-nova/src/test/resources/image_list_detail_imageextension.json new file mode 100644 index 0000000000..d1112f3181 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/image_list_detail_imageextension.json @@ -0,0 +1,121 @@ +{ + "images": [{ + "status": "UNRECOGNIZED", + "updated": "2012-02-02T19:11:00Z", + "name": "oneiric-server-cloudimg-amd64", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/15", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/15", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:52Z", + "progress": 100, + "id": "15", + "metadata": { + "kernel_id": "14", + "min_disk": 0, + "min_ram": 0, + "owner": "1" + } + }, { + "status": "UNKNOWN", + "updated": "2012-02-02T19:10:51Z", + "name": "oneiric-server-cloudimg-amd64-kernel", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/14", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/14", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:50Z", + "progress": 100, + "id": "14", + "metadata": { + "min_disk": 0, + "owner": "1", + "min_ram": 0 + } + }, { + "status": "ACTIVE", + "updated": "2012-02-02T19:10:41Z", + "name": "natty-server-cloudimg-amd64", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/13", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/13", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:33Z", + "progress": 100, + "id": "13", + "metadata": { + "kernel_id": "12", + "min_disk": 0, + "min_ram": 0, + "owner": "1" + } + }, { + "status": "SAVING", + "updated": "2012-02-02T19:10:33Z", + "name": "natty-server-cloudimg-amd64-kernel", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/12", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/12", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:32Z", + "progress": 100, + "id": "12", + "metadata": { + "min_disk": 0, + "owner": "1", + "min_ram": 0 + } + }, { + "status": "ERROR", + "updated": "2012-02-02T19:10:41Z", + "name": "natty-server-cloudimg-amd64", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/11", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/11", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:33Z", + "progress": 100, + "id": "11", + "metadata": { + "kernel_id": "12", + "min_disk": 0, + "min_ram": 0, + "owner": "1" + } + }, { + "status": "ERROR", + "updated": "2012-02-02T19:10:41Z", + "name": "natty-server-cloudimg-amd64", + "links": [{ + "href": "https://nova-api.trystack.org:9774/v1.1/37/images/10", + "rel": "self" + }, { + "href": "https://nova-api.trystack.org:9774/37/images/10", + "rel": "bookmark" + }], + "created": "2012-02-02T19:10:33Z", + "progress": 100, + "id": "10", + "metadata": { + "kernel_id": "12", + "min_disk": 0, + "min_ram": 0, + "owner": "1" + } + }] +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/predicates/PredicateWithResult.java b/core/src/main/java/org/jclouds/predicates/PredicateWithResult.java index 41ce34b1bb..a424b3a991 100644 --- a/core/src/main/java/org/jclouds/predicates/PredicateWithResult.java +++ b/core/src/main/java/org/jclouds/predicates/PredicateWithResult.java @@ -22,10 +22,10 @@ import com.google.common.annotations.Beta; import com.google.common.base.Predicate; @Beta -public interface PredicateWithResult extends Predicate { - +public interface PredicateWithResult extends Predicate { + Result getResult(); - + Throwable getLastFailure(); - + }