Merge pull request #603 from dralves/image-extension-all

image extension working on hpcloud, virtualbox and aws-ec2, cloudservers...
This commit is contained in:
Adrian Cole 2012-05-10 16:43:27 -07:00
commit a6cc00e078
33 changed files with 1780 additions and 88 deletions

View File

@ -0,0 +1,120 @@
/**
* 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.cloudservers.compute;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.cloudservers.CloudServersClient;
import org.jclouds.cloudservers.domain.Server;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.CloneImageTemplate;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import org.jclouds.compute.domain.ImageTemplateBuilder;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.concurrent.Futures;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.PredicateWithResult;
import org.jclouds.predicates.Retryables;
import com.google.common.util.concurrent.ListenableFuture;
/**
* CloudServers implementation of {@link ImageExtension}
*
* @author David Alves
*
*/
@Singleton
public class CloudServersImageExtension implements ImageExtension {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final CloudServersClient client;
private final ExecutorService executor;
private final PredicateWithResult<Integer, Image> imageAvailablePredicate;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_MAX_WAIT")
private long maxWait = 3600;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_WAIT_PERIOD")
private long waitPeriod = 1;
@Inject
public CloudServersImageExtension(CloudServersClient client,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
PredicateWithResult<Integer, Image> imageAvailablePredicate) {
this.client = checkNotNull(client);
this.executor = userThreads;
this.imageAvailablePredicate = imageAvailablePredicate;
}
@Override
public ImageTemplate buildImageTemplateFromNode(String name, final String id) {
Server server = client.getServer(Integer.parseInt(id));
if (server == null)
throw new NoSuchElementException("Cannot find server with id: " + id);
CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
return template;
}
@Override
public ListenableFuture<Image> createImage(ImageTemplate template) {
checkState(template instanceof CloneImageTemplate,
" openstack-nova only supports creating images through cloning.");
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
final org.jclouds.cloudservers.domain.Image image = client.createImageFromServer(cloneTemplate.getName(),
Integer.parseInt(cloneTemplate.getSourceNodeId()));
return Futures.makeListenable(executor.submit(new Callable<Image>() {
@Override
public Image call() throws Exception {
return Retryables.retryGettingResultOrFailing(imageAvailablePredicate, image.getId(), maxWait, waitPeriod,
TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: " + maxWait
+ " secs.]");
}
}), executor);
}
@Override
public boolean deleteImage(String id) {
try {
this.client.deleteImage(Integer.parseInt(id));
} catch (Exception e) {
return false;
}
return true;
}
}

View File

@ -22,15 +22,18 @@ import java.util.Map;
import javax.inject.Singleton;
import org.jclouds.cloudservers.compute.CloudServersImageExtension;
import org.jclouds.cloudservers.compute.functions.CloudServersImageToImage;
import org.jclouds.cloudservers.compute.functions.CloudServersImageToOperatingSystem;
import org.jclouds.cloudservers.compute.functions.FlavorToHardware;
import org.jclouds.cloudservers.compute.functions.ServerToNodeMetadata;
import org.jclouds.cloudservers.compute.predicates.GetImageWhenStatusActivePredicateWithResult;
import org.jclouds.cloudservers.compute.strategy.CloudServersComputeServiceAdapter;
import org.jclouds.cloudservers.domain.Flavor;
import org.jclouds.cloudservers.domain.Server;
import org.jclouds.cloudservers.domain.ServerStatus;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
@ -40,10 +43,13 @@ import org.jclouds.compute.domain.OperatingSystem;
import org.jclouds.compute.internal.BaseComputeService;
import org.jclouds.domain.Location;
import org.jclouds.functions.IdentityFunction;
import org.jclouds.predicates.PredicateWithResult;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
@ -77,6 +83,12 @@ public class CloudServersComputeServiceContextModule extends
// we aren't converting location from a provider-specific type
bind(new TypeLiteral<Function<Location, Location>>() {
}).to((Class) IdentityFunction.class);
bind(new TypeLiteral<ImageExtension>() {
}).to(CloudServersImageExtension.class);
bind(new TypeLiteral<PredicateWithResult<Integer, Image>>() {
}).to(GetImageWhenStatusActivePredicateWithResult.class);
}
@ -112,6 +124,10 @@ public class CloudServersComputeServiceContextModule extends
Map<ServerStatus, NodeState> provideServerToNodeState() {
return serverToNodeState;
}
@Override
protected Optional<ImageExtension> provideImageExtension(Injector i) {
return Optional.of(i.getInstance(ImageExtension.class));
}
}

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.cloudservers.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.cloudservers.CloudServersClient;
import org.jclouds.cloudservers.options.ListOptions;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.PredicateWithResult;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
/**
*
* @author David Alves
*
*/
public final class GetImageWhenStatusActivePredicateWithResult implements PredicateWithResult<Integer, Image> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final CloudServersClient client;
private final Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage;
private org.jclouds.cloudservers.domain.Image result;
private RuntimeException lastFailure;
@Inject
public GetImageWhenStatusActivePredicateWithResult(CloudServersClient client,
Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage) {
this.client = client;
this.cloudserversImageToImage = cloudserversImageToImage;
}
@Override
public boolean apply(Integer input) {
result = checkNotNull(findImage(input));
switch (result.getStatus()) {
case ACTIVE:
logger.info("<< Image %s is available for use. %s", input, result);
return true;
case QUEUED:
case PREPARING:
case SAVING:
logger.debug("<< Image %s is not available yet. %s", input, result);
return false;
default:
lastFailure = new IllegalStateException("Image " + input + " was not created. " + result);
throw lastFailure;
}
}
@Override
public Image getResult() {
return cloudserversImageToImage.apply(result);
}
@Override
public Throwable getLastFailure() {
return lastFailure;
}
private org.jclouds.cloudservers.domain.Image findImage(final int id) {
return Iterables.tryFind(client.listImages(new ListOptions().withDetails()),
new Predicate<org.jclouds.cloudservers.domain.Image>() {
@Override
public boolean apply(org.jclouds.cloudservers.domain.Image input) {
return input.getId() == id;
}
}).orNull();
}
}

View File

@ -138,8 +138,8 @@ public class Image {
@Override
public String toString() {
return "Image [created=" + created + ", id=" + id + ", name=" + name + ", serverId="
+ serverId + "]";
return "Image [created=" + created + ", id=" + id + ", name=" + name + ", serverId=" + serverId + ", status="
+ status + "]";
}
}

View File

