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 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;
|
||||
|
@ -31,6 +32,7 @@ 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;
|
||||
|
@ -43,7 +45,9 @@ import org.jclouds.functions.IdentityFunction;
|
|||
|
||||
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 +81,9 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -112,6 +119,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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -104,6 +106,8 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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;
|
||||
|
||||
@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")
|
||||
long maxWait = 3600;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_WAIT_PERIOD")
|
||||
long waitPeriod = 1;
|
||||
|
||||
@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.imageInZoneToImage = imageInZoneToImage;
|
||||
this.executor = userThreads;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,16 +89,54 @@ 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(
|
||||
final ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
|
||||
|
||||
final 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()));
|
||||
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(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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ 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;
|
||||
|
||||
@Singleton
|
||||
public class VirtualBoxImageExtension implements ImageExtension {
|
||||
|
@ -104,7 +106,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 +135,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
|
|||
// registering
|
||||
manager.get().getVBox().registerMachine(clonedMachine);
|
||||
|
||||
return imachineToImage.apply(clonedMachine);
|
||||
return Futures.immediateFuture(imachineToImage.apply(clonedMachine));
|
||||
}
|
||||
|
||||
@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.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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -94,6 +96,8 @@ 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);
|
||||
}
|
||||
|
||||
@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() {
|
||||
return new SshjSshClientModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSpawnNodeMaxWait() {
|
||||
return 2400L;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue