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

View File

@ -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'

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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",

View File

@ -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,

View File

@ -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),

View File

@ -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)

View File

@ -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.

View 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.