Merge "Hyper-V: Implements attach_interface and detach_interface method"
This commit is contained in:
commit
ef265dce22
@ -24,6 +24,7 @@ import unittest2
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.tests.unit import fake_instance
|
||||
from nova.tests.unit.objects import test_virtual_interface
|
||||
from nova.tests.unit.virt.hyperv import test_base
|
||||
from nova.virt import hardware
|
||||
from nova.virt.hyperv import constants
|
||||
@ -46,6 +47,9 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
FAKE_UUID = '4f54fb69-d3a2-45b7-bb9b-b6e6b3d893b3'
|
||||
FAKE_LOG = 'fake_log'
|
||||
|
||||
_WIN_VERSION_6_3 = '6.3.0'
|
||||
_WIN_VERSION_10 = '10.0'
|
||||
|
||||
ISO9660 = 'iso9660'
|
||||
_FAKE_CONFIGDRIVE_PATH = 'C:/fake_instance_dir/configdrive.vhd'
|
||||
|
||||
@ -629,13 +633,17 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
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)
|
||||
|
||||
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)
|
||||
self._vmops._vmutils.destroy_vm.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_disconnect_volumes.assert_called_once_with(
|
||||
@ -1080,3 +1088,96 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.FAKE_DEST_PATH),
|
||||
mock.call(mock.sentinel.FAKE_DVD_PATH2,
|
||||
mock.sentinel.FAKE_DEST_PATH))
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_get_vm_state')
|
||||
def test_check_hotplug_available_vm_disabled(self, mock_get_vm_state):
|
||||
fake_vm = fake_instance.fake_instance_obj(self.context)
|
||||
mock_get_vm_state.return_value = constants.HYPERV_VM_STATE_DISABLED
|
||||
|
||||
result = self._vmops._check_hotplug_available(fake_vm)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_get_vm_state.assert_called_once_with(fake_vm.name)
|
||||
self.assertFalse(
|
||||
self._vmops._hostutils.check_min_windows_version.called)
|
||||
self.assertFalse(self._vmops._vmutils.get_vm_generation.called)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_get_vm_state')
|
||||
def _test_check_hotplug_available(
|
||||
self, mock_get_vm_state, expected_result=False,
|
||||
vm_gen=constants.VM_GEN_2, windows_version=_WIN_VERSION_10):
|
||||
|
||||
fake_vm = fake_instance.fake_instance_obj(self.context)
|
||||
mock_get_vm_state.return_value = constants.HYPERV_VM_STATE_ENABLED
|
||||
self._vmops._vmutils.get_vm_generation.return_value = vm_gen
|
||||
fake_check_win_vers = self._vmops._hostutils.check_min_windows_version
|
||||
fake_check_win_vers.return_value = (
|
||||
windows_version == self._WIN_VERSION_10)
|
||||
|
||||
result = self._vmops._check_hotplug_available(fake_vm)
|
||||
|
||||
self.assertEqual(expected_result, result)
|
||||
mock_get_vm_state.assert_called_once_with(fake_vm.name)
|
||||
fake_check_win_vers.assert_called_once_with(10, 0)
|
||||
|
||||
def test_check_if_hotplug_available(self):
|
||||
self._test_check_hotplug_available(expected_result=True)
|
||||
|
||||
def test_check_if_hotplug_available_gen1(self):
|
||||
self._test_check_hotplug_available(
|
||||
expected_result=False, vm_gen=constants.VM_GEN_1)
|
||||
|
||||
def test_check_if_hotplug_available_win_6_3(self):
|
||||
self._test_check_hotplug_available(
|
||||
expected_result=False, windows_version=self._WIN_VERSION_6_3)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_attach_interface(self, mock_check_hotplug_available):
|
||||
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)
|
||||
|
||||
mock_check_hotplug_available.assert_called_once_with(fake_vm)
|
||||
self._vmops._vif_driver.plug.assert_called_once_with(
|
||||
fake_vm, fake_vif)
|
||||
self._vmops._vmutils.create_nic.assert_called_once_with(
|
||||
fake_vm.name, fake_vif['id'], fake_vif['address'])
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_attach_interface_failed(self, mock_check_hotplug_available):
|
||||
mock_check_hotplug_available.return_value = False
|
||||
self.assertRaises(exception.InterfaceAttachFailed,
|
||||
self._vmops.attach_interface,
|
||||
mock.MagicMock(), mock.sentinel.fake_vif)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_detach_interface(self, mock_check_hotplug_available):
|
||||
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)
|
||||
|
||||
mock_check_hotplug_available.assert_called_once_with(fake_vm)
|
||||
self._vmops._vif_driver.unplug.assert_called_once_with(
|
||||
fake_vm, fake_vif)
|
||||
self._vmops._vmutils.destroy_nic.assert_called_once_with(
|
||||
fake_vm.name, fake_vif['id'])
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_detach_interface_failed(self, mock_check_hotplug_available):
|
||||
mock_check_hotplug_available.return_value = False
|
||||
self.assertRaises(exception.InterfaceDetachFailed,
|
||||
self._vmops.detach_interface,
|
||||
mock.MagicMock(), mock.sentinel.fake_vif)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_detach_interface_missing_instance(self, mock_check_hotplug):
|
||||
mock_check_hotplug.side_effect = exception.NotFound
|
||||
self.assertRaises(exception.InterfaceDetachFailed,
|
||||
self._vmops.detach_interface,
|
||||
mock.MagicMock(), mock.sentinel.fake_vif)
|
||||
|
@ -396,6 +396,17 @@ class VMUtilsTestCase(test.NoDBTestCase):
|
||||
|
||||
mock_add_virt_res.assert_called_with(mock_nic, self._FAKE_VM_PATH)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_nic_data_by_name')
|
||||
def test_destroy_nic(self, mock_get_nic_data_by_name):
|
||||
self._lookup_vm()
|
||||
fake_nic_data = mock_get_nic_data_by_name.return_value
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_remove_virt_resource') as mock_rem_virt_res:
|
||||
self._vmutils.destroy_nic(self._FAKE_VM_NAME,
|
||||
mock.sentinel.FAKE_NIC_NAME)
|
||||
mock_rem_virt_res.assert_called_once_with(fake_nic_data,
|
||||
self._FAKE_VM_PATH)
|
||||
|
||||
def test_set_vm_state(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vm.RequestStateChange.return_value = (
|
||||
@ -869,6 +880,10 @@ class VMUtilsTestCase(test.NoDBTestCase):
|
||||
|
||||
self.assertEqual(watcher.return_value, listener)
|
||||
|
||||
def test_get_vm_generation_gen1(self):
|
||||
ret = self._vmutils.get_vm_generation(mock.sentinel.FAKE_VM_NAME)
|
||||
self.assertEqual(constants.VM_GEN_1, ret)
|
||||
|
||||
def test_stop_vm_jobs(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
|
||||
|
@ -275,3 +275,20 @@ class VMUtilsV2TestCase(test_vmutils.VMUtilsTestCase):
|
||||
|
||||
ret_val = self._vmutils.get_vm_dvd_disk_paths(self._FAKE_VM_NAME)
|
||||
self.assertEqual(mock.sentinel.FAKE_DVD_PATH1, ret_val[0])
|
||||
|
||||
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_vm_setting_data')
|
||||
def _test_get_vm_generation(self, vm_gen, mock_get_vm_setting_data):
|
||||
self._lookup_vm()
|
||||
vm_gen_string = "Microsoft:Hyper-V:SubType:" + str(vm_gen)
|
||||
mock_vssd = mock.MagicMock(VirtualSystemSubType=vm_gen_string)
|
||||
mock_get_vm_setting_data.return_value = mock_vssd
|
||||
|
||||
ret = self._vmutils.get_vm_generation(mock.sentinel.FAKE_VM_NAME)
|
||||
|
||||
self.assertEqual(vm_gen, ret)
|
||||
|
||||
def test_get_vm_generation_gen1(self):
|
||||
self._test_get_vm_generation(constants.VM_GEN_1)
|
||||
|
||||
def test_get_vm_generation_gen2(self):
|
||||
self._test_get_vm_generation(constants.VM_GEN_2)
|
||||
|
@ -267,3 +267,9 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
|
||||
def get_console_output(self, context, instance):
|
||||
return self._vmops.get_console_output(instance)
|
||||
|
||||
def attach_interface(self, instance, image_meta, vif):
|
||||
return self._vmops.attach_interface(instance, vif)
|
||||
|
||||
def detach_interface(self, instance, vif):
|
||||
return self._vmops.detach_interface(instance, vif)
|
||||
|
@ -448,6 +448,10 @@ class VMOps(object):
|
||||
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._vmutils.destroy_vm(instance_name)
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
else:
|
||||
@ -717,3 +721,54 @@ class VMOps(object):
|
||||
vm_name, remote_server=dest_host)
|
||||
for path in dvd_disk_paths:
|
||||
self._pathutils.copyfile(path, dest_path)
|
||||
|
||||
def _check_hotplug_available(self, instance):
|
||||
"""Check whether attaching an interface is possible for the given
|
||||
instance.
|
||||
|
||||
:returns: True if attaching / detaching interfaces is possible for the
|
||||
given instance.
|
||||
"""
|
||||
vm_state = self._get_vm_state(instance.name)
|
||||
if vm_state == constants.HYPERV_VM_STATE_DISABLED:
|
||||
# can attach / detach interface to stopped VMs.
|
||||
return True
|
||||
|
||||
if not self._hostutils.check_min_windows_version(10, 0):
|
||||
# TODO(claudiub): add set log level to error after string freeze.
|
||||
LOG.debug("vNIC hot plugging is supported only in newer "
|
||||
"versions than Windows Hyper-V / Server 2012 R2.")
|
||||
return False
|
||||
|
||||
if (self._vmutils.get_vm_generation(instance.name) ==
|
||||
constants.VM_GEN_1):
|
||||
# TODO(claudiub): add set log level to error after string freeze.
|
||||
LOG.debug("Cannot hot plug vNIC to a first generation VM.",
|
||||
instance=instance)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def attach_interface(self, instance, vif):
|
||||
if not self._check_hotplug_available(instance):
|
||||
raise exception.InterfaceAttachFailed(instance_uuid=instance.uuid)
|
||||
|
||||
LOG.debug('Attaching vif: %s', vif['id'], instance=instance)
|
||||
self._vmutils.create_nic(instance.name, vif['id'], vif['address'])
|
||||
self._vif_driver.plug(instance, vif)
|
||||
|
||||
def detach_interface(self, instance, vif):
|
||||
try:
|
||||
if not self._check_hotplug_available(instance):
|
||||
raise exception.InterfaceDetachFailed(
|
||||
instance_uuid=instance.uuid)
|
||||
|
||||
LOG.debug('Detaching vif: %s', vif['id'], instance=instance)
|
||||
self._vif_driver.unplug(instance, vif)
|
||||
self._vmutils.destroy_nic(instance.name, vif['id'])
|
||||
except exception.NotFound:
|
||||
# TODO(claudiub): add set log level to error after string freeze.
|
||||
LOG.debug("Instance not found during detach interface. It "
|
||||
"might have been destroyed beforehand.",
|
||||
instance=instance)
|
||||
raise exception.InterfaceDetachFailed(instance_uuid=instance.uuid)
|
||||
|
@ -494,6 +494,17 @@ class VMUtils(object):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
self._modify_virt_resource(nic_data, vm.path_())
|
||||
|
||||
def destroy_nic(self, vm_name, nic_name):
|
||||
"""Destroys the NIC with the given nic_name from the given VM.
|
||||
|
||||
:param vm_name: The name of the VM which has the NIC to be destroyed.
|
||||
:param nic_name: The NIC's ElementName.
|
||||
"""
|
||||
nic_data = self._get_nic_data_by_name(nic_name)
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
self._remove_virt_resource(nic_data, vm.path_())
|
||||
|
||||
def _get_nic_data_by_name(self, name):
|
||||
return self._conn.Msvm_SyntheticEthernetPortSettingData(
|
||||
ElementName=name)[0]
|
||||
@ -841,6 +852,9 @@ class VMUtils(object):
|
||||
return self._enabled_states_map.get(vm_enabled_state,
|
||||
constants.HYPERV_VM_STATE_OTHER)
|
||||
|
||||
def get_vm_generation(self, vm_name):
|
||||
return constants.VM_GEN_1
|
||||
|
||||
def stop_vm_jobs(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vm_jobs = vm.associators(wmi_result_class=self._CONCRETE_JOB_CLASS)
|
||||
|
@ -47,6 +47,7 @@ class VMUtilsV2(vmutils.VMUtils):
|
||||
_SCSI_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller'
|
||||
_SERIAL_PORT_RES_SUB_TYPE = 'Microsoft:Hyper-V:Serial Port'
|
||||
|
||||
_VIRTUAL_SYSTEM_SUBTYPE = 'VirtualSystemSubType'
|
||||
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
|
||||
_VIRTUAL_SYSTEM_SUBTYPE_GEN2 = 'Microsoft:Hyper-V:SubType:2'
|
||||
|
||||
@ -335,3 +336,11 @@ class VMUtilsV2(vmutils.VMUtils):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vmsettings = self._get_vm_setting_data(vm)
|
||||
return [note for note in vmsettings.Notes if note]
|
||||
|
||||
def get_vm_generation(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vssd = self._get_vm_setting_data(vm)
|
||||
if hasattr(vssd, self._VIRTUAL_SYSTEM_SUBTYPE):
|
||||
# expected format: 'Microsoft:Hyper-V:SubType:2'
|
||||
return int(vssd.VirtualSystemSubType.split(':')[-1])
|
||||
return constants.VM_GEN_1
|
||||
|
Loading…
Reference in New Issue
Block a user