@ -0,0 +1,47 @@
/**
* 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.cloudservers.compute;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.internal.BaseImageExtensionLiveTest;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.inject.Module;
/**
* Live test for cloudservers {@link ImageExtension} implementation
*
* @author David Alves
*
*/
@Test(groups = "live", singleThreaded = true, testName = "CloudServersImageExtensionLiveTest")
public class CloudServersImageExtensionLiveTest extends BaseImageExtensionLiveTest {
public CloudServersImageExtensionLiveTest() {
provider = "cloudservers";
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
}

View File

@ -0,0 +1,96 @@
/**
* 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.cloudservers.compute.predicates;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import java.net.URI;
import java.util.Map;
import org.jclouds.cloudservers.internal.BaseCloudServersComputeServiceExpectTest;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.Image;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.predicates.PredicateWithResult;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.net.HttpHeaders;
import com.google.inject.Injector;
/**
*
* @author David Alves
*
*/
@Test(groups = "unit", testName = "GetImageWhenStatusActivePredicateWithResultExpectTest")
public class GetImageWhenStatusActivePredicateWithResultExpectTest extends
BaseCloudServersComputeServiceExpectTest<Injector> {
private final HttpRequest listImagesDetail = HttpRequest
.builder()
.method("GET")
.endpoint(
URI.create("https://lon.servers.api.rackspacecloud.com/v1.0/10001786/images/detail?format=json&now=1257695648897"))
.headers(ImmutableMultimap.<String, String> builder().put(HttpHeaders.ACCEPT, "application/json")
.put("X-Auth-Token", authToken).build()).build();
private final HttpResponse listImagesResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/test_list_images_detail_imageextension.json")).build();
private final Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(listImagesDetail, listImagesResponse).put(initialAuth, responseWithAuth).build();
public void testReturnsFalseOnQueuedAndSavingAndTrueOnActive() {
Injector injector = requestsSendResponses(requestResponseMap);
PredicateWithResult<Integer, Image> predicate = injector
.getInstance(GetImageWhenStatusActivePredicateWithResult.class);
assertTrue(predicate.apply(2));
assertFalse(predicate.apply(743));
assertFalse(predicate.apply(744));
}
public void testFailsOnOtherStatuses() {
Injector injector = requestsSendResponses(requestResponseMap);
PredicateWithResult<Integer, Image> predicate = injector
.getInstance(GetImageWhenStatusActivePredicateWithResult.class);
assertTrue(illegalStateExceptionThrown(predicate, 745));
assertTrue(illegalStateExceptionThrown(predicate, 746));
assertTrue(illegalStateExceptionThrown(predicate, 747));
assertTrue(illegalStateExceptionThrown(predicate, 748));
}
private boolean illegalStateExceptionThrown(PredicateWithResult<Integer, Image> predicate, Integer id) {
try {
predicate.apply(id);
} catch (IllegalStateException e) {
return true;
}
return false;
}
@Override
public Injector apply(ComputeServiceContext input) {
return input.utils().injector();
}
}

View File

@ -0,0 +1,103 @@
/**
* 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.cloudservers.internal;
import static org.jclouds.location.reference.LocationConstants.PROPERTY_REGIONS;
import java.util.Date;
import java.util.Properties;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.cloudservers.CloudServersApiMetadata;
import org.jclouds.cloudservers.config.CloudServersRestClientModule;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.openstack.keystone.v1_1.config.AuthenticationServiceModule;
import org.jclouds.openstack.keystone.v1_1.internal.BaseKeystoneRestClientExpectTest;
import org.jclouds.rest.ConfiguresRestClient;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.inject.Module;
/**
*
* @author David Alves
*
*/
public abstract class BaseCloudServersComputeServiceExpectTest<T> extends BaseKeystoneRestClientExpectTest<T> implements
Function<ComputeServiceContext, T> {
public BaseCloudServersComputeServiceExpectTest() {
provider = "cloudservers";
}
protected static final String CONSTANT_DATE = "2009-11-08T15:54:08.897Z";
public static class TestAuthenticationServiceModule extends AuthenticationServiceModule {
@Override
protected void configure() {
super.configure();
}
}
@Override
protected Module createModule() {
return new TestCloudServersRestClientModule();
}
@ConfiguresRestClient
protected static class TestCloudServersRestClientModule extends CloudServersRestClientModule {
@Override
public Supplier<Date> provideCacheBusterDate() {
return new Supplier<Date>() {
public Date get() {
return new SimpleDateFormatDateService().iso8601DateParse(CONSTANT_DATE);
}
};
}
}
@Override
protected ApiMetadata createApiMetadata() {
return new CloudServersApiMetadata();
}
@Override
protected Properties setupProperties() {
Properties overrides = new Properties();
overrides.setProperty(PROPERTY_REGIONS, "US");
overrides.setProperty(provider + ".endpoint", endpoint);
return overrides;
}
@Override
public T createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
return apply(createComputeServiceContext(fn, module, props));
}
private ComputeServiceContext createComputeServiceContext(Function<HttpRequest, HttpResponse> fn, Module module,
Properties props) {
return createInjector(fn, module, props).getInstance(ComputeServiceContext.class);
}
}

View File

@ -0,0 +1,65 @@
{
"images" : [
{
"id" : 2,
"name" : "CentOS 5.2",
"updated" : "2010-10-10T12:00:00Z",
"created" : "2010-08-10T12:00:00Z",
"status" : "ACTIVE"
},
{
"id" : 743,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "SAVING",
"progress" : 80
}
,
{
"id" : 744,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "QUEUED"
}
,
{
"id" : 745,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "UNRECOGNIZED"
}
,
{
"id" : 746,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "UNKNOWN"
}
,
{
"id" : 747,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "PREPARING"
}
,
{
"id" : 748,
"name" : "My Server Backup",
"serverId" : 12,
"updated" : "2010-10-10T12:00:00Z",
"created" : "2009-07-07T09:56:16-05:00",
"status" : "FAILED"
}
]
}

View File

