diff --git a/nova/network/model.py b/nova/network/model.py index ba74b5fedf84..cd3362633747 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -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' diff --git a/nova/tests/unit/virt/hyperv/test_driver.py b/nova/tests/unit/virt/hyperv/test_driver.py index 30a112fc99f2..c1f1dfb7ab4d 100644 --- a/nova/tests/unit/virt/hyperv/test_driver.py +++ b/nova/tests/unit/virt/hyperv/test_driver.py @@ -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, diff --git a/nova/tests/unit/virt/hyperv/test_livemigrationops.py b/nova/tests/unit/virt/hyperv/test_livemigrationops.py index 4f36787a3fd0..6b21c7388134 100644 --- a/nova/tests/unit/virt/hyperv/test_livemigrationops.py +++ b/nova/tests/unit/virt/hyperv/test_livemigrationops.py @@ -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) diff --git a/nova/tests/unit/virt/hyperv/test_migrationops.py b/nova/tests/unit/virt/hyperv/test_migrationops.py index cd250b33b22b..4d776a9d27cd 100644 --- a/nova/tests/unit/virt/hyperv/test_migrationops.py +++ b/nova/tests/unit/virt/hyperv/test_migrationops.py @@ -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) diff --git a/nova/tests/unit/virt/hyperv/test_vif.py b/nova/tests/unit/virt/hyperv/test_vif.py index a20795da550a..5f8da6a2aa4d 100644 --- a/nova/tests/unit/virt/hyperv/test_vif.py +++ b/nova/tests/unit/virt/hyperv/test_vif.py @@ -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) diff --git a/nova/tests/unit/virt/hyperv/test_vmops.py b/nova/tests/unit/virt/hyperv/test_vmops.py index f96193552583..d7b272486d08 100644 --- a/nova/tests/unit/virt/hyperv/test_vmops.py +++ b/nova/tests/unit/virt/hyperv/test_vmops.py @@ -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) diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py index 9ac8f16ffa85..452481ea9813 100644 --- a/nova/virt/hyperv/driver.py +++ b/nova/virt/hyperv/driver.py @@ -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", diff --git a/nova/virt/hyperv/livemigrationops.py b/nova/virt/hyperv/livemigrationops.py index 629865ca8b2a..7b3fa6ab5b4a 100644 --- a/nova/virt/hyperv/livemigrationops.py +++ b/nova/virt/hyperv/livemigrationops.py @@ -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, diff --git a/nova/virt/hyperv/migrationops.py b/nova/virt/hyperv/migrationops.py index 441a35d3b4bf..62f9f38ca649 100644 --- a/nova/virt/hyperv/migrationops.py +++ b/nova/virt/hyperv/migrationops.py @@ -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), diff --git a/nova/virt/hyperv/vif.py b/nova/virt/hyperv/vif.py index 71b2a9da2d10..4b21bda7491a 100644 --- a/nova/virt/hyperv/vif.py +++ b/nova/virt/hyperv/vif.py @@ -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) diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py index e88e4b60cc47..8119456167e0 100644 --- a/nova/virt/hyperv/vmops.py +++ b/nova/virt/hyperv/vmops.py @@ -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. diff --git a/releasenotes/notes/hyper-v-ovs-vif-348fca68db4918fe.yaml b/releasenotes/notes/hyper-v-ovs-vif-348fca68db4918fe.yaml new file mode 100644 index 000000000000..4c0a9ed1d9c3 --- /dev/null +++ b/releasenotes/notes/hyper-v-ovs-vif-348fca68db4918fe.yaml @@ -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.