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:
parent
cf6592df3c
commit
07b6580a16
@ -37,6 +37,7 @@ VIF_TYPE_BRIDGE = 'bridge'
|
||||
VIF_TYPE_802_QBG = '802.1qbg'
|
||||
VIF_TYPE_802_QBH = '802.1qbh'
|
||||
VIF_TYPE_HW_VEB = 'hw_veb'
|
||||
VIF_TYPE_HYPERV = 'hyperv'
|
||||
VIF_TYPE_HOSTDEV = 'hostdev_physical'
|
||||
VIF_TYPE_IB_HOSTDEV = 'ib_hostdev'
|
||||
VIF_TYPE_MIDONET = 'midonet'
|
||||
|
@ -168,6 +168,16 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.instance, mock.sentinel.network_info,
|
||||
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):
|
||||
self.driver.get_info(mock.sentinel.instance)
|
||||
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)
|
||||
|
||||
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):
|
||||
self.driver.resume_state_on_host_boot(
|
||||
@ -360,12 +371,18 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.dest_check_data)
|
||||
|
||||
def test_plug_vifs(self):
|
||||
self.assertRaises(NotImplementedError, self.driver.plug_vifs,
|
||||
mock.sentinel.instance, mock.sentinel.network_info)
|
||||
self.driver.plug_vifs(
|
||||
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):
|
||||
self.assertRaises(NotImplementedError, self.driver.unplug_vifs,
|
||||
mock.sentinel.instance, mock.sentinel.network_info)
|
||||
self.driver.unplug_vifs(
|
||||
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):
|
||||
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_check_shared_inst_dir.return_value,
|
||||
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,
|
||||
block_device_info)
|
||||
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):
|
||||
self._check_finish_revert_migration(disk_type=constants.VOLUME)
|
||||
|
@ -16,14 +16,19 @@
|
||||
|
||||
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.virt.hyperv import vif
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
class HyperVNovaNetworkVIFDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
class HyperVNovaNetworkVIFPluginTestCase(test_base.HyperVBaseTestCase):
|
||||
def setUp(self):
|
||||
super(HyperVNovaNetworkVIFDriverTestCase, self).setUp()
|
||||
self.vif_driver = vif.HyperVNovaNetworkVIFDriver()
|
||||
super(HyperVNovaNetworkVIFPluginTestCase, self).setUp()
|
||||
self.vif_driver = vif.HyperVNovaNetworkVIFPlugin()
|
||||
|
||||
def test_plug(self):
|
||||
self.flags(vswitch_name='fake_vswitch_name', group='hyperv')
|
||||
@ -33,3 +38,85 @@ class HyperVNovaNetworkVIFDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
netutils = self.vif_driver._netutils
|
||||
netutils.connect_vnic_to_vswitch.assert_called_once_with(
|
||||
'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._serial_console_ops = mock.MagicMock()
|
||||
self._vmops._block_dev_man = 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)
|
||||
self._vmops._vif_driver = mock.MagicMock()
|
||||
|
||||
def test_list_instances(self):
|
||||
mock_instance = mock.MagicMock()
|
||||
@ -471,7 +456,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_instance, fake_config_drive_path, fake_vm_gen)
|
||||
mock_set_boot_order.assert_called_once_with(
|
||||
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):
|
||||
self._test_spawn(exists=False, configdrive_required=True, fail=None)
|
||||
@ -561,8 +547,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_requires_secure_boot,
|
||||
enable_instance_metrics,
|
||||
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,
|
||||
group='hyperv')
|
||||
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(
|
||||
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
|
||||
if enable_instance_metrics:
|
||||
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.vmops.VMOps._delete_disk_files')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_off')
|
||||
def test_destroy(self, mock_power_off, mock_delete_disk_files,
|
||||
mock_disconnect_volumes):
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.unplug_vifs')
|
||||
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)
|
||||
self._vmops._vmutils.vm_exists.return_value = True
|
||||
self._vmops._vif_driver = mock.MagicMock()
|
||||
|
||||
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(
|
||||
mock_instance.name)
|
||||
mock_power_off.assert_called_once_with(mock_instance)
|
||||
self._vmops._vif_driver.unplug.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.fake_vif)
|
||||
mock_unplug_vifs.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.fake_network_info)
|
||||
self._vmops._vmutils.destroy_vm.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_disconnect_volumes.assert_called_once_with(
|
||||
@ -1041,7 +1023,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
instance, {}, vmops.REBOOT_TYPE_SOFT)
|
||||
|
||||
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):
|
||||
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_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):
|
||||
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.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):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.flavor.extra_specs = {
|
||||
@ -1404,7 +1423,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_check_hotplug_available.return_value = True
|
||||
fake_vm = fake_instance.fake_instance_obj(self.context)
|
||||
fake_vif = test_virtual_interface.fake_vif
|
||||
self._vmops._vif_driver = mock.MagicMock()
|
||||
|
||||
self._vmops.attach_interface(fake_vm, fake_vif)
|
||||
|
||||
@ -1426,7 +1444,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_check_hotplug_available.return_value = True
|
||||
fake_vm = fake_instance.fake_instance_obj(self.context)
|
||||
fake_vif = test_virtual_interface.fake_vif
|
||||
self._vmops._vif_driver = mock.MagicMock()
|
||||
|
||||
self._vmops.detach_interface(fake_vm, fake_vif)
|
||||
|
||||
|
@ -27,7 +27,7 @@ from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _, _LE
|
||||
from nova.i18n import _LE
|
||||
from nova.virt import driver
|
||||
from nova.virt.hyperv import eventhandler
|
||||
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,
|
||||
destroy_disks=True, migrate_data=None, destroy_vifs=True):
|
||||
"""Cleanup after instance being destroyed by Hypervisor."""
|
||||
pass
|
||||
self.unplug_vifs(instance, network_info)
|
||||
|
||||
def get_info(self, instance):
|
||||
return self._vmops.get_info(instance)
|
||||
@ -213,7 +213,7 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
|
||||
def power_on(self, context, instance, network_info,
|
||||
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,
|
||||
block_device_info=None):
|
||||
@ -282,13 +282,11 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
|
||||
def plug_vifs(self, instance, network_info):
|
||||
"""Plug VIFs into networks."""
|
||||
msg = _("VIF plugging is not supported by the Hyper-V driver.")
|
||||
raise NotImplementedError(msg)
|
||||
self._vmops.plug_vifs(instance, network_info)
|
||||
|
||||
def unplug_vifs(self, instance, network_info):
|
||||
"""Unplug VIFs from networks."""
|
||||
msg = _("VIF unplugging is not supported by the Hyper-V driver.")
|
||||
raise NotImplementedError(msg)
|
||||
self._vmops.unplug_vifs(instance, network_info)
|
||||
|
||||
def ensure_filtering_rules_for_instance(self, instance, network_info):
|
||||
LOG.debug("ensure_filtering_rules_for_instance called",
|
||||
|
@ -119,6 +119,7 @@ class LiveMigrationOps(object):
|
||||
network_info, block_migration):
|
||||
LOG.debug("post_live_migration_at_destination called",
|
||||
instance=instance_ref)
|
||||
self._vmops.plug_vifs(instance_ref, network_info)
|
||||
|
||||
def check_can_live_migrate_destination(self, ctxt, instance_ref,
|
||||
src_compute_info, dst_compute_info,
|
||||
|
@ -189,7 +189,7 @@ class MigrationOps(object):
|
||||
self._check_and_attach_config_drive(instance, vm_gen)
|
||||
self._vmops.set_boot_order(instance_name, vm_gen, block_device_info)
|
||||
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):
|
||||
base_vhd_copy_path = os.path.join(os.path.dirname(diff_vhd_path),
|
||||
|
@ -16,14 +16,19 @@
|
||||
|
||||
import abc
|
||||
|
||||
import os_vif
|
||||
from os_win import utilsfactory
|
||||
|
||||
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
|
||||
|
||||
|
||||
class HyperVBaseVIFDriver(object):
|
||||
class HyperVBaseVIFPlugin(object):
|
||||
@abc.abstractmethod
|
||||
def plug(self, instance, vif):
|
||||
pass
|
||||
@ -33,8 +38,8 @@ class HyperVBaseVIFDriver(object):
|
||||
pass
|
||||
|
||||
|
||||
class HyperVNeutronVIFDriver(HyperVBaseVIFDriver):
|
||||
"""Neutron VIF driver."""
|
||||
class HyperVNeutronVIFPlugin(HyperVBaseVIFPlugin):
|
||||
"""Neutron VIF plugin."""
|
||||
|
||||
def plug(self, instance, vif):
|
||||
# Neutron takes care of plugging the port
|
||||
@ -45,8 +50,8 @@ class HyperVNeutronVIFDriver(HyperVBaseVIFDriver):
|
||||
pass
|
||||
|
||||
|
||||
class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver):
|
||||
"""Nova network VIF driver."""
|
||||
class HyperVNovaNetworkVIFPlugin(HyperVBaseVIFPlugin):
|
||||
"""Nova network VIF plugin."""
|
||||
|
||||
def __init__(self):
|
||||
self._netutils = utilsfactory.get_networkutils()
|
||||
@ -58,3 +63,42 @@ class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver):
|
||||
def unplug(self, instance, vif):
|
||||
# TODO(alepilotti) Not implemented
|
||||
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_utils import excutils
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import units
|
||||
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 pathutils
|
||||
from nova.virt.hyperv import serialconsoleops
|
||||
from nova.virt.hyperv import vif as vif_utils
|
||||
from nova.virt.hyperv import volumeops
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -81,17 +81,6 @@ def check_admin_permissions(function):
|
||||
return function(self, *args, **kwds)
|
||||
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):
|
||||
# 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._block_dev_man = (
|
||||
block_device_manager.BlockDeviceInfoManager())
|
||||
self._vif_driver = None
|
||||
self._load_vif_driver_class()
|
||||
|
||||
def _load_vif_driver_class(self):
|
||||
class_name = get_network_driver()
|
||||
self._vif_driver = importutils.import_object(class_name)
|
||||
self._vif_driver = vif_utils.HyperVVIFDriver()
|
||||
|
||||
def list_instance_uuids(self):
|
||||
instance_uuids = []
|
||||
@ -318,7 +302,7 @@ class VMOps(object):
|
||||
|
||||
self.attach_config_drive(instance, configdrive_path, vm_gen)
|
||||
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:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.destroy(instance)
|
||||
@ -397,7 +381,6 @@ class VMOps(object):
|
||||
self._vmutils.create_nic(instance_name,
|
||||
vif['id'],
|
||||
vif['address'])
|
||||
self._vif_driver.plug(instance, vif)
|
||||
|
||||
if CONF.hyperv.enable_instance_metrics_collection:
|
||||
self._metricsutils.enable_vm_metrics_collection(instance_name)
|
||||
@ -643,11 +626,7 @@ class VMOps(object):
|
||||
# Stop the VM first.
|
||||
self._vmutils.stop_vm_jobs(instance_name)
|
||||
self.power_off(instance)
|
||||
|
||||
if network_info:
|
||||
for vif in network_info:
|
||||
self._vif_driver.unplug(instance, vif)
|
||||
|
||||
self.unplug_vifs(instance, network_info)
|
||||
self._vmutils.destroy_vm(instance_name)
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
else:
|
||||
@ -666,7 +645,7 @@ class VMOps(object):
|
||||
|
||||
if reboot_type == REBOOT_TYPE_SOFT:
|
||||
if self._soft_shutdown(instance):
|
||||
self.power_on(instance)
|
||||
self.power_on(instance, network_info=network_info)
|
||||
return
|
||||
|
||||
self._set_vm_state(instance,
|
||||
@ -759,7 +738,7 @@ class VMOps(object):
|
||||
LOG.debug("Instance not found. Skipping power off",
|
||||
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."""
|
||||
LOG.debug("Power on instance", instance=instance)
|
||||
|
||||
@ -768,6 +747,7 @@ class VMOps(object):
|
||||
block_device_info)
|
||||
|
||||
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):
|
||||
instance_name = instance.name
|
||||
@ -821,7 +801,7 @@ class VMOps(object):
|
||||
def resume_state_on_host_boot(self, context, instance, network_info,
|
||||
block_device_info=None):
|
||||
"""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):
|
||||
for port_number, port_type in serial_ports.items():
|
||||
@ -836,6 +816,16 @@ class VMOps(object):
|
||||
for path in dvd_disk_paths:
|
||||
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):
|
||||
"""Check whether attaching an interface is possible for the given
|
||||
instance.
|
||||
|
10
releasenotes/notes/hyper-v-ovs-vif-348fca68db4918fe.yaml
Normal file
10
releasenotes/notes/hyper-v-ovs-vif-348fca68db4918fe.yaml
Normal file
@ -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.
|
Loading…
Reference in New Issue
Block a user