started refactoring IsoToIMachine to re-use error handling

This commit is contained in:
Adrian Cole 2011-10-22 17:54:35 +02:00
parent aab1290b85
commit 926989c5f1
3 changed files with 399 additions and 207 deletions

View File

@ -0,0 +1,57 @@
/**
* 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.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.StorageBus;
import org.virtualbox_4_1.VBoxException;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*
*/
public class AddIDEControllerIfNotExists implements Function<IMachine, Void> {
private final String controllerName;
public AddIDEControllerIfNotExists(String controllerName) {
this.controllerName = checkNotNull(controllerName, "controllerName");
}
@Override
public Void apply(IMachine arg0) {
try {
arg0.addStorageController(controllerName, StorageBus.IDE);
arg0.saveSettings();
} catch (VBoxException e) {
if (e.getMessage().indexOf("already exists") == -1)
throw e;
}
return null;
}
@Override
public String toString() {
return String.format("addStorageController(%s, IDE)", controllerName);
}
}

View File

@ -21,6 +21,7 @@
package org.jclouds.virtualbox.functions; package org.jclouds.virtualbox.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate; import static com.google.common.base.Throwables.propagate;
import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot; import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot;
import static org.jclouds.compute.options.RunScriptOptions.Builder.wrapInInitScript; import static org.jclouds.compute.options.RunScriptOptions.Builder.wrapInInitScript;
@ -28,6 +29,7 @@ import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_INSTA
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_WORKINGDIR; import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_WORKINGDIR;
import static org.virtualbox_4_1.AccessMode.ReadOnly; import static org.virtualbox_4_1.AccessMode.ReadOnly;
import static org.virtualbox_4_1.DeviceType.DVD; import static org.virtualbox_4_1.DeviceType.DVD;
import static org.virtualbox_4_1.LockType.Shared;
import static org.virtualbox_4_1.LockType.Write; import static org.virtualbox_4_1.LockType.Write;
import static org.virtualbox_4_1.NATProtocol.TCP; import static org.virtualbox_4_1.NATProtocol.TCP;
import static org.virtualbox_4_1.NetworkAttachmentType.NAT; import static org.virtualbox_4_1.NetworkAttachmentType.NAT;
@ -37,11 +39,7 @@ import java.io.File;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Named; import javax.inject.Named;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.ExecResponse; import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.options.RunScriptOptions; import org.jclouds.compute.options.RunScriptOptions;
@ -60,242 +58,289 @@ import org.virtualbox_4_1.IMedium;
import org.virtualbox_4_1.IProgress; import org.virtualbox_4_1.IProgress;
import org.virtualbox_4_1.ISession; import org.virtualbox_4_1.ISession;
import org.virtualbox_4_1.LockType; import org.virtualbox_4_1.LockType;
import org.virtualbox_4_1.StorageBus; import org.virtualbox_4_1.VBoxException;
import org.virtualbox_4_1.VirtualBoxManager; import org.virtualbox_4_1.VirtualBoxManager;
import org.virtualbox_4_1.jaxws.MediumVariant; import org.virtualbox_4_1.jaxws.MediumVariant;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.inject.Inject; import com.google.inject.Inject;
public class IsoToIMachine implements Function<String, IMachine> { public class IsoToIMachine implements Function<String, IMachine> {
@Resource @Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER) @Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
private VirtualBoxManager manager; private VirtualBoxManager manager;
private String adminDisk; private String adminDisk;
private String diskFormat; private String diskFormat;
private String settingsFile; private String settingsFile;
private String vmName; private String vmName;
private String osTypeId; private String osTypeId;
private String vmId; private String vmId;
private String controllerIDE; private String controllerIDE;
private boolean forceOverwrite; private boolean forceOverwrite;
private ComputeServiceContext context; private ComputeServiceContext context;
private String hostId; private String hostId;
private String guestId; private String guestId;
private Credentials credentials; private Credentials credentials;
@Inject @Inject
public IsoToIMachine(VirtualBoxManager manager, public IsoToIMachine(VirtualBoxManager manager, String adminDisk, String diskFormat, String settingsFile,
String adminDisk, String vmName, String osTypeId, String vmId, boolean forceOverwrite, String controllerIDE,
String diskFormat, ComputeServiceContext context, String hostId, String guestId, Credentials credentials) {
String settingsFile, super();
String vmName, this.manager = manager;
String osTypeId, this.adminDisk = adminDisk;
String vmId, this.diskFormat = diskFormat;
boolean forceOverwrite, this.settingsFile = settingsFile;
String controllerIDE, this.vmName = vmName;
ComputeServiceContext context, this.osTypeId = osTypeId;
String hostId, this.vmId = vmId;
String guestId, this.controllerIDE = controllerIDE;
Credentials credentials) { this.forceOverwrite = forceOverwrite;
super(); this.context = context;
this.manager = manager; this.hostId = hostId;
this.adminDisk = adminDisk; this.guestId = guestId;
this.diskFormat = diskFormat; this.credentials = credentials;
this.settingsFile = settingsFile; }
this.vmName = vmName;
this.osTypeId = osTypeId;
this.vmId = vmId;
this.controllerIDE = controllerIDE;
this.forceOverwrite = forceOverwrite;
this.context = context;
this.hostId = hostId;
this.guestId = guestId;
this.credentials = credentials;
}
@Override @Override
public IMachine apply(@Nullable String isoName) { public IMachine apply(@Nullable String isoName) {
String port = System.getProperty(VirtualBoxConstants.VIRTUALBOX_JETTY_PORT, "8080"); String port = System.getProperty(VirtualBoxConstants.VIRTUALBOX_JETTY_PORT, "8080");
String basebaseResource = "."; String basebaseResource = ".";
Server server = new StartJettyIfNotAlreadyRunning(port).apply(basebaseResource); Server server = new StartJettyIfNotAlreadyRunning(port).apply(basebaseResource);
IMachine vm = manager.getVBox().createMachine(settingsFile, vmName, osTypeId, vmId, forceOverwrite);
manager.getVBox().registerMachine(vm);
String defaultWorkingDir = System.getProperty("user.home") + "/jclouds-virtualbox-test"; IMachine vm = manager.getVBox().createMachine(settingsFile, vmName, osTypeId, vmId, forceOverwrite);
String workingDir = System.getProperty(VIRTUALBOX_WORKINGDIR, defaultWorkingDir); manager.getVBox().registerMachine(vm);
IMedium distroMedium = manager.getVBox().openMedium(workingDir + "/" + isoName, DVD, ReadOnly, forceOverwrite);
// Change RAM String defaultWorkingDir = System.getProperty("user.home") + "/jclouds-virtualbox-test";
Long memorySize = new Long(1024); String workingDir = System.getProperty(VIRTUALBOX_WORKINGDIR, defaultWorkingDir);
ISession session = manager.getSessionObject();
IMachine machine = manager.getVBox().findMachine(vmName);
machine.lockMachine(session, Write);
IMachine mutable = session.getMachine();
mutable.setMemorySize(memorySize);
mutable.saveSettings();
session.unlockMachine();
// IDE Controller // Change RAM
machine.lockMachine(session, Write); lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
mutable = session.getMachine();
mutable.addStorageController(controllerIDE, StorageBus.IDE);
mutable.saveSettings();
session.unlockMachine();
// DISK @Override
String adminDiskPath = workingDir + "/" + adminDisk; public Void apply(IMachine arg0) {
if (new File(adminDiskPath).exists()) { arg0.setMemorySize(1024l);
new File(adminDiskPath).delete(); arg0.saveSettings();
} return null;
IMedium hd = manager.getVBox().createHardDisk(diskFormat, adminDiskPath); }
long size = 4L * 1024L * 1024L * 1024L - 4L;
IProgress storageCreation = hd.createBaseStorage(size, (long) MediumVariant.STANDARD.ordinal());
storageCreation.waitForCompletion(-1);
machine.lockMachine(session, Write); });
mutable = session.getMachine();
mutable.attachDevice(controllerIDE, 0, 0, DeviceType.DVD, distroMedium);
mutable.saveSettings();
session.unlockMachine();
// Create and attach hard disk // IDE Controller
machine.lockMachine(session, Write); ensureMachineHasIDEControllerNamed(vmName, controllerIDE);
mutable = session.getMachine();
mutable.attachDevice(controllerIDE, 0, 1, DeviceType.HardDisk, hd);
mutable.saveSettings();
session.unlockMachine();
// NIC // DISK
machine.lockMachine(session, Write); String adminDiskPath = workingDir + "/" + adminDisk;
mutable = session.getMachine(); if (new File(adminDiskPath).exists()) {
new File(adminDiskPath).delete();
}
// NAT final IMedium distroMedium = manager.getVBox().openMedium(workingDir + "/" + isoName, DVD, ReadOnly,
mutable.getNetworkAdapter(0l).setAttachmentType(NAT); forceOverwrite);
machine.getNetworkAdapter(0l)
.getNatDriver()
.addRedirect("guestssh", TCP, "127.0.0.1", 2222, "", 22);
mutable.getNetworkAdapter(0l).setEnabled(true);
mutable.saveSettings();
session.unlockMachine();
String guestAdditionsDvd = workingDir + "/VBoxGuestAdditions_4.1.2.iso"; lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
IMedium guestAdditionsDvdMedium = manager.getVBox().openMedium(guestAdditionsDvd, DeviceType.DVD, AccessMode.ReadOnly, forceOverwrite);
machine.lockMachine(session, Write);
mutable = session.getMachine();
mutable.attachDevice(controllerIDE, 1, 1, DeviceType.DVD, guestAdditionsDvdMedium);
mutable.saveSettings();
session.unlockMachine();
IProgress prog = machine.launchVMProcess(session, "gui", ""); @Override
prog.waitForCompletion(-1); public Void apply(IMachine arg0) {
try { try {
Thread.sleep(5000); arg0.attachDevice(controllerIDE, 0, 0, DeviceType.DVD, distroMedium);
} catch (InterruptedException e) { arg0.saveSettings();
propagate(e); } catch (VBoxException e) {
} if (e.getMessage().indexOf("is already attached to port") == -1)
throw e;
}
return null;
}
String installKeySequence = System.getProperty(VIRTUALBOX_INSTALLATION_KEY_SEQUENCE, defaultInstallSequence()); });
sendKeyboardSequence(installKeySequence);
session.unlockMachine();
boolean sshDeamonIsRunning = false; // Create and attach hard disk
while (!sshDeamonIsRunning) { final IMedium hd = manager.getVBox().createHardDisk(diskFormat, adminDiskPath);
try { long size = 4L * 1024L * 1024L * 1024L - 4L;
if (runScriptOnNode(guestId, "id", wrapInInitScript(false)).getExitCode() == 0) { IProgress storageCreation = hd.createBaseStorage(size, (long) MediumVariant.STANDARD.ordinal());
logger.debug("Got response from ssh daemon."); storageCreation.waitForCompletion(-1);
sshDeamonIsRunning = true;
}
} catch (SshException e) {
logger.debug("No response from ssh daemon...");
}
}
logger.debug("Installation of image complete. Powering down..."); lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
machine.lockMachine(session, LockType.Shared); @Override
IProgress powerDownProgress = session.getConsole().powerDown(); public Void apply(IMachine arg0) {
powerDownProgress.waitForCompletion(-1); arg0.attachDevice(controllerIDE, 0, 1, DeviceType.HardDisk, hd);
session.unlockMachine(); arg0.saveSettings();
return null;
}
try { });
logger.debug("Stopping Jetty server...");
server.stop();
logger.debug("Jetty server stopped.");
} catch (Exception e) {
logger.error(e, "Could not stop Jetty server.");
}
return vm;
}
private String defaultInstallSequence() { // NAT
return "<Esc><Esc><Enter> " lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
+ "/install/vmlinuz noapic preseed/url=http://10.0.2.2:8080/src/test/resources/preseed.cfg "
+ "debian-installer=en_US auto locale=en_US kbd-chooser/method=us "
+ "hostname="
+ vmName
+ " "
+ "fb=false debconf/frontend=noninteractive "
+ "keyboard-configuration/layout=USA keyboard-configuration/variant=USA console-setup/ask_detect=false "
+ "initrd=/install/initrd.gz -- <Enter>";
}
private void sendKeyboardSequence(String keyboardSequence) { @Override
String[] sequenceSplited = keyboardSequence.split(" "); public Void apply(IMachine arg0) {
StringBuilder sb = new StringBuilder(); arg0.getNetworkAdapter(0l).setAttachmentType(NAT);
for (String line : sequenceSplited) { arg0.getNetworkAdapter(0l).getNatDriver().addRedirect("guestssh", TCP, "127.0.0.1", 2222, "", 22);
String converted = stringToKeycode(line); arg0.getNetworkAdapter(0l).setEnabled(true);
for (String word : converted.split(" ")) { return null;
sb.append("vboxmanage controlvm " + vmName }
+ " keyboardputscancode " + word + "; ");
if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Enter>"))) {
runScriptOnNode(hostId, sb.toString(), runAsRoot(false).wrapInInitScript(false));
sb.delete(0, sb.length() - 1);
}
if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Return>"))) {
runScriptOnNode(hostId, sb.toString(), runAsRoot(false).wrapInInitScript(false));
sb.delete(0, sb.length() - 1);
}
} });
}
}
private String stringToKeycode(String s) { String guestAdditionsDvd = workingDir + "/VBoxGuestAdditions_4.1.2.iso";
StringBuilder keycodes = new StringBuilder(); final IMedium guestAdditionsDvdMedium = manager.getVBox().openMedium(guestAdditionsDvd, DeviceType.DVD,
if (s.startsWith("<")) { AccessMode.ReadOnly, forceOverwrite);
String[] specials = s.split("<");
for (int i = 1; i < specials.length; i++) {
keycodes.append(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP
.get("<" + specials[i]) + " ");
}
return keycodes.toString();
}
int i = 0; lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
while (i < s.length()) {
String digit = s.substring(i, i + 1);
String hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP
.get(digit);
keycodes.append(hex + " ");
if (i != 0 && i % 14 == 0)
keycodes.append(" ");
i++;
}
keycodes.append(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP
.get("<Spacebar>") + " ");
return keycodes.toString(); @Override
} public Void apply(IMachine arg0) {
arg0.attachDevice(controllerIDE, 1, 1, DeviceType.DVD, guestAdditionsDvdMedium);
return null;
}
protected ExecResponse runScriptOnNode(String nodeId, String command, RunScriptOptions options) { });
return context.getComputeService().runScriptOnNode(nodeId, command, options);
} IProgress prog = vm.launchVMProcess(manager.getSessionObject(), "gui", "");
prog.waitForCompletion(-1);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
propagate(e);
}
String installKeySequence = System.getProperty(VIRTUALBOX_INSTALLATION_KEY_SEQUENCE, defaultInstallSequence());
sendKeyboardSequence(installKeySequence);
boolean sshDeamonIsRunning = false;
while (!sshDeamonIsRunning) {
try {
if (runScriptOnNode(guestId, "id", wrapInInitScript(false)).getExitCode() == 0) {
logger.debug("Got response from ssh daemon.");
sshDeamonIsRunning = true;
}
} catch (SshException e) {
logger.debug("No response from ssh daemon...");
}
}
logger.debug("Installation of image complete. Powering down...");
lockSessionOnMachineAndApply(manager, Shared, vmName, new Function<ISession, Void>() {
@Override
public Void apply(ISession arg0) {
IProgress powerDownProgress = arg0.getConsole().powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
try {
logger.debug("Stopping Jetty server...");
server.stop();
logger.debug("Jetty server stopped.");
} catch (Exception e) {
logger.error(e, "Could not stop Jetty server.");
}
return vm;
}
public void ensureMachineHasIDEControllerNamed(String vmName, String controllerIDE) {
lockMachineAndApply(manager, Write, checkNotNull(vmName, "vmName"),
new AddIDEControllerIfNotExists(checkNotNull(controllerIDE, "controllerIDE")));
}
public static <T> T lockMachineAndApply(VirtualBoxManager manager, final LockType type, final String machineId,
final Function<IMachine, T> function) {
return lockSessionOnMachineAndApply(manager, type, machineId, new Function<ISession, T>() {
@Override
public T apply(ISession arg0) {
return function.apply(arg0.getMachine());
}
@Override
public String toString() {
return function.toString();
}
});
}
public static <T> T lockSessionOnMachineAndApply(VirtualBoxManager manager, LockType type, String machineId,
Function<ISession, T> function) {
try {
ISession session = manager.getSessionObject();
IMachine immutableMachine = manager.getVBox().findMachine(machineId);
immutableMachine.lockMachine(session, type);
try {
return function.apply(session);
} finally {
session.unlockMachine();
}
} catch (VBoxException e) {
throw new RuntimeException(String.format("error applying %s to %s with %s lock: %s", function, machineId,
type, e.getMessage()), e);
}
}
private String defaultInstallSequence() {
return "<Esc><Esc><Enter> "
+ "/install/vmlinuz noapic preseed/url=http://10.0.2.2:8080/src/test/resources/preseed.cfg "
+ "debian-installer=en_US auto locale=en_US kbd-chooser/method=us " + "hostname=" + vmName + " "
+ "fb=false debconf/frontend=noninteractive "
+ "keyboard-configuration/layout=USA keyboard-configuration/variant=USA console-setup/ask_detect=false "
+ "initrd=/install/initrd.gz -- <Enter>";
}
private void sendKeyboardSequence(String keyboardSequence) {
String[] sequenceSplited = keyboardSequence.split(" ");
StringBuilder sb = new StringBuilder();
for (String line : sequenceSplited) {
String converted = stringToKeycode(line);
for (String word : converted.split(" ")) {
sb.append("vboxmanage controlvm " + vmName + " keyboardputscancode " + word + "; ");
if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Enter>"))) {
runScriptOnNode(hostId, sb.toString(), runAsRoot(false).wrapInInitScript(false));
sb.delete(0, sb.length() - 1);
}
if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Return>"))) {
runScriptOnNode(hostId, sb.toString(), runAsRoot(false).wrapInInitScript(false));
sb.delete(0, sb.length() - 1);
}
}
}
}
private String stringToKeycode(String s) {
StringBuilder keycodes = new StringBuilder();
if (s.startsWith("<")) {
String[] specials = s.split("<");
for (int i = 1; i < specials.length; i++) {
keycodes.append(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<" + specials[i]) + " ");
}
return keycodes.toString();
}
int i = 0;
while (i < s.length()) {
String digit = s.substring(i, i + 1);
String hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP.get(digit);
keycodes.append(hex + " ");
if (i != 0 && i % 14 == 0)
keycodes.append(" ");
i++;
}
keycodes.append(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Spacebar>") + " ");
return keycodes.toString();
}
protected ExecResponse runScriptOnNode(String nodeId, String command, RunScriptOptions options) {
return context.getComputeService().runScriptOnNode(nodeId, command, options);
}
} }

