diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index ad7fcb2535..722729ed08 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -1302,3 +1302,14 @@
 #swift_max_retries=2
 
 
+[virtualbox]
+
+#
+# Options defined in ironic.drivers.modules.virtualbox
+#
+
+# Port on which VirtualBox web service is listening. (integer
+# value)
+#port=18083
+
+
diff --git a/ironic/common/exception.py b/ironic/common/exception.py
index bc2d361275..6be73d26c2 100755
--- a/ironic/common/exception.py
+++ b/ironic/common/exception.py
@@ -505,3 +505,8 @@ class FileSystemNotSupported(IronicException):
 
 class IRMCOperationError(IronicException):
     message = _('iRMC %(operation)s failed. Reason: %(error)s')
+
+
+class VirtualBoxOperationFailed(IronicException):
+    message = _("VirtualBox operation '%(operation)s' failed. "
+                "Error: %(error)s")
diff --git a/ironic/drivers/agent.py b/ironic/drivers/agent.py
index 0dfea01093..65ab0e568b 100644
--- a/ironic/drivers/agent.py
+++ b/ironic/drivers/agent.py
@@ -12,11 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from oslo.utils import importutils
+
+from ironic.common import exception
+from ironic.common.i18n import _
 from ironic.drivers import base
 from ironic.drivers.modules import agent
 from ironic.drivers.modules import ipminative
 from ironic.drivers.modules import ipmitool
 from ironic.drivers.modules import ssh
+from ironic.drivers.modules import virtualbox
 
 
 class AgentAndIPMIToolDriver(base.BaseDriver):
@@ -76,3 +81,27 @@ class AgentAndSSHDriver(base.BaseDriver):
         self.deploy = agent.AgentDeploy()
         self.management = ssh.SSHManagement()
         self.vendor = agent.AgentVendorInterface()
+
+
+class AgentAndVirtualBoxDriver(base.BaseDriver):
+    """Agent + VirtualBox driver.
+
+    NOTE: This driver is meant only for testing environments.
+
+    This driver implements the `core` functionality, combining
+    :class:`ironic.drivers.modules.virtualbox.VirtualBoxPower` (for power
+        on/off and reboot of VirtualBox virtual machines), with
+    :class:`ironic.drivers.modules.agent.AgentDeploy` (for image
+    deployment). Implementations are in those respective classes; this class
+    is merely the glue between them.
+    """
+
+    def __init__(self):
+        if not importutils.try_import('pyremotevbox'):
+            raise exception.DriverLoadError(
+                    driver=self.__class__.__name__,
+                    reason=_("Unable to import pyremotevbox library"))
+        self.power = virtualbox.VirtualBoxPower()
+        self.deploy = agent.AgentDeploy()
+        self.management = virtualbox.VirtualBoxManagement()
+        self.vendor = agent.AgentVendorInterface()
diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py
index c92de86b8d..ac01e0c8cd 100755
--- a/ironic/drivers/fake.py
+++ b/ironic/drivers/fake.py
@@ -36,6 +36,7 @@ from ironic.drivers.modules import pxe
 from ironic.drivers.modules import seamicro
 from ironic.drivers.modules import snmp
 from ironic.drivers.modules import ssh
+from ironic.drivers.modules import virtualbox
 from ironic.drivers import utils
 
 
@@ -184,3 +185,16 @@ class FakeIRMCDriver(base.BaseDriver):
                     reason=_("Unable to import python-scciclient library"))
         self.power = irmc_power.IRMCPower()
         self.deploy = fake.FakeDeploy()
