added a unit test for guest additions installer. installguestadditions is now able to mount the pre-downloaded iso

This commit is contained in:
David Ribeiro Alves 2012-03-22 15:16:19 +00:00
parent 582423bd2d
commit 28bd62edfc
9 changed files with 174 additions and 257 deletions

View File

@ -29,6 +29,7 @@ import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.options.RunScriptOptions;
@ -39,7 +40,7 @@ import org.jclouds.virtualbox.Preconfiguration;
import org.jclouds.virtualbox.domain.IsoSpec;
import org.jclouds.virtualbox.domain.MasterSpec;
import org.jclouds.virtualbox.domain.VmSpec;
import org.jclouds.virtualbox.predicates.GuestAdditionsInstaller;
import org.jclouds.virtualbox.statements.InstallGuestAdditions;
import org.jclouds.virtualbox.util.MachineController;
import org.jclouds.virtualbox.util.MachineUtils;
import org.virtualbox_4_1.IMachine;
@ -48,6 +49,7 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.cache.LoadingCache;
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;
@ -60,32 +62,32 @@ public class CreateAndInstallVm implements Function<MasterSpec, IMachine> {
protected Logger logger = Logger.NULL;
private final CreateAndRegisterMachineFromIsoIfNotAlreadyExists createAndRegisterMachineFromIsoIfNotAlreadyExists;
private final GuestAdditionsInstaller guestAdditionsInstaller;
private final Predicate<SshClient> sshResponds;
private LoadingCache<IsoSpec, URI> preConfiguration;
private final Function<IMachine, SshClient> sshClientForIMachine;
private final MachineUtils machineUtils;
private final IMachineToNodeMetadata imachineToNodeMetadata;
private final MachineController machineController;
private final String version;
@Inject
public CreateAndInstallVm(
CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists,
GuestAdditionsInstaller guestAdditionsInstaller,
IMachineToNodeMetadata imachineToNodeMetadata,
Predicate<SshClient> sshResponds,
Function<IMachine, SshClient> sshClientForIMachine,
MachineUtils machineUtils,
@Preconfiguration LoadingCache<IsoSpec, URI> preConfiguration, MachineController machineController) {
@Preconfiguration LoadingCache<IsoSpec, URI> preConfiguration,
MachineController machineController, @Named(Constants.PROPERTY_BUILD_VERSION) String version) {
this.createAndRegisterMachineFromIsoIfNotAlreadyExists = CreateAndRegisterMachineFromIsoIfNotAlreadyExists;
this.sshResponds = sshResponds;
this.sshClientForIMachine = sshClientForIMachine;
this.machineUtils = machineUtils;
this.preConfiguration = preConfiguration;
this.guestAdditionsInstaller = guestAdditionsInstaller;
this.imachineToNodeMetadata = imachineToNodeMetadata;
this.machineController = machineController;
this.version = Iterables.get(Splitter.on('r').split(version), 0);
}
@Override
@ -116,26 +118,28 @@ public class CreateAndInstallVm implements Function<MasterSpec, IMachine> {
"timed out waiting for guest %s to be accessible via ssh",
vmName);
logger.debug(">> awaiting installation of guest additions on vm: %s", vmName);
checkState(guestAdditionsInstaller.apply(vm));
logger.debug(">> awaiting installation of guest additions on vm: %s", vmName);
logger.debug(">> awaiting post-installation actions on vm: %s", vmName);
ListenableFuture<ExecResponse> execFuture = machineUtils.runScriptOnNode(imachineToNodeMetadata.apply(vm),
new InstallGuestAdditions(vmSpec, version), RunScriptOptions.NONE);
ExecResponse execResponse = Futures.getUnchecked(execFuture);
NodeMetadata vmMetadata = imachineToNodeMetadata.apply(vm);
checkState(execResponse.getExitStatus() == 0);
ListenableFuture<ExecResponse> execFuture =
machineUtils.runScriptOnNode(vmMetadata, call("cleanupUdevIfNeeded"), RunScriptOptions.NONE);
logger.debug(">> awaiting post-installation actions on vm: %s", vmName);
ExecResponse execResponse = Futures.getUnchecked(execFuture);
checkState(execResponse.getExitStatus() == 0);
NodeMetadata vmMetadata = imachineToNodeMetadata.apply(vm);
logger.debug(
"<< installation of image complete. Powering down node(%s)",
vmName);
execFuture = machineUtils.runScriptOnNode(vmMetadata, call("cleanupUdevIfNeeded"), RunScriptOptions.NONE);
machineController.ensureMachineHasPowerDown(vmName);
return vm;
}
execResponse = Futures.getUnchecked(execFuture);
checkState(execResponse.getExitStatus() == 0);
logger.debug("<< installation of image complete. Powering down node(%s)", vmName);
machineController.ensureMachineHasPowerDown(vmName);
return vm;
}
private void configureOsInstallationWithKeyboardSequence(String vmName,
String installationKeySequence) {

View File

@ -160,7 +160,7 @@ public class MastersLoadingCache extends AbstractLoadingCache<Image, Master> {
.build();
StorageController ideController = StorageController.builder().name("IDE Controller").bus(StorageBus.IDE)
.attachISO(0, 0, localIsoUrl).attachHardDisk(hardDisk).attachISO(1, 1, guestAdditionsIso).build();
.attachISO(0, 0, localIsoUrl).attachHardDisk(hardDisk).attachISO(1, 0, guestAdditionsIso).build();
VmSpec vmSpecification = VmSpec.builder().id(yamlImage.id).name(vmName).memoryMB(512).osTypeId("")
.controller(ideController).forceOverwrite(true).cleanUpMode(CleanupMode.Full).build();

View File

@ -1,72 +0,0 @@
/**
* 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.predicates;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
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<IMachine> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final IMachineToNodeMetadata imachineToNodeMetadata;
private final MachineUtils machineUtils;
private final Supplier<VirtualBoxManager> manager;
@Inject
public GuestAdditionsInstaller(Supplier<VirtualBoxManager> manager, MachineUtils machineUtils,
IMachineToNodeMetadata imachineToNodeMetadata) {
this.machineUtils = machineUtils;
this.imachineToNodeMetadata = imachineToNodeMetadata;
this.manager = manager;
}
@Override
public boolean apply(IMachine machine) {
String vboxVersion = Iterables.get(Splitter.on('r').split(manager.get().getVBox().getVersion()), 0);
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

@ -25,34 +25,82 @@ import static org.jclouds.scriptbuilder.domain.Statements.exec;
import static org.jclouds.scriptbuilder.domain.Statements.saveHttpResponseTo;
import java.net.URI;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Named;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.virtualbox.domain.IsoImage;
import org.jclouds.virtualbox.domain.StorageController;
import org.jclouds.virtualbox.domain.VmSpec;
import org.testng.collections.Lists;
public class InstallGuestAdditions extends StatementList {
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
public InstallGuestAdditions(String vboxVersion) {
this(vboxVersion, "/mnt", "VBoxGuestAdditions_" + vboxVersion + ".iso");
/**
* Mounts the DVD with guest additions that was downloaded and attached as removeable storage. If no
* guest additions is attached to the vmspec then it is downloaded.
*
* @author David Alves
*
*/
public class InstallGuestAdditions implements Statement {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final StatementList statements;
public InstallGuestAdditions(VmSpec vmSpecification, String vboxVersion) {
this.statements = new StatementList(getStatements(vmSpecification, vboxVersion));
}
public InstallGuestAdditions(String vboxVersion, String mountPoint, String vboxGuestAdditionsIso) {
this(URI.create("http://download.virtualbox.org/virtualbox/" + vboxVersion + "/" + vboxGuestAdditionsIso),
mountPoint, vboxGuestAdditionsIso);
}
public InstallGuestAdditions(URI download, String mountPoint, String vboxGuestAdditionsIso) {
super(call("setupPublicCurl"), //
saveHttpResponseTo(download, "{tmp}{fs}", vboxGuestAdditionsIso),//
exec(String.format("mount -o loop {tmp}{fs}%s %s", vboxGuestAdditionsIso, mountPoint)),
call("installModuleAssistantIfNeeded"), //
exec(String.format("%s%s", mountPoint, "/VBoxLinuxAdditions.run")), //
exec(String.format("umount %s", mountPoint)));
private List<Statement> getStatements(VmSpec vmSpecification, String vboxVersion) {
List<Statement> statements = Lists.newArrayList();
statements.add(call("installModuleAssistantIfNeeded"));
String mountPoint = "/mnt";
if (Iterables.tryFind(vmSpecification.getControllers(), new Predicate<StorageController>() {
@Override
public boolean apply(StorageController input) {
if (!input.getIsoImages().isEmpty()) {
for (IsoImage iso : input.getIsoImages()) {
if (iso.getSourcePath().contains("VBoxGuestAdditions_")) {
return true;
}
}
}
return false;
}
}).isPresent()) {
statements.add(exec("mount -t iso9660 /dev/sr1 " + mountPoint));
} else {
String vboxGuestAdditionsIso = "VBoxGuestAdditions_" + vboxVersion + ".iso";
URI download = URI.create("http://download.virtualbox.org/virtualbox/" + vboxVersion + "/"
+ vboxGuestAdditionsIso);
statements.add(call("setupPublicCurl"));
statements.add(saveHttpResponseTo(download, "{tmp}{fs}", vboxGuestAdditionsIso));//
statements.add(exec(String.format("mount -o loop {tmp}{fs}%s %s", vboxGuestAdditionsIso, mountPoint)));
}
statements.add(exec(String.format("%s%s", mountPoint, "/VBoxLinuxAdditions.run"))); //
statements.add(exec(String.format("umount %s", mountPoint)));
return statements;
}
@Override
public String render(OsFamily family) {
if (checkNotNull(family, "family") == OsFamily.WINDOWS)
throw new UnsupportedOperationException("windows not yet implemented");
return super.render(family);
return statements.render(family);
}
@Override
public Iterable<String> functionDependencies(OsFamily family) {
return statements.functionDependencies(family);
}
}

View File

@ -35,22 +35,22 @@ import com.google.common.io.Resources;
@Test(groups = "unit")
public class InstallGuestAdditionsTest {
@Test(enabled = false)
public void testUnixByItself() throws IOException {
InstallGuestAdditions statement = new InstallGuestAdditions("4.1.6");
assertEquals(statement.render(OsFamily.UNIX),
CharStreams.toString(Resources.newReaderSupplier(
Resources.getResource("test_guest_additions_installer." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)));
}
@Test(enabled = false)
public void testUnixInInitScript() throws IOException {
Statement statement = InitScript.builder().name("install_guest_additions")
.run(new InstallGuestAdditions("4.1.6")).build();
assertEquals(statement.render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(
Resources.getResource("test_guest_additions_installer_init." + ShellToken.SH.to(OsFamily.UNIX)),
Charsets.UTF_8)));
}
// @Test(enabled = false)
// public void testUnixByItself() throws IOException {
// InstallGuestAdditions statement = new InstallGuestAdditions("4.1.6");
// assertEquals(statement.render(OsFamily.UNIX),
// CharStreams.toString(Resources.newReaderSupplier(
// Resources.getResource("test_guest_additions_installer." + ShellToken.SH.to(OsFamily.UNIX)),
// Charsets.UTF_8)));
// }
//
// @Test(enabled = false)
// public void testUnixInInitScript() throws IOException {
// Statement statement = InitScript.builder().name("install_guest_additions")
// .run(new InstallGuestAdditions("4.1.6")).build();
//
// assertEquals(statement.render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(
// Resources.getResource("test_guest_additions_installer_init." + ShellToken.SH.to(OsFamily.UNIX)),
// Charsets.UTF_8)));
// }
}

View File

@ -41,7 +41,6 @@ import org.testng.annotations.Test;
import org.virtualbox_4_1.CleanupMode;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.ISession;
import org.virtualbox_4_1.LockType;
import org.virtualbox_4_1.NetworkAttachmentType;
import org.virtualbox_4_1.StorageBus;
@ -74,7 +73,7 @@ public class GuestAdditionsInstallerLiveTest extends
.attachISO(0, 0, operatingSystemIso)
.attachHardDisk(
HardDisk.builder().diskpath(adminDisk(sourceName)).controllerPort(0).deviceSlot(1)
.autoDelete(true).build()).attachISO(1, 1, guestAdditionsIso).build();
.autoDelete(true).build()).attachISO(1, 0, guestAdditionsIso).build();
VmSpec sourceVmSpec = VmSpec.builder().id(sourceName).name(sourceName)
.osTypeId("").memoryMB(512).cleanUpMode(CleanupMode.Full)

View File

@ -1,74 +0,0 @@
package org.jclouds.virtualbox.statements;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.ComputeServiceContext;
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.ssh.SshClient;
import org.jclouds.virtualbox.domain.ExecutionType;
import org.jclouds.virtualbox.functions.CreateAndRegisterMachineFromIsoIfNotAlreadyExists;
import org.jclouds.virtualbox.functions.IMachineToNodeMetadata;
import org.jclouds.virtualbox.functions.LaunchMachineIfNotAlreadyRunning;
import org.jclouds.virtualbox.util.MachineUtils;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.VirtualBoxManager;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
@Singleton
public class GuestAdditionsInstaller implements Function<String, IMachine> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final ComputeServiceContext context;
private final Supplier<VirtualBoxManager> manager;
private final ExecutionType executionType;
private final MachineUtils machineUtils;
// TODO remove this hardcoded value
private String vboxVersion = "4.1.6";
@Inject
public GuestAdditionsInstaller(ComputeServiceContext context, Supplier<VirtualBoxManager> manager,
CreateAndRegisterMachineFromIsoIfNotAlreadyExists createAndRegisterMachineFromIsoIfNotAlreadyExists,
Predicate<SshClient> installGuestAdditionsViaSshResponds, Function<IMachine, SshClient> sshClientForIMachine,
ExecutionType executionType, MachineUtils machineUtils, Factory runScriptOnNodeFactory,
Supplier<NodeMetadata> guest) {
this.context = context;
this.manager = manager;
this.executionType = executionType;
this.machineUtils = machineUtils;
}
@Override
public IMachine apply(String vmName) {
IMachine vm = manager.get().getVBox().findMachine(vmName);
ensureMachineIsLaunched(vmName);
NodeMetadata vmMetadata = new IMachineToNodeMetadata().apply(vm);
ListenableFuture<ExecResponse> execFuture = context.getComputeService().submitScriptOnNode(vmMetadata.getId(),
new InstallGuestAdditions(vboxVersion), RunScriptOptions.NONE);
Futures.getUnchecked(execFuture);
return vm;
}
private void ensureMachineIsLaunched(String vmName) {
machineUtils.applyForMachine(vmName, new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, ""));
}
}