View File

@ -0,0 +1,90 @@
/**
* 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.functions;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import org.testng.annotations.Test;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.IStorageController;
import org.virtualbox_4_1.StorageBus;
import org.virtualbox_4_1.VBoxException;
/**
*
* @author Adrian Cole
*
*/
@Test(groups = "unit", testName = "AddIDEControllerIfNotExistsTest")
public class AddIDEControllerIfNotExistsTest {
@Test
public void testFine() throws Exception {
IMachine vm = createMock(IMachine.class);
String controllerName = "IDE Controller";
expect(vm.addStorageController(controllerName, StorageBus.IDE)).andReturn(
createNiceMock(IStorageController.class));
vm.saveSettings();
replay(vm);
new AddIDEControllerIfNotExists(controllerName).apply(vm);
verify(vm);
}
@Test
public void testAcceptableException() throws Exception {
IMachine vm = createMock(IMachine.class);
String controllerName = "IDE Controller";
expect(vm.addStorageController(controllerName, StorageBus.IDE)).andThrow(
new VBoxException(createNiceMock(Throwable.class),
"VirtualBox error: Storage controller named 'IDE Controller' already exists (0x80BB000C)"));
replay(vm);
new AddIDEControllerIfNotExists(controllerName).apply(vm);
verify(vm);
}
@Test(expectedExceptions = VBoxException.class)
public void testUnacceptableException() throws Exception {
IMachine vm = createMock(IMachine.class);
String controllerName = "IDE Controller";
expect(vm.addStorageController(controllerName, StorageBus.IDE)).andThrow(
new VBoxException(createNiceMock(Throwable.class), "VirtualBox error: General Error"));
replay(vm);
new AddIDEControllerIfNotExists(controllerName).apply(vm);
verify(vm);
}
}