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); IMachine vm = manager.getVBox().createMachine(settingsFile, vmName, osTypeId, vmId, forceOverwrite);
manager.getVBox().registerMachine(vm); manager.getVBox().registerMachine(vm);
String defaultWorkingDir = System.getProperty("user.home") + "/jclouds-virtualbox-test"; String defaultWorkingDir = System.getProperty("user.home") + "/jclouds-virtualbox-test";
String workingDir = System.getProperty(VIRTUALBOX_WORKINGDIR, defaultWorkingDir); String workingDir = System.getProperty(VIRTUALBOX_WORKINGDIR, defaultWorkingDir);
IMedium distroMedium = manager.getVBox().openMedium(workingDir + "/" + isoName, DVD, ReadOnly, forceOverwrite);
// Change RAM // Change RAM
Long memorySize = new Long(1024); lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
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 @Override
machine.lockMachine(session, Write); public Void apply(IMachine arg0) {
mutable = session.getMachine(); arg0.setMemorySize(1024l);
mutable.addStorageController(controllerIDE, StorageBus.IDE); arg0.saveSettings();
mutable.saveSettings(); return null;
session.unlockMachine(); }
// DISK });
String adminDiskPath = workingDir + "/" + adminDisk;
if (new File(adminDiskPath).exists()) {
new File(adminDiskPath).delete();
}
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); // IDE Controller
mutable = session.getMachine(); ensureMachineHasIDEControllerNamed(vmName, controllerIDE);
mutable.attachDevice(controllerIDE, 0, 0, DeviceType.DVD, distroMedium);
mutable.saveSettings();
session.unlockMachine();
// Create and attach hard disk // DISK
machine.lockMachine(session, Write); String adminDiskPath = workingDir + "/" + adminDisk;
mutable = session.getMachine(); if (new File(adminDiskPath).exists()) {
mutable.attachDevice(controllerIDE, 0, 1, DeviceType.HardDisk, hd); new File(adminDiskPath).delete();
mutable.saveSettings(); }
session.unlockMachine();
// NIC final IMedium distroMedium = manager.getVBox().openMedium(workingDir + "/" + isoName, DVD, ReadOnly,
machine.lockMachine(session, Write); forceOverwrite);
mutable = session.getMachine();
// NAT lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
mutable.getNetworkAdapter(0l).setAttachmentType(NAT);
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"; @Override
IMedium guestAdditionsDvdMedium = manager.getVBox().openMedium(guestAdditionsDvd, DeviceType.DVD, AccessMode.ReadOnly, forceOverwrite); public Void apply(IMachine arg0) {
machine.lockMachine(session, Write); try {
mutable = session.getMachine(); arg0.attachDevice(controllerIDE, 0, 0, DeviceType.DVD, distroMedium);
mutable.attachDevice(controllerIDE, 1, 1, DeviceType.DVD, guestAdditionsDvdMedium); arg0.saveSettings();
mutable.saveSettings(); } catch (VBoxException e) {
session.unlockMachine(); if (e.getMessage().indexOf("is already attached to port") == -1)
throw e;
}
return null;
}
IProgress prog = machine.launchVMProcess(session, "gui", ""); });
prog.waitForCompletion(-1);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
propagate(e);
}
String installKeySequence = System.getProperty(VIRTUALBOX_INSTALLATION_KEY_SEQUENCE, defaultInstallSequence()); // Create and attach hard disk
sendKeyboardSequence(installKeySequence); final IMedium hd = manager.getVBox().createHardDisk(diskFormat, adminDiskPath);
session.unlockMachine(); long size = 4L * 1024L * 1024L * 1024L - 4L;
IProgress storageCreation = hd.createBaseStorage(size, (long) MediumVariant.STANDARD.ordinal());
storageCreation.waitForCompletion(-1);
boolean sshDeamonIsRunning = false; lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
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..."); @Override
public Void apply(IMachine arg0) {
arg0.attachDevice(controllerIDE, 0, 1, DeviceType.HardDisk, hd);
arg0.saveSettings();
return null;
}
machine.lockMachine(session, LockType.Shared); });
IProgress powerDownProgress = session.getConsole().powerDown();
powerDownProgress.waitForCompletion(-1);
session.unlockMachine();
try { // NAT
logger.debug("Stopping Jetty server..."); lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
server.stop();
logger.debug("Jetty server stopped.");
} catch (Exception e) {
logger.error(e, "Could not stop Jetty server.");
}
return vm;
}
private String defaultInstallSequence() { @Override
return "<Esc><Esc><Enter> " public Void apply(IMachine arg0) {
+ "/install/vmlinuz noapic preseed/url=http://10.0.2.2:8080/src/test/resources/preseed.cfg " arg0.getNetworkAdapter(0l).setAttachmentType(NAT);
+ "debian-installer=en_US auto locale=en_US kbd-chooser/method=us " arg0.getNetworkAdapter(0l).getNatDriver().addRedirect("guestssh", TCP, "127.0.0.1", 2222, "", 22);
+ "hostname=" arg0.getNetworkAdapter(0l).setEnabled(true);
+ vmName return null;
+ " " }
+ "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);
}
} String guestAdditionsDvd = workingDir + "/VBoxGuestAdditions_4.1.2.iso";
} final IMedium guestAdditionsDvdMedium = manager.getVBox().openMedium(guestAdditionsDvd, DeviceType.DVD,
} AccessMode.ReadOnly, forceOverwrite);
private String stringToKeycode(String s) { lockMachineAndApply(manager, Write, vmName, new Function<IMachine, Void>() {
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; @Override
while (i < s.length()) { public Void apply(IMachine arg0) {
String digit = s.substring(i, i + 1); arg0.attachDevice(controllerIDE, 1, 1, DeviceType.DVD, guestAdditionsDvdMedium);
String hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP return null;
.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) { IProgress prog = vm.launchVMProcess(manager.getSessionObject(), "gui", "");
return context.getComputeService().runScriptOnNode(nodeId, command, options); 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);
}
}