Merge "Hyper-V: Implements attach_interface and detach_interface method"

This commit is contained in:
Jenkins 2015-09-22 02:22:14 +00:00 committed by Gerrit Code Review
commit ef265dce22
7 changed files with 217 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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