@ -0,0 +1,131 @@
/**
* 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.ec2.compute;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.Constants;
import org.jclouds.aws.util.AWSUtils;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.CloneImageTemplate;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import org.jclouds.compute.domain.ImageTemplateBuilder;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.concurrent.Futures;
import org.jclouds.ec2.EC2Client;
import org.jclouds.ec2.domain.Reservation;
import org.jclouds.ec2.domain.RunningInstance;
import org.jclouds.ec2.options.CreateImageOptions;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.PredicateWithResult;
import org.jclouds.predicates.Retryables;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
/**
* EC2 implementation of {@link ImageExtension} please note that {@link #createImage(ImageTemplate)}
* only works by cloning EBS backed instances for the moment.
*
* @author David Alves
*
*/
public class EC2ImageExtension implements ImageExtension {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final EC2Client ec2Client;
private final ExecutorService executor;
private final PredicateWithResult<String, Image> imageReadyPredicate;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_MAX_WAIT")
private long maxWait = 3600;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_WAIT_PERIOD")
private long waitPeriod = 1;
@Inject
public EC2ImageExtension(EC2Client ec2Client, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
PredicateWithResult<String, Image> imageReadyPredicate) {
this.ec2Client = checkNotNull(ec2Client);
this.executor = checkNotNull(userThreads);
this.imageReadyPredicate = imageReadyPredicate;
}
@Override
public ImageTemplate buildImageTemplateFromNode(String name, String id) {
String[] parts = AWSUtils.parseHandle(id);
String region = parts[0];
String instanceId = parts[1];
Reservation<? extends RunningInstance> instance = Iterables.getOnlyElement(ec2Client.getInstanceServices()
.describeInstancesInRegion(region, instanceId));
if (instance == null)
throw new NoSuchElementException("Cannot find server with id: " + id);
CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
return template;
}
@Override
public ListenableFuture<Image> createImage(ImageTemplate template) {
checkState(template instanceof CloneImageTemplate, " ec2 only supports creating images through cloning.");
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
String[] parts = AWSUtils.parseHandle(cloneTemplate.getSourceNodeId());
final String region = parts[0];
String instanceId = parts[1];
final String imageId = ec2Client.getAMIServices().createImageInRegion(region, cloneTemplate.getName(),
instanceId, CreateImageOptions.NONE);
return Futures.makeListenable(executor.submit(new Callable<Image>() {
@Override
public Image call() throws Exception {
return Retryables.retryGettingResultOrFailing(imageReadyPredicate, region + "/" + imageId, maxWait,
waitPeriod, TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: "
+ maxWait + " secs.]");
}
}), executor);
}
@Override
public boolean deleteImage(String id) {
String[] parts = AWSUtils.parseHandle(id);
String region = parts[0];
String instanceId = parts[1];
try {
ec2Client.getAMIServices().deregisterImageInRegion(region, instanceId);
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@ -30,16 +30,19 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.config.BaseComputeServiceContextModule;
import org.jclouds.compute.domain.Image;
import org.jclouds.concurrent.RetryOnTimeOutExceptionSupplier;
import org.jclouds.ec2.compute.EC2ComputeService;
import org.jclouds.ec2.compute.EC2ImageExtension;
import org.jclouds.ec2.compute.domain.RegionAndName;
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@ -136,6 +139,11 @@ public class EC2ComputeServiceContextModule extends BaseComputeServiceContextMod
return new String[] {};
return toArray(Splitter.on(',').split(amiOwners), String.class);
}
@Override
protected Optional<ImageExtension> provideImageExtension(Injector i) {
return Optional.of(i.getInstance(ImageExtension.class));
}
}

View File

@ -29,6 +29,7 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;
@ -36,6 +37,7 @@ import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.domain.Credentials;
import org.jclouds.ec2.compute.EC2ComputeService;
import org.jclouds.ec2.compute.EC2ImageExtension;
import org.jclouds.ec2.compute.domain.RegionAndName;
import org.jclouds.ec2.compute.functions.AddElasticIpsToNodemetadata;
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
@ -47,11 +49,13 @@ import org.jclouds.ec2.compute.loaders.CreateSecurityGroupIfNeeded;
import org.jclouds.ec2.compute.loaders.LoadPublicIpForInstanceOrNull;
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
import org.jclouds.ec2.compute.options.EC2TemplateOptions;
import org.jclouds.ec2.compute.predicates.GetImageWhenStatusAvailablePredicateWithResult;
import org.jclouds.ec2.compute.predicates.SecurityGroupPresent;
import org.jclouds.ec2.domain.InstanceState;
import org.jclouds.ec2.domain.KeyPair;
import org.jclouds.ec2.domain.RunningInstance;
import org.jclouds.ec2.reference.EC2Constants;
import org.jclouds.predicates.PredicateWithResult;
import org.jclouds.predicates.RetryablePredicate;
import com.google.common.base.Function;
@ -104,6 +108,10 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
}).annotatedWith(Names.named("ELASTICIP")).to(LoadPublicIpForInstanceOrNull.class);
bind(WindowsLoginCredentialsFromEncryptedData.class);
bind(new TypeLiteral<ImageExtension>() {
}).to(EC2ImageExtension.class);
bind(new TypeLiteral<PredicateWithResult<String, Image>>() {
}).to(GetImageWhenStatusAvailablePredicateWithResult.class);
}
/**

View File

@ -0,0 +1,94 @@
/**
* 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.ec2.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.aws.util.AWSUtils;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.ec2.EC2Client;
import org.jclouds.ec2.compute.functions.EC2ImageParser;
import org.jclouds.ec2.options.DescribeImagesOptions;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.PredicateWithResult;
import com.google.common.collect.Iterables;
/**
* §
* @author David Alves
*
*/
public final class GetImageWhenStatusAvailablePredicateWithResult implements PredicateWithResult<String, Image> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final EC2Client ec2Client;
private final EC2ImageParser ec2ImageToImage;
private org.jclouds.ec2.domain.Image result;
private RuntimeException lastFailure;
@Inject
public GetImageWhenStatusAvailablePredicateWithResult(EC2Client ec2Client, EC2ImageParser ec2ImageToImage) {
this.ec2Client = ec2Client;
this.ec2ImageToImage = ec2ImageToImage;
}
@Override
public boolean apply(String input) {
String[] parts = AWSUtils.parseHandle(input);
String region = parts[0];
String imageId = parts[1];
result = checkNotNull(findImage(imageId, region));
switch (result.getImageState()) {
case AVAILABLE:
logger.info("<< Image %s is available for use.", input);
return true;
case UNRECOGNIZED:
logger.debug("<< Image %s is not available yet.", input);
return false;
default:
lastFailure = new IllegalStateException("Image was not created: " + input);
throw lastFailure;
}
}
@Override
public Image getResult() {
return ec2ImageToImage.apply(result);
}
@Override
public Throwable getLastFailure() {
return lastFailure;
}
private org.jclouds.ec2.domain.Image findImage(String id, String region) {
return Iterables.getOnlyElement(ec2Client.getAMIServices().describeImagesInRegion(region,
new DescribeImagesOptions().imageIds(id)));
}
}

