mirror of https://github.com/apache/jclouds.git
image extension working on hpcloud, virtualbox and aws-ec2, cloudservers implemented but has issues
This commit is contained in:
parent
19390ea87d
commit
082158ac3f
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* 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.cloudservers.options.ListOptions;
|
||||||
|
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.base.Function;
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
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 syncClient;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage;
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_MAX_WAIT")
|
||||||
|
long maxWait = 3600;
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_WAIT_PERIOD")
|
||||||
|
long waitPeriod = 1;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CloudServersImageExtension(CloudServersClient novaClient,
|
||||||
|
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||||
|
Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage) {
|
||||||
|
this.syncClient = checkNotNull(novaClient);
|
||||||
|
this.executor = userThreads;
|
||||||
|
this.cloudserversImageToImage = cloudserversImageToImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageTemplate buildImageTemplateFromNode(String name, final String id) {
|
||||||
|
Server server = syncClient.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 = syncClient.createImageFromServer(cloneTemplate.getName(),
|
||||||
|
Integer.parseInt(cloneTemplate.getSourceNodeId()));
|
||||||
|
return Futures.makeListenable(executor.submit(new Callable<Image>() {
|
||||||
|
@Override
|
||||||
|
public Image call() throws Exception {
|
||||||
|
return Retryables.retryGettingResultOrFailing(new PredicateWithResult<Integer, Image>() {
|
||||||
|
|
||||||
|
org.jclouds.cloudservers.domain.Image result;
|
||||||
|
RuntimeException lastFailure;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Integer input) {
|
||||||
|
result = checkNotNull(findImage(input));
|
||||||
|
switch (result.getStatus()) {
|
||||||
|
case ACTIVE:
|
||||||
|
logger.info("<< Image %s is available for use.", input);
|
||||||
|
return true;
|
||||||
|
case UNKNOWN:
|
||||||
|
case SAVING:
|
||||||
|
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 cloudserversImageToImage.apply(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable getLastFailure() {
|
||||||
|
return lastFailure;
|
||||||
|
}
|
||||||
|
}, 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.syncClient.deleteImage(Integer.parseInt(id));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.jclouds.cloudservers.domain.Image findImage(final int id) {
|
||||||
|
return Iterables.tryFind(syncClient.listImages(ListOptions.NONE),
|
||||||
|
new Predicate<org.jclouds.cloudservers.domain.Image>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(org.jclouds.cloudservers.domain.Image input) {
|
||||||
|
return input.getId() == id;
|
||||||
|
}
|
||||||
|
}).orNull();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.jclouds.cloudservers.compute.CloudServersImageExtension;
|
||||||
import org.jclouds.cloudservers.compute.functions.CloudServersImageToImage;
|
import org.jclouds.cloudservers.compute.functions.CloudServersImageToImage;
|
||||||
import org.jclouds.cloudservers.compute.functions.CloudServersImageToOperatingSystem;
|
import org.jclouds.cloudservers.compute.functions.CloudServersImageToOperatingSystem;
|
||||||
import org.jclouds.cloudservers.compute.functions.FlavorToHardware;
|
import org.jclouds.cloudservers.compute.functions.FlavorToHardware;
|
||||||
|
@ -31,6 +32,7 @@ import org.jclouds.cloudservers.domain.Flavor;
|
||||||
import org.jclouds.cloudservers.domain.Server;
|
import org.jclouds.cloudservers.domain.Server;
|
||||||
import org.jclouds.cloudservers.domain.ServerStatus;
|
import org.jclouds.cloudservers.domain.ServerStatus;
|
||||||
import org.jclouds.compute.ComputeServiceAdapter;
|
import org.jclouds.compute.ComputeServiceAdapter;
|
||||||
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
|
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
|
||||||
import org.jclouds.compute.domain.Hardware;
|
import org.jclouds.compute.domain.Hardware;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
|
@ -43,7 +45,9 @@ import org.jclouds.functions.IdentityFunction;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
|
@ -77,6 +81,9 @@ public class CloudServersComputeServiceContextModule extends
|
||||||
// we aren't converting location from a provider-specific type
|
// we aren't converting location from a provider-specific type
|
||||||
bind(new TypeLiteral<Function<Location, Location>>() {
|
bind(new TypeLiteral<Function<Location, Location>>() {
|
||||||
}).to((Class) IdentityFunction.class);
|
}).to((Class) IdentityFunction.class);
|
||||||
|
|
||||||
|
bind(new TypeLiteral<ImageExtension>() {
|
||||||
|
}).to(CloudServersImageExtension.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +119,10 @@ public class CloudServersComputeServiceContextModule extends
|
||||||
Map<ServerStatus, NodeState> provideServerToNodeState() {
|
Map<ServerStatus, NodeState> provideServerToNodeState() {
|
||||||
return serverToNodeState;
|
return serverToNodeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<ImageExtension> provideImageExtension(Injector i) {
|
||||||
|
return Optional.of(i.getInstance(ImageExtension.class));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
/**
|
||||||
|
* 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.compute.functions.EC2ImageParser;
|
||||||
|
import org.jclouds.ec2.domain.Reservation;
|
||||||
|
import org.jclouds.ec2.domain.RunningInstance;
|
||||||
|
import org.jclouds.ec2.options.CreateImageOptions;
|
||||||
|
import org.jclouds.ec2.options.DescribeImagesOptions;
|
||||||
|
import org.jclouds.logging.Logger;
|
||||||
|
import org.jclouds.predicates.PredicateWithResult;
|
||||||
|
import org.jclouds.predicates.Retryables;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_MAX_WAIT")
|
||||||
|
long maxWait = 3600;
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_WAIT_PERIOD")
|
||||||
|
long waitPeriod = 1;
|
||||||
|
private final EC2Client ec2Client;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final Function<org.jclouds.ec2.domain.Image, Image> ecImageToImage;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public EC2ImageExtension(EC2Client ec2Client, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||||
|
EC2ImageParser ec2ImageToImage) {
|
||||||
|
this.ec2Client = checkNotNull(ec2Client);
|
||||||
|
this.executor = checkNotNull(userThreads);
|
||||||
|
this.ecImageToImage = checkNotNull(ec2ImageToImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(new PredicateWithResult<String, Image>() {
|
||||||
|
|
||||||
|
org.jclouds.ec2.domain.Image result;
|
||||||
|
RuntimeException lastFailure;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(String input) {
|
||||||
|
result = checkNotNull(findImage(region, input));
|
||||||
|
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 ecImageToImage.apply(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable getLastFailure() {
|
||||||
|
return lastFailure;
|
||||||
|
}
|
||||||
|
}, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.jclouds.ec2.domain.Image findImage(String region, String id) {
|
||||||
|
return Iterables.getOnlyElement(ec2Client.getAMIServices().describeImagesInRegion(region,
|
||||||
|
new DescribeImagesOptions().imageIds(id)));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,16 +30,19 @@ import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.jclouds.compute.ComputeServiceContext;
|
import org.jclouds.compute.ComputeServiceContext;
|
||||||
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.config.BaseComputeServiceContextModule;
|
import org.jclouds.compute.config.BaseComputeServiceContextModule;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.concurrent.RetryOnTimeOutExceptionSupplier;
|
import org.jclouds.concurrent.RetryOnTimeOutExceptionSupplier;
|
||||||
import org.jclouds.ec2.compute.EC2ComputeService;
|
import org.jclouds.ec2.compute.EC2ComputeService;
|
||||||
|
import org.jclouds.ec2.compute.EC2ImageExtension;
|
||||||
import org.jclouds.ec2.compute.domain.RegionAndName;
|
import org.jclouds.ec2.compute.domain.RegionAndName;
|
||||||
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
|
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
|
||||||
import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
|
import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
|
||||||
import org.jclouds.rest.AuthorizationException;
|
import org.jclouds.rest.AuthorizationException;
|
||||||
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
|
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
|
@ -136,6 +139,11 @@ public class EC2ComputeServiceContextModule extends BaseComputeServiceContextMod
|
||||||
return new String[] {};
|
return new String[] {};
|
||||||
return toArray(Splitter.on(',').split(amiOwners), String.class);
|
return toArray(Splitter.on(',').split(amiOwners), String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<ImageExtension> provideImageExtension(Injector i) {
|
||||||
|
return Optional.of(i.getInstance(ImageExtension.class));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.jclouds.compute.ComputeService;
|
import org.jclouds.compute.ComputeService;
|
||||||
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.NodeMetadata;
|
import org.jclouds.compute.domain.NodeMetadata;
|
||||||
import org.jclouds.compute.domain.NodeState;
|
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.compute.options.TemplateOptions;
|
||||||
import org.jclouds.domain.Credentials;
|
import org.jclouds.domain.Credentials;
|
||||||
import org.jclouds.ec2.compute.EC2ComputeService;
|
import org.jclouds.ec2.compute.EC2ComputeService;
|
||||||
|
import org.jclouds.ec2.compute.EC2ImageExtension;
|
||||||
import org.jclouds.ec2.compute.domain.RegionAndName;
|
import org.jclouds.ec2.compute.domain.RegionAndName;
|
||||||
import org.jclouds.ec2.compute.functions.AddElasticIpsToNodemetadata;
|
import org.jclouds.ec2.compute.functions.AddElasticIpsToNodemetadata;
|
||||||
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
|
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
|
||||||
|
@ -104,6 +106,8 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
|
||||||
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
|
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
|
||||||
}).annotatedWith(Names.named("ELASTICIP")).to(LoadPublicIpForInstanceOrNull.class);
|
}).annotatedWith(Names.named("ELASTICIP")).to(LoadPublicIpForInstanceOrNull.class);
|
||||||
bind(WindowsLoginCredentialsFromEncryptedData.class);
|
bind(WindowsLoginCredentialsFromEncryptedData.class);
|
||||||
|
bind(new TypeLiteral<ImageExtension>() {
|
||||||
|
}).to(EC2ImageExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -132,13 +132,23 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen
|
||||||
@Override
|
@Override
|
||||||
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
|
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
|
||||||
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
|
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
|
||||||
|
|
||||||
// ensure we don't mutate the input template
|
// 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<String> ips = allocateElasticIpsInRegion(count, template);
|
||||||
|
|
||||||
Iterable<? extends RunningInstance> started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group,
|
Iterable<? extends RunningInstance> started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group,
|
||||||
count, template);
|
count, mutableTemplate);
|
||||||
|
|
||||||
Iterable<RegionAndName> ids = transform(started, instanceToRegionAndName);
|
Iterable<RegionAndName> ids = transform(started, instanceToRegionAndName);
|
||||||
|
|
||||||
|
@ -152,7 +162,7 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen
|
||||||
|
|
||||||
assignElasticIpsToInstances(ips, started);
|
assignElasticIpsToInstances(ips, started);
|
||||||
|
|
||||||
return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(template.getOptions(), transform(started,
|
return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(mutableTemplate.getOptions(), transform(started,
|
||||||
runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses);
|
runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,34 +23,59 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
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.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.jclouds.Constants;
|
||||||
import org.jclouds.compute.ImageExtension;
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.domain.CloneImageTemplate;
|
import org.jclouds.compute.domain.CloneImageTemplate;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.ImageTemplate;
|
import org.jclouds.compute.domain.ImageTemplate;
|
||||||
import org.jclouds.compute.domain.ImageTemplateBuilder;
|
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.NovaClient;
|
||||||
import org.jclouds.openstack.nova.v1_1.domain.Server;
|
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.ImageInZone;
|
||||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
|
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.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class NovaImageExtension implements ImageExtension {
|
public class NovaImageExtension implements ImageExtension {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||||
|
protected Logger logger = Logger.NULL;
|
||||||
|
|
||||||
private final NovaClient novaClient;
|
private final NovaClient novaClient;
|
||||||
private final Function<ImageInZone, Image> imageInZoneToImage;
|
private final Function<ImageInZone, Image> imageInZoneToImage;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_MAX_WAIT")
|
||||||
|
long maxWait = 3600;
|
||||||
|
@com.google.inject.Inject(optional = true)
|
||||||
|
@Named("IMAGE_WAIT_PERIOD")
|
||||||
|
long waitPeriod = 1;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NovaImageExtension(NovaClient novaClient, Function<ImageInZone, Image> imageInZoneToImage) {
|
public NovaImageExtension(NovaClient novaClient, Function<ImageInZone, Image> imageInZoneToImage,
|
||||||
|
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads) {
|
||||||
this.novaClient = checkNotNull(novaClient);
|
this.novaClient = checkNotNull(novaClient);
|
||||||
this.imageInZoneToImage = imageInZoneToImage;
|
this.imageInZoneToImage = imageInZoneToImage;
|
||||||
|
this.executor = userThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,16 +89,54 @@ public class NovaImageExtension implements ImageExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Image createImage(ImageTemplate template) {
|
public ListenableFuture<Image> createImage(ImageTemplate template) {
|
||||||
checkState(template instanceof CloneImageTemplate,
|
checkState(template instanceof CloneImageTemplate,
|
||||||
" openstack-nova only supports creating images through cloning.");
|
" openstack-nova only supports creating images through cloning.");
|
||||||
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
|
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
|
||||||
ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
|
final ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
|
||||||
String newImageId = novaClient.getServerClientForZone(zoneAndId.getZone()).createImageFromServer(
|
|
||||||
|
final String newImageId = novaClient.getServerClientForZone(zoneAndId.getZone()).createImageFromServer(
|
||||||
cloneTemplate.getName(), zoneAndId.getId());
|
cloneTemplate.getName(), zoneAndId.getId());
|
||||||
org.jclouds.openstack.nova.v1_1.domain.Image newImage = checkNotNull(findImage(ZoneAndId.fromZoneAndId(
|
logger.info(">> Registered new Image %s, waiting for it to become available.", newImageId);
|
||||||
zoneAndId.getZone(), newImageId)));
|
|
||||||
return imageInZoneToImage.apply(new ImageInZone(newImage, zoneAndId.getZone()));
|
return Futures.makeListenable(executor.submit(new Callable<Image>() {
|
||||||
|
@Override
|
||||||
|
public Image call() throws Exception {
|
||||||
|
return Retryables.retryGettingResultOrFailing(new PredicateWithResult<String, Image>() {
|
||||||
|
|
||||||
|
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.]");
|
||||||
|
}
|
||||||
|
}), executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -65,7 +65,7 @@ import com.google.common.primitives.Ints;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet extends
|
public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet extends
|
||||||
CreateNodesWithGroupEncodedIntoNameThenAddToSet {
|
CreateNodesWithGroupEncodedIntoNameThenAddToSet {
|
||||||
|
|
||||||
private final AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode;
|
private final AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode;
|
||||||
private final LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache;
|
private final LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache;
|
||||||
|
@ -75,56 +75,67 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet(
|
protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet(
|
||||||
CreateNodeWithGroupEncodedIntoName addNodeWithTagStrategy,
|
CreateNodeWithGroupEncodedIntoName addNodeWithTagStrategy,
|
||||||
ListNodesStrategy listNodesStrategy,
|
ListNodesStrategy listNodesStrategy,
|
||||||
GroupNamingConvention.Factory namingConvention,
|
GroupNamingConvention.Factory namingConvention,
|
||||||
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
|
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
|
||||||
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor,
|
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor,
|
||||||
Provider<TemplateBuilder> templateBuilderProvider,
|
Provider<TemplateBuilder> templateBuilderProvider,
|
||||||
AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode,
|
AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode,
|
||||||
LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache,
|
LoadingCache<ZoneAndName, SecurityGroupInZone> securityGroupCache,
|
||||||
LoadingCache<ZoneAndName, KeyPair> keyPairCache, NovaClient novaClient) {
|
LoadingCache<ZoneAndName, KeyPair> keyPairCache, NovaClient novaClient) {
|
||||||
super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, executor,
|
super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, executor,
|
||||||
customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
|
customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
|
||||||
this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider");
|
this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider");
|
||||||
this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
|
this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
|
||||||
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
|
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
|
||||||
this.allocateAndAddFloatingIpToNode = checkNotNull(allocateAndAddFloatingIpToNode,
|
this.allocateAndAddFloatingIpToNode = checkNotNull(allocateAndAddFloatingIpToNode,
|
||||||
"allocateAndAddFloatingIpToNode");
|
"allocateAndAddFloatingIpToNode");
|
||||||
this.novaClient = checkNotNull(novaClient, "novaClient");
|
this.novaClient = checkNotNull(novaClient, "novaClient");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
|
public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
|
||||||
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
|
Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
|
||||||
// ensure we don't mutate the input template
|
|
||||||
Template mutableTemplate = 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();
|
||||||
|
}
|
||||||
|
|
||||||
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(mutableTemplate.getOptions());
|
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(mutableTemplate.getOptions());
|
||||||
|
|
||||||
assert template.getOptions().equals(templateOptions) : "options didn't clone properly";
|
assert template.getOptions().equals(templateOptions) : "options didn't clone properly";
|
||||||
|
|
||||||
String zone = mutableTemplate.getLocation().getId();
|
String zone = mutableTemplate.getLocation().getId();
|
||||||
|
|
||||||
if (templateOptions.shouldAutoAssignFloatingIp()) {
|
if (templateOptions.shouldAutoAssignFloatingIp()) {
|
||||||
checkArgument(novaClient.getFloatingIPExtensionForZone(zone).isPresent(),
|
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();
|
boolean keyPairExensionPresent = novaClient.getKeyPairExtensionForZone(zone).isPresent();
|
||||||
if (templateOptions.shouldGenerateKeyPair()) {
|
if (templateOptions.shouldGenerateKeyPair()) {
|
||||||
checkArgument(keyPairExensionPresent,
|
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()
|
KeyPair keyPair = keyPairCache.getUnchecked(ZoneAndName.fromZoneAndName(zone, namingConvention.create()
|
||||||
.sharedNameForGroup(group)));
|
.sharedNameForGroup(group)));
|
||||||
keyPairCache.asMap().put(ZoneAndName.fromZoneAndName(zone, keyPair.getName()), keyPair);
|
keyPairCache.asMap().put(ZoneAndName.fromZoneAndName(zone, keyPair.getName()), keyPair);
|
||||||
templateOptions.keyPairName(keyPair.getName());
|
templateOptions.keyPairName(keyPair.getName());
|
||||||
} else if (templateOptions.getKeyPairName() != null) {
|
} else if (templateOptions.getKeyPairName() != null) {
|
||||||
checkArgument(keyPairExensionPresent,
|
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) {
|
if (templateOptions.getLoginPrivateKey() != null) {
|
||||||
String pem = templateOptions.getLoginPrivateKey();
|
String pem = templateOptions.getLoginPrivateKey();
|
||||||
KeyPair keyPair = KeyPair.builder().name(templateOptions.getKeyPairName())
|
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);
|
keyPairCache.asMap().put(ZoneAndName.fromZoneAndName(zone, keyPair.getName()), keyPair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,8 +144,8 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
|
||||||
List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
|
List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
|
||||||
if (templateOptions.getSecurityGroupNames().size() > 0) {
|
if (templateOptions.getSecurityGroupNames().size() > 0) {
|
||||||
checkArgument(novaClient.getSecurityGroupExtensionForZone(zone).isPresent(),
|
checkArgument(novaClient.getSecurityGroupExtensionForZone(zone).isPresent(),
|
||||||
"Security groups are required by options, but the extension is not available! options: %s",
|
"Security groups are required by options, but the extension is not available! options: %s",
|
||||||
templateOptions);
|
templateOptions);
|
||||||
} else if (securityGroupExensionPresent && inboundPorts.size() > 0) {
|
} else if (securityGroupExensionPresent && inboundPorts.size() > 0) {
|
||||||
String securityGroupName = namingConvention.create().sharedNameForGroup(group);
|
String securityGroupName = namingConvention.create().sharedNameForGroup(group);
|
||||||
try {
|
try {
|
||||||
|
@ -150,7 +161,7 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Future<AtomicReference<NodeMetadata>> createNodeInGroupWithNameAndTemplate(String group,
|
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);
|
Future<AtomicReference<NodeMetadata>> future = super.createNodeInGroupWithNameAndTemplate(group, name, template);
|
||||||
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions());
|
NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ package org.jclouds.compute;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.ImageTemplate;
|
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
|
* An extension to compute service to allow for the manipulation of {@link Image}s. Implementation
|
||||||
* is optional by providers.
|
* is optional by providers.
|
||||||
|
@ -49,7 +51,7 @@ public interface ImageExtension {
|
||||||
* template to base the new image on
|
* template to base the new image on
|
||||||
* @return the image that was just built *after* it is registered on the provider
|
* @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.
|
* Delete an {@link Image} on the provider.
|
||||||
|
|
|
@ -22,16 +22,22 @@ package org.jclouds.compute.internal;
|
||||||
import static junit.framework.Assert.assertEquals;
|
import static junit.framework.Assert.assertEquals;
|
||||||
import static junit.framework.Assert.assertTrue;
|
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.ComputeService;
|
||||||
import org.jclouds.compute.ImageExtension;
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.RunNodesException;
|
import org.jclouds.compute.RunNodesException;
|
||||||
import org.jclouds.compute.domain.ExecResponse;
|
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.ImageTemplate;
|
import org.jclouds.compute.domain.ImageTemplate;
|
||||||
import org.jclouds.compute.domain.NodeMetadata;
|
import org.jclouds.compute.domain.NodeMetadata;
|
||||||
import org.jclouds.compute.domain.Template;
|
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.jclouds.ssh.SshClient;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@ -47,40 +53,74 @@ import com.google.common.collect.Iterables;
|
||||||
*/
|
*/
|
||||||
public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceContextLiveTest {
|
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.
|
* Returns the template for the base node, override to test different templates.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Template getNodeTemplate() {
|
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)
|
@Test(groups = { "integration", "live" }, singleThreaded = true)
|
||||||
public void testCreateImage() throws RunNodesException, InterruptedException {
|
public void testCreateImage() throws RunNodesException, InterruptedException, ExecutionException {
|
||||||
|
|
||||||
ComputeService computeService = view.getComputeService();
|
ComputeService computeService = view.getComputeService();
|
||||||
|
|
||||||
Optional<ImageExtension> imageExtension = computeService.getImageExtension();
|
Optional<ImageExtension> imageExtension = computeService.getImageExtension();
|
||||||
|
|
||||||
assertTrue("image extension was not present", imageExtension.isPresent());
|
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,
|
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, template));
|
||||||
getNodeTemplate()));
|
|
||||||
|
checkReachable(node);
|
||||||
|
|
||||||
|
logger.info("Creating image from node %s, started with template: %s", node, template);
|
||||||
|
|
||||||
ImageTemplate newImageTemplate = imageExtension.get().buildImageTemplateFromNode("test-create-image",
|
ImageTemplate newImageTemplate = imageExtension.get().buildImageTemplateFromNode("test-create-image",
|
||||||
node.getId());
|
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());
|
assertEquals("test-create-image", image.getName());
|
||||||
|
|
||||||
|
imageId = image.getId();
|
||||||
|
|
||||||
computeService.destroyNode(node.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();
|
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);
|
NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, view
|
||||||
client.connect();
|
.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");
|
checkReachable(node);
|
||||||
|
|
||||||
assertEquals(hello.getOutput().trim(), "hello");
|
|
||||||
|
|
||||||
view.getComputeService().destroyNode(node.getId());
|
view.getComputeService().destroyNode(node.getId());
|
||||||
|
|
||||||
|
@ -123,12 +163,25 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<? extends Image> getImage() {
|
private Optional<? extends Image> getImage() {
|
||||||
return Iterables.tryFind(view.getComputeService().listImages(), new Predicate<Image>() {
|
return Iterables.tryFind(listImages(), new Predicate<Image>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Image input) {
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,8 @@ import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class VirtualBoxImageExtension implements ImageExtension {
|
public class VirtualBoxImageExtension implements ImageExtension {
|
||||||
|
@ -104,7 +106,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.");
|
checkState(template instanceof CloneImageTemplate, " vbox image extension only supports cloning for the moment.");
|
||||||
CloneImageTemplate cloneTemplate = CloneImageTemplate.class.cast(template);
|
CloneImageTemplate cloneTemplate = CloneImageTemplate.class.cast(template);
|
||||||
|
|
||||||
|
@ -133,7 +135,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
|
||||||
// registering
|
// registering
|
||||||
manager.get().getVBox().registerMachine(clonedMachine);
|
manager.get().getVBox().registerMachine(clonedMachine);
|
||||||
|
|
||||||
return imachineToImage.apply(clonedMachine);
|
return Futures.immediateFuture(imachineToImage.apply(clonedMachine));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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.AWSEC2ReviseParsedImage;
|
||||||
import org.jclouds.aws.ec2.compute.strategy.CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions;
|
import org.jclouds.aws.ec2.compute.strategy.CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions;
|
||||||
import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier;
|
import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier;
|
||||||
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.config.BaseComputeServiceContextModule;
|
import org.jclouds.compute.config.BaseComputeServiceContextModule;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.TemplateBuilder;
|
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.AuthorizationException;
|
||||||
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
|
import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
@ -180,4 +182,9 @@ public class AWSEC2ComputeServiceContextModule extends BaseComputeServiceContext
|
||||||
protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
|
protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
|
||||||
return options.as(EC2TemplateOptions.class).userData("#cloud-config\nrepo_upgrade: none\n".getBytes());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,13 @@ import org.jclouds.aws.ec2.functions.ImportOrReturnExistingKeypair;
|
||||||
import org.jclouds.aws.ec2.predicates.PlacementGroupAvailable;
|
import org.jclouds.aws.ec2.predicates.PlacementGroupAvailable;
|
||||||
import org.jclouds.aws.ec2.predicates.PlacementGroupDeleted;
|
import org.jclouds.aws.ec2.predicates.PlacementGroupDeleted;
|
||||||
import org.jclouds.compute.ComputeService;
|
import org.jclouds.compute.ComputeService;
|
||||||
|
import org.jclouds.compute.ImageExtension;
|
||||||
import org.jclouds.compute.domain.Image;
|
import org.jclouds.compute.domain.Image;
|
||||||
import org.jclouds.compute.domain.TemplateBuilder;
|
import org.jclouds.compute.domain.TemplateBuilder;
|
||||||
import org.jclouds.compute.options.TemplateOptions;
|
import org.jclouds.compute.options.TemplateOptions;
|
||||||
import org.jclouds.config.ValueOfConfigurationKeyOrNull;
|
import org.jclouds.config.ValueOfConfigurationKeyOrNull;
|
||||||
import org.jclouds.domain.Credentials;
|
import org.jclouds.domain.Credentials;
|
||||||
|
import org.jclouds.ec2.compute.EC2ImageExtension;
|
||||||
import org.jclouds.ec2.compute.config.EC2ComputeServiceDependenciesModule;
|
import org.jclouds.ec2.compute.config.EC2ComputeServiceDependenciesModule;
|
||||||
import org.jclouds.ec2.compute.domain.RegionAndName;
|
import org.jclouds.ec2.compute.domain.RegionAndName;
|
||||||
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
|
import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
|
||||||
|
@ -94,6 +96,8 @@ public class AWSEC2ComputeServiceDependenciesModule extends EC2ComputeServiceDep
|
||||||
bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() {
|
bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() {
|
||||||
}).to(RegionAndIdToImage.class);
|
}).to(RegionAndIdToImage.class);
|
||||||
install(new FactoryModuleBuilder().build(CallForImages.Factory.class));
|
install(new FactoryModuleBuilder().build(CallForImages.Factory.class));
|
||||||
|
bind(new TypeLiteral<ImageExtension>() {
|
||||||
|
}).to(EC2ImageExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,4 +41,9 @@ public class HPCloudComputeImageExtensionLivetest extends BaseImageExtensionLive
|
||||||
protected Module getSshModule() {
|
protected Module getSshModule() {
|
||||||
return new SshjSshClientModule();
|
return new SshjSshClientModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSpawnNodeMaxWait() {
|
||||||
|
return 2400L;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue