adapter creates the master image. cloning next

This commit is contained in:
David Ribeiro Alves 2012-03-04 23:35:18 +00:00
parent 406c6a9fc4
commit 690bc9a4dc
8 changed files with 175 additions and 149 deletions

View File

@ -19,7 +19,7 @@
package org.jclouds.virtualbox;
import static org.jclouds.Constants.PROPERTY_API_VERSION;
import static org.jclouds.Constants.*;
import static org.jclouds.Constants.PROPERTY_BUILD_VERSION;
import static org.jclouds.Constants.PROPERTY_CREDENTIAL;
import static org.jclouds.Constants.PROPERTY_ENDPOINT;
@ -30,7 +30,7 @@ import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_DEFAU
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_IMAGES_DESCRIPTOR;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_INSTALLATION_KEY_SEQUENCE;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_PRECONFIGURATION_URL;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_WORKINGDIR;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.*;
import java.io.File;
import java.util.Properties;
@ -58,6 +58,7 @@ public class VirtualBoxPropertiesBuilder extends PropertiesBuilder {
properties.put(PROPERTY_ENDPOINT, "http://localhost:18083/");
// later version not in maven, yet
properties.put(PROPERTY_API_VERSION, "4.1.4");
properties.put(PROPERTY_BUILD_VERSION, "4.1.8r75467");
properties.put(PROPERTY_IDENTITY, "administrator");
properties.put(PROPERTY_CREDENTIAL, "12345");

View File

@ -29,13 +29,12 @@ import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.ssh.SshClient;
import org.jclouds.virtualbox.Host;
import org.jclouds.virtualbox.Preconfiguration;
import org.jclouds.virtualbox.domain.ExecutionType;
import org.jclouds.virtualbox.domain.IsoSpec;
@ -63,108 +62,118 @@ import com.google.inject.Inject;
@Singleton
public class CreateAndInstallVm implements Function<MasterSpec, IMachine> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final ComputeServiceContext context;
private final Supplier<VirtualBoxManager> manager;
private final CreateAndRegisterMachineFromIsoIfNotAlreadyExists createAndRegisterMachineFromIsoIfNotAlreadyExists;
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final Predicate<SshClient> sshResponds;
private final ExecutionType executionType;
private final Supplier<VirtualBoxManager> manager;
private final CreateAndRegisterMachineFromIsoIfNotAlreadyExists createAndRegisterMachineFromIsoIfNotAlreadyExists;
private final GuestAdditionsInstaller guestAdditionsInstaller;
private final Predicate<SshClient> sshResponds;
private final ExecutionType executionType;
private LoadingCache<IsoSpec, URI> preConfiguration;
private final Function<IMachine, SshClient> sshClientForIMachine;
private final MachineUtils machineUtils;
private final IMachineToNodeMetadata imachineToNodeMetadata;
private LoadingCache<IsoSpec, URI> preConfiguration;
@Inject
public CreateAndInstallVm(Supplier<VirtualBoxManager> manager,
CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists,
GuestAdditionsInstaller guestAdditionsInstaller, IMachineToNodeMetadata imachineToNodeMetadata,
Predicate<SshClient> sshResponds, Function<IMachine, SshClient> sshClientForIMachine,
ExecutionType executionType, MachineUtils machineUtils,
@Preconfiguration LoadingCache<IsoSpec, URI> preConfiguration) {
this.manager = manager;
this.createAndRegisterMachineFromIsoIfNotAlreadyExists = CreateAndRegisterMachineFromIsoIfNotAlreadyExists;
this.sshResponds = sshResponds;
this.sshClientForIMachine = sshClientForIMachine;
this.executionType = executionType;
this.machineUtils = machineUtils;
this.preConfiguration = preConfiguration;
this.guestAdditionsInstaller = guestAdditionsInstaller;
this.imachineToNodeMetadata = imachineToNodeMetadata;
}
private final Function<IMachine, SshClient> sshClientForIMachine;
@Override
public IMachine apply(MasterSpec masterSpec) {
private final MachineUtils machineUtils;
@Inject
public CreateAndInstallVm(@Host ComputeServiceContext context, Supplier<VirtualBoxManager> manager,
CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists,
Predicate<SshClient> sshResponds, Function<IMachine, SshClient> sshClientForIMachine,
ExecutionType executionType, MachineUtils machineUtils, @Preconfiguration LoadingCache<IsoSpec, URI> preConfiguration) {
this.context = context;
this.manager = manager;
this.createAndRegisterMachineFromIsoIfNotAlreadyExists = CreateAndRegisterMachineFromIsoIfNotAlreadyExists;
this.sshResponds = sshResponds;
this.sshClientForIMachine = sshClientForIMachine;
this.executionType = executionType;
this.machineUtils = machineUtils;
this.preConfiguration = preConfiguration;
}
@Override
public IMachine apply(MasterSpec masterSpec) {
VmSpec vmSpec = masterSpec.getVmSpec();
IsoSpec isoSpec = masterSpec.getIsoSpec();
String vmName = vmSpec.getVmName();
final IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists.apply(masterSpec);
// Launch machine and wait for it to come online
ensureMachineIsLaunched(vmName);
VmSpec vmSpec = masterSpec.getVmSpec();
IsoSpec isoSpec = masterSpec.getIsoSpec();
String vmName = vmSpec.getVmName();
URI uri = preConfiguration.getUnchecked(isoSpec);
String installationKeySequence = isoSpec.getInstallationKeySequence().replace("PRECONFIGURATION_URL",
uri.toASCIIString());
IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists.apply(masterSpec);
configureOsInstallationWithKeyboardSequence(vmName, installationKeySequence);
SshClient client = sshClientForIMachine.apply(vm);
logger.debug(">> awaiting installation to finish node(%s)", vmName);
// Launch machine and wait for it to come online
ensureMachineIsLaunched(vmName);
checkState(sshResponds.apply(client), "timed out waiting for guest %s to be accessible via ssh", vmName);
logger.debug(">> awaiting installation of guest additions on vm: %s", vmName);
checkState(new GuestAdditionsInstaller(context).apply(vmName));
logger.debug(">> awaiting post-installation actions on vm: %s", vmName);
ListenableFuture<ExecResponse> execFuture = context.getComputeService().submitScriptOnNode(vmName,
call("cleanupUdevIfNeeded"), RunScriptOptions.NONE);
ExecResponse execResponse = Futures.getUnchecked(execFuture);
checkState(execResponse.getExitCode() == 0);
logger.debug("<< installation of image complete. Powering down node(%s)", vmName);
ensureMachineHasPowerDown(vmName);
return vm;
}
URI uri = preConfiguration.getUnchecked(isoSpec);
String installationKeySequence = isoSpec.getInstallationKeySequence().replace("PRECONFIGURATION_URL",
uri.toASCIIString());
private void configureOsInstallationWithKeyboardSequence(String vmName, String installationKeySequence) {
Iterable<List<Integer>> scancodelist =
transform(Splitter.on(" ").split(installationKeySequence), new StringToKeyCode());
configureOsInstallationWithKeyboardSequence(vmName, installationKeySequence);
SshClient client = sshClientForIMachine.apply(vm);
logger.debug(">> awaiting installation to finish node(%s)", vmName);
for (List<Integer> scancodes : scancodelist) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new SendScancodes(scancodes));
}
}
checkState(sshResponds.apply(client), "timed out waiting for guest %s to be accessible via ssh", vmName);
/**
* ensureMachineHasPowerDown needs to have this delay just to ensure that the machine is completely powered off
*
* @param vmName
*/
private void ensureMachineHasPowerDown(String vmName) {
while(!manager.get().getVBox().findMachine(vmName).getState().equals(MachineState.POWERED_OFF)) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
logger.debug(">> awaiting installation of guest additions on vm: %s", vmName);
checkState(guestAdditionsInstaller.apply(vm));
logger.debug(">> awaiting post-installation actions on vm: %s", vmName);
NodeMetadata vmMetadata = imachineToNodeMetadata.apply(vm);
ListenableFuture<ExecResponse> execFuture = machineUtils.runScriptOnNode(vmMetadata, call("cleanupUdevIfNeeded"),
RunScriptOptions.NONE);
ExecResponse execResponse = Futures.getUnchecked(execFuture);
checkState(execResponse.getExitCode() == 0);
logger.debug("<< installation of image complete. Powering down node(%s)", vmName);
ensureMachineHasPowerDown(vmName);
return vm;
}
private void configureOsInstallationWithKeyboardSequence(String vmName, String installationKeySequence) {
Iterable<List<Integer>> scancodelist = transform(Splitter.on(" ").split(installationKeySequence),
new StringToKeyCode());
for (List<Integer> scancodes : scancodelist) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new SendScancodes(scancodes));
}
}
/**
* ensureMachineHasPowerDown needs to have this delay just to ensure that the machine is completely powered off
*
* @param vmName
*/
private void ensureMachineHasPowerDown(String vmName) {
while (!manager.get().getVBox().findMachine(vmName).getState().equals(MachineState.POWERED_OFF)) {
try {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole().powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
}
}
});
} catch (RuntimeException e) {
// sometimes the machine might be powered of between the while test and the call to lockSessionOnMachineAndApply
if (e.getMessage().contains("Invalid machine state: PoweredOff")){
return;
} else if(e.getMessage().contains("VirtualBox error: The object is not ready")){
continue;
} else {
throw e;
}
}
}
}
private void ensureMachineIsLaunched(String vmName) {
machineUtils.applyForMachine(vmName, new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, ""));
}
private void ensureMachineIsLaunched(String vmName) {
machineUtils.applyForMachine(vmName, new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, ""));
}
}

View File

@ -63,7 +63,8 @@ public class IMachineToImage implements Function<IMachine, Image> {
OperatingSystem os = OperatingSystem.builder().description(guestOSType.getDescription()).family(family)
.version(version).is64Bit(guestOSType.getIs64Bit()).build();
return new ImageBuilder().id("" + from.getId()).description(from.getDescription()).operatingSystem(os).build();
return new ImageBuilder().id("" + from.getId()).name(from.getName()).description(from.getDescription())
.operatingSystem(os).build();
}
}

View File

@ -4,40 +4,48 @@ import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.virtualbox.functions.IMachineToNodeMetadata;
import org.jclouds.virtualbox.statements.InstallGuestAdditions;
import org.jclouds.virtualbox.util.MachineUtils;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.VirtualBoxManager;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
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;
import com.google.inject.Inject;
@Singleton
public class GuestAdditionsInstaller implements Predicate<String> {
public class GuestAdditionsInstaller implements Predicate<IMachine> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected Logger logger = Logger.NULL;
private final ComputeServiceContext vboxHostContext;
private String vboxVersion;
private final IMachineToNodeMetadata imachineToNodeMetadata;
private final MachineUtils machineUtils;
private final Supplier<VirtualBoxManager> manager;
@Inject
public GuestAdditionsInstaller(ComputeServiceContext vboxHostContext) {
this.vboxHostContext = vboxHostContext;
public GuestAdditionsInstaller(Supplier<VirtualBoxManager> manager, MachineUtils machineUtils,
IMachineToNodeMetadata imachineToNodeMetadata) {
this.machineUtils = machineUtils;
this.imachineToNodeMetadata = imachineToNodeMetadata;
this.manager = manager;
}
@Override
public boolean apply(String vmName) {
vboxVersion = Iterables.get(Splitter.on('r').split(vboxHostContext.getProviderSpecificContext().getBuildVersion()),
0);
ListenableFuture<ExecResponse> execFuture = vboxHostContext.getComputeService().submitScriptOnNode(vmName,
public boolean apply(IMachine machine) {
String vboxVersion = Iterables.get(Splitter.on('r').split(manager.get().getVBox().getVersion()), 0);
System.out.println("VERSION: " + vboxVersion);
ListenableFuture<ExecResponse> execFuture = machineUtils.runScriptOnNode(imachineToNodeMetadata.apply(machine),
new InstallGuestAdditions(vboxVersion), RunScriptOptions.NONE);
ExecResponse execResponse = Futures.getUnchecked(execFuture);
return execResponse == null ? false : execResponse.getExitStatus() == 0;

View File

@ -30,7 +30,9 @@ import javax.inject.Singleton;
import org.jclouds.compute.callables.RunScriptOnNode;
import org.jclouds.compute.callables.RunScriptOnNode.Factory;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.domain.Statement;
@ -45,6 +47,7 @@ import org.virtualbox_4_1.VirtualBoxManager;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
/**
@ -71,6 +74,11 @@ public class MachineUtils {
this.scriptRunner = scriptRunner;
this.host = host;
}
public ListenableFuture<ExecResponse> runScriptOnNode(NodeMetadata metadata, Statement statement,
RunScriptOptions options) {
return scriptRunner.submit(metadata, statement, options);
}
/**
* Locks the machine and executes the given function using the machine

View File

@ -19,6 +19,7 @@
package org.jclouds.virtualbox;
import java.io.File;
import java.net.URI;
import java.util.Properties;
@ -42,7 +43,11 @@ import org.jclouds.virtualbox.util.MachineUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.virtualbox_4_1.IProgress;
import org.virtualbox_4_1.ISession;
import org.virtualbox_4_1.LockType;
import org.virtualbox_4_1.VirtualBoxManager;
import org.virtualbox_4_1.jaxws.MachineState;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
@ -75,6 +80,7 @@ public class BaseVirtualBoxClientLiveTest extends BaseVersionedServiceLiveTest {
protected String guestAdditionsIso;
protected String adminDisk;
protected String workingDir;
protected String isosDir;
protected Supplier<NodeMetadata> host;
@Override
@ -122,10 +128,14 @@ public class BaseVirtualBoxClientLiveTest extends BaseVersionedServiceLiveTest {
hostModule), overrides);
Function<String, String> configProperties = context.utils().injector()
.getInstance(ValueOfConfigurationKeyOrNull.class);
imageId = configProperties
.apply(ComputeServiceConstants.PROPERTY_IMAGE_ID);
imageId = "ubuntu-11.04-server-i386";
workingDir = configProperties
.apply(VirtualBoxConstants.VIRTUALBOX_WORKINGDIR);
isosDir = workingDir+File.separator+"isos";
File isosDirFile = new File(isosDir);
if(!isosDirFile.exists()){
isosDirFile.mkdirs();
}
host = context.utils().injector()
.getInstance(Key.get(new TypeLiteral<Supplier<NodeMetadata>>() {
}));
@ -146,9 +156,9 @@ public class BaseVirtualBoxClientLiveTest extends BaseVersionedServiceLiveTest {
Splitter.on('r').split(
context.getProviderSpecificContext().getBuildVersion()), 0);
adminDisk = workingDir + "/testadmin.vdi";
operatingSystemIso = String.format("%s/%s.iso", workingDir, imageId);
operatingSystemIso = String.format("%s/%s.iso", isosDir, imageId);
guestAdditionsIso = String.format("%s/VBoxGuestAdditions_%s.iso",
workingDir, hostVersion);
isosDir, hostVersion);
}
protected void undoVm(VmSpec vmSpecification) {
@ -156,6 +166,30 @@ public class BaseVirtualBoxClientLiveTest extends BaseVersionedServiceLiveTest {
vmSpecification.getVmId(),
new UnregisterMachineIfExistsAndDeleteItsMedia(vmSpecification));
}
protected void ensureMachineHasPowerDown(String vmName) {
while (!manager.get().getVBox().findMachine(vmName).getState().equals(MachineState.POWERED_OFF)) {
try {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole().powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
} catch (RuntimeException e) {
// sometimes the machine might be powered of between the while test and the call to lockSessionOnMachineAndApply
if (e.getMessage().contains("Invalid machine state: PoweredOff")){
return;
} else if(e.getMessage().contains("VirtualBox error: The object is not ready")){
continue;
} else {
throw e;
}
}
}
}
@AfterClass(groups = "live")
protected void tearDown() throws Exception {

View File

@ -23,6 +23,7 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
import static junit.framework.Assert.assertEquals;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_INSTALLATION_KEY_SEQUENCE;
import static org.testng.Assert.assertTrue;
@ -88,12 +89,13 @@ public class CreateAndInstallVmLiveTest extends BaseVirtualBoxClientLiveTest {
private Injector injector;
private Function<IMachine, SshClient> sshClientForIMachine;
private Predicate<SshClient> sshResponds;
private String vmName;
@Override
@BeforeClass(groups = "live")
public void setupClient() {
super.setupClient();
String vmName = VIRTUALBOX_IMAGE_PREFIX
this.vmName = VIRTUALBOX_IMAGE_PREFIX
+ CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, getClass()
.getSimpleName());
@ -136,16 +138,12 @@ public class CreateAndInstallVmLiveTest extends BaseVirtualBoxClientLiveTest {
undoVm(vmSpecification);
}
@Test
public void testCreateImageMachineFromIso() throws Exception {
IMachine imageMachine = getVmWithGuestAdditionsInstalled();
IMachineToImage iMachineToImage = new IMachineToImage(manager, map);
Image newImage = iMachineToImage.apply(imageMachine);
// TODO add the description to the cache of the images or serialize to
// YAML the image desc
Set<? extends Image> images = context.getComputeService().listImages();
Iterable<String> imageIds = transform(images, extractId());
assertTrue(any(imageIds, equalTo(newImage.getId())));
assertEquals(vmName,newImage.getName());
}
@Test
@ -212,27 +210,6 @@ public class CreateAndInstallVmLiveTest extends BaseVirtualBoxClientLiveTest {
}
}
private void ensureMachineHasPowerDown(String vmName) {
while (!manager.get().getVBox().findMachine(vmName).getState()
.equals(MachineState.POWERED_OFF)) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared,
new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole()
.powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
}
@Override
@AfterClass(groups = "live")
protected void tearDown() throws Exception {

View File

@ -144,16 +144,4 @@ public class GuestAdditionsInstallerLiveTest extends
}
}
private void ensureMachineHasPowerDown(String vmName) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared,
new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole()
.powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
}
}