View File

@ -132,13 +132,23 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen
@Override
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
// ensure we don't mutate the input template
template = templateBuilderProvider.get().fromTemplate(template).build();
Template mutableTemplate;
// ensure we don't mutate the input template, fromTemplate ignores imageId so
// build directly from imageId if we have it
if (template.getImage() != null && template.getImage().getId() != null) {
mutableTemplate = templateBuilderProvider.get().imageId(template.getImage().getId()).fromTemplate(template)
.build();
// otherwise build from generic parameters
} else {
mutableTemplate = templateBuilderProvider.get().fromTemplate(template).build();
}
Iterable<String> ips = allocateElasticIpsInRegion(count, template);
Iterable<? extends RunningInstance> started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group,
count, template);
count, mutableTemplate);
Iterable<RegionAndName> ids = transform(started, instanceToRegionAndName);
@ -152,7 +162,7 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen
assignElasticIpsToInstances(ips, started);
return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(template.getOptions(), transform(started,
return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(mutableTemplate.getOptions(), transform(started,
runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses);
}

View File

@ -0,0 +1,88 @@
/**
* 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.ec2.compute;
import java.util.Properties;
import javax.inject.Named;
import org.jclouds.Constants;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.date.DateService;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.ec2.EC2ApiMetadata;
import org.jclouds.ec2.EC2AsyncClient;
import org.jclouds.ec2.EC2Client;
import org.jclouds.ec2.config.EC2RestClientModule;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.internal.BaseRestClientExpectTest;
import com.google.common.base.Function;
import com.google.inject.Module;
import com.google.inject.Provides;
/**
*
* @author David Alves
*/
public abstract class BaseEC2ComputeServiceExpectTest<T> extends BaseRestClientExpectTest<T> implements
Function<ComputeServiceContext, T> {
protected static final String CONSTANT_DATE = "2012-04-16T15:54:08.897Z";
protected DateService dateService = new SimpleDateFormatDateService();
public BaseEC2ComputeServiceExpectTest() {
provider = "ec2";
}
@ConfiguresRestClient
private static final class TestEC2RestClientModule extends EC2RestClientModule<EC2Client, EC2AsyncClient> {
@Override
@Provides
protected String provideTimeStamp(final DateService dateService,
@Named(Constants.PROPERTY_SESSION_INTERVAL) final int expiration) {
return CONSTANT_DATE;
}
}
@Override
public T createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
return apply(createComputeServiceContext(fn, module, props));
}
private ComputeServiceContext createComputeServiceContext(Function<HttpRequest, HttpResponse> fn, Module module,
Properties props) {
return createInjector(fn, module, props).getInstance(ComputeServiceContext.class);
}
@Override
protected Module createModule() {
return new TestEC2RestClientModule();
}
@Override
protected ApiMetadata createApiMetadata() {
return new EC2ApiMetadata();
}
}

View File

@ -0,0 +1,47 @@
/**
* 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.ec2.compute;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.internal.BaseImageExtensionLiveTest;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.inject.Module;
/**
* Live test for ec2 {@link ImageExtension} implementation
*
* @author David Alves
*
*/
@Test(groups = "live", singleThreaded = true, testName = "EC2ImageExtensionLiveTest")
public class EC2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
public EC2ImageExtensionLiveTest() {
provider = "ec2";
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
}

View File

@ -0,0 +1,132 @@
/**
* 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.ec2.compute.predicates;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import java.net.URI;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.Image;
import org.jclouds.ec2.compute.BaseEC2ComputeServiceExpectTest;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.predicates.PredicateWithResult;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Injector;
/**
*
* @author David Alves
*/
@Test(groups = "unit", testName = "GetImageWhenStatusAvailablePredicateWithResultExpectTest")
public class GetImageWhenStatusAvailablePredicateWithResultExpectTest extends BaseEC2ComputeServiceExpectTest<Injector> {
protected HttpRequest describeRegionsRequest = HttpRequest
.builder()
.method("POST")
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
.payload(payloadFromStringWithContentType(
"Action=DescribeRegions&Signature=s5OXKqaaeKhJW5FVrRntuMsUL4Ed5fjzgUWeukU96ko%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
"application/x-www-form-urlencoded")).build();
protected HttpRequest describeImagesRequest0 = HttpRequest
.builder()
.method("POST")
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
.payload(payloadFromStringWithContentType(
"Action=DescribeImages&ImageId.1=ami-0&Signature=k9douTXFWkAZecPiZfBLUm3LIS3bTLanMV%2F%2BWrB1jFA%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
"application/x-www-form-urlencoded")).build();
protected HttpRequest describeImagesRequest1 = HttpRequest
.builder()
.method("POST")
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
.payload(payloadFromStringWithContentType(
"Action=DescribeImages&ImageId.1=ami-1&Signature=IVunQEvp8vTKTIxXex2Uh5SWQY1PJCx0ExUe9FRujBY%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
"application/x-www-form-urlencoded")).build();
protected HttpRequest describeImagesRequest2 = HttpRequest
.builder()
.method("POST")
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
.payload(payloadFromStringWithContentType(
"Action=DescribeImages&ImageId.1=ami-2&Signature=8TfP8BJlg1hiY6EqUbS73A7PQO7dlpqnRMyi7hPu76U%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
"application/x-www-form-urlencoded")).build();
protected HttpResponse describeRegionsResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/regionEndpoints.xml", MediaType.APPLICATION_XML)).build();
protected HttpResponse describeImagesResponse0 = HttpResponse
.builder()
.statusCode(200)
.payload(payloadFromResourceWithContentType("/describe_images_imageextension0.xml",
MediaType.APPLICATION_XML)).build();
protected HttpResponse describeImagesResponse1 = HttpResponse
.builder()
.statusCode(200)
.payload(payloadFromResourceWithContentType("/describe_images_imageextension1.xml",
MediaType.APPLICATION_XML)).build();
protected HttpResponse describeImagesResponse2 = HttpResponse
.builder()
.statusCode(200)
.payload(payloadFromResourceWithContentType("/describe_images_imageextension2.xml",
MediaType.APPLICATION_XML)).build();
private final Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(describeRegionsRequest, describeRegionsResponse).put(describeImagesRequest0, describeImagesResponse0)
.put(describeImagesRequest1, describeImagesResponse1).put(describeImagesRequest2, describeImagesResponse2)
.build();
public void testReturnsFalseOnQueuedAndSavingAndTrueOnActive() {
Injector injector = requestsSendResponses(requestResponseMap);
PredicateWithResult<String, Image> predicate = injector
.getInstance(GetImageWhenStatusAvailablePredicateWithResult.class);
assertTrue(predicate.apply("us-east-1/ami-0"));
assertFalse(predicate.apply("us-east-1/ami-2"));
}
@Test(groups = "unit", testName = "GetImageWhenStatusAvailablePredicateWithResultExpectTest", expectedExceptions = IllegalStateException.class)
public void testFailsOnOtherStatuses() {
Injector injector = requestsSendResponses(requestResponseMap);
PredicateWithResult<String, Image> predicate = injector
.getInstance(GetImageWhenStatusAvailablePredicateWithResult.class);
predicate.apply("us-east-1/ami-1");
}
@Override
public Injector apply(ComputeServiceContext input) {
return input.utils().injector();
}
}

View File

@ -0,0 +1,24 @@
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
<imagesSet>
<item>
<imageId>ami-0</imageId>
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
</imageLocation>
<imageState>available</imageState>
<imageOwnerId>206029621532</imageOwnerId>
<isPublic>false</isPublic>
<productCodes>
<item>
<productCode>9961934F</productCode>
</item>
</productCodes>
<architecture>i386</architecture>
<imageType>machine</imageType>
<kernelId>aki-4438dd2d</kernelId>
<ramdiskId>ari-4538dd2c</ramdiskId>
<rootDeviceType>instance-store</rootDeviceType>
<blockDeviceMapping />
<virtualizationType>paravirtual</virtualizationType>
</item>
</imagesSet>
</DescribeImagesResponse>

View File

@ -0,0 +1,24 @@
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
<imagesSet>
<item>
<imageId>ami-1</imageId>
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
</imageLocation>
<imageState>deregistered</imageState>
<imageOwnerId>206029621532</imageOwnerId>
<isPublic>false</isPublic>
<productCodes>
<item>
<productCode>9961934F</productCode>
</item>
</productCodes>
<architecture>i386</architecture>
<imageType>machine</imageType>
<kernelId>aki-4438dd2d</kernelId>
<ramdiskId>ari-4538dd2c</ramdiskId>
<rootDeviceType>instance-store</rootDeviceType>
<blockDeviceMapping />
<virtualizationType>paravirtual</virtualizationType>
</item>
</imagesSet>
</DescribeImagesResponse>

View File

@ -0,0 +1,24 @@
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
<imagesSet>
<item>
<imageId>ami-2</imageId>
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
</imageLocation>
<imageState>unrecognized</imageState>
<imageOwnerId>206029621532</imageOwnerId>
<isPublic>false</isPublic>
<productCodes>
<item>
<productCode>9961934F</productCode>
</item>
</productCodes>
<architecture>i386</architecture>
<imageType>machine</imageType>
<kernelId>aki-4438dd2d</kernelId>
<ramdiskId>ari-4538dd2c</ramdiskId>
<rootDeviceType>instance-store</rootDeviceType>
<blockDeviceMapping />
<virtualizationType>paravirtual</virtualizationType>
</item>
</imagesSet>
</DescribeImagesResponse>

View File

@ -21,17 +21,21 @@ import com.google.inject.Provides;
public abstract class BaseNovaEC2RestClientExpectTest extends BaseRestClientExpectTest<NovaEC2Client> {
protected static final String CONSTANT_DATE = "2012-04-16T15:54:08.897Z";
protected DateService dateService = new SimpleDateFormatDateService();
protected URI endpoint = URI.create("http://localhost:8773/services/Cloud/");
protected HttpRequest describeAvailabilityZonesRequest = HttpRequest.builder().method("POST")
.endpoint(endpoint)
.headers(ImmutableMultimap.of("Host", "localhost:8773"))
.payload(payloadFromStringWithContentType("Action=DescribeAvailabilityZones&Signature=S3fa5fybw4KAq4o11IpKHlqwx3cVJdKfeAKw3FIJYvM%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2009-04-04&AWSAccessKeyId=identity", MediaType.APPLICATION_FORM_URLENCODED))
.build();
protected HttpResponse describeAvailabilityZonesResponse = HttpResponse.builder()
.statusCode(200).payload(payloadFromResourceWithContentType("/nova_ec2_availabilityZones.xml", MediaType.APPLICATION_XML)).build();
protected HttpRequest describeAvailabilityZonesRequest = HttpRequest
.builder()
.method("POST")
.endpoint(endpoint)
.headers(ImmutableMultimap.of("Host", "localhost:8773"))
.payload(payloadFromStringWithContentType(
"Action=DescribeAvailabilityZones&Signature=s5OXKqaaeKhJW5FVrRntuMsUL4Ed5fjzgUWeukU96ko%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2009-04-04&AWSAccessKeyId=identity",
MediaType.APPLICATION_FORM_URLENCODED)).build();
protected HttpResponse describeAvailabilityZonesResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/nova_ec2_availabilityZones.xml", MediaType.APPLICATION_XML))
.build();
public BaseNovaEC2RestClientExpectTest() {
provider = "openstack-nova-ec2";
@ -42,7 +46,7 @@ public abstract class BaseNovaEC2RestClientExpectTest extends BaseRestClientExpe
@Override
@Provides
protected String provideTimeStamp(final DateService dateService,
@Named(Constants.PROPERTY_SESSION_INTERVAL) final int expiration) {
@Named(Constants.PROPERTY_SESSION_INTERVAL) final int expiration) {
return CONSTANT_DATE;
}
}

View File

@ -23,34 +23,62 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.CloneImageTemplate;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import org.jclouds.compute.domain.ImageTemplateBuilder;
import org.jclouds.compute.reference.ComputeServiceConstants;
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;
/**
* Nova implementation of {@link ImageExtension}
*
* @author David Alves
*
*/
@Singleton
public class NovaImageExtension implements ImageExtension {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final NovaClient novaClient;
private final Function<ImageInZone, Image> imageInZoneToImage;
private final ExecutorService executor;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_MAX_WAIT")
private long maxWait = 3600;
@com.google.inject.Inject(optional = true)
@Named("IMAGE_WAIT_PERIOD")
private long waitPeriod = 1;
private PredicateWithResult<ZoneAndId, Image> imageReadyPredicate;
@Inject
public NovaImageExtension(NovaClient novaClient, Function<ImageInZone, Image> imageInZoneToImage) {
public NovaImageExtension(NovaClient novaClient,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
PredicateWithResult<ZoneAndId, Image> imageReadyPredicate) {
this.novaClient = checkNotNull(novaClient);
this.imageInZoneToImage = imageInZoneToImage;
this.executor = userThreads;
this.imageReadyPredicate = imageReadyPredicate;
}
@Override
@ -64,16 +92,27 @@ public class NovaImageExtension implements ImageExtension {
}
@Override
public Image createImage(ImageTemplate template) {
public ListenableFuture<Image> createImage(ImageTemplate template) {
checkState(template instanceof CloneImageTemplate,
" openstack-nova only supports creating images through cloning.");
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
String newImageId = novaClient.getServerClientForZone(zoneAndId.getZone()).createImageFromServer(
cloneTemplate.getName(), zoneAndId.getId());
org.jclouds.openstack.nova.v1_1.domain.Image newImage = checkNotNull(findImage(ZoneAndId.fromZoneAndId(
zoneAndId.getZone(), newImageId)));
return imageInZoneToImage.apply(new ImageInZone(newImage, zoneAndId.getZone()));
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);
logger.info(">> Registered new Image %s, waiting for it to become available.", newImageId);
return Futures.makeListenable(executor.submit(new Callable<Image>() {
@Override
public Image call() throws Exception {
return Retryables.retryGettingResultOrFailing(imageReadyPredicate, targetImageZoneAndId, maxWait,
waitPeriod, TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: "
+ maxWait + " secs.]");
}
}), executor);
}
@Override
@ -87,15 +126,4 @@ public class NovaImageExtension implements ImageExtension {
return true;
}
private org.jclouds.openstack.nova.v1_1.domain.Image findImage(final ZoneAndId zoneAndId) {
return Iterables.tryFind(novaClient.getImageClientForZone(zoneAndId.getZone()).listImagesInDetail(),
new Predicate<org.jclouds.openstack.nova.v1_1.domain.Image>() {
@Override
public boolean apply(org.jclouds.openstack.nova.v1_1.domain.Image input) {
return input.getId().equals(zoneAndId.getId());
}
}).orNull();
}
}

View File

@ -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<ImageExtension>() {
}).to(NovaImageExtension.class);
bind(new TypeLiteral<PredicateWithResult<ZoneAndId, Image>>() {
}).to(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class);
}
@Override

View File

@ -0,0 +1,101 @@
/**
* 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.domain.zonescoped.ImageInZone;
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
import org.jclouds.predicates.PredicateWithResult;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
/**
* @author David Alves
*/
public final class GetImageWhenImageInZoneHasActiveStatusPredicateWithResult implements
PredicateWithResult<ZoneAndId, Image> {
@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<ImageInZone, Image> imageInZoneToImage;
private NovaClient client;
@Inject
public GetImageWhenImageInZoneHasActiveStatusPredicateWithResult(Function<ImageInZone, Image> imageInZoneToImage,
NovaClient client) {
this.imageInZoneToImage = imageInZoneToImage;
this.client = client;
}
@Override
public boolean apply(ZoneAndId input) {
result = checkNotNull(findImage(ZoneAndId.fromZoneAndId(input.getZone(), input.getId())));
resultZoneAndId = input;
switch (result.getStatus()) {
case ACTIVE:
logger.info("<< Image %s is available for use. %s", input.getId(), result);
return true;
case UNRECOGNIZED:
case SAVING:
logger.debug("<< Image %s is not available yet. %s", input.getId(), result);
return false;
default:
lastFailure = new IllegalStateException("Image " + input.getId() + " was not created. " + result);
throw lastFailure;
}
}
@Override
public Image getResult() {
return imageInZoneToImage.apply(new ImageInZone(result, resultZoneAndId.getZone()));
}
@Override
public Throwable getLastFailure() {
return lastFailure;
}
public org.jclouds.openstack.nova.v1_1.domain.Image findImage(final ZoneAndId zoneAndId) {
return Iterables.tryFind(client.getImageClientForZone(zoneAndId.getZone()).listImagesInDetail(),
new Predicate<org.jclouds.openstack.nova.v1_1.domain.Image>() {
@Override
public boolean apply(org.jclouds.openstack.nova.v1_1.domain.Image input) {
return input.getId().equals(zoneAndId.getId());
}
}).orNull();
}
}

View File

@ -65,7 +65,7 @@ import com.google.common.primitives.Ints;
*/
@Singleton
public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet extends
CreateNodesWithGroupEncodedIntoNameThenAddToSet {
CreateNodesWithGroupEncodedIntoNameThenAddToSet {
private final AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode;
private final LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache;
@ -75,56 +75,67 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
@Inject
protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet(
CreateNodeWithGroupEncodedIntoName addNodeWithTagStrategy,
ListNodesStrategy listNodesStrategy,
GroupNamingConvention.Factory namingConvention,
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor,
Provider<TemplateBuilder> templateBuilderProvider,
AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode,
LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache,
LoadingCache<ZoneAndName, KeyPair> keyPairCache, NovaClient novaClient) {
CreateNodeWithGroupEncodedIntoName addNodeWithTagStrategy,
ListNodesStrategy listNodesStrategy,
GroupNamingConvention.Factory namingConvention,
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor,
Provider<TemplateBuilder> templateBuilderProvider,
AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode,
LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache,
LoadingCache<ZoneAndName, KeyPair> keyPairCache, NovaClient novaClient) {
super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, executor,
customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider");
this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
this.allocateAndAddFloatingIpToNode = checkNotNull(allocateAndAddFloatingIpToNode,
"allocateAndAddFloatingIpToNode");
"allocateAndAddFloatingIpToNode");
this.novaClient = checkNotNull(novaClient, "novaClient");
}
@Override
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
// ensure we don't mutate the input template
Template mutableTemplate = templateBuilderProvider.get().fromTemplate(template).build();
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
Template mutableTemplate;
// ensure we don't mutate the input template, fromTemplate ignores imageId so
// build directly from imageId if we have it
if (template.getImage() != null && template.getImage().getId() != null) {
mutableTemplate = templateBuilderProvider.get().imageId(template.getImage().getId()).fromTemplate(template)
.build();
// otherwise build from generic parameters
} else {
mutableTemplate = templateBuilderProvider.get().fromTemplate(template).build();
}
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(mutableTemplate.getOptions());
assert template.getOptions().equals(templateOptions) : "options didn't clone properly";
String zone = mutableTemplate.getLocation().getId();
if (templateOptions.shouldAutoAssignFloatingIp()) {
checkArgument(novaClient.getFloatingIPExtensionForZone(zone).isPresent(),
"Floating IPs are required by options, but the extension is not available! options: %s", templateOptions);
"Floating IPs are required by options, but the extension is not available! options: %s",
templateOptions);
}
boolean keyPairExensionPresent = novaClient.getKeyPairExtensionForZone(zone).isPresent();
if (templateOptions.shouldGenerateKeyPair()) {
checkArgument(keyPairExensionPresent,
"Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
"Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
KeyPair keyPair = keyPairCache.getUnchecked(ZoneAndName.fromZoneAndName(zone, namingConvention.create()
.sharedNameForGroup(group)));
.sharedNameForGroup(group)));
keyPairCache.asMap().put(ZoneAndName.fromZoneAndName(zone, keyPair.getName()), keyPair);
templateOptions.keyPairName(keyPair.getName());
} else if (templateOptions.getKeyPairName() != null) {
checkArgument(keyPairExensionPresent,
"Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
"Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
if (templateOptions.getLoginPrivateKey() != null) {
String pem = templateOptions.getLoginPrivateKey();
KeyPair keyPair = KeyPair.builder().name(templateOptions.getKeyPairName())
.fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
.fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
keyPairCache.asMap().put(ZoneAndName.fromZoneAndName(zone, keyPair.getName()), keyPair);
}
}
@ -133,8 +144,8 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
if (templateOptions.getSecurityGroupNames().size() > 0) {
checkArgument(novaClient.getSecurityGroupExtensionForZone(zone).isPresent(),
"Security groups are required by options, but the extension is not available! options: %s",
templateOptions);
"Security groups are required by options, but the extension is not available! options: %s",
templateOptions);
} else if (securityGroupExensionPresent && inboundPorts.size() > 0) {
String securityGroupName = namingConvention.create().sharedNameForGroup(group);
try {
@ -150,7 +161,7 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
@Override
protected Future<AtomicReference<NodeMetadata>> createNodeInGroupWithNameAndTemplate(String group,
final String name, Template template) {
final String name, Template template) {
Future<AtomicReference<NodeMetadata>> future = super.createNodeInGroupWithNameAndTemplate(group, name, template);
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions());

View File

@ -0,0 +1,47 @@
/**
* 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;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.internal.BaseImageExtensionLiveTest;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.inject.Module;
/**
* Live test for openstack-nova {@link ImageExtension} implementation.
*
* @author David Alves
*
*/
@Test(groups = "live", singleThreaded = true, testName = "NovaImageExtensionLiveTest")
public class NovaImageExtensionLiveTest extends BaseImageExtensionLiveTest {
public NovaImageExtensionLiveTest() {
provider = "openstack-nova";
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
}

View File

@ -0,0 +1,94 @@
/**
* 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 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;
/**
*
* @author David Alves
*
*/
@Test(groups = "unit", testName = "GetImageWhenImageInZoneHasActiveStatucPredicateWithResultExpectTest")
public class GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest extends
BaseNovaComputeServiceContextExpectTest<Injector> {
private final HttpResponse listImagesDetailImageExtensionResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/image_list_detail_imageextension.json")).build();
private Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess)
.put(listImagesDetail, listImagesDetailImageExtensionResponse).build();
public void testReturnsFalseOnImageStatusSavingAndTrueOnActive() {
Injector injector = requestsSendResponses(requestResponseMap);
PredicateWithResult<ZoneAndId, Image> 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<ZoneAndId, Image> 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<ZoneAndId, Image> predicate, ZoneAndId zoneAndId) {
try {
predicate.apply(zoneAndId);
} catch (IllegalStateException e) {
return true;
}
return false;
}
@Override
public Injector apply(ComputeServiceContext input) {
return input.utils().injector();
}
}

View File

@ -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"
}
}]
}