+
+
+class FakeVirtualBoxDriver(base.BaseDriver):
+    """Fake VirtualBox driver."""
+
+    def __init__(self):
+        if not importutils.try_import('pyremotevbox'):
+            raise exception.DriverLoadError(
+                    driver=self.__class__.__name__,
+                    reason=_("Unable to import pyremotevbox library"))
+        self.power = virtualbox.VirtualBoxPower()
+        self.deploy = fake.FakeDeploy()
+        self.management = virtualbox.VirtualBoxManagement()
diff --git a/ironic/drivers/modules/virtualbox.py b/ironic/drivers/modules/virtualbox.py
new file mode 100644
index 0000000000..bb83bc4e7a
--- /dev/null
+++ b/ironic/drivers/modules/virtualbox.py
@@ -0,0 +1,367 @@
+# Licensed 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.
+
+"""
+VirtualBox Driver Modules
+"""
+
+from oslo.config import cfg
+from oslo.utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common.i18n import _
+from ironic.common.i18n import _LE
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers import base
+from ironic.openstack.common import log as logging
+
+pyremotevbox = importutils.try_import('pyremotevbox')
+if pyremotevbox:
+    from pyremotevbox import exception as virtualbox_exc
+    from pyremotevbox import vbox as virtualbox
+
+IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING = {
+                                 boot_devices.PXE: 'Network',
+                                 boot_devices.DISK: 'HardDisk',
+                                 boot_devices.CDROM: 'DVD',
+                                }
+VIRTUALBOX_TO_IRONIC_DEVICE_MAPPING = {v: k
+                       for k, v in IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING.items()}
+
+VIRTUALBOX_TO_IRONIC_POWER_MAPPING = {
+                                'PoweredOff': states.POWER_OFF,
+                                'Running': states.POWER_ON,
+                                'Error': states.ERROR
+                               }
+
+opts = [
+    cfg.IntOpt('port',
+               default=18083,
+               help='Port on which VirtualBox web service is listening.'),
+]
+CONF = cfg.CONF
+CONF.register_opts(opts, group='virtualbox')
+
+LOG = logging.getLogger(__name__)
+
+REQUIRED_PROPERTIES = {
+    'virtualbox_vmname': _("Name of the VM in VirtualBox. Required."),
+    'virtualbox_host': _("IP address or hostname of the VirtualBox host. "
+                         "Required.")
+}
+
+OPTIONAL_PROPERTIES = {
+    'virtualbox_username': _("Username for the VirtualBox host. "
+                             "Default value is ''. Optional."),
+    'virtualbox_password': _("Password for 'virtualbox_username'. "
+                             "Default value is ''. Optional."),
+    'virtualbox_port': _("Port on which VirtualBox web service is listening. "
+                         "Optional."),
+}
+
+COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
+COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
+
+
+def _strip_virtualbox_from_param_name(param_name):
+
+    if param_name.startswith('virtualbox_'):
+        return param_name[11:]
+    else:
+        return param_name
+
+
+def _parse_driver_info(node):
+    """Gets the driver specific node driver info.
+
+    This method validates whether the 'driver_info' property of the
+    supplied node contains the required information for this driver.
+
+    :param node: an Ironic Node object.
+    :returns: a dict containing information from driver_info (or where
+        applicable, config values).
+    :raises: MissingParameterValue, if some required parameter(s) are missing
+        in the node's driver_info.
+    :raises: InvalidParameterValue, if some parameter(s) have invalid value(s)
+        in the node's driver_info.
+    """
+    info = node.driver_info
+    d_info = {}
+
+    missing_params = []
+    for param in REQUIRED_PROPERTIES:
+        try:
+            d_info_param_name = _strip_virtualbox_from_param_name(param)
+            d_info[d_info_param_name] = info[param]
+        except KeyError:
+            missing_params.append(param)
+
+    if missing_params:
+        msg = (_("The following parameters are missing in driver_info: %s") %
+                 ', '.join(missing_params))
+        raise exception.MissingParameterValue(msg)
+
+    for param in OPTIONAL_PROPERTIES:
+        if param in info:
+            d_info_param_name = _strip_virtualbox_from_param_name(param)
+            d_info[d_info_param_name] = info[param]
+
+    try:
+        d_info['port'] = int(d_info.get('port', CONF.virtualbox.port))
+    except ValueError:
+        msg = _("'virtualbox_port' is not an integer.")
+        raise exception.InvalidParameterValue(msg)
+
+    return d_info
+
+
+def _run_virtualbox_method(node, ironic_method, vm_object_method,
+                           *call_args, **call_kwargs):
+    """Runs a method of pyremotevbox.vbox.VirtualMachine
+
+    This runs a method from pyremotevbox.vbox.VirtualMachine.
+    The VirtualMachine method to be invoked and the argument(s) to be
+    passed to it are to be provided.
+
+    :param node: an Ironic Node object.
+    :param ironic_method: the Ironic method which called
+        '_run_virtualbox_method'. This is used for logging only.
+    :param vm_object_method: The method on the VirtualMachine object
+        to be called.
+    :param call_args: The args to be passed to 'vm_object_method'.
+    :param call_kwargs: The kwargs to be passed to the 'vm_object_method'.
+    :returns: The value returned by 'vm_object_method'
+    :raises: VirtualBoxOperationFailed, if execution of 'vm_object_method'
+        failed.
+    :raises: InvalidParameterValue,
+        - if 'vm_object_method' is not a valid 'VirtualMachine' method.
+        - if some parameter(s) have invalid value(s) in the node's driver_info.
+    :raises: MissingParameterValue, if some required parameter(s) are missing
+        in the node's driver_info.
+    :raises: pyremotevbox.exception.VmInWrongPowerState, if operation cannot
+        be performed when vm is in the current power state.
+    """
+    driver_info = _parse_driver_info(node)
+    try:
+        host = virtualbox.VirtualBoxHost(**driver_info)
+        vm_object = host.find_vm(driver_info['vmname'])
+    except virtualbox_exc.PyRemoteVBoxException as exc:
+        LOG.error(_LE("Failed while creating a VirtualMachine object for "
+                      "node %(node)s. Error: %(error)s."),
+                  {'node_id': node.uuid, 'error': exc})
+        raise exception.VirtualBoxOperationFailed(operation=vm_object_method,
+                                                  error=exc)
+
+    try:
+        func = getattr(vm_object, vm_object_method)
+    except AttributeError:
+        error_msg = _("Invalid VirtualMachine method '%s' passed "
+                      "to '_run_virtualbox_method'.")
+        raise exception.InvalidParameterValue(error_msg % vm_object_method)
+
+    try:
+        return func(*call_args, **call_kwargs)
+    except virtualbox_exc.PyRemoteVBoxException as exc:
+        error_msg = _LE("'%(ironic_method)s' failed for node %(node_id)s with "
+                        "error: %(error)s.")
+        LOG.error(error_msg, {'ironic_method': ironic_method,
+                              'node_id': node.uuid,
+                              'error': exc})
+        raise exception.VirtualBoxOperationFailed(operation=vm_object_method,
+                                                  error=exc)
+
+
+class VirtualBoxPower(base.PowerInterface):
+
+    def get_properties(self):
+        return COMMON_PROPERTIES
+
+    def validate(self, task):
+        """Check if node.driver_info contains the required credentials.
+
+        :param task: a TaskManager instance.
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        """
+        _parse_driver_info(task.node)
+
+    def get_power_state(self, task):
+        """Gets the current power state.
+
+        :param task: a TaskManager instance.
+        :returns: one of :mod:`ironic.common.states`
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        :raises: VirtualBoxOperationFailed, if error encountered from
+            VirtualBox operation.
+        """
+        power_status = _run_virtualbox_method(task.node, 'get_power_state',
+                                              'get_power_status')
+        try:
+            return VIRTUALBOX_TO_IRONIC_POWER_MAPPING[power_status]
+        except KeyError:
+            msg = _LE("VirtualBox returned unknown state '%(state)s' for "
+                      "node %(node)s")
+            LOG.error(msg, {'state': power_status, 'node': task.node.uuid})
+            return states.ERROR
+
+    @task_manager.require_exclusive_lock
+    def set_power_state(self, task, target_state):
+        """Turn the current power state on or off.
+
+        :param task: a TaskManager instance.
+        :param target_state: The desired power state POWER_ON,POWER_OFF or
+            REBOOT from :mod:`ironic.common.states`.
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info OR if an invalid power state
+            was specified.
+        :raises: VirtualBoxOperationFailed, if error encountered from
+            VirtualBox operation.
+        """
+        if target_state == states.POWER_OFF:
+            _run_virtualbox_method(task.node, 'set_power_state', 'stop')
+        elif target_state == states.POWER_ON:
+            _run_virtualbox_method(task.node, 'set_power_state', 'start')
+        elif target_state == states.REBOOT:
+            self.reboot(task)
+        else:
+            msg = _("'set_power_state' called with invalid power "
+                    "state '%s'") % target_state
+            raise exception.InvalidParameterValue(msg)
+
+    @task_manager.require_exclusive_lock
+    def reboot(self, task):
+        """Reboot the node.
+
+        :param task: a TaskManager instance.
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        :raises: VirtualBoxOperationFailed, if error encountered from
+            VirtualBox operation.
+        """
+        _run_virtualbox_method(task.node, 'reboot', 'stop')
+        _run_virtualbox_method(task.node, 'reboot', 'start')
+
+
+class VirtualBoxManagement(base.ManagementInterface):
+
+    def get_properties(self):
+        return COMMON_PROPERTIES
+
+    def validate(self, task):
+        """Check that 'driver_info' contains required credentials.
+
+        Validates whether the 'driver_info' property of the supplied
+        task's node contains the required credentials information.
+
+        :param task: a task from TaskManager.
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        """
+        _parse_driver_info(task.node)
+
+    def get_supported_boot_devices(self):
+        """Get a list of the supported boot devices.
+
+        :returns: A list with the supported boot devices defined
+                  in :mod:`ironic.common.boot_devices`.
+        """
+        return list(IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING.keys())
+
+    def get_boot_device(self, task):
+        """Get the current boot device for a node.
+
+        :param task: a task from TaskManager.
+        :returns: a dictionary containing:
+            'boot_device': one of the ironic.common.boot_devices or None
+            'persistent': True if boot device is persistent, False otherwise
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        :raises: VirtualBoxOperationFailed, if error encountered from
+            VirtualBox operation.
+        """
+        boot_dev = _run_virtualbox_method(task.node, 'get_boot_device',
+                                          'get_boot_device')
+        persistent = True
+        ironic_boot_dev = VIRTUALBOX_TO_IRONIC_DEVICE_MAPPING.get(boot_dev,
+                                                                  None)
+        if not ironic_boot_dev:
+            persistent = None
+            msg = _LE("VirtualBox returned unknown boot device '%(device)s' "
+                      "for node %(node)s")
+            LOG.error(msg, {'device': boot_dev, 'node': task.node.uuid})
+
+        return {'boot_device': ironic_boot_dev, 'persistent': persistent}
+
+    @task_manager.require_exclusive_lock
+    def set_boot_device(self, task, device, persistent=False):
+        """Set the boot device for a node.
+
+        :param task: a task from TaskManager.
+        :param device: ironic.common.boot_devices
+        :param persistent: This argument is ignored as VirtualBox support only
+            persistent boot devices.
+        :raises: MissingParameterValue, if some required parameter(s) are
+            missing in the node's driver_info.
+        :raises: InvalidParameterValue, if some parameter(s) have invalid
+            value(s) in the node's driver_info.
+        :raises: VirtualBoxOperationFailed, if error encountered from
+            VirtualBox operation.
+        """
+        # NOTE(rameshg87): VirtualBox has only persistent boot devices.
+        try:
+            boot_dev = IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING[device]
+        except KeyError:
+            raise exception.InvalidParameterValue(_(
+                 "Invalid boot device %s specified.") % device)
+
+        try:
+            _run_virtualbox_method(task.node, 'set_boot_device',
+                                   'set_boot_device', boot_dev)
+        except virtualbox_exc.VmInWrongPowerState as exc:
+            # NOTE(rameshg87): We cannot change the boot device when the vm
+            # is powered on. This is a VirtualBox limitation. We just log
+            # the error silently and return because throwing error will cause
+            # deploys to fail (pxe and agent deploy mechanisms change the boot
+            # device after completing the deployment, when node is powered on).
+            # Since this is driver that is meant only for developers, this
+            # should be okay. Developers will need to set the boot device
+            # manually after powering off the vm when deployment is complete.
+            # This will be documented.
+            LOG.error(_LE("'set_boot_device' failed for node %(node_id)s "
+                          "with error: %(error)s"),
+                      {'node_id': task.node.uuid, 'error': exc})
+
+    def get_sensors_data(self, task):
+        """Get sensors data.
+
+        :param task: a TaskManager instance.
+        :raises: FailedToGetSensorData when getting the sensor data fails.
+        :raises: FailedToParseSensorData when parsing sensor data fails.
+        :returns: returns a consistent format dict of sensor data grouped by
+        sensor type, which can be processed by Ceilometer.
+        """
+        raise NotImplementedError()
diff --git a/ironic/drivers/pxe.py b/ironic/drivers/pxe.py
index 877de5a503..c14c1aa2d0 100644
--- a/ironic/drivers/pxe.py
+++ b/ironic/drivers/pxe.py
@@ -33,6 +33,7 @@ from ironic.drivers.modules import pxe
 from ironic.drivers.modules import seamicro
 from ironic.drivers.modules import snmp
 from ironic.drivers.modules import ssh