View File

@ -1,55 +0,0 @@
/**
* 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.statements;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX;
import org.jclouds.virtualbox.BaseVirtualBoxClientLiveTest;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.CaseFormat;
import com.google.inject.Injector;
/**
* @author Andrea Turli
*/
@Test(groups = "live", singleThreaded = true, testName = "InstallGuestAdditionsLiveTest")
public class InstallGuestAdditionsLiveTest extends BaseVirtualBoxClientLiveTest {
private String vmName;
@Override
@BeforeClass(groups = "live")
public void setupClient() {
super.setupClient();
vmName = VIRTUALBOX_IMAGE_PREFIX
+ CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, getClass()
.getSimpleName());
vmName = "jclouds-image-create-and-install-vm-live-test";
}
public void testInstallGuestAdditionsOnTheMachine() throws Exception {
Injector injector = context.utils().injector();
injector.getInstance(GuestAdditionsInstaller.class).apply(vmName);
}
}

View File

@ -0,0 +1,67 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.virtualbox.statements;
import static junit.framework.Assert.assertEquals;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.virtualbox.BaseVirtualBoxClientLiveTest;
import org.jclouds.virtualbox.domain.StorageController;
import org.jclouds.virtualbox.domain.VmSpec;
import org.testng.annotations.Test;
import org.virtualbox_4_1.CleanupMode;
import org.virtualbox_4_1.StorageBus;
/**
* @author Andrea Turli, David Alves
*/
@Test(testName = "InstallGuestAdditionsTest")
public class InstallGuestAdditionsTest extends BaseVirtualBoxClientLiveTest {
public void testIsoPresent() {
StorageController ideController = StorageController.builder().name("IDE Controller").bus(StorageBus.IDE)
.attachISO(1, 0, "VBoxGuestAdditions_").build();
VmSpec vmSpecification = VmSpec.builder().id("").name("").memoryMB(512).osTypeId("").controller(ideController)
.forceOverwrite(true).cleanUpMode(CleanupMode.Full).build();
InstallGuestAdditions installer = new InstallGuestAdditions(vmSpecification, "4.1.8");
String scripts = installer.render(OsFamily.UNIX);
assertEquals("installModuleAssistantIfNeeded || return 1\n" + "mount -t iso9660 /dev/sr1 /mnt\n"
+ "/mnt/VBoxLinuxAdditions.run\n" + "umount /mnt\n", scripts);
}
public void testIsoNotPresent() {
StorageController ideController = StorageController.builder().name("IDE Controller").bus(StorageBus.IDE).build();
VmSpec vmSpecification = VmSpec.builder().id("").name("").memoryMB(512).osTypeId("").controller(ideController)
.forceOverwrite(true).cleanUpMode(CleanupMode.Full).build();
InstallGuestAdditions installer = new InstallGuestAdditions(vmSpecification, "4.1.8");
String scripts = installer.render(OsFamily.UNIX);
assertEquals(
"installModuleAssistantIfNeeded || return 1\n"
+ "setupPublicCurl || return 1\n"
+ "(mkdir -p /tmp/ && cd /tmp/ && [ ! -f VBoxGuestAdditions_4.1.8.iso ] && curl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 -C - -X GET http://download.virtualbox.org/virtualbox/4.1.8/VBoxGuestAdditions_4.1.8.iso >VBoxGuestAdditions_4.1.8.iso)\n"
+ "mount -o loop /tmp/VBoxGuestAdditions_4.1.8.iso /mnt\n"
+ "/mnt/VBoxLinuxAdditions.run\n" + "umount /mnt\n", scripts);
}
}