View File

@ -22,6 +22,8 @@ package org.jclouds.compute;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import com.google.common.util.concurrent.ListenableFuture;
/**
* An extension to compute service to allow for the manipulation of {@link Image}s. Implementation
* is optional by providers.
@ -49,7 +51,7 @@ public interface ImageExtension {
* template to base the new image on
* @return the image that was just built *after* it is registered on the provider
*/
Image createImage(ImageTemplate template);
ListenableFuture<Image> createImage(ImageTemplate template);
/**
* Delete an {@link Image} on the provider.

View File

@ -22,16 +22,22 @@ package org.jclouds.compute.internal;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.inject.Named;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.ssh.SshClient;
import org.testng.annotations.Test;
@ -47,40 +53,74 @@ import com.google.common.collect.Iterables;
*/
public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceContextLiveTest {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected String imageId;
/**
* Returns the template for the base node, override to test different templates.
*
* @return
*/
public Template getNodeTemplate() {
return view.getComputeService().templateBuilder().any().build();
return view.getComputeService().templateBuilder().build();
}
/**
* Returns the maximum amount of time (in seconds) to wait for a node spawned from the new image
* to become available, override to increase this time.
*
* @return
*/
public long getSpawnNodeMaxWait() {
return 600L;
}
/**
* Lists the images found in the {@link ComputeService}, subclasses may override to constrain
* search.
*
* @return
*/
protected Iterable<? extends Image> listImages() {
return view.getComputeService().listImages();
}
@Test(groups = { "integration", "live" }, singleThreaded = true)
public void testCreateImage() throws RunNodesException, InterruptedException {
public void testCreateImage() throws RunNodesException, InterruptedException, ExecutionException {
ComputeService computeService = view.getComputeService();
Optional<ImageExtension> imageExtension = computeService.getImageExtension();
assertTrue("image extension was not present", imageExtension.isPresent());
Set<? extends Image> imagesBefore = computeService.listImages();
Template template = getNodeTemplate();
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1,
getNodeTemplate()));
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, template));
checkReachable(node);
logger.info("Creating image from node %s, started with template: %s", node, template);
ImageTemplate newImageTemplate = imageExtension.get().buildImageTemplateFromNode("test-create-image",
node.getId());
Image image = imageExtension.get().createImage(newImageTemplate);
Image image = imageExtension.get().createImage(newImageTemplate).get();
logger.info("Image created: %s", image);
assertEquals("test-create-image", image.getName());
imageId = image.getId();
computeService.destroyNode(node.getId());
Set<? extends Image> imagesAfter = computeService.listImages();
Optional<? extends Image> optImage = getImage();
assertTrue(imagesBefore.size() == imagesAfter.size() - 1);
assertTrue(optImage.isPresent());
}
@ -89,16 +129,16 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
ComputeService computeService = view.getComputeService();
Template template = computeService.templateBuilder().fromImage(getImage().get()).build();
Optional<? extends Image> optImage = getImage();
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, template));
assertTrue(optImage.isPresent());
SshClient client = view.utils().sshForNode().apply(node);
client.connect();
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, view
.getComputeService()
// fromImage does not use the arg image's id (but we do need to set location)
.templateBuilder().imageId(optImage.get().getId()).fromImage(optImage.get()).build()));
ExecResponse hello = client.exec("echo hello");
assertEquals(hello.getOutput().trim(), "hello");
checkReachable(node);
view.getComputeService().destroyNode(node.getId());
@ -123,12 +163,25 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
}
private Optional<? extends Image> getImage() {
return Iterables.tryFind(view.getComputeService().listImages(), new Predicate<Image>() {
return Iterables.tryFind(listImages(), new Predicate<Image>() {
@Override
public boolean apply(Image input) {
return input.getId().contains("test-create-image");
return input.getId().equals(imageId);
}
});
}
private void checkReachable(NodeMetadata node) {
SshClient client = view.utils().sshForNode().apply(node);
assertTrue(new RetryablePredicate<SshClient>(new Predicate<SshClient>() {
@Override
public boolean apply(SshClient input) {
input.connect();
if (input.exec("id").getExitStatus() == 0) {
return true;
}
return false;
}
}, getSpawnNodeMaxWait(), 1l, TimeUnit.SECONDS).apply(client));
}
}

View File

@ -60,7 +60,15 @@ import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Virtualbox implementation of {@link ImageExtension}
*
* @author David Alves
*
*/
@Singleton
public class VirtualBoxImageExtension implements ImageExtension {
@ -104,7 +112,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
}
@Override
public Image createImage(ImageTemplate template) {
public ListenableFuture<Image> createImage(ImageTemplate template) {
checkState(template instanceof CloneImageTemplate, " vbox image extension only supports cloning for the moment.");
CloneImageTemplate cloneTemplate = CloneImageTemplate.class.cast(template);
@ -133,7 +141,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
// registering
manager.get().getVBox().registerMachine(clonedMachine);
return imachineToImage.apply(clonedMachine);
return Futures.immediateFuture(imachineToImage.apply(clonedMachine));
}
@Override

View File

@ -39,6 +39,7 @@ import org.jclouds.aws.ec2.compute.strategy.AWSEC2ListNodesStrategy;
import org.jclouds.aws.ec2.compute.strategy.AWSEC2ReviseParsedImage;
import org.jclouds.aws.ec2.compute.strategy.CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions;
import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.config.BaseComputeServiceContextModule;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.TemplateBuilder;
@ -62,6 +63,7 @@ import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
@ -180,4 +182,9 @@ public class AWSEC2ComputeServiceContextModule extends BaseComputeServiceContext
protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
return options.as(EC2TemplateOptions.class).userData("#cloud-config\nrepo_upgrade: none\n".getBytes());
}
@Override
protected Optional<ImageExtension> provideImageExtension(Injector i) {
return Optional.of(i.getInstance(ImageExtension.class));
}
}