+from ironic.drivers.modules import virtualbox
 from ironic.drivers import utils
 
 
@@ -209,3 +210,26 @@ class PXEAndIRMCDriver(base.BaseDriver):
         self.deploy = pxe.PXEDeploy()
         self.management = ipmitool.IPMIManagement()
         self.vendor = pxe.VendorPassthru()
+
+
+class PXEAndVirtualBoxDriver(base.BaseDriver):
+    """PXE + VirtualBox driver.
+
+    NOTE: This driver is meant only for testing environments.
+
+    This driver implements the `core` functionality, combining
+    :class:`ironic.drivers.virtualbox.VirtualBoxPower` for power on/off and
+    reboot of VirtualBox virtual machines, with :class:`ironic.driver.pxe.PXE`
+    for image deployment. Implementations are in those respective classes;
+    this class is merely the glue between them.
+    """
+
+    def __init__(self):
+        if not importutils.try_import('pyremotevbox'):
+            raise exception.DriverLoadError(
+                    driver=self.__class__.__name__,
+                    reason=_("Unable to import pyremotevbox library"))
+        self.power = virtualbox.VirtualBoxPower()
+        self.deploy = pxe.PXEDeploy()
+        self.management = virtualbox.VirtualBoxManagement()
+        self.vendor = pxe.VendorPassthru()
diff --git a/ironic/tests/drivers/test_virtualbox.py b/ironic/tests/drivers/test_virtualbox.py
new file mode 100644
index 0000000000..b3dffddac0
--- /dev/null
+++ b/ironic/tests/drivers/test_virtualbox.py
@@ -0,0 +1,374 @@
+# Licensed 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.
+
+"""Test class for VirtualBox Driver Modules."""
+
+import mock
+from oslo.config import cfg
+from pyremotevbox import exception as pyremotevbox_exc
+from pyremotevbox import vbox as pyremotevbox_vbox
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import virtualbox
+from ironic.tests.conductor import utils as mgr_utils
+from ironic.tests.db import base as db_base
+from ironic.tests.objects import utils as obj_utils
+
+INFO_DICT = {
+             'virtualbox_vmname': 'baremetal1',
+             'virtualbox_host': '10.0.2.2',
+             'virtualbox_username': 'username',
+             'virtualbox_password': 'password',
+             'virtualbox_port': 12345,
+            }
+
+CONF = cfg.CONF
+
+
+class VirtualBoxMethodsTestCase(db_base.DbTestCase):
+
+    def setUp(self):
+        super(VirtualBoxMethodsTestCase, self).setUp()
+        driver_info = INFO_DICT.copy()
+        mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+        self.node = obj_utils.create_test_node(self.context,
+                                               driver='fake_vbox',
+                                               driver_info=driver_info)
+
+    def test__parse_driver_info(self):
+        info = virtualbox._parse_driver_info(self.node)
+        self.assertEqual('baremetal1', info['vmname'])
+        self.assertEqual('10.0.2.2', info['host'])
+        self.assertEqual('username', info['username'])
+        self.assertEqual('password', info['password'])
+        self.assertEqual(12345, info['port'])
+
+    def test__parse_driver_info_missing_vmname(self):
+        del self.node.driver_info['virtualbox_vmname']
+        self.assertRaises(exception.MissingParameterValue,
+                          virtualbox._parse_driver_info, self.node)
+
+    def test__parse_driver_info_missing_host(self):
+        del self.node.driver_info['virtualbox_host']
+        self.assertRaises(exception.MissingParameterValue,
+                          virtualbox._parse_driver_info, self.node)
+
+    def test__parse_driver_info_invalid_port(self):
+        self.node.driver_info['virtualbox_port'] = 'invalid-port'
+        self.assertRaises(exception.InvalidParameterValue,
+                          virtualbox._parse_driver_info, self.node)
+
+    def test__parse_driver_info_missing_port(self):
+        del self.node.driver_info['virtualbox_port']
+        info = virtualbox._parse_driver_info(self.node)
+        self.assertEqual(18083, info['port'])
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method(self, host_mock):
+        host_object_mock = mock.MagicMock()
+        func_mock = mock.MagicMock()
+        vm_object_mock = mock.MagicMock(foo=func_mock)
+        host_mock.return_value = host_object_mock
+        host_object_mock.find_vm.return_value = vm_object_mock
+        func_mock.return_value = 'return-value'
+
+        return_value = virtualbox._run_virtualbox_method(self.node,
+                'some-ironic-method', 'foo', 'args', kwarg='kwarg')
+
+        host_mock.assert_called_once_with(vmname='baremetal1',
+                                          host='10.0.2.2',
+                                          username='username',
+                                          password='password',
+                                          port=12345)
+        host_object_mock.find_vm.assert_called_once_with('baremetal1')
+        func_mock.assert_called_once_with('args', kwarg='kwarg')
+        self.assertEqual('return-value', return_value)
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method_get_host_fails(self, host_mock):
+        host_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
+
+        self.assertRaises(exception.VirtualBoxOperationFailed,
+                          virtualbox._run_virtualbox_method,
+                          self.node, 'some-ironic-method', 'foo',
+                          'args', kwarg='kwarg')
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method_find_vm_fails(self, host_mock):
+        host_object_mock = mock.MagicMock()
+        host_mock.return_value = host_object_mock
+        exc = pyremotevbox_exc.PyRemoteVBoxException
+        host_object_mock.find_vm.side_effect = exc
+
+        self.assertRaises(exception.VirtualBoxOperationFailed,
+                          virtualbox._run_virtualbox_method,
+                          self.node, 'some-ironic-method', 'foo', 'args',
+                          kwarg='kwarg')
+        host_mock.assert_called_once_with(vmname='baremetal1',
+                                          host='10.0.2.2',
+                                          username='username',
+                                          password='password',
+                                          port=12345)
+        host_object_mock.find_vm.assert_called_once_with('baremetal1')
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method_func_fails(self, host_mock):
+        host_object_mock = mock.MagicMock()
+        host_mock.return_value = host_object_mock
+        func_mock = mock.MagicMock()
+        vm_object_mock = mock.MagicMock(foo=func_mock)
+        host_object_mock.find_vm.return_value = vm_object_mock
+        func_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
+
+        self.assertRaises(exception.VirtualBoxOperationFailed,
+                          virtualbox._run_virtualbox_method,
+                          self.node, 'some-ironic-method', 'foo',
+                          'args', kwarg='kwarg')
+        host_mock.assert_called_once_with(vmname='baremetal1',
+                                          host='10.0.2.2',
+                                          username='username',
+                                          password='password',
+                                          port=12345)
+        host_object_mock.find_vm.assert_called_once_with('baremetal1')
+        func_mock.assert_called_once_with('args', kwarg='kwarg')
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method_invalid_method(self, host_mock):
+        host_object_mock = mock.MagicMock()
+        host_mock.return_value = host_object_mock
+        vm_object_mock = mock.MagicMock()
+        host_object_mock.find_vm.return_value = vm_object_mock
+        del vm_object_mock.foo
+
+        self.assertRaises(exception.InvalidParameterValue,
+                          virtualbox._run_virtualbox_method,
+                          self.node, 'some-ironic-method', 'foo',
+                          'args', kwarg='kwarg')
+        host_mock.assert_called_once_with(vmname='baremetal1',
+                                          host='10.0.2.2',
+                                          username='username',
+                                          password='password',
+                                          port=12345)
+        host_object_mock.find_vm.assert_called_once_with('baremetal1')
+
+    @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost')
+    def test__run_virtualbox_method_vm_wrong_power_state(self, host_mock):
+        host_object_mock = mock.MagicMock()
+        host_mock.return_value = host_object_mock
+        func_mock = mock.MagicMock()
+        vm_object_mock = mock.MagicMock(foo=func_mock)
+        host_object_mock.find_vm.return_value = vm_object_mock
+        func_mock.side_effect = pyremotevbox_exc.VmInWrongPowerState
+
+        # _run_virtualbox_method() doesn't catch VmInWrongPowerState and
+        # lets caller handle it.
+        self.assertRaises(pyremotevbox_exc.VmInWrongPowerState,
+                          virtualbox._run_virtualbox_method,
+                          self.node, 'some-ironic-method', 'foo',
+                          'args', kwarg='kwarg')
+        host_mock.assert_called_once_with(vmname='baremetal1',
+                                          host='10.0.2.2',
+                                          username='username',
+                                          password='password',
+                                          port=12345)
+        host_object_mock.find_vm.assert_called_once_with('baremetal1')
+        func_mock.assert_called_once_with('args', kwarg='kwarg')
+
+
+class VirtualBoxPowerTestCase(db_base.DbTestCase):
+
+    def setUp(self):
+        super(VirtualBoxPowerTestCase, self).setUp()
+        driver_info = INFO_DICT.copy()
+        mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+        self.node = obj_utils.create_test_node(self.context,
+                                               driver='fake_vbox',
+                                               driver_info=driver_info)
+
+    def test_get_properties(self):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            properties = task.driver.power.get_properties()
+
+        self.assertIn('virtualbox_vmname', properties)
+        self.assertIn('virtualbox_host', properties)
+
+    @mock.patch.object(virtualbox, '_parse_driver_info')
+    def test_validate(self, parse_info_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.power.validate(task)
+            parse_info_mock.assert_called_once_with(task.node)
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_get_power_state(self, run_method_mock):
+        run_method_mock.return_value = 'PoweredOff'
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            power_state = task.driver.power.get_power_state(task)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'get_power_state',
+                                                         'get_power_status')
+            self.assertEqual(states.POWER_OFF, power_state)
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_get_power_state_invalid_state(self, run_method_mock):
+        run_method_mock.return_value = 'invalid-state'
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            power_state = task.driver.power.get_power_state(task)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'get_power_state',
+                                                         'get_power_status')
+            self.assertEqual(states.ERROR, power_state)
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_power_state_off(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.power.set_power_state(task, states.POWER_OFF)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'set_power_state',
+                                                         'stop')
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_power_state_on(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.power.set_power_state(task, states.POWER_ON)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'set_power_state',
+                                                         'start')
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_power_state_reboot(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.power.set_power_state(task, states.REBOOT)
+            run_method_mock.assert_any_call(task.node,
+                                                 'reboot',
+                                                 'stop')
+            run_method_mock.assert_any_call(task.node,
+                                                 'reboot',
+                                                 'start')
+
+    def test_set_power_state_invalid_state(self):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            self.assertRaises(exception.InvalidParameterValue,
+                              task.driver.power.set_power_state,
+                              task, 'invalid-state')
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_reboot(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.power.reboot(task)
+            run_method_mock.assert_any_call(task.node,
+                                                 'reboot',
+                                                 'stop')
+            run_method_mock.assert_any_call(task.node,
+                                                 'reboot',
+                                                 'start')
+
+
+class VirtualBoxManagementTestCase(db_base.DbTestCase):
+
+    def setUp(self):
+        super(VirtualBoxManagementTestCase, self).setUp()
+        driver_info = INFO_DICT.copy()
+        mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+        self.node = obj_utils.create_test_node(self.context,
+                                               driver='fake_vbox',
+                                               driver_info=driver_info)
+
+    def test_get_properties(self):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            properties = task.driver.management.get_properties()
+
+        self.assertIn('virtualbox_vmname', properties)
+        self.assertIn('virtualbox_host', properties)
+
+    @mock.patch.object(virtualbox, '_parse_driver_info')
+    def test_validate(self, parse_info_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.management.validate(task)
+            parse_info_mock.assert_called_once_with(task.node)
+
+    def test_get_supported_boot_devices(self):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            devices = task.driver.management.get_supported_boot_devices()
+            self.assertIn(boot_devices.PXE, devices)
+            self.assertIn(boot_devices.DISK, devices)
+            self.assertIn(boot_devices.CDROM, devices)
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_get_boot_device_ok(self, run_method_mock):
+        run_method_mock.return_value = 'Network'
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            ret_val = task.driver.management.get_boot_device(task)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'get_boot_device',
+                                                         'get_boot_device')
+            self.assertEqual(boot_devices.PXE, ret_val['boot_device'])
+            self.assertTrue(ret_val['persistent'])
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_get_boot_device_invalid(self, run_method_mock):
+        run_method_mock.return_value = 'invalid-boot-device'
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            ret_val = task.driver.management.get_boot_device(task)
+            self.assertIsNone(ret_val['boot_device'])
+            self.assertIsNone(ret_val['persistent'])
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_boot_device_ok(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.management.set_boot_device(task, boot_devices.PXE)
+            run_method_mock.assert_called_once_with(task.node,
+                                                         'set_boot_device',
+                                                         'set_boot_device',
+                                                         'Network')
+
+    @mock.patch.object(virtualbox, 'LOG')
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_boot_device_wrong_power_state(self, run_method_mock,
+                                               log_mock):
+        run_method_mock.side_effect = pyremotevbox_exc.VmInWrongPowerState
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            task.driver.management.set_boot_device(task, boot_devices.PXE)
+            log_mock.error.assert_called_once_with(mock.ANY, mock.ANY)
+
+    @mock.patch.object(virtualbox, '_run_virtualbox_method')
+    def test_set_boot_device_invalid(self, run_method_mock):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            self.assertRaises(exception.InvalidParameterValue,
+                              task.driver.management.set_boot_device,
+                              task, 'invalid-boot-device')
+
+    def test_get_sensors_data(self):
+        with task_manager.acquire(self.context, self.node.uuid,
+                                  shared=False) as task:
+            self.assertRaises(NotImplementedError,
+                              task.driver.management.get_sensors_data,
+                              task)
diff --git a/ironic/tests/drivers/third_party_driver_mocks.py b/ironic/tests/drivers/third_party_driver_mocks.py
index 40cda34829..8206ccde23 100755
--- a/ironic/tests/drivers/third_party_driver_mocks.py
+++ b/ironic/tests/drivers/third_party_driver_mocks.py
@@ -161,3 +161,13 @@ if not scciclient:
 # external library has been mocked
 if 'ironic.drivers.modules.irmc' in sys.modules:
     reload(sys.modules['ironic.drivers.modules.irmc'])
+
+pyremotevbox = importutils.try_import('pyremotevbox')
+if not pyremotevbox:
+    pyremotevbox = mock.MagicMock()
+    pyremotevbox.exception = mock.MagicMock()
+    pyremotevbox.exception.PyRemoteVBoxException = Exception
+    pyremotevbox.exception.VmInWrongPowerState = Exception
+    sys.modules['pyremotevbox'] = pyremotevbox
+    if 'ironic.drivers.modules.virtualbox' in sys.modules:
+        reload(sys.modules['ironic.drivers.modules.virtualbox'])
diff --git a/setup.cfg b/setup.cfg
index 174b5a231d..994eac77a2 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -38,6 +38,7 @@ ironic.drivers =
     agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
     agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver
     agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
+    agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
     fake = ironic.drivers.fake:FakeDriver
     fake_agent = ironic.drivers.fake:FakeAgentDriver
     fake_ipmitool = ironic.drivers.fake:FakeIPMIToolDriver
@@ -50,10 +51,12 @@ ironic.drivers =
     fake_drac = ironic.drivers.fake:FakeDracDriver
     fake_snmp = ironic.drivers.fake:FakeSNMPDriver
     fake_irmc = ironic.drivers.fake:FakeIRMCDriver
+    fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
     iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
     pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
     pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
     pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
+    pxe_vbox = ironic.drivers.pxe:PXEAndVirtualBoxDriver
     pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver
     pxe_iboot = ironic.drivers.pxe:PXEAndIBootDriver
     pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver