Adds Hyper-V OVS ViF driver

This branch adds a new ViF driver to the Nova Hyper-V compute driver.
It's responsible for creating the OVS port and the related association
with the Hyper-V virtual switch port. Aside from the openvswitch binaries
it has no other dependencies. Flow rules are added by an external agent
such as OpenDaylight or by a Neutron OVS agent.

The new VIF driver is using os-vif in order to create the OVS ports.

Co-Authored-By: Adelina Tuvenie <atuvenie@cloudbasesolutions.com>
Co-Authored-By: Claudiu Belu <cbelu@cloudbasesolutions.com>

Depends-On: Ia07ade945ca7469b9c23c801a6bfe08135d39466
Depends-On: Ia33e76a28f96fb94e6fdeedc4947b8a353e3d5bc

Implements: blueprint hyper-v-ovs-vif

Change-Id: I4cee52f65968fa503f2c8a2c9a206489719fb15a
This commit is contained in:
Gabriel Adrian Samfira 2014-12-01 00:14:00 +02:00 committed by Claudiu Belu
parent cf6592df3c
commit 07b6580a16
12 changed files with 255 additions and 81 deletions

@ -37,6 +37,7 @@ VIF_TYPE_BRIDGE = 'bridge'
VIF_TYPE_802_QBG = '802.1qbg' VIF_TYPE_802_QBG = '802.1qbg'
VIF_TYPE_802_QBH = '802.1qbh' VIF_TYPE_802_QBH = '802.1qbh'
VIF_TYPE_HW_VEB = 'hw_veb' VIF_TYPE_HW_VEB = 'hw_veb'
VIF_TYPE_HYPERV = 'hyperv'
VIF_TYPE_HOSTDEV = 'hostdev_physical' VIF_TYPE_HOSTDEV = 'hostdev_physical'
VIF_TYPE_IB_HOSTDEV = 'ib_hostdev' VIF_TYPE_IB_HOSTDEV = 'ib_hostdev'
VIF_TYPE_MIDONET = 'midonet' VIF_TYPE_MIDONET = 'midonet'

@ -168,6 +168,16 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
mock.sentinel.instance, mock.sentinel.network_info, mock.sentinel.instance, mock.sentinel.network_info,
mock.sentinel.block_device_info, mock.sentinel.destroy_disks) mock.sentinel.block_device_info, mock.sentinel.destroy_disks)
def test_cleanup(self):
self.driver.cleanup(
mock.sentinel.context, mock.sentinel.instance,
mock.sentinel.network_info, mock.sentinel.block_device_info,
mock.sentinel.destroy_disks, mock.sentinel.migrate_data,
mock.sentinel.destroy_vifs)
self.driver._vmops.unplug_vifs.assert_called_once_with(
mock.sentinel.instance, mock.sentinel.network_info)
def test_get_info(self): def test_get_info(self):
self.driver.get_info(mock.sentinel.instance) self.driver.get_info(mock.sentinel.instance)
self.driver._vmops.get_info.assert_called_once_with( self.driver._vmops.get_info.assert_called_once_with(
@ -258,7 +268,8 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
mock.sentinel.network_info, mock.sentinel.block_device_info) mock.sentinel.network_info, mock.sentinel.block_device_info)
self.driver._vmops.power_on.assert_called_once_with( self.driver._vmops.power_on.assert_called_once_with(
mock.sentinel.instance, mock.sentinel.block_device_info) mock.sentinel.instance, mock.sentinel.block_device_info,
mock.sentinel.network_info)
def test_resume_state_on_host_boot(self): def test_resume_state_on_host_boot(self):
self.driver.resume_state_on_host_boot( self.driver.resume_state_on_host_boot(
@ -360,12 +371,18 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
mock.sentinel.dest_check_data) mock.sentinel.dest_check_data)
def test_plug_vifs(self): def test_plug_vifs(self):
self.assertRaises(NotImplementedError, self.driver.plug_vifs, self.driver.plug_vifs(
mock.sentinel.instance, mock.sentinel.network_info) mock.sentinel.instance, mock.sentinel.network_info)
self.driver._vmops.plug_vifs.assert_called_once_with(
mock.sentinel.instance, mock.sentinel.network_info)
def test_unplug_vifs(self): def test_unplug_vifs(self):
self.assertRaises(NotImplementedError, self.driver.unplug_vifs, self.driver.unplug_vifs(
mock.sentinel.instance, mock.sentinel.network_info) mock.sentinel.instance, mock.sentinel.network_info)
self.driver._vmops.unplug_vifs.assert_called_once_with(
mock.sentinel.instance, mock.sentinel.network_info)
def test_refresh_instance_security_rules(self): def test_refresh_instance_security_rules(self):
self.assertRaises(NotImplementedError, self.assertRaises(NotImplementedError,

@ -218,3 +218,12 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
self.assertEqual(mock_migr_data_cls.return_value, migr_data) self.assertEqual(mock_migr_data_cls.return_value, migr_data)
self.assertEqual(mock_check_shared_inst_dir.return_value, self.assertEqual(mock_check_shared_inst_dir.return_value,
migr_data.is_shared_instance_path) migr_data.is_shared_instance_path)
@mock.patch('nova.virt.hyperv.vmops.VMOps.plug_vifs')
def test_post_live_migration_at_destination(self, mock_plug_vifs):
self._livemigrops.post_live_migration_at_destination(
self.context, mock.sentinel.instance,
network_info=mock.sentinel.NET_INFO,
block_migration=mock.sentinel.BLOCK_INFO)
mock_plug_vifs.assert_called_once_with(mock.sentinel.instance,
mock.sentinel.NET_INFO)

@ -281,7 +281,7 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase):
mock_instance.name, get_image_vm_gen.return_value, mock_instance.name, get_image_vm_gen.return_value,
block_device_info) block_device_info)
self._migrationops._vmops.power_on.assert_called_once_with( self._migrationops._vmops.power_on.assert_called_once_with(
mock_instance) mock_instance, network_info=mock.sentinel.network_info)
def test_finish_revert_migration_boot_from_volume(self): def test_finish_revert_migration_boot_from_volume(self):
self._check_finish_revert_migration(disk_type=constants.VOLUME) self._check_finish_revert_migration(disk_type=constants.VOLUME)

@ -16,14 +16,19 @@
import mock import mock
import nova.conf
from nova import exception
from nova.network import model
from nova.tests.unit.virt.hyperv import test_base from nova.tests.unit.virt.hyperv import test_base
from nova.virt.hyperv import vif from nova.virt.hyperv import vif
CONF = nova.conf.CONF
class HyperVNovaNetworkVIFDriverTestCase(test_base.HyperVBaseTestCase):
class HyperVNovaNetworkVIFPluginTestCase(test_base.HyperVBaseTestCase):
def setUp(self): def setUp(self):
super(HyperVNovaNetworkVIFDriverTestCase, self).setUp() super(HyperVNovaNetworkVIFPluginTestCase, self).setUp()
self.vif_driver = vif.HyperVNovaNetworkVIFDriver() self.vif_driver = vif.HyperVNovaNetworkVIFPlugin()
def test_plug(self): def test_plug(self):
self.flags(vswitch_name='fake_vswitch_name', group='hyperv') self.flags(vswitch_name='fake_vswitch_name', group='hyperv')
@ -33,3 +38,85 @@ class HyperVNovaNetworkVIFDriverTestCase(test_base.HyperVBaseTestCase):
netutils = self.vif_driver._netutils netutils = self.vif_driver._netutils
netutils.connect_vnic_to_vswitch.assert_called_once_with( netutils.connect_vnic_to_vswitch.assert_called_once_with(
'fake_vswitch_name', mock.sentinel.fake_id) 'fake_vswitch_name', mock.sentinel.fake_id)
class HyperVVIFDriverTestCase(test_base.HyperVBaseTestCase):
def setUp(self):
super(HyperVVIFDriverTestCase, self).setUp()
self.vif_driver = vif.HyperVVIFDriver()
self.vif_driver._netutils = mock.MagicMock()
self.vif_driver._vif_plugin = mock.MagicMock()
@mock.patch.object(vif.nova.network, 'is_neutron')
def test_init_neutron(self, mock_is_neutron):
mock_is_neutron.return_value = True
driver = vif.HyperVVIFDriver()
self.assertIsInstance(driver._vif_plugin, vif.HyperVNeutronVIFPlugin)
@mock.patch.object(vif.nova.network, 'is_neutron')
def test_init_nova(self, mock_is_neutron):
mock_is_neutron.return_value = False
driver = vif.HyperVVIFDriver()
self.assertIsInstance(driver._vif_plugin,
vif.HyperVNovaNetworkVIFPlugin)
def test_plug(self):
vif = {'type': model.VIF_TYPE_HYPERV}
self.vif_driver.plug(mock.sentinel.instance, vif)
self.vif_driver._vif_plugin.plug.assert_called_once_with(
mock.sentinel.instance, vif)
@mock.patch.object(vif, 'os_vif')
@mock.patch.object(vif.os_vif_util, 'nova_to_osvif_instance')
@mock.patch.object(vif.os_vif_util, 'nova_to_osvif_vif')
def test_plug_ovs(self, mock_nova_to_osvif_vif,
mock_nova_to_osvif_instance, mock_os_vif):
vif = {'type': model.VIF_TYPE_OVS}
self.vif_driver.plug(mock.sentinel.instance, vif)
mock_nova_to_osvif_vif.assert_called_once_with(vif)
mock_nova_to_osvif_instance.assert_called_once_with(
mock.sentinel.instance)
connect_vnic = self.vif_driver._netutils.connect_vnic_to_vswitch
connect_vnic.assert_called_once_with(
CONF.hyperv.vswitch_name, mock_nova_to_osvif_vif.return_value.id)
mock_os_vif.plug.assert_called_once_with(
mock_nova_to_osvif_vif.return_value,
mock_nova_to_osvif_instance.return_value)
def test_plug_type_unknown(self):
vif = {'type': mock.sentinel.vif_type}
self.assertRaises(exception.VirtualInterfacePlugException,
self.vif_driver.plug,
mock.sentinel.instance, vif)
def test_unplug(self):
vif = {'type': model.VIF_TYPE_HYPERV}
self.vif_driver.unplug(mock.sentinel.instance, vif)
self.vif_driver._vif_plugin.unplug.assert_called_once_with(
mock.sentinel.instance, vif)
@mock.patch.object(vif, 'os_vif')
@mock.patch.object(vif.os_vif_util, 'nova_to_osvif_instance')
@mock.patch.object(vif.os_vif_util, 'nova_to_osvif_vif')
def test_unplug_ovs(self, mock_nova_to_osvif_vif,
mock_nova_to_osvif_instance, mock_os_vif):
vif = {'type': model.VIF_TYPE_OVS}
self.vif_driver.unplug(mock.sentinel.instance, vif)
mock_nova_to_osvif_vif.assert_called_once_with(vif)
mock_nova_to_osvif_instance.assert_called_once_with(
mock.sentinel.instance)
mock_os_vif.unplug.assert_called_once_with(
mock_nova_to_osvif_vif.return_value,
mock_nova_to_osvif_instance.return_value)
def test_unplug_type_unknown(self):
vif = {'type': mock.sentinel.vif_type}
self.assertRaises(exception.VirtualInterfaceUnplugException,
self.vif_driver.unplug,
mock.sentinel.instance, vif)

@ -70,22 +70,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
self._vmops._hostutils = mock.MagicMock() self._vmops._hostutils = mock.MagicMock()
self._vmops._serial_console_ops = mock.MagicMock() self._vmops._serial_console_ops = mock.MagicMock()
self._vmops._block_dev_man = mock.MagicMock() self._vmops._block_dev_man = mock.MagicMock()
self._vmops._vif_driver = mock.MagicMock()
@mock.patch('nova.network.is_neutron')
@mock.patch('nova.virt.hyperv.vmops.importutils.import_object')
def test_load_vif_driver_neutron(self, mock_import_object, is_neutron):
is_neutron.return_value = True
self._vmops._load_vif_driver_class()
mock_import_object.assert_called_once_with(
vmops.NEUTRON_VIF_DRIVER)
@mock.patch('nova.network.is_neutron')
@mock.patch('nova.virt.hyperv.vmops.importutils.import_object')
def test_load_vif_driver_nova(self, mock_import_object, is_neutron):
is_neutron.return_value = False
self._vmops._load_vif_driver_class()
mock_import_object.assert_called_once_with(
vmops.NOVA_VIF_DRIVER)
def test_list_instances(self): def test_list_instances(self):
mock_instance = mock.MagicMock() mock_instance = mock.MagicMock()
@ -471,7 +456,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_instance, fake_config_drive_path, fake_vm_gen) mock_instance, fake_config_drive_path, fake_vm_gen)
mock_set_boot_order.assert_called_once_with( mock_set_boot_order.assert_called_once_with(
mock_instance.name, fake_vm_gen, block_device_info) mock_instance.name, fake_vm_gen, block_device_info)
mock_power_on.assert_called_once_with(mock_instance) mock_power_on.assert_called_once_with(
mock_instance, network_info=mock.sentinel.INFO)
def test_spawn(self): def test_spawn(self):
self._test_spawn(exists=False, configdrive_required=True, fail=None) self._test_spawn(exists=False, configdrive_required=True, fail=None)
@ -561,8 +547,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_requires_secure_boot, mock_requires_secure_boot,
enable_instance_metrics, enable_instance_metrics,
vm_gen=constants.VM_GEN_1): vm_gen=constants.VM_GEN_1):
mock_vif_driver = mock.MagicMock()
self._vmops._vif_driver = mock_vif_driver
self.flags(enable_instance_metrics_collection=enable_instance_metrics, self.flags(enable_instance_metrics_collection=enable_instance_metrics,
group='hyperv') group='hyperv')
root_device_info = mock.sentinel.ROOT_DEV_INFO root_device_info = mock.sentinel.ROOT_DEV_INFO
@ -601,8 +585,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
self._vmops._vmutils.create_nic.assert_called_once_with( self._vmops._vmutils.create_nic.assert_called_once_with(
mock_instance.name, mock.sentinel.ID, mock.sentinel.ADDRESS) mock_instance.name, mock.sentinel.ID, mock.sentinel.ADDRESS)
mock_vif_driver.plug.assert_called_once_with(mock_instance,
fake_network_info)
mock_enable = self._vmops._metricsutils.enable_vm_metrics_collection mock_enable = self._vmops._metricsutils.enable_vm_metrics_collection
if enable_instance_metrics: if enable_instance_metrics:
mock_enable.assert_called_once_with(mock_instance.name) mock_enable.assert_called_once_with(mock_instance.name)
@ -974,21 +956,21 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps.disconnect_volumes') @mock.patch('nova.virt.hyperv.volumeops.VolumeOps.disconnect_volumes')
@mock.patch('nova.virt.hyperv.vmops.VMOps._delete_disk_files') @mock.patch('nova.virt.hyperv.vmops.VMOps._delete_disk_files')
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_off') @mock.patch('nova.virt.hyperv.vmops.VMOps.power_off')
def test_destroy(self, mock_power_off, mock_delete_disk_files, @mock.patch('nova.virt.hyperv.vmops.VMOps.unplug_vifs')
mock_disconnect_volumes): def test_destroy(self, mock_unplug_vifs, mock_power_off,
mock_delete_disk_files, mock_disconnect_volumes):
mock_instance = fake_instance.fake_instance_obj(self.context) mock_instance = fake_instance.fake_instance_obj(self.context)
self._vmops._vmutils.vm_exists.return_value = True self._vmops._vmutils.vm_exists.return_value = True
self._vmops._vif_driver = mock.MagicMock()
self._vmops.destroy(instance=mock_instance, self._vmops.destroy(instance=mock_instance,
network_info=[mock.sentinel.fake_vif], block_device_info=mock.sentinel.FAKE_BD_INFO,
block_device_info=mock.sentinel.FAKE_BD_INFO) network_info=mock.sentinel.fake_network_info)
self._vmops._vmutils.vm_exists.assert_called_with( self._vmops._vmutils.vm_exists.assert_called_with(
mock_instance.name) mock_instance.name)
mock_power_off.assert_called_once_with(mock_instance) mock_power_off.assert_called_once_with(mock_instance)
self._vmops._vif_driver.unplug.assert_called_once_with( mock_unplug_vifs.assert_called_once_with(
mock_instance, mock.sentinel.fake_vif) mock_instance, mock.sentinel.fake_network_info)
self._vmops._vmutils.destroy_vm.assert_called_once_with( self._vmops._vmutils.destroy_vm.assert_called_once_with(
mock_instance.name) mock_instance.name)
mock_disconnect_volumes.assert_called_once_with( mock_disconnect_volumes.assert_called_once_with(
@ -1041,7 +1023,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
instance, {}, vmops.REBOOT_TYPE_SOFT) instance, {}, vmops.REBOOT_TYPE_SOFT)
mock_soft_shutdown.assert_called_once_with(instance) mock_soft_shutdown.assert_called_once_with(instance)
mock_power_on.assert_called_once_with(instance) mock_power_on.assert_called_once_with(instance, network_info={})
def _test_reboot(self, reboot_type, vm_state): def _test_reboot(self, reboot_type, vm_state):
instance = fake_instance.fake_instance_obj(self.context) instance = fake_instance.fake_instance_obj(self.context)
@ -1198,6 +1180,15 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_set_vm_state.assert_called_once_with( mock_set_vm_state.assert_called_once_with(
mock_instance, os_win_const.HYPERV_VM_STATE_ENABLED) mock_instance, os_win_const.HYPERV_VM_STATE_ENABLED)
@mock.patch.object(vmops.VMOps, 'plug_vifs')
def test_power_on_with_network_info(self, mock_plug_vifs):
mock_instance = fake_instance.fake_instance_obj(self.context)
self._vmops.power_on(mock_instance,
network_info=mock.sentinel.fake_network_info)
mock_plug_vifs.assert_called_once_with(
mock_instance, mock.sentinel.fake_network_info)
def _test_set_vm_state(self, state): def _test_set_vm_state(self, state):
mock_instance = fake_instance.fake_instance_obj(self.context) mock_instance = fake_instance.fake_instance_obj(self.context)
@ -1299,6 +1290,34 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock.call(mock.sentinel.FAKE_DVD_PATH2, mock.call(mock.sentinel.FAKE_DVD_PATH2,
mock.sentinel.FAKE_DEST_PATH)) mock.sentinel.FAKE_DEST_PATH))
def test_plug_vifs(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
fake_vif1 = {'id': mock.sentinel.ID1,
'type': mock.sentinel.vif_type1}
fake_vif2 = {'id': mock.sentinel.ID2,
'type': mock.sentinel.vif_type2}
mock_network_info = [fake_vif1, fake_vif2]
calls = [mock.call(mock_instance, fake_vif1),
mock.call(mock_instance, fake_vif2)]
self._vmops.plug_vifs(mock_instance,
network_info=mock_network_info)
self._vmops._vif_driver.plug.assert_has_calls(calls)
def test_unplug_vifs(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
fake_vif1 = {'id': mock.sentinel.ID1,
'type': mock.sentinel.vif_type1}
fake_vif2 = {'id': mock.sentinel.ID2,
'type': mock.sentinel.vif_type2}
mock_network_info = [fake_vif1, fake_vif2]
calls = [mock.call(mock_instance, fake_vif1),
mock.call(mock_instance, fake_vif2)]
self._vmops.unplug_vifs(mock_instance,
network_info=mock_network_info)
self._vmops._vif_driver.unplug.assert_has_calls(calls)
def _setup_remotefx_mocks(self): def _setup_remotefx_mocks(self):
mock_instance = fake_instance.fake_instance_obj(self.context) mock_instance = fake_instance.fake_instance_obj(self.context)
mock_instance.flavor.extra_specs = { mock_instance.flavor.extra_specs = {
@ -1404,7 +1423,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_check_hotplug_available.return_value = True mock_check_hotplug_available.return_value = True
fake_vm = fake_instance.fake_instance_obj(self.context) fake_vm = fake_instance.fake_instance_obj(self.context)
fake_vif = test_virtual_interface.fake_vif fake_vif = test_virtual_interface.fake_vif
self._vmops._vif_driver = mock.MagicMock()
self._vmops.attach_interface(fake_vm, fake_vif) self._vmops.attach_interface(fake_vm, fake_vif)
@ -1426,7 +1444,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_check_hotplug_available.return_value = True mock_check_hotplug_available.return_value = True
fake_vm = fake_instance.fake_instance_obj(self.context) fake_vm = fake_instance.fake_instance_obj(self.context)
fake_vif = test_virtual_interface.fake_vif fake_vif = test_virtual_interface.fake_vif
self._vmops._vif_driver = mock.MagicMock()
self._vmops.detach_interface(fake_vm, fake_vif) self._vmops.detach_interface(fake_vm, fake_vif)

@ -27,7 +27,7 @@ from oslo_log import log as logging
import six import six
from nova import exception from nova import exception
from nova.i18n import _, _LE from nova.i18n import _LE
from nova.virt import driver from nova.virt import driver
from nova.virt.hyperv import eventhandler from nova.virt.hyperv import eventhandler
from nova.virt.hyperv import hostops from nova.virt.hyperv import hostops
@ -165,7 +165,7 @@ class HyperVDriver(driver.ComputeDriver):
def cleanup(self, context, instance, network_info, block_device_info=None, def cleanup(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None, destroy_vifs=True): destroy_disks=True, migrate_data=None, destroy_vifs=True):
"""Cleanup after instance being destroyed by Hypervisor.""" """Cleanup after instance being destroyed by Hypervisor."""
pass self.unplug_vifs(instance, network_info)
def get_info(self, instance): def get_info(self, instance):
return self._vmops.get_info(instance) return self._vmops.get_info(instance)
@ -213,7 +213,7 @@ class HyperVDriver(driver.ComputeDriver):
def power_on(self, context, instance, network_info, def power_on(self, context, instance, network_info,
block_device_info=None): block_device_info=None):
self._vmops.power_on(instance, block_device_info) self._vmops.power_on(instance, block_device_info, network_info)
def resume_state_on_host_boot(self, context, instance, network_info, def resume_state_on_host_boot(self, context, instance, network_info,
block_device_info=None): block_device_info=None):
@ -282,13 +282,11 @@ class HyperVDriver(driver.ComputeDriver):
def plug_vifs(self, instance, network_info): def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks.""" """Plug VIFs into networks."""
msg = _("VIF plugging is not supported by the Hyper-V driver.") self._vmops.plug_vifs(instance, network_info)
raise NotImplementedError(msg)
def unplug_vifs(self, instance, network_info): def unplug_vifs(self, instance, network_info):
"""Unplug VIFs from networks.""" """Unplug VIFs from networks."""
msg = _("VIF unplugging is not supported by the Hyper-V driver.") self._vmops.unplug_vifs(instance, network_info)
raise NotImplementedError(msg)
def ensure_filtering_rules_for_instance(self, instance, network_info): def ensure_filtering_rules_for_instance(self, instance, network_info):
LOG.debug("ensure_filtering_rules_for_instance called", LOG.debug("ensure_filtering_rules_for_instance called",

@ -119,6 +119,7 @@ class LiveMigrationOps(object):
network_info, block_migration): network_info, block_migration):
LOG.debug("post_live_migration_at_destination called", LOG.debug("post_live_migration_at_destination called",
instance=instance_ref) instance=instance_ref)
self._vmops.plug_vifs(instance_ref, network_info)
def check_can_live_migrate_destination(self, ctxt, instance_ref, def check_can_live_migrate_destination(self, ctxt, instance_ref,
src_compute_info, dst_compute_info, src_compute_info, dst_compute_info,

@ -189,7 +189,7 @@ class MigrationOps(object):
self._check_and_attach_config_drive(instance, vm_gen) self._check_and_attach_config_drive(instance, vm_gen)
self._vmops.set_boot_order(instance_name, vm_gen, block_device_info) self._vmops.set_boot_order(instance_name, vm_gen, block_device_info)
if power_on: if power_on:
self._vmops.power_on(instance) self._vmops.power_on(instance, network_info=network_info)
def _merge_base_vhd(self, diff_vhd_path, base_vhd_path): def _merge_base_vhd(self, diff_vhd_path, base_vhd_path):
base_vhd_copy_path = os.path.join(os.path.dirname(diff_vhd_path), base_vhd_copy_path = os.path.join(os.path.dirname(diff_vhd_path),

@ -16,14 +16,19 @@
import abc import abc
import os_vif
from os_win import utilsfactory from os_win import utilsfactory
import nova.conf import nova.conf
from nova import exception
from nova.i18n import _
from nova.network import model
from nova.network import os_vif_util
CONF = nova.conf.CONF CONF = nova.conf.CONF
class HyperVBaseVIFDriver(object): class HyperVBaseVIFPlugin(object):
@abc.abstractmethod @abc.abstractmethod
def plug(self, instance, vif): def plug(self, instance, vif):
pass pass
@ -33,8 +38,8 @@ class HyperVBaseVIFDriver(object):
pass pass
class HyperVNeutronVIFDriver(HyperVBaseVIFDriver): class HyperVNeutronVIFPlugin(HyperVBaseVIFPlugin):
"""Neutron VIF driver.""" """Neutron VIF plugin."""
def plug(self, instance, vif): def plug(self, instance, vif):
# Neutron takes care of plugging the port # Neutron takes care of plugging the port
@ -45,8 +50,8 @@ class HyperVNeutronVIFDriver(HyperVBaseVIFDriver):
pass pass
class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver): class HyperVNovaNetworkVIFPlugin(HyperVBaseVIFPlugin):
"""Nova network VIF driver.""" """Nova network VIF plugin."""
def __init__(self): def __init__(self):
self._netutils = utilsfactory.get_networkutils() self._netutils = utilsfactory.get_networkutils()
@ -58,3 +63,42 @@ class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver):
def unplug(self, instance, vif): def unplug(self, instance, vif):
# TODO(alepilotti) Not implemented # TODO(alepilotti) Not implemented
pass pass
class HyperVVIFDriver(object):
def __init__(self):
self._netutils = utilsfactory.get_networkutils()
if nova.network.is_neutron():
self._vif_plugin = HyperVNeutronVIFPlugin()
else:
self._vif_plugin = HyperVNovaNetworkVIFPlugin()
def plug(self, instance, vif):
vif_type = vif['type']
if vif_type == model.VIF_TYPE_HYPERV:
self._vif_plugin.plug(instance, vif)
elif vif_type == model.VIF_TYPE_OVS:
vif = os_vif_util.nova_to_osvif_vif(vif)
instance = os_vif_util.nova_to_osvif_instance(instance)
# NOTE(claudiub): the vNIC has to be connected to a vSwitch
# before the ovs port is created.
self._netutils.connect_vnic_to_vswitch(CONF.hyperv.vswitch_name,
vif.id)
os_vif.plug(vif, instance)
else:
reason = _("Failed to plug virtual interface: "
"unexpected vif_type=%s") % vif_type
raise exception.VirtualInterfacePlugException(reason)
def unplug(self, instance, vif):
vif_type = vif['type']
if vif_type == model.VIF_TYPE_HYPERV:
self._vif_plugin.unplug(instance, vif)
elif vif_type == model.VIF_TYPE_OVS:
vif = os_vif_util.nova_to_osvif_vif(vif)
instance = os_vif_util.nova_to_osvif_instance(instance)
os_vif.unplug(vif, instance)
else:
reason = _("unexpected vif_type=%s") % vif_type
raise exception.VirtualInterfaceUnplugException(reason=reason)

@ -31,7 +31,6 @@ from oslo_log import log as logging
from oslo_service import loopingcall from oslo_service import loopingcall
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import fileutils from oslo_utils import fileutils
from oslo_utils import importutils
from oslo_utils import units from oslo_utils import units
from oslo_utils import uuidutils from oslo_utils import uuidutils
@ -50,6 +49,7 @@ from nova.virt.hyperv import constants
from nova.virt.hyperv import imagecache from nova.virt.hyperv import imagecache
from nova.virt.hyperv import pathutils from nova.virt.hyperv import pathutils
from nova.virt.hyperv import serialconsoleops from nova.virt.hyperv import serialconsoleops
from nova.virt.hyperv import vif as vif_utils
from nova.virt.hyperv import volumeops from nova.virt.hyperv import volumeops
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -81,17 +81,6 @@ def check_admin_permissions(function):
return function(self, *args, **kwds) return function(self, *args, **kwds)
return wrapper return wrapper
NEUTRON_VIF_DRIVER = 'nova.virt.hyperv.vif.HyperVNeutronVIFDriver'
NOVA_VIF_DRIVER = 'nova.virt.hyperv.vif.HyperVNovaNetworkVIFDriver'
def get_network_driver():
""""Return the correct network module"""
if nova.network.is_neutron():
return NEUTRON_VIF_DRIVER
else:
return NOVA_VIF_DRIVER
class VMOps(object): class VMOps(object):
# The console log is stored in two files, each should have at most half of # The console log is stored in two files, each should have at most half of
@ -111,12 +100,7 @@ class VMOps(object):
self._serial_console_ops = serialconsoleops.SerialConsoleOps() self._serial_console_ops = serialconsoleops.SerialConsoleOps()
self._block_dev_man = ( self._block_dev_man = (
block_device_manager.BlockDeviceInfoManager()) block_device_manager.BlockDeviceInfoManager())
self._vif_driver = None self._vif_driver = vif_utils.HyperVVIFDriver()
self._load_vif_driver_class()
def _load_vif_driver_class(self):
class_name = get_network_driver()
self._vif_driver = importutils.import_object(class_name)
def list_instance_uuids(self): def list_instance_uuids(self):
instance_uuids = [] instance_uuids = []
@ -318,7 +302,7 @@ class VMOps(object):
self.attach_config_drive(instance, configdrive_path, vm_gen) self.attach_config_drive(instance, configdrive_path, vm_gen)
self.set_boot_order(instance.name, vm_gen, block_device_info) self.set_boot_order(instance.name, vm_gen, block_device_info)
self.power_on(instance) self.power_on(instance, network_info=network_info)
except Exception: except Exception:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
self.destroy(instance) self.destroy(instance)
@ -397,7 +381,6 @@ class VMOps(object):
self._vmutils.create_nic(instance_name, self._vmutils.create_nic(instance_name,
vif['id'], vif['id'],
vif['address']) vif['address'])
self._vif_driver.plug(instance, vif)
if CONF.hyperv.enable_instance_metrics_collection: if CONF.hyperv.enable_instance_metrics_collection:
self._metricsutils.enable_vm_metrics_collection(instance_name) self._metricsutils.enable_vm_metrics_collection(instance_name)
@ -643,11 +626,7 @@ class VMOps(object):
# Stop the VM first. # Stop the VM first.
self._vmutils.stop_vm_jobs(instance_name) self._vmutils.stop_vm_jobs(instance_name)
self.power_off(instance) self.power_off(instance)
self.unplug_vifs(instance, network_info)
if network_info:
for vif in network_info:
self._vif_driver.unplug(instance, vif)
self._vmutils.destroy_vm(instance_name) self._vmutils.destroy_vm(instance_name)
self._volumeops.disconnect_volumes(block_device_info) self._volumeops.disconnect_volumes(block_device_info)
else: else:
@ -666,7 +645,7 @@ class VMOps(object):
if reboot_type == REBOOT_TYPE_SOFT: if reboot_type == REBOOT_TYPE_SOFT:
if self._soft_shutdown(instance): if self._soft_shutdown(instance):
self.power_on(instance) self.power_on(instance, network_info=network_info)
return return
self._set_vm_state(instance, self._set_vm_state(instance,
@ -759,7 +738,7 @@ class VMOps(object):
LOG.debug("Instance not found. Skipping power off", LOG.debug("Instance not found. Skipping power off",
instance=instance) instance=instance)
def power_on(self, instance, block_device_info=None): def power_on(self, instance, block_device_info=None, network_info=None):
"""Power on the specified instance.""" """Power on the specified instance."""
LOG.debug("Power on instance", instance=instance) LOG.debug("Power on instance", instance=instance)
@ -768,6 +747,7 @@ class VMOps(object):
block_device_info) block_device_info)
self._set_vm_state(instance, os_win_const.HYPERV_VM_STATE_ENABLED) self._set_vm_state(instance, os_win_const.HYPERV_VM_STATE_ENABLED)
self.plug_vifs(instance, network_info)
def _set_vm_state(self, instance, req_state): def _set_vm_state(self, instance, req_state):
instance_name = instance.name instance_name = instance.name
@ -821,7 +801,7 @@ class VMOps(object):
def resume_state_on_host_boot(self, context, instance, network_info, def resume_state_on_host_boot(self, context, instance, network_info,
block_device_info=None): block_device_info=None):
"""Resume guest state when a host is booted.""" """Resume guest state when a host is booted."""
self.power_on(instance, block_device_info) self.power_on(instance, block_device_info, network_info)
def _create_vm_com_port_pipes(self, instance, serial_ports): def _create_vm_com_port_pipes(self, instance, serial_ports):
for port_number, port_type in serial_ports.items(): for port_number, port_type in serial_ports.items():
@ -836,6 +816,16 @@ class VMOps(object):
for path in dvd_disk_paths: for path in dvd_disk_paths:
self._pathutils.copyfile(path, dest_path) self._pathutils.copyfile(path, dest_path)
def plug_vifs(self, instance, network_info):
if network_info:
for vif in network_info:
self._vif_driver.plug(instance, vif)
def unplug_vifs(self, instance, network_info):
if network_info:
for vif in network_info:
self._vif_driver.unplug(instance, vif)
def _check_hotplug_available(self, instance): def _check_hotplug_available(self, instance):
"""Check whether attaching an interface is possible for the given """Check whether attaching an interface is possible for the given
instance. instance.

@ -0,0 +1,10 @@
---
features:
- The nova Hyper-V driver can now plug OVS VIFs. This means that
neutron-ovs-agent can be used as an L2 agent instead of
neutron-hyperv-agent.
In order to plug OVS VIFs, the configuration option "vswitch_name"
from the "hyperv" section must be set to the vSwitch which has the OVS
extension enabled.
Hot-plugging is only supported on Windows / Hyper-V Server 2016 + Generation 2 VMs.
Older Hyper-V versions only support attaching vNICs while the VM is turned off.