View File

@ -40,11 +40,13 @@ import org.jclouds.aws.ec2.functions.ImportOrReturnExistingKeypair;
import org.jclouds.aws.ec2.predicates.PlacementGroupAvailable;
import org.jclouds.aws.ec2.predicates.PlacementGroupDeleted;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.config.ValueOfConfigurationKeyOrNull;
import org.jclouds.domain.Credentials;
import org.jclouds.ec2.compute.EC2ImageExtension;
import org.jclouds.ec2.compute.config.EC2ComputeServiceDependenciesModule;
import org.jclouds.ec2.compute.domain.RegionAndName;
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
@ -53,8 +55,10 @@ import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
import org.jclouds.ec2.compute.loaders.CreateSecurityGroupIfNeeded;
import org.jclouds.ec2.compute.loaders.LoadPublicIpForInstanceOrNull;
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
import org.jclouds.ec2.compute.predicates.GetImageWhenStatusAvailablePredicateWithResult;
import org.jclouds.ec2.domain.KeyPair;
import org.jclouds.ec2.domain.RunningInstance;
import org.jclouds.predicates.PredicateWithResult;
import org.jclouds.predicates.RetryablePredicate;
import com.google.common.base.Function;
@ -94,6 +98,10 @@ public class AWSEC2ComputeServiceDependenciesModule extends EC2ComputeServiceDep
bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() {
}).to(RegionAndIdToImage.class);
install(new FactoryModuleBuilder().build(CallForImages.Factory.class));
bind(new TypeLiteral<ImageExtension>() {
}).to(EC2ImageExtension.class);
bind(new TypeLiteral<PredicateWithResult<String, Image>>() {
}).to(GetImageWhenStatusAvailablePredicateWithResult.class);
}
@Provides

