mirror of https://github.com/apache/jclouds.git
Merge pull request #603 from dralves/image-extension-all
image extension working on hpcloud, virtualbox and aws-ec2, cloudservers...
This commit is contained in:
commit
a6cc00e078
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.cloudservers.compute;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.cloudservers.CloudServersClient;
|
||||
import org.jclouds.cloudservers.domain.Server;
|
||||
import org.jclouds.compute.ImageExtension;
|
||||
import org.jclouds.compute.domain.CloneImageTemplate;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.domain.ImageTemplate;
|
||||
import org.jclouds.compute.domain.ImageTemplateBuilder;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.concurrent.Futures;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.Retryables;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* CloudServers implementation of {@link ImageExtension}
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class CloudServersImageExtension implements ImageExtension {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final CloudServersClient client;
|
||||
private final ExecutorService executor;
|
||||
private final PredicateWithResult<Integer, Image> imageAvailablePredicate;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_MAX_WAIT")
|
||||
private long maxWait = 3600;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_WAIT_PERIOD")
|
||||
private long waitPeriod = 1;
|
||||
|
||||
@Inject
|
||||
public CloudServersImageExtension(CloudServersClient client,
|
||||
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||
PredicateWithResult<Integer, Image> imageAvailablePredicate) {
|
||||
this.client = checkNotNull(client);
|
||||
this.executor = userThreads;
|
||||
this.imageAvailablePredicate = imageAvailablePredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTemplate buildImageTemplateFromNode(String name, final String id) {
|
||||
Server server = client.getServer(Integer.parseInt(id));
|
||||
if (server == null)
|
||||
throw new NoSuchElementException("Cannot find server with id: " + id);
|
||||
CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Image> createImage(ImageTemplate template) {
|
||||
checkState(template instanceof CloneImageTemplate,
|
||||
" openstack-nova only supports creating images through cloning.");
|
||||
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
|
||||
final org.jclouds.cloudservers.domain.Image image = client.createImageFromServer(cloneTemplate.getName(),
|
||||
Integer.parseInt(cloneTemplate.getSourceNodeId()));
|
||||
return Futures.makeListenable(executor.submit(new Callable<Image>() {
|
||||
@Override
|
||||
public Image call() throws Exception {
|
||||
return Retryables.retryGettingResultOrFailing(imageAvailablePredicate, image.getId(), maxWait, waitPeriod,
|
||||
TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: " + maxWait
|
||||
+ " secs.]");
|
||||
}
|
||||
}), executor);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteImage(String id) {
|
||||
try {
|
||||
this.client.deleteImage(Integer.parseInt(id));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,15 +22,18 @@ import java.util.Map;
|
|||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.cloudservers.compute.CloudServersImageExtension;
|
||||
import org.jclouds.cloudservers.compute.functions.CloudServersImageToImage;
|
||||
import org.jclouds.cloudservers.compute.functions.CloudServersImageToOperatingSystem;
|
||||
import org.jclouds.cloudservers.compute.functions.FlavorToHardware;
|
||||
import org.jclouds.cloudservers.compute.functions.ServerToNodeMetadata;
|
||||
import org.jclouds.cloudservers.compute.predicates.GetImageWhenStatusActivePredicateWithResult;
|
||||
import org.jclouds.cloudservers.compute.strategy.CloudServersComputeServiceAdapter;
|
||||
import org.jclouds.cloudservers.domain.Flavor;
|
||||
import org.jclouds.cloudservers.domain.Server;
|
||||
import org.jclouds.cloudservers.domain.ServerStatus;
|
||||
import org.jclouds.compute.ComputeServiceAdapter;
|
||||
import org.jclouds.compute.ImageExtension;
|
||||
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
|
||||
import org.jclouds.compute.domain.Hardware;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
|
@ -40,10 +43,13 @@ import org.jclouds.compute.domain.OperatingSystem;
|
|||
import org.jclouds.compute.internal.BaseComputeService;
|
||||
import org.jclouds.domain.Location;
|
||||
import org.jclouds.functions.IdentityFunction;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.TypeLiteral;
|
||||
|
||||
|
@ -78,6 +84,12 @@ public class CloudServersComputeServiceContextModule extends
|
|||
bind(new TypeLiteral<Function<Location, Location>>() {
|
||||
}).to((Class) IdentityFunction.class);
|
||||
|
||||
bind(new TypeLiteral<ImageExtension>() {
|
||||
}).to(CloudServersImageExtension.class);
|
||||
|
||||
bind(new TypeLiteral<PredicateWithResult<Integer, Image>>() {
|
||||
}).to(GetImageWhenStatusActivePredicateWithResult.class);
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -113,5 +125,9 @@ public class CloudServersComputeServiceContextModule extends
|
|||
return serverToNodeState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ImageExtension> provideImageExtension(Injector i) {
|
||||
return Optional.of(i.getInstance(ImageExtension.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.cloudservers.compute.predicates;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.cloudservers.CloudServersClient;
|
||||
import org.jclouds.cloudservers.options.ListOptions;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
public final class GetImageWhenStatusActivePredicateWithResult implements PredicateWithResult<Integer, Image> {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final CloudServersClient client;
|
||||
private final Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage;
|
||||
private org.jclouds.cloudservers.domain.Image result;
|
||||
private RuntimeException lastFailure;
|
||||
|
||||
@Inject
|
||||
public GetImageWhenStatusActivePredicateWithResult(CloudServersClient client,
|
||||
Function<org.jclouds.cloudservers.domain.Image, Image> cloudserversImageToImage) {
|
||||
this.client = client;
|
||||
this.cloudserversImageToImage = cloudserversImageToImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Integer input) {
|
||||
result = checkNotNull(findImage(input));
|
||||
switch (result.getStatus()) {
|
||||
case ACTIVE:
|
||||
logger.info("<< Image %s is available for use. %s", input, result);
|
||||
return true;
|
||||
case QUEUED:
|
||||
case PREPARING:
|
||||
case SAVING:
|
||||
logger.debug("<< Image %s is not available yet. %s", input, result);
|
||||
return false;
|
||||
default:
|
||||
lastFailure = new IllegalStateException("Image " + input + " was not created. " + result);
|
||||
throw lastFailure;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getResult() {
|
||||
return cloudserversImageToImage.apply(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getLastFailure() {
|
||||
return lastFailure;
|
||||
}
|
||||
|
||||
private org.jclouds.cloudservers.domain.Image findImage(final int id) {
|
||||
return Iterables.tryFind(client.listImages(new ListOptions().withDetails()),
|
||||
new Predicate<org.jclouds.cloudservers.domain.Image>() {
|
||||
@Override
|
||||
public boolean apply(org.jclouds.cloudservers.domain.Image input) {
|
||||
return input.getId() == id;
|
||||
}
|
||||
}).orNull();
|
||||
|
||||
}
|
||||
}
|
|
@ -138,8 +138,8 @@ public class Image {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Image [created=" + created + ", id=" + id + ", name=" + name + ", serverId="
|
||||
+ serverId + "]";
|
||||
return "Image [created=" + created + ", id=" + id + ", name=" + name + ", serverId=" + serverId + ", status="
|
||||
+ status + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,96 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.cloudservers.compute.predicates;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.cloudservers.internal.BaseCloudServersComputeServiceExpectTest;
|
||||
import org.jclouds.compute.ComputeServiceContext;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
@Test(groups = "unit", testName = "GetImageWhenStatusActivePredicateWithResultExpectTest")
|
||||
public class GetImageWhenStatusActivePredicateWithResultExpectTest extends
|
||||
BaseCloudServersComputeServiceExpectTest<Injector> {
|
||||
|
||||
private final HttpRequest listImagesDetail = HttpRequest
|
||||
.builder()
|
||||
.method("GET")
|
||||
.endpoint(
|
||||
URI.create("https://lon.servers.api.rackspacecloud.com/v1.0/10001786/images/detail?format=json&now=1257695648897"))
|
||||
.headers(ImmutableMultimap.<String, String> builder().put(HttpHeaders.ACCEPT, "application/json")
|
||||
.put("X-Auth-Token", authToken).build()).build();
|
||||
|
||||
private final HttpResponse listImagesResponse = HttpResponse.builder().statusCode(200)
|
||||
.payload(payloadFromResource("/test_list_images_detail_imageextension.json")).build();
|
||||
|
||||
private final Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
|
||||
.put(listImagesDetail, listImagesResponse).put(initialAuth, responseWithAuth).build();
|
||||
|
||||
public void testReturnsFalseOnQueuedAndSavingAndTrueOnActive() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<Integer, Image> predicate = injector
|
||||
.getInstance(GetImageWhenStatusActivePredicateWithResult.class);
|
||||
assertTrue(predicate.apply(2));
|
||||
assertFalse(predicate.apply(743));
|
||||
assertFalse(predicate.apply(744));
|
||||
}
|
||||
|
||||
public void testFailsOnOtherStatuses() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<Integer, Image> predicate = injector
|
||||
.getInstance(GetImageWhenStatusActivePredicateWithResult.class);
|
||||
assertTrue(illegalStateExceptionThrown(predicate, 745));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, 746));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, 747));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, 748));
|
||||
}
|
||||
|
||||
private boolean illegalStateExceptionThrown(PredicateWithResult<Integer, Image> predicate, Integer id) {
|
||||
try {
|
||||
predicate.apply(id);
|
||||
} catch (IllegalStateException e) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Injector apply(ComputeServiceContext input) {
|
||||
return input.utils().injector();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.cloudservers.internal;
|
||||
|
||||
import static org.jclouds.location.reference.LocationConstants.PROPERTY_REGIONS;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.jclouds.apis.ApiMetadata;
|
||||
import org.jclouds.cloudservers.CloudServersApiMetadata;
|
||||
import org.jclouds.cloudservers.config.CloudServersRestClientModule;
|
||||
import org.jclouds.compute.ComputeServiceContext;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.openstack.keystone.v1_1.config.AuthenticationServiceModule;
|
||||
import org.jclouds.openstack.keystone.v1_1.internal.BaseKeystoneRestClientExpectTest;
|
||||
import org.jclouds.rest.ConfiguresRestClient;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.inject.Module;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
public abstract class BaseCloudServersComputeServiceExpectTest<T> extends BaseKeystoneRestClientExpectTest<T> implements
|
||||
Function<ComputeServiceContext, T> {
|
||||
|
||||
public BaseCloudServersComputeServiceExpectTest() {
|
||||
provider = "cloudservers";
|
||||
}
|
||||
|
||||
protected static final String CONSTANT_DATE = "2009-11-08T15:54:08.897Z";
|
||||
|
||||
public static class TestAuthenticationServiceModule extends AuthenticationServiceModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Module createModule() {
|
||||
return new TestCloudServersRestClientModule();
|
||||
}
|
||||
|
||||
@ConfiguresRestClient
|
||||
protected static class TestCloudServersRestClientModule extends CloudServersRestClientModule {
|
||||
|
||||
@Override
|
||||
public Supplier<Date> provideCacheBusterDate() {
|
||||
return new Supplier<Date>() {
|
||||
public Date get() {
|
||||
return new SimpleDateFormatDateService().iso8601DateParse(CONSTANT_DATE);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApiMetadata createApiMetadata() {
|
||||
return new CloudServersApiMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties setupProperties() {
|
||||
Properties overrides = new Properties();
|
||||
overrides.setProperty(PROPERTY_REGIONS, "US");
|
||||
overrides.setProperty(provider + ".endpoint", endpoint);
|
||||
return overrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
return apply(createComputeServiceContext(fn, module, props));
|
||||
}
|
||||
|
||||
private ComputeServiceContext createComputeServiceContext(Function<HttpRequest, HttpResponse> fn, Module module,
|
||||
Properties props) {
|
||||
return createInjector(fn, module, props).getInstance(ComputeServiceContext.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"id" : 2,
|
||||
"name" : "CentOS 5.2",
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2010-08-10T12:00:00Z",
|
||||
"status" : "ACTIVE"
|
||||
},
|
||||
{
|
||||
"id" : 743,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "SAVING",
|
||||
"progress" : 80
|
||||
}
|
||||
,
|
||||
{
|
||||
"id" : 744,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "QUEUED"
|
||||
}
|
||||
,
|
||||
{
|
||||
"id" : 745,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "UNRECOGNIZED"
|
||||
}
|
||||
,
|
||||
{
|
||||
"id" : 746,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "UNKNOWN"
|
||||
}
|
||||
,
|
||||
{
|
||||
"id" : 747,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "PREPARING"
|
||||
}
|
||||
,
|
||||
{
|
||||
"id" : 748,
|
||||
"name" : "My Server Backup",
|
||||
"serverId" : 12,
|
||||
"updated" : "2010-10-10T12:00:00Z",
|
||||
"created" : "2009-07-07T09:56:16-05:00",
|
||||
"status" : "FAILED"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.ec2.compute;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.aws.util.AWSUtils;
|
||||
import org.jclouds.compute.ImageExtension;
|
||||
import org.jclouds.compute.domain.CloneImageTemplate;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.domain.ImageTemplate;
|
||||
import org.jclouds.compute.domain.ImageTemplateBuilder;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.concurrent.Futures;
|
||||
import org.jclouds.ec2.EC2Client;
|
||||
import org.jclouds.ec2.domain.Reservation;
|
||||
import org.jclouds.ec2.domain.RunningInstance;
|
||||
import org.jclouds.ec2.options.CreateImageOptions;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.Retryables;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* EC2 implementation of {@link ImageExtension} please note that {@link #createImage(ImageTemplate)}
|
||||
* only works by cloning EBS backed instances for the moment.
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
public class EC2ImageExtension implements ImageExtension {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
private final EC2Client ec2Client;
|
||||
private final ExecutorService executor;
|
||||
private final PredicateWithResult<String, Image> imageReadyPredicate;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_MAX_WAIT")
|
||||
private long maxWait = 3600;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_WAIT_PERIOD")
|
||||
private long waitPeriod = 1;
|
||||
|
||||
@Inject
|
||||
public EC2ImageExtension(EC2Client ec2Client, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||
PredicateWithResult<String, Image> imageReadyPredicate) {
|
||||
this.ec2Client = checkNotNull(ec2Client);
|
||||
this.executor = checkNotNull(userThreads);
|
||||
this.imageReadyPredicate = imageReadyPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTemplate buildImageTemplateFromNode(String name, String id) {
|
||||
String[] parts = AWSUtils.parseHandle(id);
|
||||
String region = parts[0];
|
||||
String instanceId = parts[1];
|
||||
Reservation<? extends RunningInstance> instance = Iterables.getOnlyElement(ec2Client.getInstanceServices()
|
||||
.describeInstancesInRegion(region, instanceId));
|
||||
if (instance == null)
|
||||
throw new NoSuchElementException("Cannot find server with id: " + id);
|
||||
CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Image> createImage(ImageTemplate template) {
|
||||
checkState(template instanceof CloneImageTemplate, " ec2 only supports creating images through cloning.");
|
||||
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
|
||||
String[] parts = AWSUtils.parseHandle(cloneTemplate.getSourceNodeId());
|
||||
final String region = parts[0];
|
||||
String instanceId = parts[1];
|
||||
|
||||
final String imageId = ec2Client.getAMIServices().createImageInRegion(region, cloneTemplate.getName(),
|
||||
instanceId, CreateImageOptions.NONE);
|
||||
|
||||
return Futures.makeListenable(executor.submit(new Callable<Image>() {
|
||||
@Override
|
||||
public Image call() throws Exception {
|
||||
return Retryables.retryGettingResultOrFailing(imageReadyPredicate, region + "/" + imageId, maxWait,
|
||||
waitPeriod, TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: "
|
||||
+ maxWait + " secs.]");
|
||||
}
|
||||
}), executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteImage(String id) {
|
||||
String[] parts = AWSUtils.parseHandle(id);
|
||||
String region = parts[0];
|
||||
String instanceId = parts[1];
|
||||
try {
|
||||
ec2Client.getAMIServices().deregisterImageInRegion(region, instanceId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
@ -137,5 +140,10 @@ public class EC2ComputeServiceContextModule extends BaseComputeServiceContextMod
|
|||
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;
|
||||
|
@ -47,11 +49,13 @@ import org.jclouds.ec2.compute.loaders.CreateSecurityGroupIfNeeded;
|
|||
import org.jclouds.ec2.compute.loaders.LoadPublicIpForInstanceOrNull;
|
||||
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
|
||||
import org.jclouds.ec2.compute.options.EC2TemplateOptions;
|
||||
import org.jclouds.ec2.compute.predicates.GetImageWhenStatusAvailablePredicateWithResult;
|
||||
import org.jclouds.ec2.compute.predicates.SecurityGroupPresent;
|
||||
import org.jclouds.ec2.domain.InstanceState;
|
||||
import org.jclouds.ec2.domain.KeyPair;
|
||||
import org.jclouds.ec2.domain.RunningInstance;
|
||||
import org.jclouds.ec2.reference.EC2Constants;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -104,6 +108,10 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
|
|||
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
|
||||
}).annotatedWith(Names.named("ELASTICIP")).to(LoadPublicIpForInstanceOrNull.class);
|
||||
bind(WindowsLoginCredentialsFromEncryptedData.class);
|
||||
bind(new TypeLiteral<ImageExtension>() {
|
||||
}).to(EC2ImageExtension.class);
|
||||
bind(new TypeLiteral<PredicateWithResult<String, Image>>() {
|
||||
}).to(GetImageWhenStatusAvailablePredicateWithResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.ec2.compute.predicates;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.aws.util.AWSUtils;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.ec2.EC2Client;
|
||||
import org.jclouds.ec2.compute.functions.EC2ImageParser;
|
||||
import org.jclouds.ec2.options.DescribeImagesOptions;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* §
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
public final class GetImageWhenStatusAvailablePredicateWithResult implements PredicateWithResult<String, Image> {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final EC2Client ec2Client;
|
||||
private final EC2ImageParser ec2ImageToImage;
|
||||
private org.jclouds.ec2.domain.Image result;
|
||||
private RuntimeException lastFailure;
|
||||
|
||||
@Inject
|
||||
public GetImageWhenStatusAvailablePredicateWithResult(EC2Client ec2Client, EC2ImageParser ec2ImageToImage) {
|
||||
this.ec2Client = ec2Client;
|
||||
this.ec2ImageToImage = ec2ImageToImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
String[] parts = AWSUtils.parseHandle(input);
|
||||
String region = parts[0];
|
||||
String imageId = parts[1];
|
||||
result = checkNotNull(findImage(imageId, region));
|
||||
switch (result.getImageState()) {
|
||||
case AVAILABLE:
|
||||
logger.info("<< Image %s is available for use.", input);
|
||||
return true;
|
||||
case UNRECOGNIZED:
|
||||
logger.debug("<< Image %s is not available yet.", input);
|
||||
return false;
|
||||
default:
|
||||
lastFailure = new IllegalStateException("Image was not created: " + input);
|
||||
throw lastFailure;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getResult() {
|
||||
return ec2ImageToImage.apply(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getLastFailure() {
|
||||
return lastFailure;
|
||||
}
|
||||
|
||||
private org.jclouds.ec2.domain.Image findImage(String id, String region) {
|
||||
return Iterables.getOnlyElement(ec2Client.getAMIServices().describeImagesInRegion(region,
|
||||
new DescribeImagesOptions().imageIds(id)));
|
||||
|
||||
}
|
||||
}
|
|
@ -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,88 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.ec2.compute;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.apis.ApiMetadata;
|
||||
import org.jclouds.compute.ComputeServiceContext;
|
||||
import org.jclouds.date.DateService;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.ec2.EC2ApiMetadata;
|
||||
import org.jclouds.ec2.EC2AsyncClient;
|
||||
import org.jclouds.ec2.EC2Client;
|
||||
import org.jclouds.ec2.config.EC2RestClientModule;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.rest.ConfiguresRestClient;
|
||||
import org.jclouds.rest.internal.BaseRestClientExpectTest;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*/
|
||||
public abstract class BaseEC2ComputeServiceExpectTest<T> extends BaseRestClientExpectTest<T> implements
|
||||
Function<ComputeServiceContext, T> {
|
||||
|
||||
protected static final String CONSTANT_DATE = "2012-04-16T15:54:08.897Z";
|
||||
protected DateService dateService = new SimpleDateFormatDateService();
|
||||
|
||||
public BaseEC2ComputeServiceExpectTest() {
|
||||
provider = "ec2";
|
||||
}
|
||||
|
||||
@ConfiguresRestClient
|
||||
private static final class TestEC2RestClientModule extends EC2RestClientModule<EC2Client, EC2AsyncClient> {
|
||||
@Override
|
||||
@Provides
|
||||
protected String provideTimeStamp(final DateService dateService,
|
||||
@Named(Constants.PROPERTY_SESSION_INTERVAL) final int expiration) {
|
||||
return CONSTANT_DATE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
return apply(createComputeServiceContext(fn, module, props));
|
||||
}
|
||||
|
||||
private ComputeServiceContext createComputeServiceContext(Function<HttpRequest, HttpResponse> fn, Module module,
|
||||
Properties props) {
|
||||
return createInjector(fn, module, props).getInstance(ComputeServiceContext.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Module createModule() {
|
||||
return new TestEC2RestClientModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApiMetadata createApiMetadata() {
|
||||
return new EC2ApiMetadata();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.ec2.compute.predicates;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.jclouds.compute.ComputeServiceContext;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.ec2.compute.BaseEC2ComputeServiceExpectTest;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*/
|
||||
@Test(groups = "unit", testName = "GetImageWhenStatusAvailablePredicateWithResultExpectTest")
|
||||
public class GetImageWhenStatusAvailablePredicateWithResultExpectTest extends BaseEC2ComputeServiceExpectTest<Injector> {
|
||||
|
||||
protected HttpRequest describeRegionsRequest = HttpRequest
|
||||
.builder()
|
||||
.method("POST")
|
||||
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
|
||||
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
|
||||
.payload(payloadFromStringWithContentType(
|
||||
"Action=DescribeRegions&Signature=s5OXKqaaeKhJW5FVrRntuMsUL4Ed5fjzgUWeukU96ko%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
|
||||
"application/x-www-form-urlencoded")).build();
|
||||
|
||||
protected HttpRequest describeImagesRequest0 = HttpRequest
|
||||
.builder()
|
||||
.method("POST")
|
||||
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
|
||||
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
|
||||
.payload(payloadFromStringWithContentType(
|
||||
"Action=DescribeImages&ImageId.1=ami-0&Signature=k9douTXFWkAZecPiZfBLUm3LIS3bTLanMV%2F%2BWrB1jFA%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
|
||||
"application/x-www-form-urlencoded")).build();
|
||||
|
||||
protected HttpRequest describeImagesRequest1 = HttpRequest
|
||||
.builder()
|
||||
.method("POST")
|
||||
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
|
||||
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
|
||||
.payload(payloadFromStringWithContentType(
|
||||
"Action=DescribeImages&ImageId.1=ami-1&Signature=IVunQEvp8vTKTIxXex2Uh5SWQY1PJCx0ExUe9FRujBY%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
|
||||
"application/x-www-form-urlencoded")).build();
|
||||
|
||||
protected HttpRequest describeImagesRequest2 = HttpRequest
|
||||
.builder()
|
||||
.method("POST")
|
||||
.endpoint(URI.create("https://ec2.us-east-1.amazonaws.com/"))
|
||||
.headers(ImmutableMultimap.of("Host", "ec2.us-east-1.amazonaws.com"))
|
||||
.payload(payloadFromStringWithContentType(
|
||||
"Action=DescribeImages&ImageId.1=ami-2&Signature=8TfP8BJlg1hiY6EqUbS73A7PQO7dlpqnRMyi7hPu76U%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2010-06-15&AWSAccessKeyId=identity",
|
||||
"application/x-www-form-urlencoded")).build();
|
||||
|
||||
protected HttpResponse describeRegionsResponse = HttpResponse.builder().statusCode(200)
|
||||
.payload(payloadFromResourceWithContentType("/regionEndpoints.xml", MediaType.APPLICATION_XML)).build();
|
||||
|
||||
protected HttpResponse describeImagesResponse0 = HttpResponse
|
||||
.builder()
|
||||
.statusCode(200)
|
||||
.payload(payloadFromResourceWithContentType("/describe_images_imageextension0.xml",
|
||||
MediaType.APPLICATION_XML)).build();
|
||||
|
||||
protected HttpResponse describeImagesResponse1 = HttpResponse
|
||||
.builder()
|
||||
.statusCode(200)
|
||||
.payload(payloadFromResourceWithContentType("/describe_images_imageextension1.xml",
|
||||
MediaType.APPLICATION_XML)).build();
|
||||
|
||||
protected HttpResponse describeImagesResponse2 = HttpResponse
|
||||
.builder()
|
||||
.statusCode(200)
|
||||
.payload(payloadFromResourceWithContentType("/describe_images_imageextension2.xml",
|
||||
MediaType.APPLICATION_XML)).build();
|
||||
|
||||
private final Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
|
||||
.put(describeRegionsRequest, describeRegionsResponse).put(describeImagesRequest0, describeImagesResponse0)
|
||||
.put(describeImagesRequest1, describeImagesResponse1).put(describeImagesRequest2, describeImagesResponse2)
|
||||
.build();
|
||||
|
||||
public void testReturnsFalseOnQueuedAndSavingAndTrueOnActive() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<String, Image> predicate = injector
|
||||
.getInstance(GetImageWhenStatusAvailablePredicateWithResult.class);
|
||||
assertTrue(predicate.apply("us-east-1/ami-0"));
|
||||
assertFalse(predicate.apply("us-east-1/ami-2"));
|
||||
}
|
||||
|
||||
@Test(groups = "unit", testName = "GetImageWhenStatusAvailablePredicateWithResultExpectTest", expectedExceptions = IllegalStateException.class)
|
||||
public void testFailsOnOtherStatuses() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<String, Image> predicate = injector
|
||||
.getInstance(GetImageWhenStatusAvailablePredicateWithResult.class);
|
||||
predicate.apply("us-east-1/ami-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Injector apply(ComputeServiceContext input) {
|
||||
return input.utils().injector();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
|
||||
<imagesSet>
|
||||
<item>
|
||||
<imageId>ami-0</imageId>
|
||||
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
|
||||
</imageLocation>
|
||||
<imageState>available</imageState>
|
||||
<imageOwnerId>206029621532</imageOwnerId>
|
||||
<isPublic>false</isPublic>
|
||||
<productCodes>
|
||||
<item>
|
||||
<productCode>9961934F</productCode>
|
||||
</item>
|
||||
</productCodes>
|
||||
<architecture>i386</architecture>
|
||||
<imageType>machine</imageType>
|
||||
<kernelId>aki-4438dd2d</kernelId>
|
||||
<ramdiskId>ari-4538dd2c</ramdiskId>
|
||||
<rootDeviceType>instance-store</rootDeviceType>
|
||||
<blockDeviceMapping />
|
||||
<virtualizationType>paravirtual</virtualizationType>
|
||||
</item>
|
||||
</imagesSet>
|
||||
</DescribeImagesResponse>
|
|
@ -0,0 +1,24 @@
|
|||
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
|
||||
<imagesSet>
|
||||
<item>
|
||||
<imageId>ami-1</imageId>
|
||||
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
|
||||
</imageLocation>
|
||||
<imageState>deregistered</imageState>
|
||||
<imageOwnerId>206029621532</imageOwnerId>
|
||||
<isPublic>false</isPublic>
|
||||
<productCodes>
|
||||
<item>
|
||||
<productCode>9961934F</productCode>
|
||||
</item>
|
||||
</productCodes>
|
||||
<architecture>i386</architecture>
|
||||
<imageType>machine</imageType>
|
||||
<kernelId>aki-4438dd2d</kernelId>
|
||||
<ramdiskId>ari-4538dd2c</ramdiskId>
|
||||
<rootDeviceType>instance-store</rootDeviceType>
|
||||
<blockDeviceMapping />
|
||||
<virtualizationType>paravirtual</virtualizationType>
|
||||
</item>
|
||||
</imagesSet>
|
||||
</DescribeImagesResponse>
|
|
@ -0,0 +1,24 @@
|
|||
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2009-11-30/">
|
||||
<imagesSet>
|
||||
<item>
|
||||
<imageId>ami-2</imageId>
|
||||
<imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml
|
||||
</imageLocation>
|
||||
<imageState>unrecognized</imageState>
|
||||
<imageOwnerId>206029621532</imageOwnerId>
|
||||
<isPublic>false</isPublic>
|
||||
<productCodes>
|
||||
<item>
|
||||
<productCode>9961934F</productCode>
|
||||
</item>
|
||||
</productCodes>
|
||||
<architecture>i386</architecture>
|
||||
<imageType>machine</imageType>
|
||||
<kernelId>aki-4438dd2d</kernelId>
|
||||
<ramdiskId>ari-4538dd2c</ramdiskId>
|
||||
<rootDeviceType>instance-store</rootDeviceType>
|
||||
<blockDeviceMapping />
|
||||
<virtualizationType>paravirtual</virtualizationType>
|
||||
</item>
|
||||
</imagesSet>
|
||||
</DescribeImagesResponse>
|
|
@ -21,17 +21,21 @@ import com.google.inject.Provides;
|
|||
|
||||
public abstract class BaseNovaEC2RestClientExpectTest extends BaseRestClientExpectTest<NovaEC2Client> {
|
||||
protected static final String CONSTANT_DATE = "2012-04-16T15:54:08.897Z";
|
||||
|
||||
protected DateService dateService = new SimpleDateFormatDateService();
|
||||
protected URI endpoint = URI.create("http://localhost:8773/services/Cloud/");
|
||||
|
||||
protected HttpRequest describeAvailabilityZonesRequest = HttpRequest.builder().method("POST")
|
||||
protected HttpRequest describeAvailabilityZonesRequest = HttpRequest
|
||||
.builder()
|
||||
.method("POST")
|
||||
.endpoint(endpoint)
|
||||
.headers(ImmutableMultimap.of("Host", "localhost:8773"))
|
||||
.payload(payloadFromStringWithContentType("Action=DescribeAvailabilityZones&Signature=S3fa5fybw4KAq4o11IpKHlqwx3cVJdKfeAKw3FIJYvM%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2009-04-04&AWSAccessKeyId=identity", MediaType.APPLICATION_FORM_URLENCODED))
|
||||
.payload(payloadFromStringWithContentType(
|
||||
"Action=DescribeAvailabilityZones&Signature=s5OXKqaaeKhJW5FVrRntuMsUL4Ed5fjzgUWeukU96ko%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-04-16T15%3A54%3A08.897Z&Version=2009-04-04&AWSAccessKeyId=identity",
|
||||
MediaType.APPLICATION_FORM_URLENCODED)).build();
|
||||
protected HttpResponse describeAvailabilityZonesResponse = HttpResponse.builder().statusCode(200)
|
||||
.payload(payloadFromResourceWithContentType("/nova_ec2_availabilityZones.xml", MediaType.APPLICATION_XML))
|
||||
.build();
|
||||
protected HttpResponse describeAvailabilityZonesResponse = HttpResponse.builder()
|
||||
.statusCode(200).payload(payloadFromResourceWithContentType("/nova_ec2_availabilityZones.xml", MediaType.APPLICATION_XML)).build();
|
||||
|
||||
|
||||
public BaseNovaEC2RestClientExpectTest() {
|
||||
provider = "openstack-nova-ec2";
|
||||
|
|
|
@ -23,34 +23,62 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.compute.ImageExtension;
|
||||
import org.jclouds.compute.domain.CloneImageTemplate;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.domain.ImageTemplate;
|
||||
import org.jclouds.compute.domain.ImageTemplateBuilder;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.concurrent.Futures;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.openstack.nova.v1_1.NovaClient;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.Server;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.Retryables;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Nova implementation of {@link ImageExtension}
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class NovaImageExtension implements ImageExtension {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final NovaClient novaClient;
|
||||
private final Function<ImageInZone, Image> imageInZoneToImage;
|
||||
private final ExecutorService executor;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_MAX_WAIT")
|
||||
private long maxWait = 3600;
|
||||
@com.google.inject.Inject(optional = true)
|
||||
@Named("IMAGE_WAIT_PERIOD")
|
||||
private long waitPeriod = 1;
|
||||
private PredicateWithResult<ZoneAndId, Image> imageReadyPredicate;
|
||||
|
||||
@Inject
|
||||
public NovaImageExtension(NovaClient novaClient, Function<ImageInZone, Image> imageInZoneToImage) {
|
||||
public NovaImageExtension(NovaClient novaClient,
|
||||
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||
PredicateWithResult<ZoneAndId, Image> imageReadyPredicate) {
|
||||
this.novaClient = checkNotNull(novaClient);
|
||||
this.imageInZoneToImage = imageInZoneToImage;
|
||||
this.executor = userThreads;
|
||||
this.imageReadyPredicate = imageReadyPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,16 +92,27 @@ public class NovaImageExtension implements ImageExtension {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(ImageTemplate template) {
|
||||
public ListenableFuture<Image> createImage(ImageTemplate template) {
|
||||
checkState(template instanceof CloneImageTemplate,
|
||||
" openstack-nova only supports creating images through cloning.");
|
||||
CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
|
||||
ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
|
||||
String newImageId = novaClient.getServerClientForZone(zoneAndId.getZone()).createImageFromServer(
|
||||
cloneTemplate.getName(), zoneAndId.getId());
|
||||
org.jclouds.openstack.nova.v1_1.domain.Image newImage = checkNotNull(findImage(ZoneAndId.fromZoneAndId(
|
||||
zoneAndId.getZone(), newImageId)));
|
||||
return imageInZoneToImage.apply(new ImageInZone(newImage, zoneAndId.getZone()));
|
||||
ZoneAndId sourceImageZoneAndId = ZoneAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
|
||||
|
||||
String newImageId = novaClient.getServerClientForZone(sourceImageZoneAndId.getZone()).createImageFromServer(
|
||||
cloneTemplate.getName(), sourceImageZoneAndId.getId());
|
||||
|
||||
final ZoneAndId targetImageZoneAndId = ZoneAndId.fromZoneAndId(sourceImageZoneAndId.getZone(), newImageId);
|
||||
|
||||
logger.info(">> Registered new Image %s, waiting for it to become available.", newImageId);
|
||||
|
||||
return Futures.makeListenable(executor.submit(new Callable<Image>() {
|
||||
@Override
|
||||
public Image call() throws Exception {
|
||||
return Retryables.retryGettingResultOrFailing(imageReadyPredicate, targetImageZoneAndId, maxWait,
|
||||
waitPeriod, TimeUnit.SECONDS, "Image was not created within the time limit, Giving up! [Limit: "
|
||||
+ maxWait + " secs.]");
|
||||
}
|
||||
}), executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,15 +126,4 @@ public class NovaImageExtension implements ImageExtension {
|
|||
return true;
|
||||
}
|
||||
|
||||
private org.jclouds.openstack.nova.v1_1.domain.Image findImage(final ZoneAndId zoneAndId) {
|
||||
return Iterables.tryFind(novaClient.getImageClientForZone(zoneAndId.getZone()).listImagesInDetail(),
|
||||
new Predicate<org.jclouds.openstack.nova.v1_1.domain.Image>() {
|
||||
@Override
|
||||
public boolean apply(org.jclouds.openstack.nova.v1_1.domain.Image input) {
|
||||
return input.getId().equals(zoneAndId.getId());
|
||||
}
|
||||
}).orNull();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ import org.jclouds.openstack.nova.v1_1.compute.loaders.CreateUniqueKeyPair;
|
|||
import org.jclouds.openstack.nova.v1_1.compute.loaders.FindSecurityGroupOrCreate;
|
||||
import org.jclouds.openstack.nova.v1_1.compute.loaders.LoadFloatingIpsForInstance;
|
||||
import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions;
|
||||
import org.jclouds.openstack.nova.v1_1.compute.predicates.GetImageWhenImageInZoneHasActiveStatusPredicateWithResult;
|
||||
import org.jclouds.openstack.nova.v1_1.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.KeyPair;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone;
|
||||
|
@ -68,6 +69,7 @@ import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
|
|||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts;
|
||||
import org.jclouds.openstack.nova.v1_1.predicates.FindSecurityGroupWithNameAndReturnTrue;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -141,6 +143,9 @@ public class NovaComputeServiceContextModule extends
|
|||
|
||||
bind(new TypeLiteral<ImageExtension>() {
|
||||
}).to(NovaImageExtension.class);
|
||||
|
||||
bind(new TypeLiteral<PredicateWithResult<ZoneAndId, Image>>() {
|
||||
}).to(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.openstack.nova.v1_1.compute.predicates;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.openstack.nova.v1_1.NovaClient;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* @author David Alves
|
||||
*/
|
||||
public final class GetImageWhenImageInZoneHasActiveStatusPredicateWithResult implements
|
||||
PredicateWithResult<ZoneAndId, Image> {
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private org.jclouds.openstack.nova.v1_1.domain.Image result;
|
||||
private ZoneAndId resultZoneAndId;
|
||||
private RuntimeException lastFailure;
|
||||
private Function<ImageInZone, Image> imageInZoneToImage;
|
||||
private NovaClient client;
|
||||
|
||||
@Inject
|
||||
public GetImageWhenImageInZoneHasActiveStatusPredicateWithResult(Function<ImageInZone, Image> imageInZoneToImage,
|
||||
NovaClient client) {
|
||||
this.imageInZoneToImage = imageInZoneToImage;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(ZoneAndId input) {
|
||||
result = checkNotNull(findImage(ZoneAndId.fromZoneAndId(input.getZone(), input.getId())));
|
||||
resultZoneAndId = input;
|
||||
switch (result.getStatus()) {
|
||||
case ACTIVE:
|
||||
logger.info("<< Image %s is available for use. %s", input.getId(), result);
|
||||
return true;
|
||||
case UNRECOGNIZED:
|
||||
case SAVING:
|
||||
logger.debug("<< Image %s is not available yet. %s", input.getId(), result);
|
||||
return false;
|
||||
default:
|
||||
lastFailure = new IllegalStateException("Image " + input.getId() + " was not created. " + result);
|
||||
throw lastFailure;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getResult() {
|
||||
return imageInZoneToImage.apply(new ImageInZone(result, resultZoneAndId.getZone()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getLastFailure() {
|
||||
return lastFailure;
|
||||
}
|
||||
|
||||
public org.jclouds.openstack.nova.v1_1.domain.Image findImage(final ZoneAndId zoneAndId) {
|
||||
return Iterables.tryFind(client.getImageClientForZone(zoneAndId.getZone()).listImagesInDetail(),
|
||||
new Predicate<org.jclouds.openstack.nova.v1_1.domain.Image>() {
|
||||
@Override
|
||||
public boolean apply(org.jclouds.openstack.nova.v1_1.domain.Image input) {
|
||||
return input.getId().equals(zoneAndId.getId());
|
||||
}
|
||||
}).orNull();
|
||||
|
||||
}
|
||||
}
|
|
@ -97,8 +97,18 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
|
|||
@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();
|
||||
|
||||
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";
|
||||
|
@ -107,7 +117,8 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
|
|||
|
||||
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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jclouds.openstack.nova.v1_1.compute.predicates;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.compute.ComputeServiceContext;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId;
|
||||
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaComputeServiceContextExpectTest;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
@Test(groups = "unit", testName = "GetImageWhenImageInZoneHasActiveStatucPredicateWithResultExpectTest")
|
||||
public class GetImageWhenImageInZoneHasActiveStatusPredicateWithResultExpectTest extends
|
||||
BaseNovaComputeServiceContextExpectTest<Injector> {
|
||||
|
||||
private final HttpResponse listImagesDetailImageExtensionResponse = HttpResponse.builder().statusCode(200)
|
||||
.payload(payloadFromResource("/image_list_detail_imageextension.json")).build();
|
||||
|
||||
private Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
|
||||
.put(keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess)
|
||||
.put(listImagesDetail, listImagesDetailImageExtensionResponse).build();
|
||||
|
||||
public void testReturnsFalseOnImageStatusSavingAndTrueOnActive() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<ZoneAndId, Image> predicate = injector
|
||||
.getInstance(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class);
|
||||
ZoneAndId zoneAdnId0 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "13");
|
||||
ZoneAndId zoneAdnId1 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "12");
|
||||
assertTrue(!predicate.apply(zoneAdnId1));
|
||||
assertTrue(predicate.apply(zoneAdnId0));
|
||||
assertEquals("natty-server-cloudimg-amd64", predicate.getResult().getName());
|
||||
}
|
||||
|
||||
public void testFailsOnOtherStatuses() {
|
||||
Injector injector = requestsSendResponses(requestResponseMap);
|
||||
PredicateWithResult<ZoneAndId, Image> predicate = injector
|
||||
.getInstance(GetImageWhenImageInZoneHasActiveStatusPredicateWithResult.class);
|
||||
ZoneAndId zoneAdnId0 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "15");
|
||||
ZoneAndId zoneAdnId1 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "14");
|
||||
ZoneAndId zoneAdnId2 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "11");
|
||||
ZoneAndId zoneAdnId3 = ZoneAndId.fromZoneAndId("az-1.region-a.geo-1", "10");
|
||||
assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId0));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId1));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId2));
|
||||
assertTrue(illegalStateExceptionThrown(predicate, zoneAdnId3));
|
||||
}
|
||||
|
||||
private boolean illegalStateExceptionThrown(PredicateWithResult<ZoneAndId, Image> predicate, ZoneAndId zoneAndId) {
|
||||
try {
|
||||
predicate.apply(zoneAndId);
|
||||
} catch (IllegalStateException e) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Injector apply(ComputeServiceContext input) {
|
||||
return input.utils().injector();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
"images": [{
|
||||
"status": "UNRECOGNIZED",
|
||||
"updated": "2012-02-02T19:11:00Z",
|
||||
"name": "oneiric-server-cloudimg-amd64",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/15",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/15",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:52Z",
|
||||
"progress": 100,
|
||||
"id": "15",
|
||||
"metadata": {
|
||||
"kernel_id": "14",
|
||||
"min_disk": 0,
|
||||
"min_ram": 0,
|
||||
"owner": "1"
|
||||
}
|
||||
}, {
|
||||
"status": "UNKNOWN",
|
||||
"updated": "2012-02-02T19:10:51Z",
|
||||
"name": "oneiric-server-cloudimg-amd64-kernel",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/14",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/14",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:50Z",
|
||||
"progress": 100,
|
||||
"id": "14",
|
||||
"metadata": {
|
||||
"min_disk": 0,
|
||||
"owner": "1",
|
||||
"min_ram": 0
|
||||
}
|
||||
}, {
|
||||
"status": "ACTIVE",
|
||||
"updated": "2012-02-02T19:10:41Z",
|
||||
"name": "natty-server-cloudimg-amd64",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/13",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/13",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:33Z",
|
||||
"progress": 100,
|
||||
"id": "13",
|
||||
"metadata": {
|
||||
"kernel_id": "12",
|
||||
"min_disk": 0,
|
||||
"min_ram": 0,
|
||||
"owner": "1"
|
||||
}
|
||||
}, {
|
||||
"status": "SAVING",
|
||||
"updated": "2012-02-02T19:10:33Z",
|
||||
"name": "natty-server-cloudimg-amd64-kernel",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/12",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/12",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:32Z",
|
||||
"progress": 100,
|
||||
"id": "12",
|
||||
"metadata": {
|
||||
"min_disk": 0,
|
||||
"owner": "1",
|
||||
"min_ram": 0
|
||||
}
|
||||
}, {
|
||||
"status": "ERROR",
|
||||
"updated": "2012-02-02T19:10:41Z",
|
||||
"name": "natty-server-cloudimg-amd64",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/11",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/11",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:33Z",
|
||||
"progress": 100,
|
||||
"id": "11",
|
||||
"metadata": {
|
||||
"kernel_id": "12",
|
||||
"min_disk": 0,
|
||||
"min_ram": 0,
|
||||
"owner": "1"
|
||||
}
|
||||
}, {
|
||||
"status": "ERROR",
|
||||
"updated": "2012-02-02T19:10:41Z",
|
||||
"name": "natty-server-cloudimg-amd64",
|
||||
"links": [{
|
||||
"href": "https://nova-api.trystack.org:9774/v1.1/37/images/10",
|
||||
"rel": "self"
|
||||
}, {
|
||||
"href": "https://nova-api.trystack.org:9774/37/images/10",
|
||||
"rel": "bookmark"
|
||||
}],
|
||||
"created": "2012-02-02T19:10:33Z",
|
||||
"progress": 100,
|
||||
"id": "10",
|
||||
"metadata": {
|
||||
"kernel_id": "12",
|
||||
"min_disk": 0,
|
||||
"min_ram": 0,
|
||||
"owner": "1"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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,7 +60,15 @@ import com.google.common.base.Optional;
|
|||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Virtualbox implementation of {@link ImageExtension}
|
||||
*
|
||||
* @author David Alves
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class VirtualBoxImageExtension implements ImageExtension {
|
||||
|
||||
|
@ -104,7 +112,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(ImageTemplate template) {
|
||||
public ListenableFuture<Image> createImage(ImageTemplate template) {
|
||||
checkState(template instanceof CloneImageTemplate, " vbox image extension only supports cloning for the moment.");
|
||||
CloneImageTemplate cloneTemplate = CloneImageTemplate.class.cast(template);
|
||||
|
||||
|
@ -133,7 +141,7 @@ public class VirtualBoxImageExtension implements ImageExtension {
|
|||
// registering
|
||||
manager.get().getVBox().registerMachine(clonedMachine);
|
||||
|
||||
return imachineToImage.apply(clonedMachine);
|
||||
return Futures.immediateFuture(imachineToImage.apply(clonedMachine));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
|
@ -53,8 +55,10 @@ import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
|
|||
import org.jclouds.ec2.compute.loaders.CreateSecurityGroupIfNeeded;
|
||||
import org.jclouds.ec2.compute.loaders.LoadPublicIpForInstanceOrNull;
|
||||
import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
|
||||
import org.jclouds.ec2.compute.predicates.GetImageWhenStatusAvailablePredicateWithResult;
|
||||
import org.jclouds.ec2.domain.KeyPair;
|
||||
import org.jclouds.ec2.domain.RunningInstance;
|
||||
import org.jclouds.predicates.PredicateWithResult;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -94,6 +98,10 @@ public class AWSEC2ComputeServiceDependenciesModule extends EC2ComputeServiceDep
|
|||
bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() {
|
||||
}).to(RegionAndIdToImage.class);
|
||||
install(new FactoryModuleBuilder().build(CallForImages.Factory.class));
|
||||
bind(new TypeLiteral<ImageExtension>() {
|
||||
}).to(EC2ImageExtension.class);
|
||||
bind(new TypeLiteral<PredicateWithResult<String, Image>>() {
|
||||
}).to(GetImageWhenStatusAvailablePredicateWithResult.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue