diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java index 3ea04e1638..c30ec5a7e8 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java @@ -25,8 +25,8 @@ import static org.jclouds.compute.config.ComputeServiceProperties.RESOURCENAME_D import static org.jclouds.util.Preconditions2.checkNotEmpty; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; @@ -39,6 +39,7 @@ import org.jclouds.Constants; import org.jclouds.aws.util.AWSUtils; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.Image; @@ -69,12 +70,13 @@ import org.jclouds.scriptbuilder.functions.InitAdminAccess; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; /** @@ -101,12 +103,13 @@ public class EC2ComputeService extends BaseComputeService { RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, EC2Client ec2Client, - ConcurrentMap credentialsMap, @Named("SECURITY") LoadingCache securityGroupMap) { + ConcurrentMap credentialsMap, @Named("SECURITY") LoadingCache securityGroupMap, + Optional imageExtension) { super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, timeouts, - executor); + executor, imageExtension); this.ec2Client = ec2Client; this.credentialsMap = credentialsMap; this.securityGroupMap = securityGroupMap; diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java index bd799523ce..f95732648a 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java @@ -33,6 +33,7 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.Image; @@ -103,12 +104,12 @@ public class NovaComputeService extends BaseComputeService { LoadingCache securityGroupMap, LoadingCache keyPairCache, Function, Multimap> orphanedGroupsByZoneId, - GroupNamingConvention.Factory namingConvention) { + GroupNamingConvention.Factory namingConvention, Optional imageExtension) { super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, - runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy, - templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, - initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, timeouts, - executor); + runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, + stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, + nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, + timeouts, executor, imageExtension); this.novaClient = checkNotNull(novaClient, "novaClient"); this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap"); this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache"); @@ -134,7 +135,7 @@ public class NovaComputeService extends BaseComputeService { if (securityGroupClient.isPresent()) { for (String group : groups) { for (SecurityGroup securityGroup : Iterables.filter(securityGroupClient.get().listSecurityGroups(), - SecurityGroupPredicates.nameMatches(namingConvention.create().containsGroup(group)))) { + SecurityGroupPredicates.nameMatches(namingConvention.create().containsGroup(group)))) { ZoneAndName zoneAndName = ZoneAndName.fromZoneAndName(zoneId, securityGroup.getName()); logger.debug(">> deleting securityGroup(%s)", zoneAndName); securityGroupClient.get().deleteSecurityGroup(securityGroup.getId()); @@ -152,7 +153,7 @@ public class NovaComputeService extends BaseComputeService { for (String group : groups) { for (Map wrapper : keyPairClient.get().listKeyPairs()) { for (KeyPair pair : Iterables.filter(wrapper.values(), - KeyPairPredicates.nameMatches(namingConvention.create().containsGroup(group)))) { + KeyPairPredicates.nameMatches(namingConvention.create().containsGroup(group)))) { ZoneAndName zoneAndName = ZoneAndName.fromZoneAndName(zoneId, pair.getName()); logger.debug(">> deleting keypair(%s)", zoneAndName); keyPairClient.get().deleteKeyPair(pair.getName()); @@ -162,7 +163,7 @@ public class NovaComputeService extends BaseComputeService { } } keyPairCache.invalidate(ZoneAndName.fromZoneAndName(zoneId, - namingConvention.create().sharedNameForGroup(group))); + namingConvention.create().sharedNameForGroup(group))); } } } @@ -174,5 +175,7 @@ public class NovaComputeService extends BaseComputeService { public NovaTemplateOptions templateOptions() { return NovaTemplateOptions.class.cast(super.templateOptions()); } + + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java new file mode 100644 index 0000000000..0b92881c5b --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaImageExtension.java @@ -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; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.NoSuchElementException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +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.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 com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +@Singleton +public class NovaImageExtension implements ImageExtension { + + private final NovaClient novaClient; + private final Function imageInZoneToImage; + + @Inject + public NovaImageExtension(NovaClient novaClient, Function imageInZoneToImage) { + this.novaClient = checkNotNull(novaClient); + this.imageInZoneToImage = imageInZoneToImage; + } + + @Override + public ImageTemplate buildImageTemplateFromNode(String name, final String id) { + ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(id); + Server server = novaClient.getServerClientForZone(zoneAndId.getZone()).getServer(zoneAndId.getId()); + if (server == null) + throw new NoSuchElementException("Cannot find server with id: " + zoneAndId); + CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build(); + return template; + } + + @Override + public 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())); + } + + @Override + public boolean deleteImage(String id) { + ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(id); + try { + this.novaClient.getImageClientForZone(zoneAndId.getZone()).deleteImage(zoneAndId.getId()); + } catch (Exception e) { + return false; + } + 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() { + @Override + public boolean apply(org.jclouds.openstack.nova.v1_1.domain.Image input) { + return input.getId().equals(zoneAndId.getId()); + } + }).orNull(); + + } + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java index 2351ac7164..2ceaa9db49 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java @@ -33,6 +33,7 @@ import javax.inject.Singleton; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeService; 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; @@ -46,6 +47,7 @@ import org.jclouds.domain.LoginCredentials; import org.jclouds.functions.IdentityFunction; import org.jclouds.openstack.nova.v1_1.compute.NovaComputeService; import org.jclouds.openstack.nova.v1_1.compute.NovaComputeServiceAdapter; +import org.jclouds.openstack.nova.v1_1.compute.NovaImageExtension; import org.jclouds.openstack.nova.v1_1.compute.functions.CreateSecurityGroupIfNeeded; import org.jclouds.openstack.nova.v1_1.compute.functions.FlavorInZoneToHardware; import org.jclouds.openstack.nova.v1_1.compute.functions.ImageInZoneToImage; @@ -69,6 +71,7 @@ import org.jclouds.openstack.nova.v1_1.predicates.FindSecurityGroupWithNameAndRe import org.jclouds.predicates.RetryablePredicate; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -135,6 +138,9 @@ public class NovaComputeServiceContextModule extends bind(new TypeLiteral>() { }).to(CreateUniqueKeyPair.class); + + bind(new TypeLiteral() { + }).to(NovaImageExtension.class); } @Override @@ -206,4 +212,9 @@ public class NovaComputeServiceContextModule extends }, locations); } + + @Override + protected Optional provideImageExtension(Injector i) { + return Optional.of(i.getInstance(ImageExtension.class)); + } } \ No newline at end of file diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java index 87107915e6..723e886e01 100644 --- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java +++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java @@ -31,6 +31,7 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.Image; @@ -54,6 +55,7 @@ import org.jclouds.scriptbuilder.functions.InitAdminAccess; import org.jclouds.trmk.vcloud_0_8.compute.options.TerremarkVCloudTemplateOptions; import org.jclouds.trmk.vcloud_0_8.compute.strategy.CleanupOrphanKeys; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; @@ -78,12 +80,13 @@ public class TerremarkVCloudComputeService extends BaseComputeService { InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, - @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, CleanupOrphanKeys cleanupOrphanKeys) { + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, CleanupOrphanKeys cleanupOrphanKeys, + Optional imageExtension) { super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, resumeNodeStrategy, suspendNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, - timeouts, executor); + timeouts, executor, imageExtension); this.cleanupOrphanKeys = cleanupOrphanKeys; } diff --git a/compute/src/main/java/org/jclouds/compute/ComputeService.java b/compute/src/main/java/org/jclouds/compute/ComputeService.java index 701f8f3102..3c04e8a30f 100644 --- a/compute/src/main/java/org/jclouds/compute/ComputeService.java +++ b/compute/src/main/java/org/jclouds/compute/ComputeService.java @@ -38,6 +38,7 @@ import org.jclouds.domain.Location; import org.jclouds.scriptbuilder.domain.Statement; import com.google.common.annotations.Beta; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.ImplementedBy; @@ -345,5 +346,14 @@ public interface ComputeService { * @see #runScriptOnNode(String, String, RunScriptOptions) */ ExecResponse runScriptOnNode(String id, String runScript); + + /** + * Returns the {@link ImageExtension} for this provider if it implements it. + * + * @return an optional of the {@link ImageExtension} or {@link Optional#absent()} if not + * implemented + */ + @Beta + Optional getImageExtension(); } diff --git a/compute/src/main/java/org/jclouds/compute/ImageExtension.java b/compute/src/main/java/org/jclouds/compute/ImageExtension.java new file mode 100644 index 0000000000..56da218825 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/ImageExtension.java @@ -0,0 +1,63 @@ +/** + * 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.compute; + +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageTemplate; + +/** + * An extension to compute service to allow for the manipulation of {@link Image}s. Implementation + * is optional by providers. + * + * @author David Alves + */ +public interface ImageExtension { + + /** + * Build an ImageTemplate from a running node, to use later to create a new {@link Image}. + * + * @param name + * name to give the new image + * + * @param id + * node to base the template on + * @return an image template that can be used to create a new image + */ + ImageTemplate buildImageTemplateFromNode(String name, String id); + + /** + * Transform the {@link ImageTemplate} on an {@link Image} that can be used to create nodes. + * + * @param template + * 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); + + /** + * Delete an {@link Image} on the provider. + * + * @param id + * the id of the image to delete + * @return + */ + boolean deleteImage(String id); + +} diff --git a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java index ecc4774005..a522f13116 100644 --- a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java +++ b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java @@ -31,6 +31,7 @@ import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.collect.Memoized; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.BlockUntilInitScriptStatusIsZeroThenReturnOutput; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.callables.RunScriptOnNodeAsInitScriptUsingSsh; @@ -61,6 +62,7 @@ import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.ssh.SshClient; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; @@ -299,5 +301,12 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule { } }; } + + @Provides + @Singleton + protected Optional provideImageExtension(Injector i){ + return Optional.absent(); + } + } \ No newline at end of file diff --git a/compute/src/main/java/org/jclouds/compute/domain/CloneImageTemplate.java b/compute/src/main/java/org/jclouds/compute/domain/CloneImageTemplate.java new file mode 100644 index 0000000000..238fdef541 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/domain/CloneImageTemplate.java @@ -0,0 +1,31 @@ +/** + * 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.compute.domain; + +/** + * An {@link ImageTemplate} for the purpose of cloning an existing node. + * + * @author David Alves + */ +public interface CloneImageTemplate extends ImageTemplate { + + public String getSourceNodeId(); + +} diff --git a/compute/src/main/java/org/jclouds/compute/domain/ImageTemplate.java b/compute/src/main/java/org/jclouds/compute/domain/ImageTemplate.java new file mode 100644 index 0000000000..651a899e21 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/domain/ImageTemplate.java @@ -0,0 +1,35 @@ +/** + * 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.compute.domain; + +/** + * A template for building new {@link Image}s. + * + * @author David Alves + * + */ +public interface ImageTemplate { + + /** + * @return the name of the new image + */ + public String getName(); + +} diff --git a/compute/src/main/java/org/jclouds/compute/domain/ImageTemplateBuilder.java b/compute/src/main/java/org/jclouds/compute/domain/ImageTemplateBuilder.java new file mode 100644 index 0000000000..6104cd999e --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/domain/ImageTemplateBuilder.java @@ -0,0 +1,62 @@ +/** + * 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.compute.domain; + +import org.jclouds.compute.domain.internal.ImageTemplateImpl; + +/** + * A builder for {@link ImageTemplate}s. Includes sub-builders to build specific + * {@link ImageTemplate}s for different purposes, such as cloning, creating from iso, creating from + * netboot. + * + * @author David Alves + * + */ +public abstract class ImageTemplateBuilder { + + String name; + + private ImageTemplateBuilder() { + } + + public ImageTemplateBuilder name(String name) { + this.name = name; + return this; + } + + public static class CloneImageTemplateBuilder extends ImageTemplateBuilder { + + String nodeId; + + @Override + public CloneImageTemplateBuilder name(String name) { + return CloneImageTemplateBuilder.class.cast(super.name(name)); + } + + public CloneImageTemplateBuilder nodeId(String nodeId) { + this.nodeId = nodeId; + return this; + } + + public CloneImageTemplate build() { + return new ImageTemplateImpl.CloneImageTemplateImpl(name, nodeId); + } + } +} diff --git a/compute/src/main/java/org/jclouds/compute/domain/internal/ImageTemplateImpl.java b/compute/src/main/java/org/jclouds/compute/domain/internal/ImageTemplateImpl.java new file mode 100644 index 0000000000..2745f17ce5 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/domain/internal/ImageTemplateImpl.java @@ -0,0 +1,53 @@ +/** + * 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.compute.domain.internal; + +import org.jclouds.compute.domain.CloneImageTemplate; +import org.jclouds.compute.domain.ImageTemplate; + +public abstract class ImageTemplateImpl implements ImageTemplate { + + protected final String name; + + public ImageTemplateImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + public static class CloneImageTemplateImpl extends ImageTemplateImpl implements CloneImageTemplate { + + protected final String sourceNodeId; + + public CloneImageTemplateImpl(String name, String sourceNodeId) { + super(name); + this.sourceNodeId = sourceNodeId; + } + + @Override + public String getSourceNodeId() { + return this.sourceNodeId; + } + } + +} diff --git a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java index bd4496b219..4e492fb6a9 100644 --- a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java +++ b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java @@ -51,6 +51,7 @@ import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.RunNodesException; import org.jclouds.compute.RunScriptOnNodesException; import org.jclouds.compute.callables.RunScriptOnNode; @@ -90,6 +91,7 @@ import org.jclouds.scriptbuilder.functions.InitAdminAccess; import org.jclouds.util.Maps2; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; @@ -134,6 +136,7 @@ public class BaseComputeService implements ComputeService { private final PersistNodeCredentials persistNodeCredentials; private final RunScriptOnNode.Factory runScriptOnNodeFactory; private final ExecutorService executor; + private final Optional imageExtension; @Inject protected BaseComputeService(ComputeServiceContext context, Map credentialStore, @@ -148,7 +151,8 @@ public class BaseComputeService implements ComputeService { @Named("NODE_SUSPENDED") Predicate> nodeSuspended, InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, InitAdminAccess initAdminAccess, RunScriptOnNode.Factory runScriptOnNodeFactory, PersistNodeCredentials persistNodeCredentials, - Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) { + Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, + Optional imageExtension) { this.context = checkNotNull(context, "context"); this.credentialStore = checkNotNull(credentialStore, "credentialStore"); this.images = checkNotNull(images, "images"); @@ -172,6 +176,7 @@ public class BaseComputeService implements ComputeService { this.runScriptOnNodeFactory = checkNotNull(runScriptOnNodeFactory, "runScriptOnNodeFactory"); this.persistNodeCredentials = checkNotNull(persistNodeCredentials, "persistNodeCredentials"); this.executor = checkNotNull(executor, "executor"); + this.imageExtension = imageExtension; } /** @@ -668,5 +673,13 @@ public class BaseComputeService implements ComputeService { } } + + /** + * {@inheritDoc} + */ + @Override + public Optional getImageExtension() { + return imageExtension; + } } \ No newline at end of file diff --git a/compute/src/test/java/org/jclouds/compute/internal/BaseImageExtensionLiveTest.java b/compute/src/test/java/org/jclouds/compute/internal/BaseImageExtensionLiveTest.java new file mode 100644 index 0000000000..896e1ffdf4 --- /dev/null +++ b/compute/src/test/java/org/jclouds/compute/internal/BaseImageExtensionLiveTest.java @@ -0,0 +1,134 @@ +/** + * 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.compute.internal; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import java.util.Set; + +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.ssh.SshClient; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +/** + * Base test for {@link ImageExtension} implementations. + * + * @author David Alves + * + */ +public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceContextLiveTest { + + /** + * Returns the template for the base node, override to test different templates. + * + * @return + */ + public Template getNodeTemplate() { + return context.getComputeService().templateBuilder().any().build(); + } + + @Test(groups = { "integration", "live" }, singleThreaded = true) + public void testCreateImage() throws RunNodesException, InterruptedException { + + ComputeService computeService = context.getComputeService(); + + Optional imageExtension = computeService.getImageExtension(); + assertTrue("image extension was not present", imageExtension.isPresent()); + + Set imagesBefore = computeService.listImages(); + + NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, + getNodeTemplate())); + + ImageTemplate newImageTemplate = imageExtension.get().buildImageTemplateFromNode("test-create-image", + node.getId()); + + Image image = imageExtension.get().createImage(newImageTemplate); + + assertEquals("test-create-image", image.getName()); + + computeService.destroyNode(node.getId()); + + Set imagesAfter = computeService.listImages(); + + assertTrue(imagesBefore.size() == imagesAfter.size() - 1); + + } + + @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testCreateImage") + public void testSpawnNodeFromImage() throws RunNodesException { + + ComputeService computeService = context.getComputeService(); + + Template template = computeService.templateBuilder().fromImage(getImage().get()).build(); + + NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-image", 1, template)); + + SshClient client = context.utils().sshForNode().apply(node); + client.connect(); + + ExecResponse hello = client.exec("echo hello"); + + assertEquals(hello.getOutput().trim(), "hello"); + + context.getComputeService().destroyNode(node.getId()); + + } + + @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = { "testCreateImage", + "testSpawnNodeFromImage" }) + public void testDeleteImage() { + + ComputeService computeService = context.getComputeService(); + + Optional imageExtension = computeService.getImageExtension(); + assertTrue("image extension was not present", imageExtension.isPresent()); + + Optional optImage = getImage(); + + assertTrue(optImage.isPresent()); + + Image image = optImage.get(); + + assertTrue(imageExtension.get().deleteImage(image.getId())); + } + + private Optional getImage() { + return Iterables.tryFind(context.getComputeService().listImages(), new Predicate() { + @Override + public boolean apply(Image input) { + return input.getId().contains("test-create-image"); + } + }); + } + +} diff --git a/labs/virtualbox/pom.xml b/labs/virtualbox/pom.xml index e6dd502692..89fccebb82 100644 --- a/labs/virtualbox/pom.xml +++ b/labs/virtualbox/pom.xml @@ -40,7 +40,7 @@ 4.1.8r75467 administrator 12345 - default-ubuntu-11.04-i386 + test-ubuntu-11.10-i386 toor:password true diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/compute/VirtualBoxComputeServiceAdapter.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/compute/VirtualBoxComputeServiceAdapter.java index eb0a889c3a..e2e1d21a1c 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/compute/VirtualBoxComputeServiceAdapter.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/compute/VirtualBoxComputeServiceAdapter.java @@ -27,12 +27,15 @@ import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_NODE_ import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_NODE_PREFIX; import java.util.Map; +import java.util.Set; import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; import org.jclouds.compute.domain.Image; import org.jclouds.compute.domain.Template; import org.jclouds.compute.reference.ComputeServiceConstants; @@ -58,6 +61,7 @@ import com.google.common.base.Throwables; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; import com.google.inject.Singleton; /** @@ -67,25 +71,28 @@ import com.google.inject.Singleton; * @author Mattias Holmqvist, Andrea Turli, David Alves */ @Singleton -public class VirtualBoxComputeServiceAdapter implements ComputeServiceAdapter { +public class VirtualBoxComputeServiceAdapter implements ComputeServiceAdapter { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; - + private final Supplier manager; - private final Map images; + private final Map imagesToYamlImages; private final LoadingCache mastersLoader; private final Function> cloneCreator; + private final Function imachineToImage; @Inject public VirtualBoxComputeServiceAdapter(Supplier manager, Supplier> imagesMapper, LoadingCache mastersLoader, - Function> cloneCreator) { + Function> cloneCreator, + Function imachineToImage) { this.manager = checkNotNull(manager, "manager"); - this.images = imagesMapper.get(); + this.imagesToYamlImages = imagesMapper.get(); this.mastersLoader = mastersLoader; this.cloneCreator = cloneCreator; + this.imachineToImage = imachineToImage; } @Override @@ -97,7 +104,7 @@ public class VirtualBoxComputeServiceAdapter implements ComputeServiceAdapter listHardwareProfiles() { - return imageMachines(); + public Iterable listHardwareProfiles() { + Set hardware = Sets.newLinkedHashSet(); + hardware.add(new HardwareBuilder().ids("t1.micro").hypervisor("VirtualBox").name("t1.micro").ram(512).build()); + hardware.add(new HardwareBuilder().ids("m1.small").hypervisor("VirtualBox").name("m1.small").ram(1024).build()); + hardware.add(new HardwareBuilder().ids("m1.medium").hypervisor("VirtualBox").name("m1.medium").ram(3840).build()); + hardware.add(new HardwareBuilder().ids("m1.large").hypervisor("VirtualBox").name("m1.large").ram(7680).build()); + return hardware; } @Override public Iterable listImages() { - return images.keySet(); + // the set of image vm names that were (or could be) built from the yaml file + final Set imagesFromYamlNames = Sets.newHashSet(Iterables.transform(imagesToYamlImages.keySet(), + new Function() { + @Override + public String apply(Image input) { + return VIRTUALBOX_IMAGE_PREFIX + input.getId(); + } + + })); + + // IMachines that were not built from the yaml file transformed to Images + Set imagesFromCloning = Sets.newHashSet(Iterables.transform( + Iterables.filter(imageMachines(), new Predicate() { + @Override + public boolean apply(IMachine input) { + return !imagesFromYamlNames.contains(input.getName()); + } + }), imachineToImage)); + + // final set of images are those from yaml and those from vbox that were not a transformation + // of the yaml ones + return Sets.union(imagesToYamlImages.keySet(), imagesFromCloning); } private Iterable imageMachines() { @@ -147,7 +180,7 @@ public class VirtualBoxComputeServiceAdapter implements ComputeServiceAdapter vboxAdapter; + private Function machineToNode; + private Supplier manager; + private String workingDir; + private boolean isLinkedClone = true; + private Function imachineToImage; + private MachineUtils machineUtils; + + @Inject + public VirtualBoxImageExtension(ComputeServiceAdapter vboxAdapter, + Function machineToNode, Supplier manager, + @Named(VIRTUALBOX_WORKINGDIR) String workingDir, Function imachineToImage, + MachineUtils machineUtils) { + this.vboxAdapter = vboxAdapter; + this.machineToNode = machineToNode; + this.manager = manager; + this.workingDir = workingDir == null ? VIRTUALBOX_DEFAULT_DIR : workingDir; + this.imachineToImage = imachineToImage; + this.machineUtils = machineUtils; + } + + @Override + public ImageTemplate buildImageTemplateFromNode(String name, final String id) { + Optional sourceNode = getNodeById(id); + checkState(sourceNode.isPresent(), " there is no node with id " + id); + String vmName = VIRTUALBOX_IMAGE_PREFIX + name; + + IMachine vm = null; + try { + vm = manager.get().getVBox().findMachine(vmName); + } catch (Exception e) { + } + checkState(vm == null, " a machine exists with name: " + vmName); + return new ImageTemplateBuilder.CloneImageTemplateBuilder().name(vmName).nodeId(id).build(); + } + + @Override + public Image createImage(ImageTemplate template) { + checkState(template instanceof CloneImageTemplate, " vbox image extension only supports cloning for the moment."); + CloneImageTemplate cloneTemplate = CloneImageTemplate.class.cast(template); + + IMachine source = manager.get().getVBox().findMachine(cloneTemplate.getSourceNodeId()); + + String settingsFile = manager.get().getVBox().composeMachineFilename(template.getName(), workingDir); + IMachine clonedMachine = manager.get().getVBox() + .createMachine(settingsFile, template.getName(), source.getOSTypeId(), template.getName(), true); + + List options = new ArrayList(); + if (isLinkedClone) + options.add(CloneOptions.Link); + + // TODO snapshot name + ISnapshot currentSnapshot = new TakeSnapshotIfNotAlreadyAttached(manager, "pre-image-spawn", "before spawning " + + template.getName(), logger).apply(source); + + checkNotNull(currentSnapshot); + + // clone + IProgress progress = currentSnapshot.getMachine().cloneTo(clonedMachine, CloneMode.MachineState, options); + progress.waitForCompletion(-1); + + logger.debug(String.format("Machine %s is cloned correctly", clonedMachine.getName())); + + // registering + manager.get().getVBox().registerMachine(clonedMachine); + + return imachineToImage.apply(clonedMachine); + } + + @Override + public boolean deleteImage(String id) { + try { + IMachine machine = manager.get().getVBox().findMachine(VIRTUALBOX_IMAGE_PREFIX + id); + machineUtils.applyForMachine(machine.getId(), new UnregisterMachineIfExistsAndDeleteItsMedia( + new IMachineToVmSpec().apply(machine))); + } catch (Exception e) { + logger.error(e, "Could not delete machine with id %s ", id); + return false; + } + return true; + } + + private Optional getNodeById(final String id) { + return Iterables.tryFind(Iterables.transform(vboxAdapter.listNodes(), machineToNode), + new Predicate() { + @Override + public boolean apply(NodeMetadata input) { + return input.getId().equals(id); + } + }); + } + +} diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/config/VirtualBoxComputeServiceContextModule.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/config/VirtualBoxComputeServiceContextModule.java index 55160e8a7b..120c862921 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/config/VirtualBoxComputeServiceContextModule.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/config/VirtualBoxComputeServiceContextModule.java @@ -40,8 +40,9 @@ import org.jclouds.byon.Node; import org.jclouds.byon.functions.NodeToNodeMetadata; import org.jclouds.byon.suppliers.SupplyFromProviderURIOrNodesProperty; import org.jclouds.compute.ComputeServiceAdapter; -import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.config.ComputeServiceAdapterContextModule; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.HardwareBuilder; @@ -58,6 +59,7 @@ import org.jclouds.ssh.SshClient; import org.jclouds.sshj.config.SshjSshClientModule; import org.jclouds.virtualbox.Host; import org.jclouds.virtualbox.compute.VirtualBoxComputeServiceAdapter; +import org.jclouds.virtualbox.compute.VirtualBoxImageExtension; import org.jclouds.virtualbox.domain.CloneSpec; import org.jclouds.virtualbox.domain.ExecutionType; import org.jclouds.virtualbox.domain.IsoSpec; @@ -88,6 +90,7 @@ import org.virtualbox_4_1.VirtualBoxManager; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Functions; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -105,17 +108,19 @@ import com.google.inject.TypeLiteral; */ @SuppressWarnings("unchecked") public class VirtualBoxComputeServiceContextModule extends - ComputeServiceAdapterContextModule { + ComputeServiceAdapterContextModule { @Override protected void configure() { super.configure(); - bind(new TypeLiteral>() { + bind(new TypeLiteral>() { }).to(VirtualBoxComputeServiceAdapter.class); bind(new TypeLiteral>() { }).to(IMachineToNodeMetadata.class); bind(new TypeLiteral>() { }).to((Class) IdentityFunction.class); + bind(new TypeLiteral>() { + }).to((Class) IdentityFunction.class); bind(new TypeLiteral>() { }).to((Class) IdentityFunction.class); bind(new TypeLiteral>() { @@ -139,6 +144,10 @@ public class VirtualBoxComputeServiceContextModule extends bind(new TypeLiteral>() { }).to(MastersLoadingCache.class); + // the vbox image extension + bind(new TypeLiteral() { + }).to(VirtualBoxImageExtension.class); + // the master creating function bind(new TypeLiteral>() { }).to((Class) CreateAndInstallVm.class); @@ -166,10 +175,9 @@ public class VirtualBoxComputeServiceContextModule extends @Host @Singleton protected ComputeServiceContext provideHostController() { - return ContextBuilder.newBuilder(new BYONApiMetadata()) - .credentials("", "") - .modules(ImmutableSet. of(new SLF4JLoggingModule(), new SshjSshClientModule())) - .build(ComputeServiceContext.class); + return ContextBuilder.newBuilder(new BYONApiMetadata()).credentials("", "") + .modules(ImmutableSet. of(new SLF4JLoggingModule(), new SshjSshClientModule())) + .build(ComputeServiceContext.class); } @Provides @@ -202,18 +210,6 @@ public class VirtualBoxComputeServiceContextModule extends return new RetryablePredicate(sshResponds, timeouts.nodeRunning, 500l, TimeUnit.MILLISECONDS); } - @Override - protected Supplier provideHardware(ComputeServiceAdapter adapter, - Function transformer) { - // since no vms might be available we need to list images - Iterable images = adapter.listImages(); - Set hardware = Sets.newHashSet(); - for (Image image : images) { - hardware.add(new HardwareBuilder().ids(image.getId()).hypervisor("VirtualBox").name(image.getName()).build()); - } - return Suppliers.ofInstance(hardware); - } - @Override protected TemplateBuilder provideTemplate(Injector injector, TemplateBuilder template) { return template.osFamily(VIRTUALBOX_DEFAULT_IMAGE_OS).osVersionMatches(VIRTUALBOX_DEFAULT_IMAGE_VERSION) @@ -233,6 +229,11 @@ public class VirtualBoxComputeServiceContextModule extends }), nodes); } + @Override + protected Optional provideImageExtension(Injector i) { + return Optional.of(i.getInstance(ImageExtension.class)); + } + @VisibleForTesting public static final Map machineToNodeState = ImmutableMap . builder().put(MachineState.Running, NodeState.RUNNING) diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CloneAndRegisterMachineFromIMachineIfNotAlreadyExists.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CloneAndRegisterMachineFromIMachineIfNotAlreadyExists.java index 0fb2f36414..46521df88a 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CloneAndRegisterMachineFromIMachineIfNotAlreadyExists.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CloneAndRegisterMachineFromIMachineIfNotAlreadyExists.java @@ -99,13 +99,13 @@ public class CloneAndRegisterMachineFromIMachineIfNotAlreadyExists implements Fu .getVBox() .createMachine(settingsFile, vmSpec.getVmName(), vmSpec.getOsTypeId(), vmSpec.getVmId(), vmSpec.isForceOverwrite()); - + List options = new ArrayList(); if (isLinkedClone) options.add(CloneOptions.Link); // TODO snapshot name - ISnapshot currentSnapshot = new TakeSnapshotIfNotAlreadyAttached(manager, "snapshotName", "snapshotDesc") + ISnapshot currentSnapshot = new TakeSnapshotIfNotAlreadyAttached(manager, "snapshotName", "snapshotDesc", logger) .apply(master); // clone @@ -114,6 +114,9 @@ public class CloneAndRegisterMachineFromIMachineIfNotAlreadyExists implements Fu progress.waitForCompletion(-1); logger.debug(String.format("Machine %s is cloned correctly", clonedMachine.getName())); + // memory may not be the same as the master vm + clonedMachine.setMemorySize(cloneSpec.getVmSpec().getMemory()); + // registering manager.get().getVBox().registerMachine(clonedMachine); diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToImage.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToImage.java index 36805d7e94..dab6dac6ec 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToImage.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToImage.java @@ -33,6 +33,7 @@ import org.jclouds.compute.domain.ImageBuilder; import org.jclouds.compute.domain.OperatingSystem; import org.jclouds.compute.domain.OsFamily; import org.jclouds.javax.annotation.Nullable; +import org.jclouds.virtualbox.config.VirtualBoxConstants; import org.virtualbox_4_1.IGuestOSType; import org.virtualbox_4_1.IMachine; import org.virtualbox_4_1.VirtualBoxManager; @@ -63,7 +64,9 @@ public class IMachineToImage implements Function { OperatingSystem os = OperatingSystem.builder().description(guestOSType.getDescription()).family(family) .version(version).is64Bit(guestOSType.getIs64Bit()).build(); - return new ImageBuilder().id("" + from.getId()).name(from.getName()).description(from.getDescription()) + return new ImageBuilder() + .id(from.getName().substring(VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX.length(), + from.getName().length())).name(from.getName()).description(from.getDescription()) .operatingSystem(os).build(); } diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToNodeMetadata.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToNodeMetadata.java index 9e99d8c2c0..5379d38cba 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToNodeMetadata.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/IMachineToNodeMetadata.java @@ -95,8 +95,6 @@ public class IMachineToNodeMetadata implements Function nodeState = NodeState.UNRECOGNIZED; nodeMetadataBuilder.state(nodeState); - logger.debug("Setting virtualbox node to: " + nodeState + " from machine state: " + vmState); - /* // nat adapter INetworkAdapter natAdapter = vm.getNetworkAdapter(0l); @@ -139,7 +137,6 @@ public class IMachineToNodeMetadata implements Function private NodeMetadataBuilder getIpAddresses(IMachine vm, NodeMetadataBuilder nodeMetadataBuilder) { List publicIpAddresses = Lists.newArrayList(); - List privateIpAddresses = Lists.newArrayList(); for(long slot = 0; slot < 4; slot ++) { INetworkAdapter adapter = vm.getNetworkAdapter(slot); diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/MastersLoadingCache.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/MastersLoadingCache.java index 5aa668205e..e54c01101e 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/MastersLoadingCache.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/MastersLoadingCache.java @@ -141,14 +141,51 @@ public class MastersLoadingCache extends AbstractLoadingCache { return masters.get(key.getId()); } - // the yaml image - YamlImage currentImage = imageMapping.get(key.getId()); - - checkNotNull(currentImage, "could not find yaml image for image: " + key); - - checkState(!currentImage.id.contains(VIRTUALBOX_NODE_NAME_SEPARATOR), "master image names cannot contain \"" + checkState(!key.getId().contains(VIRTUALBOX_NODE_NAME_SEPARATOR), "master image names cannot contain \"" + VIRTUALBOX_NODE_NAME_SEPARATOR + "\""); + String vmName = VIRTUALBOX_IMAGE_PREFIX + key.getId(); + + IMachine masterMachine; + + Master master; + + // ready the preseed file server + PreseedCfgServer server = new PreseedCfgServer(); + try { + // try and find a master machine in vbox + masterMachine = manager.get().getVBox().findMachine(vmName); + master = Master.builder().machine(masterMachine).build(); + + } catch (VBoxException e) { + if (machineNotFoundException(e)) { + // machine was not found try to build one from a yaml file + YamlImage currentImage = imageMapping.get(key.getId()); + + checkNotNull(currentImage); + + server.start(preconfigurationUrl, currentImage.preseed_cfg); + + MasterSpec masterSpec = buildMasterSpecFromYaml(currentImage, vmName); + + // create the master machine if it can't be found + masterMachine = masterCreatorAndInstaller.apply(masterSpec); + + // build the master + master = Master.builder().machine(masterMachine).spec(masterSpec).build(); + } else { + throw e; + } + } finally { + server.stop(); + } + + masters.put(key.getId(), master); + return master; + } + + private MasterSpec buildMasterSpecFromYaml(YamlImage currentImage, String vmName) throws ExecutionException { + String guestAdditionsFileName = String.format("VBoxGuestAdditions_%s.iso", version); String guestAdditionsIso = String.format("%s/%s", isosDir, guestAdditionsFileName); String guestAdditionsUri = "http://download.virtualbox.org/virtualbox/" + version + "/" + guestAdditionsFileName; @@ -160,8 +197,6 @@ public class MastersLoadingCache extends AbstractLoadingCache { // check if the iso is here, download if not String localIsoUrl = getFilePathOrDownload(currentImage.iso); - String vmName = VIRTUALBOX_IMAGE_PREFIX + currentImage.id; - String adminDisk = workingDir + File.separator + vmName + ".vdi"; HardDisk hardDisk = HardDisk.builder().diskpath(adminDisk).autoDelete(true).controllerPort(0).deviceSlot(1) @@ -174,46 +209,19 @@ public class MastersLoadingCache extends AbstractLoadingCache { .controller(ideController).forceOverwrite(true).cleanUpMode(CleanupMode.Full).build(); NetworkAdapter networkAdapter = NetworkAdapter.builder().networkAttachmentType(NetworkAttachmentType.NAT) - .tcpRedirectRule("127.0.0.1", MASTER_PORT , "", 22).build(); + .tcpRedirectRule("127.0.0.1", MASTER_PORT, "", 22).build(); NetworkInterfaceCard networkInterfaceCard = NetworkInterfaceCard.builder().addNetworkAdapter(networkAdapter) .slot(0L).build(); - + NetworkSpec networkSpec = NetworkSpec.builder().addNIC(networkInterfaceCard).build(); - MasterSpec masterSpec = MasterSpec + return MasterSpec .builder() .vm(vmSpecification) .iso(IsoSpec.builder().sourcePath(localIsoUrl) .installationScript(installationKeySequence.replace("HOSTNAME", vmSpecification.getVmName())) .build()).network(networkSpec).build(); - - IMachine masterMachine; - - - // ready the preseed file server - PreseedCfgServer server = new PreseedCfgServer(); - try { - // try and find a master machine in vbox - masterMachine = manager.get().getVBox().findMachine(vmName); - } catch (VBoxException e) { - if (machineNotFoundException(e)) { - server.start(preconfigurationUrl,currentImage.preseed_cfg); - // create the master machine if it can't be found - masterMachine = masterCreatorAndInstaller.apply(masterSpec); - } else { - throw e; - } - } finally { - server.stop(); - } - - - Master master = Master.builder().machine(masterMachine).spec(masterSpec).build(); - - masters.put(key.getId(), master); - - return master; } @Override diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/NodeCreator.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/NodeCreator.java index a7f2ff9ded..a70f4ef2c4 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/NodeCreator.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/NodeCreator.java @@ -118,12 +118,18 @@ public class NodeCreator implements Function 0) { + ram = nodeSpec.getTemplate().getHardware().getRam(); + } + + VmSpec cloneVmSpec = VmSpec.builder().id(cloneName).name(cloneName).memoryMB(ram).cleanUpMode(CleanupMode.Full) .forceOverwrite(true).build(); // CASE NAT + HOST-ONLY diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttached.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttached.java index e496cb5b94..830c23ec61 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttached.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttached.java @@ -20,15 +20,13 @@ package org.jclouds.virtualbox.functions; import javax.annotation.Nullable; -import javax.annotation.Resource; -import javax.inject.Named; -import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.virtualbox_4_1.IMachine; import org.virtualbox_4_1.IProgress; import org.virtualbox_4_1.ISession; import org.virtualbox_4_1.ISnapshot; +import org.virtualbox_4_1.MachineState; import org.virtualbox_4_1.VirtualBoxManager; import com.google.common.base.Function; @@ -40,55 +38,80 @@ import com.google.common.base.Throwables; */ public class TakeSnapshotIfNotAlreadyAttached implements Function { - @Resource - @Named(ComputeServiceConstants.COMPUTE_LOGGER) - protected Logger logger = Logger.NULL; - private Supplier manager; private String snapshotName; private String snapshotDesc; + private Logger logger; - public TakeSnapshotIfNotAlreadyAttached(Supplier manager, String snapshotName, String snapshotDesc) { + public TakeSnapshotIfNotAlreadyAttached(Supplier manager, String snapshotName, + String snapshotDesc, Logger logger) { this.manager = manager; this.snapshotName = snapshotName; this.snapshotDesc = snapshotDesc; + this.logger = logger; } @Override public ISnapshot apply(@Nullable IMachine machine) { // Snapshot a machine ISession session = null; - if (machine.getCurrentSnapshot() == null) { - int retries = 10; - while (true) { - try { - session = manager.get().openMachineSession(machine); - IProgress progress = session.getConsole().takeSnapshot(snapshotName, snapshotDesc); - progress.waitForCompletion(-1); - logger.debug("Snapshot %s (description: %s) taken from %s", snapshotName, snapshotDesc, - machine.getName()); - break; - } catch (Exception e) { - if (e.getMessage().contains("VirtualBox error: The object is not ready")) { - retries--; - if (retries == 0) { - logger.error(e, - "Problem creating snapshot (too many retries) %s (descripton: %s) from machine %s", - snapshotName, snapshotDesc, machine.getName()); - throw Throwables.propagate(e); + ISnapshot snap = machine.getCurrentSnapshot(); + + if (snap == null) { + try { + session = manager.get().openMachineSession(machine); + logger.debug("No snapshot available taking new one: %s (description: %s) taken from %s", snapshotName, + snapshotDesc, machine.getName()); + int retries = 10; + while (true) { + try { + + // running machines need to be pause before a snapshot can be taken + // due to a vbox bug see https://www.virtualbox.org/ticket/9255 + boolean paused = false; + if (machine.getState() == MachineState.Running) { + session.getConsole().pause(); + paused = true; } - } - logger.error(e, "Problem creating snapshot %s (descripton: %s) from machine %s", snapshotName, - snapshotDesc, machine.getName()); - throw Throwables.propagate(e); - } finally { - if (session != null) { - session.unlockMachine(); + + IProgress progress = session.getConsole().takeSnapshot(snapshotName, snapshotDesc); + progress.waitForCompletion(-1); + + if (paused) { + session.getConsole().resume(); + } + + snap = machine.getCurrentSnapshot(); + logger.debug("Snapshot %s (description: %s) taken from %s", snapshotName, snapshotDesc, + machine.getName()); + break; + } catch (Exception e) { + if (e.getMessage().contains("VirtualBox error: The object is not ready") + || e.getMessage().contains("This machine does not have any snapshots")) { + retries--; + if (retries == 0) { + logger.error(e, + "Problem creating snapshot (too many retries) %s (descripton: %s) from machine %s", + snapshotName, snapshotDesc, machine.getName()); + throw Throwables.propagate(e); + } + Thread.sleep(1000L); + continue; + } + logger.error(e, "Problem creating snapshot %s (descripton: %s) from machine %s", snapshotName, + snapshotDesc, machine.getName()); + throw Throwables.propagate(e); } } + } catch (Exception e) { + Throwables.propagate(e); + } finally { + if (session != null) { + manager.get().closeMachineSession(session); + } } - } - return machine.getCurrentSnapshot(); - } + } + return snap; + } } diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunning.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunning.java index dda2cb3253..8c231f11f2 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunning.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunning.java @@ -26,7 +26,6 @@ import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot; import java.net.URI; import javax.annotation.Nullable; -import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; @@ -74,10 +73,10 @@ public class StartVBoxIfNotAlreadyRunning implements Supplier this.host = checkNotNull(host, "host"); this.providerSupplier = checkNotNull(providerSupplier, "endpoint to virtualbox websrvd is needed"); this.managerForNode = checkNotNull(managerForNode, "managerForNode"); + start(); } - @PostConstruct - public void start() { + public synchronized void start() { URI provider = providerSupplier.get(); if (!socketTester.apply(new IPSocket(provider.getHost(), provider.getPort()))) { logger.debug("disabling password access"); diff --git a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/UnregisterMachineIfExistsAndDeleteItsMedia.java b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/UnregisterMachineIfExistsAndDeleteItsMedia.java index ce02937e31..bc3dde9430 100644 --- a/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/UnregisterMachineIfExistsAndDeleteItsMedia.java +++ b/labs/virtualbox/src/main/java/org/jclouds/virtualbox/functions/admin/UnregisterMachineIfExistsAndDeleteItsMedia.java @@ -17,7 +17,7 @@ * under the License. */ /* -U * Licensed to jclouds, Inc. (jclouds) under one or more + U * 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 @@ -93,8 +93,8 @@ public class UnregisterMachineIfExistsAndDeleteItsMedia implements Function filteredMediaToBeDeleted = Lists.newArrayList(transform(filter(mediaToBeDeleted, - new AutoDeleteHardDiskPredicate(vmSpec)), new DeleteChildrenOfMedium())); + List filteredMediaToBeDeleted = Lists.newArrayList(transform( + filter(mediaToBeDeleted, new AutoDeleteHardDiskPredicate(vmSpec)), new DeleteChildrenOfMedium())); if (!filteredMediaToBeDeleted.isEmpty()) { try { @@ -105,7 +105,7 @@ public class UnregisterMachineIfExistsAndDeleteItsMedia implements Function profiles = adapter.listHardwareProfiles(); + Iterable profiles = adapter.listHardwareProfiles(); assertTrue(!Iterables.isEmpty(profiles)); } diff --git a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/compute/VirtualBoxImageExtensionLiveTest.java b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/compute/VirtualBoxImageExtensionLiveTest.java new file mode 100644 index 0000000000..c498ce91ff --- /dev/null +++ b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/compute/VirtualBoxImageExtensionLiveTest.java @@ -0,0 +1,40 @@ +/** + * 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.virtualbox.compute; + +import org.jclouds.compute.internal.BaseImageExtensionLiveTest; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.Test; + +import com.google.inject.Module; + +@Test(groups = "live", singleThreaded = true, testName = "VirtualBoxImageExtensionLiveTest") +public class VirtualBoxImageExtensionLiveTest extends BaseImageExtensionLiveTest { + + public VirtualBoxImageExtensionLiveTest() { + provider = "virtualbox"; + } + + @Override + protected Module getSshModule() { + return new SshjSshClientModule(); + } + +} diff --git a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/CreateAndInstallVmLiveTest.java b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/CreateAndInstallVmLiveTest.java index d475cd8c14..45bd5dfa11 100644 --- a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/CreateAndInstallVmLiveTest.java +++ b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/CreateAndInstallVmLiveTest.java @@ -20,7 +20,7 @@ package org.jclouds.virtualbox.functions; import static com.google.common.base.Preconditions.checkState; -import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX; import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_INSTALLATION_KEY_SEQUENCE; @@ -56,9 +56,7 @@ import org.virtualbox_4_1.StorageBus; import com.google.common.base.CaseFormat; import com.google.common.base.Function; import com.google.common.base.Predicate; -import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.inject.Guice; import com.google.inject.Injector; @@ -166,15 +164,14 @@ public class CreateAndInstallVmLiveTest extends BaseVirtualBoxClientLiveTest { checkState(sshResponds.apply(client), "timed out waiting for guest %s to be accessible via ssh", machine.getName()); - String vboxVersion = Iterables.get( - Splitter.on('r').split(context.getProviderSpecificContext().getBuildVersion()), 0); - assertEquals(vboxVersion, - machineUtils.sharedLockMachineAndApplyToSession(machine.getName(), new Function() { - @Override - public String apply(ISession session) { - return session.getMachine().getGuestPropertyValue("/VirtualBox/GuestAdd/Version"); - } - })); + String version = machineUtils.sharedLockMachineAndApplyToSession(machine.getName(), new Function() { + @Override + public String apply(ISession session) { + return session.getMachine().getGuestPropertyValue("/VirtualBox/GuestAdd/Version"); + } + }); + + assertTrue(version != null && !version.isEmpty()); } finally { for (VmSpec spec : ImmutableSet.of(machineSpec.getVmSpec())) { machineController.ensureMachineIsShutdown(spec.getVmName()); diff --git a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/IMachineToImageTest.java b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/IMachineToImageTest.java index b47f5ded0b..8db2dfbeb8 100644 --- a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/IMachineToImageTest.java +++ b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/IMachineToImageTest.java @@ -34,6 +34,7 @@ import org.jclouds.compute.domain.OsFamily; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.json.Json; import org.jclouds.json.config.GsonModule; +import org.jclouds.virtualbox.config.VirtualBoxConstants; import org.testng.annotations.Test; import org.virtualbox_4_1.IGuestOSType; import org.virtualbox_4_1.IMachine; @@ -48,7 +49,7 @@ public class IMachineToImageTest { Map> map = new BaseComputeServiceContextModule() { }.provideOsVersionMap(new ComputeServiceConstants.ReferenceData(), Guice.createInjector(new GsonModule()) - .getInstance(Json.class)); + .getInstance(Json.class)); @Test public void testConvert() throws Exception { @@ -61,6 +62,7 @@ public class IMachineToImageTest { expect(vbm.getVBox()).andReturn(vBox).anyTimes(); expect(vm.getOSTypeId()).andReturn("os-type").anyTimes(); + expect(vm.getName()).andReturn(VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX + "my-vm-id").anyTimes(); expect(vBox.getGuestOSType(eq("os-type"))).andReturn(guestOsType); expect(vm.getDescription()).andReturn("my-ubuntu-machine").anyTimes(); expect(guestOsType.getDescription()).andReturn(linuxDescription).anyTimes(); @@ -77,6 +79,7 @@ public class IMachineToImageTest { assertTrue(image.getOperatingSystem().is64Bit()); assertEquals(image.getOperatingSystem().getFamily(), OsFamily.UBUNTU); assertEquals(image.getOperatingSystem().getVersion(), "10.04"); + assertEquals(image.getId(), "my-vm-id"); } @@ -91,6 +94,7 @@ public class IMachineToImageTest { String vmDescription = "ubuntu-11.04-server-i386"; expect(vbm.getVBox()).andReturn(vBox).anyTimes(); + expect(vm.getName()).andReturn(VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX + "my-vm-id").anyTimes(); expect(vm.getOSTypeId()).andReturn("os-type").anyTimes(); expect(vBox.getGuestOSType(eq("os-type"))).andReturn(guestOsType); expect(vm.getDescription()).andReturn(vmDescription).anyTimes(); @@ -108,6 +112,7 @@ public class IMachineToImageTest { assertTrue(image.getOperatingSystem().is64Bit()); assertEquals(image.getOperatingSystem().getFamily(), OsFamily.UBUNTU); assertEquals(image.getOperatingSystem().getVersion(), "11.04"); + assertEquals(image.getId(), "my-vm-id"); } @@ -121,6 +126,7 @@ public class IMachineToImageTest { expect(vbm.getVBox()).andReturn(vBox).anyTimes(); + expect(vm.getName()).andReturn(VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX + "my-vm-id").anyTimes(); String unknownOsDescription = "SomeOtherOs 2.04"; expect(vm.getOSTypeId()).andReturn("os-type").anyTimes(); expect(vm.getDescription()).andReturn("my-unknown-machine").anyTimes(); @@ -136,6 +142,7 @@ public class IMachineToImageTest { assertEquals(image.getOperatingSystem().getDescription(), "SomeOtherOs 2.04"); assertEquals(image.getOperatingSystem().getVersion(), ""); + assertEquals(image.getId(), "my-vm-id"); } diff --git a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttachedTest.java b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttachedTest.java index 6b2f3a028e..3e3bb58643 100644 --- a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttachedTest.java +++ b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/TakeSnapshotIfNotAlreadyAttachedTest.java @@ -24,6 +24,7 @@ import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; +import org.jclouds.logging.Logger; import org.testng.annotations.Test; import org.virtualbox_4_1.IConsole; import org.virtualbox_4_1.IMachine; @@ -31,6 +32,7 @@ import org.virtualbox_4_1.IProgress; import org.virtualbox_4_1.ISession; import org.virtualbox_4_1.ISnapshot; import org.virtualbox_4_1.IVirtualBox; +import org.virtualbox_4_1.MachineState; import org.virtualbox_4_1.VirtualBoxManager; import com.google.common.base.Suppliers; @@ -55,6 +57,7 @@ public class TakeSnapshotIfNotAlreadyAttachedTest { IProgress progress = createNiceMock(IProgress.class); ISnapshot snapshot = createNiceMock(ISnapshot.class); expect(machine.getCurrentSnapshot()).andReturn(snapshot).anyTimes(); + expect(machine.getState()).andReturn(MachineState.PoweredOff).anyTimes(); expect(manager.openMachineSession(machine)).andReturn(session); @@ -66,7 +69,7 @@ public class TakeSnapshotIfNotAlreadyAttachedTest { session.unlockMachine(); replay(manager, machine, vBox, session, console, progress); - new TakeSnapshotIfNotAlreadyAttached(Suppliers.ofInstance(manager), snapshotName, snapshotDesc) + new TakeSnapshotIfNotAlreadyAttached(Suppliers.ofInstance(manager), snapshotName, snapshotDesc, Logger.CONSOLE) .apply(machine); verify(machine); @@ -87,6 +90,7 @@ public class TakeSnapshotIfNotAlreadyAttachedTest { expect(progress.getCompleted()).andReturn(true); expect(machine.getCurrentSnapshot()).andReturn(null).anyTimes(); expect(manager.openMachineSession(machine)).andReturn(session); + expect(machine.getState()).andReturn(MachineState.PoweredOff).anyTimes(); expect(machine.getName()).andReturn("machine").anyTimes(); expect(session.getConsole()).andReturn(console); @@ -96,7 +100,7 @@ public class TakeSnapshotIfNotAlreadyAttachedTest { session.unlockMachine(); replay(manager, machine, vBox, session, console, progress); - new TakeSnapshotIfNotAlreadyAttached(Suppliers.ofInstance(manager), snapshotName, snapshotDesc) + new TakeSnapshotIfNotAlreadyAttached(Suppliers.ofInstance(manager), snapshotName, snapshotDesc, Logger.CONSOLE) .apply(machine); verify(machine); diff --git a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunningLiveTest.java b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunningLiveTest.java index 6be7199b3b..be64f61b88 100644 --- a/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunningLiveTest.java +++ b/labs/virtualbox/src/test/java/org/jclouds/virtualbox/functions/admin/StartVBoxIfNotAlreadyRunningLiveTest.java @@ -21,6 +21,7 @@ package org.jclouds.virtualbox.functions.admin; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot; @@ -58,10 +59,9 @@ public class StartVBoxIfNotAlreadyRunningLiveTest { String identity = "adminstrator"; String credential = "12345"; expect(client.seconds(3)).andReturn(client); - - expect(client.apply(new IPSocket(provider.getHost(), provider.getPort()))).andReturn(true); - + expect(client.apply(new IPSocket(provider.getHost(), provider.getPort()))).andReturn(true).anyTimes(); manager.connect(provider.toASCIIString(), "", ""); + expectLastCall().anyTimes(); replay(manager, runScriptOnNodeFactory, client); @@ -87,17 +87,18 @@ public class StartVBoxIfNotAlreadyRunningLiveTest { String credential = "12345"; expect(client.seconds(3)).andReturn(client); - expect(client.apply(new IPSocket(provider.getHost(), provider.getPort()))).andReturn(false); + expect(client.apply(new IPSocket(provider.getHost(), provider.getPort()))).andReturn(false).once().andReturn(true).once(); expect( runScriptOnNodeFactory.create(host, Statements.exec("VBoxManage setproperty websrvauthlibrary null"), runAsRoot(false).wrapInInitScript(false))).andReturn(runScriptOnNode); expect(runScriptOnNode.init()).andReturn(runScriptOnNode); expect(runScriptOnNode.call()).andReturn(new ExecResponse("", "", 0)); - expect(client.apply(new IPSocket(provider.getHost(), provider.getPort()))).andReturn(true); + expect( runScriptOnNodeFactory.create(host, Statements.exec("vboxwebsrv -t 10000 -v -b"), runAsRoot(false) .wrapInInitScript(false).blockOnComplete(false).nameTask("vboxwebsrv"))).andReturn( runScriptOnNode); + expect(runScriptOnNode.init()).andReturn(runScriptOnNode); expect(runScriptOnNode.call()).andReturn(new ExecResponse("", "", 0)); @@ -105,7 +106,7 @@ public class StartVBoxIfNotAlreadyRunningLiveTest { replay(manager, runScriptOnNodeFactory, runScriptOnNode, client); new StartVBoxIfNotAlreadyRunning((Function) Functions.constant(manager), runScriptOnNodeFactory, client, - Suppliers.ofInstance(host), Suppliers.ofInstance(provider), identity, credential).start(); + Suppliers.ofInstance(host), Suppliers.ofInstance(provider), identity, credential); verify(manager, runScriptOnNodeFactory, runScriptOnNode, client); } diff --git a/labs/virtualbox/src/test/resources/logback.xml b/labs/virtualbox/src/test/resources/logback.xml index f9b64ed856..3a5a73c80d 100644 --- a/labs/virtualbox/src/test/resources/logback.xml +++ b/labs/virtualbox/src/test/resources/logback.xml @@ -39,7 +39,7 @@ - + @@ -49,7 +49,7 @@ - + diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java index d54c7ed19d..eccd369768 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java @@ -42,6 +42,7 @@ import org.jclouds.aws.ec2.domain.PlacementGroup.State; import org.jclouds.aws.util.AWSUtils; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.RunNodesException; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; @@ -72,6 +73,7 @@ import org.jclouds.util.Preconditions2; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.LoadingCache; @@ -111,12 +113,13 @@ public class AWSEC2ComputeService extends EC2ComputeService { @Named("SECURITY") LoadingCache securityGroupMap, @Named("PLACEMENT") LoadingCache placementGroupMap, @Named("DELETED") Predicate placementGroupDeleted, - @Named(PROPERTY_EC2_GENERATE_INSTANCE_NAMES) boolean generateInstanceNames, AWSEC2AsyncClient aclient) { + @Named(PROPERTY_EC2_GENERATE_INSTANCE_NAMES) boolean generateInstanceNames, AWSEC2AsyncClient aclient, + Optional imageExtension) { super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, initScriptRunnerFactory, runScriptOnNodeFactory, initAdminAccess, persistNodeCredentials, - timeouts, executor, ec2Client, credentialsMap, securityGroupMap); + timeouts, executor, ec2Client, credentialsMap, securityGroupMap, imageExtension); this.ec2Client = ec2Client; this.placementGroupMap = placementGroupMap; this.placementGroupDeleted = placementGroupDeleted; diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/GoGridComputeService.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/GoGridComputeService.java index b03aadf71d..aaa3b6e079 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/GoGridComputeService.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/GoGridComputeService.java @@ -31,6 +31,7 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.Image; @@ -53,6 +54,7 @@ import org.jclouds.domain.Location; import org.jclouds.gogrid.compute.options.GoGridTemplateOptions; import org.jclouds.scriptbuilder.functions.InitAdminAccess; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; @@ -74,12 +76,12 @@ public class GoGridComputeService extends BaseComputeService { @Named("NODE_SUSPENDED") Predicate> nodeSuspended, InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, InitAdminAccess initAdminAccess, RunScriptOnNode.Factory runScriptOnNodeFactory, PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, - @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) { + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, Optional imageExtension) { super(context, credentialStore, images, hardwareProfiles, locations, listNodesStrategy, getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, resumeNodeStrategy, suspendNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, timeouts, - executor); + executor, imageExtension); } /** diff --git a/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeImageExtensionLivetest.java b/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeImageExtensionLivetest.java new file mode 100644 index 0000000000..f632f51cc0 --- /dev/null +++ b/providers/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeImageExtensionLivetest.java @@ -0,0 +1,44 @@ +/** + * 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.hpcloud.compute.compute; + +import org.jclouds.compute.internal.BaseImageExtensionLiveTest; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.Test; + +import com.google.inject.Module; + +/** + * + * @author David Alves + * + */ +@Test(groups = "live", singleThreaded = true, testName = "HPCloudComputeImageExtensionLivetest") +public class HPCloudComputeImageExtensionLivetest extends BaseImageExtensionLiveTest { + + public HPCloudComputeImageExtensionLivetest() { + provider = "hpcloud-compute"; + } + + @Override + protected Module getSshModule() { + return new SshjSshClientModule(); + } +} diff --git a/sandbox-apis/libvirt/src/main/java/org/jclouds/libvirt/compute/LibvirtComputeService.java b/sandbox-apis/libvirt/src/main/java/org/jclouds/libvirt/compute/LibvirtComputeService.java index 93a4c97e3f..2f83bdeddd 100644 --- a/sandbox-apis/libvirt/src/main/java/org/jclouds/libvirt/compute/LibvirtComputeService.java +++ b/sandbox-apis/libvirt/src/main/java/org/jclouds/libvirt/compute/LibvirtComputeService.java @@ -31,6 +31,7 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ImageExtension; import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.Image; @@ -55,6 +56,7 @@ import org.libvirt.Connect; import org.libvirt.StorageVol; import org.xml.sax.InputSource; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Throwables; @@ -85,12 +87,13 @@ public class LibvirtComputeService extends BaseComputeService { @Named("NODE_SUSPENDED") Predicate nodeSuspended, InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, InitAdminAccess initAdminAccess, RunScriptOnNode.Factory runScriptOnNodeFactory, PersistNodeCredentials persistNodeCredentials, - Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, Connect client) { + Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, Connect client, + Optional imageExtension) { super(context, credentialStore, images, hardwareProfiles, locations, listNodesStrategy, getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, resumeNodeStrategy, suspendNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, - timeouts, executor); + timeouts, executor, imageExtension); this.client = client; }