View File

@ -0,0 +1,67 @@
/**
* 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.aws.ec2.compute;
import org.jclouds.aws.ec2.AWSEC2AsyncClient;
import org.jclouds.aws.ec2.AWSEC2Client;
import org.jclouds.aws.util.AWSUtils;
import org.jclouds.compute.ImageExtension;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.internal.BaseImageExtensionLiveTest;
import org.jclouds.ec2.compute.functions.EC2ImageParser;
import org.jclouds.ec2.options.DescribeImagesOptions;
import org.jclouds.rest.RestContext;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.common.collect.Iterables;
import com.google.inject.Module;
/**
* Live test for aws-ec2 {@link ImageExtension} implementation
*
* @author David Alves
*
*/
@Test(groups = "live", singleThreaded = true, testName = "AWSEC2ImageExtensionLiveTest")
public class AWSEC2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
public AWSEC2ImageExtensionLiveTest() {
provider = "aws-ec2";
}
@Override
protected Iterable<? extends Image> listImages() {
RestContext<AWSEC2Client, AWSEC2AsyncClient> unwrapped = view.unwrap();
String[] parts = AWSUtils.parseHandle(imageId);
String region = parts[0];
String imageId = parts[1];
EC2ImageParser parser = view.utils().getInjector().getInstance(EC2ImageParser.class);
return Iterables.transform(
unwrapped.getApi().getAMIServices()
.describeImagesInRegion(region, new DescribeImagesOptions().imageIds(imageId)), parser);
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
}