Added support for new block device format in vmops
Modified vmops methods to make use of the new block device format. This new format allows for more flexibility when using block device, such as the ability to select the bus for the attachment and the boot index. Co-Authored-By: Claudiu Belu <cbelu@cloudbasesolutions.com> Partially implements: blueprint hyper-v-block-device-mapping-support Change-Id: Ib6ff12b2710143798c1c29376edd9c1364d451b9
This commit is contained in:
parent
e1b8a1e4bf
commit
76c6359ed4
@ -50,14 +50,7 @@ class BlockDeviceManagerTestCase(test_base.HyperVBaseTestCase):
|
||||
self.assertEqual(slot_map[constants.CTRL_TYPE_IDE][1],
|
||||
os_win_const.IDE_CONTROLLER_SLOTS_NUMBER - 1)
|
||||
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
def test_init_controller_slot_counter_gen2(self, mock_cfg_drive_req):
|
||||
slot_map = self._bdman._initialize_controller_slot_counter(
|
||||
mock.sentinel.FAKE_INSTANCE, constants.VM_GEN_2)
|
||||
|
||||
self.assertEqual(slot_map[constants.CTRL_TYPE_SCSI][0],
|
||||
os_win_const.SCSI_CONTROLLER_SLOTS_NUMBER - 1)
|
||||
|
||||
@mock.patch.object(block_device_manager.configdrive, 'required_by')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_initialize_controller_slot_counter')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
@ -66,28 +59,44 @@ class BlockDeviceManagerTestCase(test_base.HyperVBaseTestCase):
|
||||
'_check_and_update_ephemerals')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_check_and_update_volumes')
|
||||
def test_validate_and_update_bdi(self, mock_check_and_update_vol,
|
||||
mock_check_and_update_eph,
|
||||
mock_check_and_update_root,
|
||||
mock_init_ctrl_cntr):
|
||||
mock_init_ctrl_cntr.return_value = mock.sentinel.FAKE_SLOT_MAP
|
||||
def _check_validate_and_update_bdi(self, mock_check_and_update_vol,
|
||||
mock_check_and_update_eph,
|
||||
mock_check_and_update_root,
|
||||
mock_init_ctrl_cntr,
|
||||
mock_required_by, available_slots=1):
|
||||
mock_required_by.return_value = True
|
||||
slot_map = {constants.CTRL_TYPE_SCSI: [available_slots]}
|
||||
mock_init_ctrl_cntr.return_value = slot_map
|
||||
|
||||
self._bdman.validate_and_update_bdi(mock.sentinel.FAKE_INSTANCE,
|
||||
mock.sentinel.IMAGE_META,
|
||||
mock.sentinel.VM_GEN,
|
||||
mock.sentinel.BLOCK_DEV_INFO)
|
||||
if available_slots:
|
||||
self._bdman.validate_and_update_bdi(mock.sentinel.FAKE_INSTANCE,
|
||||
mock.sentinel.IMAGE_META,
|
||||
constants.VM_GEN_2,
|
||||
mock.sentinel.BLOCK_DEV_INFO)
|
||||
else:
|
||||
self.assertRaises(exception.InvalidBDMFormat,
|
||||
self._bdman.validate_and_update_bdi,
|
||||
mock.sentinel.FAKE_INSTANCE,
|
||||
mock.sentinel.IMAGE_META,
|
||||
constants.VM_GEN_2,
|
||||
mock.sentinel.BLOCK_DEV_INFO)
|
||||
|
||||
mock_init_ctrl_cntr.assert_called_once_with(
|
||||
mock.sentinel.FAKE_INSTANCE, mock.sentinel.VM_GEN)
|
||||
mock.sentinel.FAKE_INSTANCE, constants.VM_GEN_2)
|
||||
mock_check_and_update_root.assert_called_once_with(
|
||||
mock.sentinel.VM_GEN, mock.sentinel.IMAGE_META,
|
||||
mock.sentinel.BLOCK_DEV_INFO, mock.sentinel.FAKE_SLOT_MAP)
|
||||
constants.VM_GEN_2, mock.sentinel.IMAGE_META,
|
||||
mock.sentinel.BLOCK_DEV_INFO, slot_map)
|
||||
mock_check_and_update_eph.assert_called_once_with(
|
||||
mock.sentinel.VM_GEN, mock.sentinel.BLOCK_DEV_INFO,
|
||||
mock.sentinel.FAKE_SLOT_MAP)
|
||||
constants.VM_GEN_2, mock.sentinel.BLOCK_DEV_INFO, slot_map)
|
||||
mock_check_and_update_vol.assert_called_once_with(
|
||||
mock.sentinel.VM_GEN, mock.sentinel.BLOCK_DEV_INFO,
|
||||
mock.sentinel.FAKE_SLOT_MAP)
|
||||
constants.VM_GEN_2, mock.sentinel.BLOCK_DEV_INFO, slot_map)
|
||||
mock_required_by.assert_called_once_with(mock.sentinel.FAKE_INSTANCE)
|
||||
|
||||
def test_validate_and_update_bdi(self):
|
||||
self._check_validate_and_update_bdi()
|
||||
|
||||
def test_validate_and_update_bdi_insufficient_slots(self):
|
||||
self._check_validate_and_update_bdi(available_slots=0)
|
||||
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_get_available_controller_slot')
|
||||
|
@ -34,6 +34,7 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._livemigrops = livemigrationops.LiveMigrationOps()
|
||||
self._livemigrops._livemigrutils = mock.MagicMock()
|
||||
self._livemigrops._pathutils = mock.MagicMock()
|
||||
self._livemigrops._block_dev_man = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(serialconsoleops.SerialConsoleOps,
|
||||
'stop_console_handler')
|
||||
@ -78,22 +79,21 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._test_live_migration(side_effect=os_win_exc.HyperVException)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps.get_disk_path_mapping')
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps'
|
||||
'.ebs_root_in_block_devices')
|
||||
@mock.patch('nova.virt.hyperv.imagecache.ImageCache.get_cached_image')
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps'
|
||||
'.initialize_volumes_connection')
|
||||
def _test_pre_live_migration(self, mock_initialize_connection,
|
||||
mock_get_cached_image,
|
||||
mock_ebs_root_in_block_devices,
|
||||
mock_get_disk_path_mapping,
|
||||
phys_disks_attached=True):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.image_ref = "fake_image_ref"
|
||||
mock_ebs_root_in_block_devices.return_value = None
|
||||
mock_get_disk_path_mapping.return_value = (
|
||||
mock.sentinel.disk_path_mapping if phys_disks_attached
|
||||
else None)
|
||||
bdman = self._livemigrops._block_dev_man
|
||||
mock_is_boot_from_vol = bdman.is_boot_from_volume
|
||||
mock_is_boot_from_vol.return_value = None
|
||||
CONF.set_override('use_cow_images', True)
|
||||
self._livemigrops.pre_live_migration(
|
||||
self.context, mock_instance,
|
||||
@ -103,7 +103,7 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
check_config = (
|
||||
self._livemigrops._livemigrutils.check_live_migration_config)
|
||||
check_config.assert_called_once_with()
|
||||
mock_ebs_root_in_block_devices.assert_called_once_with(
|
||||
mock_is_boot_from_vol.assert_called_once_with(
|
||||
mock.sentinel.BLOCK_INFO)
|
||||
mock_get_cached_image.assert_called_once_with(self.context,
|
||||
mock_instance)
|
||||
|
@ -23,6 +23,7 @@ from nova import objects
|
||||
from nova import test
|
||||
from nova.tests.unit import fake_instance
|
||||
from nova.tests.unit.virt.hyperv import test_base
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import migrationops
|
||||
|
||||
|
||||
@ -46,6 +47,7 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._migrationops._pathutils = mock.MagicMock()
|
||||
self._migrationops._volumeops = mock.MagicMock()
|
||||
self._migrationops._imagecache = mock.MagicMock()
|
||||
self._migrationops._block_dev_man = mock.MagicMock()
|
||||
|
||||
def _check_migrate_disk_files(self, host):
|
||||
instance_path = 'fake/instance/path'
|
||||
@ -223,56 +225,61 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch.object(migrationops.MigrationOps,
|
||||
'_check_and_attach_config_drive')
|
||||
@mock.patch.object(migrationops.MigrationOps, '_revert_migration_files')
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_ephemeral_disks')
|
||||
@mock.patch.object(objects.ImageMeta, "from_instance")
|
||||
def _check_finish_revert_migration(self, mock_image,
|
||||
mock_check_eph_disks,
|
||||
mock_revert_migration_files,
|
||||
mock_check_attach_config_drive,
|
||||
boot_from_volume=False):
|
||||
disk_type=constants.DISK):
|
||||
mock_image.return_value = objects.ImageMeta.from_dict({})
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_ebs_root_in_block_devices = (
|
||||
self._migrationops._volumeops.ebs_root_in_block_devices)
|
||||
mock_ebs_root_in_block_devices.return_value = boot_from_volume
|
||||
lookup_ephemeral = (
|
||||
self._migrationops._pathutils.lookup_ephemeral_vhd_path)
|
||||
root_device = {'type': disk_type}
|
||||
block_device_info = {'root_disk': root_device, 'ephemerals': []}
|
||||
|
||||
self._migrationops.finish_revert_migration(
|
||||
context=self.context, instance=mock_instance,
|
||||
network_info=mock.sentinel.network_info,
|
||||
block_device_info=mock.sentinel.block_device,
|
||||
block_device_info=block_device_info,
|
||||
power_on=True)
|
||||
|
||||
mock_revert_migration_files.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_ebs_root_in_block_devices.assert_called_once_with(
|
||||
mock.sentinel.block_device)
|
||||
if not boot_from_volume:
|
||||
if root_device['type'] == constants.DISK:
|
||||
lookup_root_vhd = (
|
||||
self._migrationops._pathutils.lookup_root_vhd_path)
|
||||
lookup_root_vhd.assert_called_once_with(mock_instance.name)
|
||||
fake_root_path = lookup_root_vhd.return_value
|
||||
else:
|
||||
fake_root_path = None
|
||||
self.assertEqual(lookup_root_vhd.return_value,
|
||||
root_device['path'])
|
||||
|
||||
lookup_ephemeral.assert_called_with(mock_instance.name)
|
||||
get_image_vm_gen = self._migrationops._vmops.get_image_vm_generation
|
||||
get_image_vm_gen.assert_called_once_with(
|
||||
mock_instance.uuid, fake_root_path,
|
||||
test.MatchType(objects.ImageMeta))
|
||||
mock_instance.uuid, test.MatchType(objects.ImageMeta))
|
||||
self._migrationops._vmops.create_instance.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.network_info,
|
||||
mock.sentinel.block_device, fake_root_path,
|
||||
lookup_ephemeral.return_value, get_image_vm_gen.return_value)
|
||||
mock_instance, mock.sentinel.network_info, root_device,
|
||||
block_device_info, get_image_vm_gen.return_value)
|
||||
mock_check_attach_config_drive.assert_called_once_with(
|
||||
mock_instance, get_image_vm_gen.return_value)
|
||||
self._migrationops._vmops.power_on.assert_called_once_with(
|
||||
mock_instance)
|
||||
|
||||
def test_finish_revert_migration_boot_from_volume(self):
|
||||
self._check_finish_revert_migration(boot_from_volume=True)
|
||||
self._check_finish_revert_migration(disk_type=constants.VOLUME)
|
||||
|
||||
def test_finish_revert_migration_not_in_block_device(self):
|
||||
self._check_finish_revert_migration()
|
||||
def test_finish_revert_migration_boot_from_disk(self):
|
||||
self._check_finish_revert_migration(disk_type=constants.DISK)
|
||||
|
||||
@mock.patch.object(objects.ImageMeta, "from_instance")
|
||||
def test_finish_revert_migration_no_root_vhd(self, mock_image):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
self._migrationops._pathutils.lookup_root_vhd_path.return_value = None
|
||||
bdi = {'root_disk': {'type': constants.DISK},
|
||||
'ephemerals': []}
|
||||
|
||||
self.assertRaises(
|
||||
exception.DiskNotFound,
|
||||
self._migrationops.finish_revert_migration, self.context,
|
||||
mock_instance, mock.sentinel.network_info, bdi, True)
|
||||
|
||||
def test_merge_base_vhd(self):
|
||||
fake_diff_vhd_path = 'fake/diff/path'
|
||||
@ -362,24 +369,21 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
'_check_and_attach_config_drive')
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_base_disk')
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_resize_vhd')
|
||||
def _check_finish_migration(self, mock_check_resize_vhd,
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_ephemeral_disks')
|
||||
def _check_finish_migration(self, mock_check_eph_disks,
|
||||
mock_check_resize_vhd,
|
||||
mock_check_base_disk,
|
||||
mock_check_attach_config_drive,
|
||||
ephemeral_path=None,
|
||||
boot_from_volume=False):
|
||||
disk_type=constants.DISK):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.ephemeral_gb = 1
|
||||
mock_vhd_info = mock.MagicMock()
|
||||
mock_eph_info = mock.MagicMock()
|
||||
root_device = {'type': disk_type}
|
||||
block_device_info = {'root_disk': root_device, 'ephemerals': []}
|
||||
|
||||
lookup_root_vhd = self._migrationops._pathutils.lookup_root_vhd_path
|
||||
side_effect = [mock_eph_info] if boot_from_volume else [mock_vhd_info,
|
||||
mock_eph_info]
|
||||
mock_ebs_root_in_block_devices = (
|
||||
self._migrationops._volumeops.ebs_root_in_block_devices)
|
||||
mock_ebs_root_in_block_devices.return_value = boot_from_volume
|
||||
self._migrationops._vhdutils.get_vhd_info.side_effect = side_effect
|
||||
look_up_ephem = self._migrationops._pathutils.lookup_ephemeral_vhd_path
|
||||
look_up_ephem.return_value = ephemeral_path
|
||||
get_vhd_info = self._migrationops._vhdutils.get_vhd_info
|
||||
mock_vhd_info = get_vhd_info.return_value
|
||||
|
||||
expected_check_resize = []
|
||||
expected_get_info = []
|
||||
|
||||
@ -387,71 +391,131 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
context=self.context, migration=mock.sentinel.migration,
|
||||
instance=mock_instance, disk_info=mock.sentinel.disk_info,
|
||||
network_info=mock.sentinel.network_info,
|
||||
image_meta=mock.sentinel.image_meta, resize_instance=True)
|
||||
image_meta=mock.sentinel.image_meta, resize_instance=True,
|
||||
block_device_info=block_device_info)
|
||||
|
||||
mock_ebs_root_in_block_devices.assert_called_once_with(None)
|
||||
if not boot_from_volume:
|
||||
root_vhd_path = lookup_root_vhd.return_value
|
||||
if root_device['type'] == constants.DISK:
|
||||
root_device_path = lookup_root_vhd.return_value
|
||||
lookup_root_vhd.assert_called_with(mock_instance.name)
|
||||
expected_get_info = [mock.call(root_vhd_path)]
|
||||
expected_get_info = [mock.call(root_device_path)]
|
||||
mock_vhd_info.get.assert_called_once_with("ParentPath")
|
||||
mock_check_base_disk.assert_called_once_with(
|
||||
self.context, mock_instance, root_vhd_path,
|
||||
self.context, mock_instance, root_device_path,
|
||||
mock_vhd_info.get.return_value)
|
||||
expected_check_resize.append(
|
||||
mock.call(root_vhd_path, mock_vhd_info,
|
||||
mock.call(root_device_path, mock_vhd_info,
|
||||
mock_instance.root_gb * units.Gi))
|
||||
else:
|
||||
root_vhd_path = None
|
||||
|
||||
look_up_ephem.assert_called_once_with(mock_instance.name)
|
||||
if ephemeral_path is None:
|
||||
create_eph_vhd = self._migrationops._vmops.create_ephemeral_vhd
|
||||
create_eph_vhd.assert_called_once_with(mock_instance)
|
||||
ephemeral_path = create_eph_vhd.return_value
|
||||
else:
|
||||
expected_get_info.append(mock.call(ephemeral_path))
|
||||
expected_check_resize.append(
|
||||
mock.call(ephemeral_path, mock_eph_info,
|
||||
mock_instance.ephemeral_gb * units.Gi))
|
||||
ephemerals = block_device_info['ephemerals']
|
||||
mock_check_eph_disks.assert_called_once_with(
|
||||
mock_instance, ephemerals, True)
|
||||
|
||||
mock_check_resize_vhd.assert_has_calls(expected_check_resize)
|
||||
self._migrationops._vhdutils.get_vhd_info.assert_has_calls(
|
||||
expected_get_info)
|
||||
get_image_vm_gen = self._migrationops._vmops.get_image_vm_generation
|
||||
get_image_vm_gen.assert_called_once_with(mock_instance.uuid,
|
||||
root_vhd_path,
|
||||
mock.sentinel.image_meta)
|
||||
self._migrationops._vmops.create_instance.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.network_info, None, root_vhd_path,
|
||||
ephemeral_path, get_image_vm_gen.return_value)
|
||||
mock_instance, mock.sentinel.network_info, root_device,
|
||||
block_device_info, get_image_vm_gen.return_value)
|
||||
mock_check_attach_config_drive.assert_called_once_with(
|
||||
mock_instance, get_image_vm_gen.return_value)
|
||||
self._migrationops._vmops.power_on.assert_called_once_with(
|
||||
mock_instance)
|
||||
|
||||
def test_finish_migration(self):
|
||||
self._check_finish_migration(
|
||||
ephemeral_path=mock.sentinel.ephemeral_path)
|
||||
self._check_finish_migration(disk_type=constants.DISK)
|
||||
|
||||
def test_finish_migration_boot_from_volume(self):
|
||||
self._check_finish_migration(
|
||||
ephemeral_path=mock.sentinel.ephemeral_path,
|
||||
boot_from_volume=True)
|
||||
|
||||
def test_finish_migration_no_ephemeral(self):
|
||||
self._check_finish_migration()
|
||||
self._check_finish_migration(disk_type=constants.VOLUME)
|
||||
|
||||
def test_finish_migration_no_root(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_ebs_root_in_block_devices = (
|
||||
self._migrationops._volumeops.ebs_root_in_block_devices)
|
||||
mock_ebs_root_in_block_devices.return_value = False
|
||||
self._migrationops._pathutils.lookup_root_vhd_path.return_value = None
|
||||
bdi = {'root_disk': {'type': constants.DISK},
|
||||
'ephemerals': []}
|
||||
|
||||
self.assertRaises(exception.DiskNotFound,
|
||||
self._migrationops.finish_migration,
|
||||
self.context, mock.sentinel.migration,
|
||||
mock_instance, mock.sentinel.disk_info,
|
||||
mock.sentinel.network_info,
|
||||
mock.sentinel.image_meta, True, None, True)
|
||||
mock.sentinel.image_meta, True, bdi, True)
|
||||
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_resize_vhd')
|
||||
@mock.patch.object(migrationops.LOG, 'warn')
|
||||
def test_check_ephemeral_disks_multiple_eph_warn(self, mock_warn,
|
||||
mock_check_resize_vhd):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.ephemeral_gb = 3
|
||||
mock_ephemerals = [{'size': 1}, {'size': 1}]
|
||||
|
||||
self._migrationops._check_ephemeral_disks(mock_instance,
|
||||
mock_ephemerals,
|
||||
True)
|
||||
|
||||
mock_warn.assert_called_once_with(
|
||||
"Cannot resize multiple ephemeral disks for instance.",
|
||||
instance=mock_instance)
|
||||
|
||||
def test_check_ephemeral_disks_exception(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_ephemerals = [dict()]
|
||||
|
||||
lookup_eph_path = (
|
||||
self._migrationops._pathutils.lookup_ephemeral_vhd_path)
|
||||
lookup_eph_path.return_value = None
|
||||
|
||||
self.assertRaises(exception.DiskNotFound,
|
||||
self._migrationops._check_ephemeral_disks,
|
||||
mock_instance, mock_ephemerals)
|
||||
|
||||
@mock.patch.object(migrationops.MigrationOps, '_check_resize_vhd')
|
||||
def _test_check_ephemeral_disks(self, mock_check_resize_vhd,
|
||||
existing_eph_path=None, new_eph_size=42):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.ephemeral_gb = new_eph_size
|
||||
eph = {}
|
||||
mock_ephemerals = [eph]
|
||||
|
||||
mock_pathutils = self._migrationops._pathutils
|
||||
lookup_eph_path = mock_pathutils.lookup_ephemeral_vhd_path
|
||||
lookup_eph_path.return_value = existing_eph_path
|
||||
mock_get_eph_vhd_path = mock_pathutils.get_ephemeral_vhd_path
|
||||
mock_get_eph_vhd_path.return_value = mock.sentinel.get_path
|
||||
|
||||
mock_vhdutils = self._migrationops._vhdutils
|
||||
mock_get_vhd_format = mock_vhdutils.get_best_supported_vhd_format
|
||||
mock_get_vhd_format.return_value = mock.sentinel.vhd_format
|
||||
|
||||
self._migrationops._check_ephemeral_disks(mock_instance,
|
||||
mock_ephemerals,
|
||||
True)
|
||||
|
||||
self.assertEqual(mock_instance.ephemeral_gb, eph['size'])
|
||||
if not existing_eph_path:
|
||||
mock_vmops = self._migrationops._vmops
|
||||
mock_vmops.create_ephemeral_disk.assert_called_once_with(
|
||||
mock_instance.name, eph)
|
||||
self.assertEqual(mock.sentinel.vhd_format, eph['format'])
|
||||
self.assertEqual(mock.sentinel.get_path, eph['path'])
|
||||
elif new_eph_size:
|
||||
mock_check_resize_vhd.assert_called_once_with(
|
||||
existing_eph_path,
|
||||
self._migrationops._vhdutils.get_vhd_info.return_value,
|
||||
mock_instance.ephemeral_gb * units.Gi)
|
||||
self.assertEqual(existing_eph_path, eph['path'])
|
||||
else:
|
||||
self._migrationops._pathutils.remove.assert_called_once_with(
|
||||
existing_eph_path)
|
||||
|
||||
def test_check_ephemeral_disks_create(self):
|
||||
self._test_check_ephemeral_disks()
|
||||
|
||||
def test_check_ephemeral_disks_resize(self):
|
||||
self._test_check_ephemeral_disks(existing_eph_path=mock.sentinel.path)
|
||||
|
||||
def test_check_ephemeral_disks_remove(self):
|
||||
self._test_check_ephemeral_disks(existing_eph_path=mock.sentinel.path,
|
||||
new_eph_size=0)
|
||||
|
@ -67,6 +67,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._vmops._pathutils = mock.MagicMock()
|
||||
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')
|
||||
@ -151,7 +152,24 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
def test_get_info_exception(self):
|
||||
self._test_get_info(vm_exists=False)
|
||||
|
||||
def _prepare_create_root_vhd_mocks(self, use_cow_images, vhd_format,
|
||||
@mock.patch.object(vmops.VMOps, 'check_vm_image_type')
|
||||
@mock.patch.object(vmops.VMOps, '_create_root_vhd')
|
||||
def test_create_root_device_type_disk(self, mock_create_root_device,
|
||||
mock_check_vm_image_type):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_root_disk_info = {'type': constants.DISK}
|
||||
|
||||
self._vmops._create_root_device(self.context, mock_instance,
|
||||
mock_root_disk_info,
|
||||
mock.sentinel.VM_GEN_1)
|
||||
|
||||
mock_create_root_device.assert_called_once_with(
|
||||
self.context, mock_instance)
|
||||
mock_check_vm_image_type.assert_called_once_with(
|
||||
mock_instance.uuid, mock.sentinel.VM_GEN_1,
|
||||
mock_create_root_device.return_value)
|
||||
|
||||
def _prepare_create_root_device_mocks(self, use_cow_images, vhd_format,
|
||||
vhd_size):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.root_gb = self.FAKE_SIZE
|
||||
@ -169,7 +187,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch('nova.virt.hyperv.imagecache.ImageCache.get_cached_image')
|
||||
def _test_create_root_vhd_exception(self, mock_get_cached_image,
|
||||
vhd_format):
|
||||
mock_instance = self._prepare_create_root_vhd_mocks(
|
||||
mock_instance = self._prepare_create_root_device_mocks(
|
||||
use_cow_images=False, vhd_format=vhd_format,
|
||||
vhd_size=(self.FAKE_SIZE + 1))
|
||||
fake_vhd_path = self.FAKE_ROOT_PATH % vhd_format
|
||||
@ -188,7 +206,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
@mock.patch('nova.virt.hyperv.imagecache.ImageCache.get_cached_image')
|
||||
def _test_create_root_vhd_qcow(self, mock_get_cached_image, vhd_format):
|
||||
mock_instance = self._prepare_create_root_vhd_mocks(
|
||||
mock_instance = self._prepare_create_root_device_mocks(
|
||||
use_cow_images=True, vhd_format=vhd_format,
|
||||
vhd_size=(self.FAKE_SIZE - 1))
|
||||
fake_vhd_path = self.FAKE_ROOT_PATH % vhd_format
|
||||
@ -221,7 +239,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch('nova.virt.hyperv.imagecache.ImageCache.get_cached_image')
|
||||
def _test_create_root_vhd(self, mock_get_cached_image, vhd_format,
|
||||
is_rescue_vhd=False):
|
||||
mock_instance = self._prepare_create_root_vhd_mocks(
|
||||
mock_instance = self._prepare_create_root_device_mocks(
|
||||
use_cow_images=False, vhd_format=vhd_format,
|
||||
vhd_size=(self.FAKE_SIZE - 1))
|
||||
fake_vhd_path = self.FAKE_ROOT_PATH % vhd_format
|
||||
@ -292,21 +310,36 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self.assertFalse(self._vmops._is_resize_needed(
|
||||
mock.sentinel.FAKE_PATH, self.FAKE_SIZE, self.FAKE_SIZE, inst))
|
||||
|
||||
def test_create_ephemeral_vhd(self):
|
||||
@mock.patch.object(vmops.VMOps, 'create_ephemeral_disk')
|
||||
def test_create_ephemerals(self, mock_create_ephemeral_disk):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_instance.ephemeral_gb = self.FAKE_SIZE
|
||||
best_supported = self._vmops._vhdutils.get_best_supported_vhd_format
|
||||
best_supported.return_value = mock.sentinel.FAKE_FORMAT
|
||||
self._vmops._pathutils.get_ephemeral_vhd_path.return_value = (
|
||||
mock.sentinel.FAKE_PATH)
|
||||
|
||||
response = self._vmops.create_ephemeral_vhd(instance=mock_instance)
|
||||
fake_ephemerals = [dict(), dict()]
|
||||
self._vmops._vhdutils.get_best_supported_vhd_format.return_value = (
|
||||
mock.sentinel.format)
|
||||
self._vmops._pathutils.get_ephemeral_vhd_path.side_effect = [
|
||||
mock.sentinel.FAKE_PATH0, mock.sentinel.FAKE_PATH1]
|
||||
|
||||
self._vmops._pathutils.get_ephemeral_vhd_path.assert_called_with(
|
||||
mock_instance.name, mock.sentinel.FAKE_FORMAT)
|
||||
self._vmops._vhdutils.create_dynamic_vhd.assert_called_with(
|
||||
mock.sentinel.FAKE_PATH, mock_instance.ephemeral_gb * units.Gi)
|
||||
self.assertEqual(mock.sentinel.FAKE_PATH, response)
|
||||
self._vmops._create_ephemerals(mock_instance, fake_ephemerals)
|
||||
|
||||
self._vmops._pathutils.get_ephemeral_vhd_path.assert_has_calls(
|
||||
[mock.call(mock_instance.name, mock.sentinel.format, 'eph0'),
|
||||
mock.call(mock_instance.name, mock.sentinel.format, 'eph1')])
|
||||
mock_create_ephemeral_disk.assert_has_calls(
|
||||
[mock.call(mock_instance.name, fake_ephemerals[0]),
|
||||
mock.call(mock_instance.name, fake_ephemerals[1])])
|
||||
|
||||
def test_create_ephemeral_disk(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_ephemeral_info = {'path': 'fake_eph_path',
|
||||
'size': 10}
|
||||
|
||||
self._vmops.create_ephemeral_disk(mock_instance.name,
|
||||
mock_ephemeral_info)
|
||||
|
||||
mock_create_dynamic_vhd = self._vmops._vhdutils.create_dynamic_vhd
|
||||
mock_create_dynamic_vhd.assert_called_once_with('fake_eph_path',
|
||||
10 * units.Gi)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.destroy')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_on')
|
||||
@ -315,62 +348,59 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.create_instance')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.get_image_vm_generation')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.create_ephemeral_vhd')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._create_root_vhd')
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps.'
|
||||
'ebs_root_in_block_devices')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._create_ephemerals')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._create_root_device')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._delete_disk_files')
|
||||
def _test_spawn(self, mock_delete_disk_files,
|
||||
mock_ebs_root_in_block_devices, mock_create_root_vhd,
|
||||
mock_create_ephemeral_vhd, mock_get_image_vm_gen,
|
||||
def _test_spawn(self, mock_delete_disk_files, mock_create_root_device,
|
||||
mock_create_ephemerals, mock_get_image_vm_gen,
|
||||
mock_create_instance, mock_configdrive_required,
|
||||
mock_create_config_drive, mock_attach_config_drive,
|
||||
mock_power_on, mock_destroy, exists, boot_from_volume,
|
||||
mock_power_on, mock_destroy, exists,
|
||||
configdrive_required, fail):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_image_meta = mock.MagicMock()
|
||||
|
||||
fake_root_path = mock_create_root_vhd.return_value
|
||||
fake_root_path = None if boot_from_volume else fake_root_path
|
||||
fake_ephemeral_path = mock_create_ephemeral_vhd.return_value
|
||||
root_device_info = mock.sentinel.ROOT_DEV_INFO
|
||||
fake_vm_gen = mock_get_image_vm_gen.return_value
|
||||
fake_config_drive_path = mock_create_config_drive.return_value
|
||||
block_device_info = {'ephemerals': [], 'root_disk': root_device_info}
|
||||
|
||||
self._vmops._vmutils.vm_exists.return_value = exists
|
||||
mock_ebs_root_in_block_devices.return_value = boot_from_volume
|
||||
mock_create_root_vhd.return_value = fake_root_path
|
||||
mock_configdrive_required.return_value = configdrive_required
|
||||
mock_create_instance.side_effect = fail
|
||||
if exists:
|
||||
self.assertRaises(exception.InstanceExists, self._vmops.spawn,
|
||||
self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, mock.sentinel.DEV_INFO)
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
elif fail is os_win_exc.HyperVException:
|
||||
self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn,
|
||||
self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, mock.sentinel.DEV_INFO)
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
mock_destroy.assert_called_once_with(mock_instance)
|
||||
else:
|
||||
self._vmops.spawn(self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, mock.sentinel.DEV_INFO)
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
self._vmops._vmutils.vm_exists.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_delete_disk_files.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_ebs_root_in_block_devices.assert_called_once_with(
|
||||
mock.sentinel.DEV_INFO)
|
||||
if not boot_from_volume:
|
||||
mock_create_root_vhd.assert_called_once_with(self.context,
|
||||
mock_instance)
|
||||
mock_create_ephemeral_vhd.assert_called_once_with(mock_instance)
|
||||
mock_get_image_vm_gen.assert_called_once_with(
|
||||
mock_instance.uuid, fake_root_path, mock_image_meta)
|
||||
mock_validate_and_update_bdi = (
|
||||
self._vmops._block_dev_man.validate_and_update_bdi)
|
||||
mock_validate_and_update_bdi.assert_called_once_with(
|
||||
mock_instance, mock_image_meta, fake_vm_gen, block_device_info)
|
||||
mock_create_root_device.assert_called_once_with(self.context,
|
||||
mock_instance,
|
||||
root_device_info,
|
||||
fake_vm_gen)
|
||||
mock_create_ephemerals.assert_called_once_with(
|
||||
mock_instance, block_device_info['ephemerals'])
|
||||
mock_get_image_vm_gen.assert_called_once_with(mock_instance.uuid,
|
||||
mock_image_meta)
|
||||
mock_create_instance.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.INFO, mock.sentinel.DEV_INFO,
|
||||
fake_root_path, fake_ephemeral_path, fake_vm_gen)
|
||||
mock_instance, mock.sentinel.INFO, root_device_info,
|
||||
block_device_info, fake_vm_gen)
|
||||
mock_configdrive_required.assert_called_once_with(mock_instance)
|
||||
if configdrive_required:
|
||||
mock_create_config_drive.assert_called_once_with(
|
||||
@ -382,25 +412,17 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_power_on.assert_called_once_with(mock_instance)
|
||||
|
||||
def test_spawn(self):
|
||||
self._test_spawn(exists=False, boot_from_volume=False,
|
||||
configdrive_required=True, fail=None)
|
||||
self._test_spawn(exists=False, configdrive_required=True, fail=None)
|
||||
|
||||
def test_spawn_instance_exists(self):
|
||||
self._test_spawn(exists=True, boot_from_volume=False,
|
||||
configdrive_required=True, fail=None)
|
||||
self._test_spawn(exists=True, configdrive_required=True, fail=None)
|
||||
|
||||
def test_spawn_create_instance_exception(self):
|
||||
self._test_spawn(exists=False, boot_from_volume=False,
|
||||
configdrive_required=True,
|
||||
self._test_spawn(exists=False, configdrive_required=True,
|
||||
fail=os_win_exc.HyperVException)
|
||||
|
||||
def test_spawn_not_required(self):
|
||||
self._test_spawn(exists=False, boot_from_volume=False,
|
||||
configdrive_required=False, fail=None)
|
||||
|
||||
def test_spawn_root_in_block(self):
|
||||
self._test_spawn(exists=False, boot_from_volume=True,
|
||||
configdrive_required=False, fail=None)
|
||||
self._test_spawn(exists=False, configdrive_required=False, fail=None)
|
||||
|
||||
def test_spawn_no_admin_permissions(self):
|
||||
self._vmops._vmutils.check_admin_permissions.side_effect = (
|
||||
@ -413,19 +435,23 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps'
|
||||
'.attach_volumes')
|
||||
@mock.patch.object(vmops.VMOps, '_attach_drive')
|
||||
@mock.patch.object(vmops.VMOps, '_create_vm_com_port_pipes')
|
||||
@mock.patch.object(vmops.VMOps, '_attach_ephemerals')
|
||||
@mock.patch.object(vmops.VMOps, '_attach_root_device')
|
||||
@mock.patch.object(vmops.VMOps, '_configure_remotefx')
|
||||
def _test_create_instance(self, mock_configure_remotefx,
|
||||
mock_create_pipes, mock_attach_drive,
|
||||
mock_attach_volumes, fake_root_path,
|
||||
fake_ephemeral_path,
|
||||
mock_attach_root_device,
|
||||
mock_attach_ephemerals,
|
||||
mock_create_pipes,
|
||||
mock_attach_volumes,
|
||||
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
|
||||
block_device_info = {'ephemerals': [], 'block_device_mapping': []}
|
||||
fake_network_info = {'id': mock.sentinel.ID,
|
||||
'address': mock.sentinel.ADDRESS}
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
@ -436,9 +462,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
self._vmops.create_instance(instance=mock_instance,
|
||||
network_info=[fake_network_info],
|
||||
block_device_info=mock.sentinel.DEV_INFO,
|
||||
root_vhd_path=fake_root_path,
|
||||
eph_vhd_path=fake_ephemeral_path,
|
||||
root_device=root_device_info,
|
||||
block_device_info=block_device_info,
|
||||
vm_gen=vm_gen)
|
||||
self._vmops._vmutils.create_vm.assert_called_once_with(
|
||||
mock_instance.name, mock_instance.memory_mb,
|
||||
@ -447,32 +472,15 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
[mock_instance.uuid])
|
||||
|
||||
mock_configure_remotefx.assert_called_once_with(mock_instance, vm_gen)
|
||||
expected = []
|
||||
ctrl_type = vmops.VM_GENERATIONS_CONTROLLER_TYPES[vm_gen]
|
||||
ctrl_disk_addr = 0
|
||||
if fake_root_path:
|
||||
expected.append(mock.call(mock_instance.name, fake_root_path,
|
||||
0, ctrl_disk_addr, ctrl_type,
|
||||
constants.DISK))
|
||||
ctrl_disk_addr = 1
|
||||
if fake_ephemeral_path:
|
||||
expected.append(mock.call(mock_instance.name,
|
||||
fake_ephemeral_path, 0, ctrl_disk_addr,
|
||||
ctrl_type, constants.DISK))
|
||||
mock_attach_drive.has_calls(expected)
|
||||
self._vmops._vmutils.create_scsi_controller.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_create_scsi_ctrl = self._vmops._vmutils.create_scsi_controller
|
||||
mock_create_scsi_ctrl.assert_called_once_with(mock_instance.name)
|
||||
|
||||
ebs_root = vm_gen is not constants.VM_GEN_2 and fake_root_path is None
|
||||
mock_attach_volumes.assert_called_once_with(mock.sentinel.DEV_INFO,
|
||||
mock_instance.name,
|
||||
ebs_root)
|
||||
|
||||
expected_port_settings = {
|
||||
constants.DEFAULT_SERIAL_CONSOLE_PORT:
|
||||
constants.SERIAL_PORT_TYPE_RW}
|
||||
mock_create_pipes.assert_called_once_with(
|
||||
mock_instance, expected_port_settings)
|
||||
mock_attach_root_device.assert_called_once_with(mock_instance.name,
|
||||
root_device_info)
|
||||
mock_attach_ephemerals.assert_called_once_with(mock_instance.name,
|
||||
block_device_info['ephemerals'])
|
||||
mock_attach_volumes.assert_called_once_with(
|
||||
block_device_info['block_device_mapping'], mock_instance.name)
|
||||
|
||||
self._vmops._vmutils.create_nic.assert_called_once_with(
|
||||
mock_instance.name, mock.sentinel.ID, mock.sentinel.ADDRESS)
|
||||
@ -483,39 +491,72 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_enable.assert_called_once_with(mock_instance.name)
|
||||
|
||||
def test_create_instance(self):
|
||||
fake_ephemeral_path = mock.sentinel.FAKE_EPHEMERAL_PATH
|
||||
self._test_create_instance(fake_root_path=mock.sentinel.FAKE_ROOT_PATH,
|
||||
fake_ephemeral_path=fake_ephemeral_path,
|
||||
enable_instance_metrics=True)
|
||||
|
||||
def test_create_instance_no_root_path(self):
|
||||
fake_ephemeral_path = mock.sentinel.FAKE_EPHEMERAL_PATH
|
||||
self._test_create_instance(fake_root_path=None,
|
||||
fake_ephemeral_path=fake_ephemeral_path,
|
||||
enable_instance_metrics=True)
|
||||
|
||||
def test_create_instance_no_ephemeral_path(self):
|
||||
self._test_create_instance(fake_root_path=mock.sentinel.FAKE_ROOT_PATH,
|
||||
fake_ephemeral_path=None,
|
||||
enable_instance_metrics=True)
|
||||
|
||||
def test_create_instance_no_path(self):
|
||||
self._test_create_instance(fake_root_path=None,
|
||||
fake_ephemeral_path=None,
|
||||
enable_instance_metrics=False)
|
||||
self._test_create_instance(enable_instance_metrics=True)
|
||||
|
||||
def test_create_instance_enable_instance_metrics_false(self):
|
||||
fake_ephemeral_path = mock.sentinel.FAKE_EPHEMERAL_PATH
|
||||
self._test_create_instance(fake_root_path=mock.sentinel.FAKE_ROOT_PATH,
|
||||
fake_ephemeral_path=fake_ephemeral_path,
|
||||
enable_instance_metrics=False)
|
||||
self._test_create_instance(enable_instance_metrics=False)
|
||||
|
||||
def test_create_instance_gen2(self):
|
||||
self._test_create_instance(fake_root_path=None,
|
||||
fake_ephemeral_path=None,
|
||||
enable_instance_metrics=False,
|
||||
self._test_create_instance(enable_instance_metrics=False,
|
||||
vm_gen=constants.VM_GEN_2)
|
||||
|
||||
@mock.patch.object(vmops.volumeops.VolumeOps, 'attach_volume')
|
||||
def test_attach_root_device_volume(self, mock_attach_volume):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
root_device_info = {'type': constants.VOLUME,
|
||||
'connection_info': mock.sentinel.CONN_INFO,
|
||||
'disk_bus': constants.CTRL_TYPE_IDE}
|
||||
|
||||
self._vmops._attach_root_device(mock_instance.name, root_device_info)
|
||||
|
||||
mock_attach_volume.assert_called_once_with(
|
||||
root_device_info['connection_info'], mock_instance.name,
|
||||
disk_bus=root_device_info['disk_bus'])
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_attach_drive')
|
||||
def test_attach_root_device_disk(self, mock_attach_drive):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
root_device_info = {'type': constants.DISK,
|
||||
'boot_index': 0,
|
||||
'disk_bus': constants.CTRL_TYPE_IDE,
|
||||
'path': 'fake_path',
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 1}
|
||||
|
||||
self._vmops._attach_root_device(mock_instance.name, root_device_info)
|
||||
|
||||
mock_attach_drive.assert_called_once_with(
|
||||
mock_instance.name, root_device_info['path'],
|
||||
root_device_info['drive_addr'], root_device_info['ctrl_disk_addr'],
|
||||
root_device_info['disk_bus'], root_device_info['type'])
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_attach_drive')
|
||||
def test_attach_ephemerals(self, mock_attach_drive):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
|
||||
ephemerals = [{'path': mock.sentinel.PATH1,
|
||||
'boot_index': 1,
|
||||
'disk_bus': constants.CTRL_TYPE_IDE,
|
||||
'device_type': 'disk',
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 1},
|
||||
{'path': mock.sentinel.PATH2,
|
||||
'boot_index': 2,
|
||||
'disk_bus': constants.CTRL_TYPE_SCSI,
|
||||
'device_type': 'disk',
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 0},
|
||||
{'path': None}]
|
||||
|
||||
self._vmops._attach_ephemerals(mock_instance.name, ephemerals)
|
||||
|
||||
mock_attach_drive.assert_has_calls(
|
||||
[mock.call(mock_instance.name, mock.sentinel.PATH1, 0,
|
||||
1, constants.CTRL_TYPE_IDE, constants.DISK),
|
||||
mock.call(mock_instance.name, mock.sentinel.PATH2, 0,
|
||||
0, constants.CTRL_TYPE_SCSI, constants.DISK)
|
||||
])
|
||||
|
||||
def test_attach_drive_vm_to_scsi(self):
|
||||
self._vmops._attach_drive(
|
||||
mock.sentinel.FAKE_VM_NAME, mock.sentinel.FAKE_PATH,
|
||||
@ -545,7 +586,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
constants.IMAGE_PROP_VM_GEN_1, constants.IMAGE_PROP_VM_GEN_2]
|
||||
|
||||
response = self._vmops.get_image_vm_generation(
|
||||
mock.sentinel.instance_id, mock.sentinel.FAKE_PATH, image_meta)
|
||||
mock.sentinel.instance_id, image_meta)
|
||||
|
||||
self.assertEqual(constants.VM_GEN_1, response)
|
||||
|
||||
@ -555,28 +596,20 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
{"hw_machine_type": constants.IMAGE_PROP_VM_GEN_2}})
|
||||
self._vmops._hostutils.get_supported_vm_types.return_value = [
|
||||
constants.IMAGE_PROP_VM_GEN_1, constants.IMAGE_PROP_VM_GEN_2]
|
||||
self._vmops._vhdutils.get_vhd_format.return_value = (
|
||||
constants.DISK_FORMAT_VHDX)
|
||||
|
||||
response = self._vmops.get_image_vm_generation(
|
||||
mock.sentinel.instance_id, mock.sentinel.FAKE_PATH, image_meta)
|
||||
mock.sentinel.instance_id, image_meta)
|
||||
|
||||
self.assertEqual(constants.VM_GEN_2, response)
|
||||
|
||||
def test_get_image_vm_generation_not_vhdx(self):
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{"properties":
|
||||
{'hw_machine_type': constants.IMAGE_PROP_VM_GEN_2}})
|
||||
self._vmops._hostutils.get_supported_vm_types.return_value = [
|
||||
constants.IMAGE_PROP_VM_GEN_1, constants.IMAGE_PROP_VM_GEN_2]
|
||||
def test_check_vm_image_type_exception(self):
|
||||
self._vmops._vhdutils.get_vhd_format.return_value = (
|
||||
constants.DISK_FORMAT_VHD)
|
||||
|
||||
self.assertRaises(exception.InstanceUnacceptable,
|
||||
self._vmops.get_image_vm_generation,
|
||||
mock.sentinel.instance_id,
|
||||
mock.sentinel.FAKE_PATH,
|
||||
image_meta)
|
||||
self._vmops.check_vm_image_type,
|
||||
mock.sentinel.instance_id, constants.VM_GEN_2,
|
||||
mock.sentinel.FAKE_PATH)
|
||||
|
||||
@mock.patch('nova.api.metadata.base.InstanceMetadata')
|
||||
@mock.patch('nova.virt.configdrive.ConfigDriveBuilder')
|
||||
@ -1244,8 +1277,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.rescue_password)
|
||||
|
||||
mock_get_image_vm_gen.assert_called_once_with(
|
||||
mock_instance.uuid, mock.sentinel.rescue_vhd_path,
|
||||
mock_image_meta)
|
||||
mock_instance.uuid, mock_image_meta)
|
||||
self._vmops._vmutils.detach_vm_disk.assert_called_once_with(
|
||||
mock_instance.name, mock.sentinel.root_vhd_path,
|
||||
is_physical=False)
|
||||
|
@ -24,6 +24,7 @@ from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit import fake_block_device
|
||||
from nova.tests.unit.virt.hyperv import test_base
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import volumeops
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -76,13 +77,13 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
def test_attach_volumes(self, mock_attach_volume):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
|
||||
self._volumeops.attach_volumes(block_device_info,
|
||||
mock.sentinel.instance_name,
|
||||
ebs_root=True)
|
||||
self._volumeops.attach_volumes(
|
||||
block_device_info['block_device_mapping'],
|
||||
mock.sentinel.instance_name)
|
||||
|
||||
mock_attach_volume.assert_called_once_with(
|
||||
block_device_info['block_device_mapping'][0]['connection_info'],
|
||||
mock.sentinel.instance_name, True)
|
||||
mock.sentinel.instance_name)
|
||||
|
||||
def test_fix_instance_volume_disk_paths_empty_bdm(self):
|
||||
self._volumeops.fix_instance_volume_disk_paths(
|
||||
@ -144,16 +145,6 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
fake_volume_driver.disconnect_volumes.assert_called_once_with(
|
||||
block_device_mapping)
|
||||
|
||||
@mock.patch('nova.block_device.volume_in_mapping')
|
||||
def test_ebs_root_in_block_devices(self, mock_vol_in_mapping):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
|
||||
response = self._volumeops.ebs_root_in_block_devices(block_device_info)
|
||||
|
||||
mock_vol_in_mapping.assert_called_once_with(
|
||||
self._volumeops._default_root_device, block_device_info)
|
||||
self.assertEqual(mock_vol_in_mapping.return_value, response)
|
||||
|
||||
def test_get_volume_connector(self):
|
||||
mock_instance = mock.DEFAULT
|
||||
initiator = self._volumeops._volutils.get_iscsi_initiator.return_value
|
||||
@ -324,7 +315,7 @@ class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
'_get_mounted_disk_from_lun')
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, 'login_storage_target')
|
||||
def _check_attach_volume(self, mock_login_storage_target,
|
||||
mock_get_mounted_disk_from_lun, ebs_root):
|
||||
mock_get_mounted_disk_from_lun, disk_bus):
|
||||
connection_info = get_fake_connection_info()
|
||||
|
||||
get_ide_path = self._volume_driver._vmutils.get_vm_ide_controller
|
||||
@ -340,14 +331,14 @@ class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
self._volume_driver.attach_volume(
|
||||
connection_info=connection_info,
|
||||
instance_name=mock.sentinel.instance_name,
|
||||
ebs_root=ebs_root)
|
||||
disk_bus=disk_bus)
|
||||
|
||||
mock_login_storage_target.assert_called_once_with(connection_info)
|
||||
mock_get_mounted_disk_from_lun.assert_called_once_with(
|
||||
mock.sentinel.fake_iqn,
|
||||
mock.sentinel.fake_lun,
|
||||
wait_for_device=True)
|
||||
if ebs_root:
|
||||
if disk_bus == constants.CTRL_TYPE_IDE:
|
||||
get_ide_path.assert_called_once_with(
|
||||
mock.sentinel.instance_name, 0)
|
||||
attach_vol.assert_called_once_with(mock.sentinel.instance_name,
|
||||
@ -362,11 +353,11 @@ class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
fake_mounted_disk_path,
|
||||
serial=mock.sentinel.serial)
|
||||
|
||||
def test_attach_volume_ebs(self):
|
||||
self._check_attach_volume(ebs_root=True)
|
||||
def test_attach_volume_ide(self):
|
||||
self._check_attach_volume(disk_bus=constants.CTRL_TYPE_IDE)
|
||||
|
||||
def test_attach_volume(self):
|
||||
self._check_attach_volume(ebs_root=False)
|
||||
def test_attach_volume_scsi(self):
|
||||
self._check_attach_volume(disk_bus=constants.CTRL_TYPE_SCSI)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver,
|
||||
'_get_mounted_disk_from_lun')
|
||||
@ -491,15 +482,15 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch.object(volumeops.SMBFSVolumeDriver, 'ensure_share_mounted')
|
||||
@mock.patch.object(volumeops.SMBFSVolumeDriver, '_get_disk_path')
|
||||
def _check_attach_volume(self, mock_get_disk_path,
|
||||
mock_ensure_share_mounted, ebs_root=False):
|
||||
mock_ensure_share_mounted, disk_bus):
|
||||
mock_get_disk_path.return_value = mock.sentinel.disk_path
|
||||
|
||||
self._volume_driver.attach_volume(
|
||||
self._FAKE_CONNECTION_INFO,
|
||||
mock.sentinel.instance_name,
|
||||
ebs_root)
|
||||
disk_bus)
|
||||
|
||||
if ebs_root:
|
||||
if disk_bus == constants.CTRL_TYPE_IDE:
|
||||
get_vm_ide_controller = (
|
||||
self._volume_driver._vmutils.get_vm_ide_controller)
|
||||
get_vm_ide_controller.assert_called_once_with(
|
||||
@ -527,10 +518,10 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
ctrller_path, slot)
|
||||
|
||||
def test_attach_volume_ide(self):
|
||||
self._check_attach_volume(ebs_root=True)
|
||||
self._check_attach_volume(disk_bus=constants.CTRL_TYPE_IDE)
|
||||
|
||||
def test_attach_volume_scsi(self):
|
||||
self._check_attach_volume()
|
||||
self._check_attach_volume(disk_bus=constants.CTRL_TYPE_SCSI)
|
||||
|
||||
@mock.patch.object(volumeops.SMBFSVolumeDriver, 'ensure_share_mounted')
|
||||
@mock.patch.object(volumeops.SMBFSVolumeDriver, '_get_disk_path')
|
||||
|
@ -58,8 +58,6 @@ class BlockDeviceInfoManager(object):
|
||||
# reserve one slot for the config drive on the second
|
||||
# controller in case of generation 1 virtual machines
|
||||
free_slots_by_device_type[constants.CTRL_TYPE_IDE][1] -= 1
|
||||
else:
|
||||
free_slots_by_device_type[constants.CTRL_TYPE_SCSI][0] -= 1
|
||||
return free_slots_by_device_type
|
||||
|
||||
def validate_and_update_bdi(self, instance, image_meta, vm_gen,
|
||||
@ -70,6 +68,14 @@ class BlockDeviceInfoManager(object):
|
||||
self._check_and_update_ephemerals(vm_gen, block_device_info, slot_map)
|
||||
self._check_and_update_volumes(vm_gen, block_device_info, slot_map)
|
||||
|
||||
if vm_gen == constants.VM_GEN_2 and configdrive.required_by(instance):
|
||||
# for Generation 2 VMs, the configdrive is attached to the SCSI
|
||||
# controller. Check that there is still a slot available for it.
|
||||
if slot_map[constants.CTRL_TYPE_SCSI][0] == 0:
|
||||
msg = _("There are no more free slots on controller %s for "
|
||||
"configdrive.") % constants.CTRL_TYPE_SCSI
|
||||
raise exception.InvalidBDMFormat(details=msg)
|
||||
|
||||
def _check_and_update_root_device(self, vm_gen, image_meta,
|
||||
block_device_info, slot_map):
|
||||
# either booting from volume, or booting from image/iso
|
||||
|
@ -56,6 +56,8 @@ DISK_FORMAT_MAP = {
|
||||
DVD_FORMAT.lower(): DVD
|
||||
}
|
||||
|
||||
BDI_DEVICE_TYPE_TO_DRIVE_TYPE = {'disk': DISK}
|
||||
|
||||
DISK_FORMAT_VHD = "VHD"
|
||||
DISK_FORMAT_VHDX = "VHDX"
|
||||
|
||||
|
@ -128,6 +128,10 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
'Windows has been removed in Mitaka.'))
|
||||
raise exception.HypervisorTooOld(version='6.2')
|
||||
|
||||
@property
|
||||
def need_legacy_block_device_info(self):
|
||||
return False
|
||||
|
||||
def init_host(self, host):
|
||||
self._serialconsoleops.start_console_handlers()
|
||||
event_handler = eventhandler.InstanceEventHandler(
|
||||
|
@ -23,6 +23,7 @@ from oslo_utils import excutils
|
||||
|
||||
import nova.conf
|
||||
from nova.objects import migrate_data as migrate_data_obj
|
||||
from nova.virt.hyperv import block_device_manager
|
||||
from nova.virt.hyperv import imagecache
|
||||
from nova.virt.hyperv import pathutils
|
||||
from nova.virt.hyperv import serialconsoleops
|
||||
@ -42,6 +43,7 @@ class LiveMigrationOps(object):
|
||||
self._serial_console_ops = serialconsoleops.SerialConsoleOps()
|
||||
self._imagecache = imagecache.ImageCache()
|
||||
self._vmutils = utilsfactory.get_vmutils()
|
||||
self._block_dev_man = block_device_manager.BlockDeviceInfoManager()
|
||||
|
||||
def live_migration(self, context, instance_ref, dest, post_method,
|
||||
recover_method, block_migration=False,
|
||||
@ -75,7 +77,7 @@ class LiveMigrationOps(object):
|
||||
self._livemigrutils.check_live_migration_config()
|
||||
|
||||
if CONF.use_cow_images:
|
||||
boot_from_volume = self._volumeops.ebs_root_in_block_devices(
|
||||
boot_from_volume = self._block_dev_man.is_boot_from_volume(
|
||||
block_device_info)
|
||||
if not boot_from_volume and instance.image_ref:
|
||||
self._imagecache.get_cached_image(context, instance)
|
||||
|
@ -24,9 +24,11 @@ from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _, _LE
|
||||
from nova.i18n import _, _LW, _LE
|
||||
from nova import objects
|
||||
from nova.virt import configdrive
|
||||
from nova.virt.hyperv import block_device_manager
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import imagecache
|
||||
from nova.virt.hyperv import pathutils
|
||||
from nova.virt.hyperv import vmops
|
||||
@ -44,6 +46,7 @@ class MigrationOps(object):
|
||||
self._volumeops = volumeops.VolumeOps()
|
||||
self._vmops = vmops.VMOps()
|
||||
self._imagecache = imagecache.ImageCache()
|
||||
self._block_dev_man = block_device_manager.BlockDeviceInfoManager()
|
||||
|
||||
def _migrate_disk_files(self, instance_name, disk_files, dest):
|
||||
# TODO(mikal): it would be nice if this method took a full instance,
|
||||
@ -167,18 +170,25 @@ class MigrationOps(object):
|
||||
instance_name = instance.name
|
||||
self._revert_migration_files(instance_name)
|
||||
|
||||
if self._volumeops.ebs_root_in_block_devices(block_device_info):
|
||||
root_vhd_path = None
|
||||
else:
|
||||
root_vhd_path = self._pathutils.lookup_root_vhd_path(instance_name)
|
||||
|
||||
eph_vhd_path = self._pathutils.lookup_ephemeral_vhd_path(instance_name)
|
||||
|
||||
image_meta = objects.ImageMeta.from_instance(instance)
|
||||
vm_gen = self._vmops.get_image_vm_generation(
|
||||
instance.uuid, root_vhd_path, image_meta)
|
||||
self._vmops.create_instance(instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path, vm_gen)
|
||||
vm_gen = self._vmops.get_image_vm_generation(instance.uuid, image_meta)
|
||||
|
||||
self._block_dev_man.validate_and_update_bdi(instance, image_meta,
|
||||
vm_gen, block_device_info)
|
||||
root_device = block_device_info['root_disk']
|
||||
|
||||
if root_device['type'] == constants.DISK:
|
||||
root_vhd_path = self._pathutils.lookup_root_vhd_path(instance_name)
|
||||
root_device['path'] = root_vhd_path
|
||||
if not root_vhd_path:
|
||||
base_vhd_path = self._pathutils.get_instance_dir(instance_name)
|
||||
raise exception.DiskNotFound(location=base_vhd_path)
|
||||
|
||||
ephemerals = block_device_info['ephemerals']
|
||||
self._check_ephemeral_disks(instance, ephemerals)
|
||||
|
||||
self._vmops.create_instance(instance, network_info, root_device,
|
||||
block_device_info, vm_gen)
|
||||
|
||||
self._check_and_attach_config_drive(instance, vm_gen)
|
||||
|
||||
@ -221,8 +231,8 @@ class MigrationOps(object):
|
||||
reason=_("Cannot resize the root disk to a smaller size. "
|
||||
"Current size: %(curr_root_gb)s GB. Requested "
|
||||
"size: %(new_root_gb)s GB.") % {
|
||||
'curr_root_gb': curr_size,
|
||||
'new_root_gb': new_size})
|
||||
'curr_root_gb': curr_size / units.Gi,
|
||||
'new_root_gb': new_size / units.Gi})
|
||||
elif new_size > curr_size:
|
||||
self._resize_vhd(vhd_path, new_size)
|
||||
|
||||
@ -260,13 +270,18 @@ class MigrationOps(object):
|
||||
LOG.debug("finish_migration called", instance=instance)
|
||||
|
||||
instance_name = instance.name
|
||||
vm_gen = self._vmops.get_image_vm_generation(instance.uuid, image_meta)
|
||||
|
||||
if self._volumeops.ebs_root_in_block_devices(block_device_info):
|
||||
root_vhd_path = None
|
||||
else:
|
||||
self._block_dev_man.validate_and_update_bdi(instance, image_meta,
|
||||
vm_gen, block_device_info)
|
||||
root_device = block_device_info['root_disk']
|
||||
|
||||
if root_device['type'] == constants.DISK:
|
||||
root_vhd_path = self._pathutils.lookup_root_vhd_path(instance_name)
|
||||
root_device['path'] = root_vhd_path
|
||||
if not root_vhd_path:
|
||||
raise exception.DiskNotFound(location=root_vhd_path)
|
||||
base_vhd_path = self._pathutils.get_instance_dir(instance_name)
|
||||
raise exception.DiskNotFound(location=base_vhd_path)
|
||||
|
||||
root_vhd_info = self._vhdutils.get_vhd_info(root_vhd_path)
|
||||
src_base_disk_path = root_vhd_info.get("ParentPath")
|
||||
@ -278,22 +293,57 @@ class MigrationOps(object):
|
||||
new_size = instance.root_gb * units.Gi
|
||||
self._check_resize_vhd(root_vhd_path, root_vhd_info, new_size)
|
||||
|
||||
eph_vhd_path = self._pathutils.lookup_ephemeral_vhd_path(instance_name)
|
||||
if resize_instance:
|
||||
new_size = instance.get('ephemeral_gb', 0) * units.Gi
|
||||
if not eph_vhd_path:
|
||||
if new_size:
|
||||
eph_vhd_path = self._vmops.create_ephemeral_vhd(instance)
|
||||
else:
|
||||
eph_vhd_info = self._vhdutils.get_vhd_info(eph_vhd_path)
|
||||
self._check_resize_vhd(eph_vhd_path, eph_vhd_info, new_size)
|
||||
ephemerals = block_device_info['ephemerals']
|
||||
self._check_ephemeral_disks(instance, ephemerals, resize_instance)
|
||||
|
||||
vm_gen = self._vmops.get_image_vm_generation(
|
||||
instance.uuid, root_vhd_path, image_meta)
|
||||
self._vmops.create_instance(instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path, vm_gen)
|
||||
self._vmops.create_instance(instance, network_info, root_device,
|
||||
block_device_info, vm_gen)
|
||||
|
||||
self._check_and_attach_config_drive(instance, vm_gen)
|
||||
|
||||
if power_on:
|
||||
self._vmops.power_on(instance)
|
||||
|
||||
def _check_ephemeral_disks(self, instance, ephemerals,
|
||||
resize_instance=False):
|
||||
instance_name = instance.name
|
||||
new_eph_gb = instance.get('ephemeral_gb', 0)
|
||||
|
||||
if len(ephemerals) == 1:
|
||||
# NOTE(claudiub): Resize only if there is one ephemeral. If there
|
||||
# are more than 1, resizing them can be problematic. This behaviour
|
||||
# also exists in the libvirt driver and it has to be addressed in
|
||||
# the future.
|
||||
ephemerals[0]['size'] = new_eph_gb
|
||||
elif sum(eph['size'] for eph in ephemerals) != new_eph_gb:
|
||||
# New ephemeral size is different from the original ephemeral size
|
||||
# and there are multiple ephemerals.
|
||||
LOG.warn(_LW("Cannot resize multiple ephemeral disks for "
|
||||
"instance."), instance=instance)
|
||||
|
||||
for index, eph in enumerate(ephemerals):
|
||||
eph_name = "eph%s" % index
|
||||
existing_eph_path = self._pathutils.lookup_ephemeral_vhd_path(
|
||||
instance_name, eph_name)
|
||||
|
||||
if not existing_eph_path:
|
||||
eph['format'] = self._vhdutils.get_best_supported_vhd_format()
|
||||
eph['path'] = self._pathutils.get_ephemeral_vhd_path(
|
||||
instance_name, eph['format'], eph_name)
|
||||
if not resize_instance:
|
||||
# ephemerals should have existed.
|
||||
raise exception.DiskNotFound(location=eph['path'])
|
||||
|
||||
if eph['size']:
|
||||
# create ephemerals
|
||||
self._vmops.create_ephemeral_disk(instance.name, eph)
|
||||
elif eph['size'] > 0:
|
||||
# ephemerals exist. resize them.
|
||||
eph['path'] = existing_eph_path
|
||||
eph_vhd_info = self._vhdutils.get_vhd_info(eph['path'])
|
||||
self._check_resize_vhd(
|
||||
eph['path'], eph_vhd_info, eph['size'] * units.Gi)
|
||||
else:
|
||||
# ephemeral new size is 0, remove it.
|
||||
self._pathutils.remove(existing_eph_path)
|
||||
eph['path'] = None
|
||||
|
@ -106,9 +106,10 @@ class PathUtils(pathutils.PathUtils):
|
||||
break
|
||||
return configdrive_path
|
||||
|
||||
def lookup_ephemeral_vhd_path(self, instance_name):
|
||||
def lookup_ephemeral_vhd_path(self, instance_name, eph_name):
|
||||
return self._lookup_vhd_path(instance_name,
|
||||
self.get_ephemeral_vhd_path)
|
||||
self.get_ephemeral_vhd_path,
|
||||
eph_name)
|
||||
|
||||
def get_root_vhd_path(self, instance_name, format_ext, rescue=False):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
@ -127,9 +128,9 @@ class PathUtils(pathutils.PathUtils):
|
||||
return os.path.join(instance_path,
|
||||
configdrive_image_name + '.' + format_ext.lower())
|
||||
|
||||
def get_ephemeral_vhd_path(self, instance_name, format_ext):
|
||||
def get_ephemeral_vhd_path(self, instance_name, format_ext, eph_name):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'ephemeral.' + format_ext.lower())
|
||||
return os.path.join(instance_path, eph_name + '.' + format_ext.lower())
|
||||
|
||||
def get_base_vhd_dir(self):
|
||||
return self._get_instances_sub_dir('_base')
|
||||
|
@ -42,6 +42,7 @@ from nova.i18n import _, _LI, _LE, _LW
|
||||
from nova import utils
|
||||
from nova.virt import configdrive
|
||||
from nova.virt import hardware
|
||||
from nova.virt.hyperv import block_device_manager
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import imagecache
|
||||
from nova.virt.hyperv import pathutils
|
||||
@ -107,6 +108,8 @@ class VMOps(object):
|
||||
self._volumeops = volumeops.VolumeOps()
|
||||
self._imagecache = imagecache.ImageCache()
|
||||
self._serial_console_ops = serialconsoleops.SerialConsoleOps()
|
||||
self._block_dev_man = (
|
||||
block_device_manager.BlockDeviceInfoManager())
|
||||
self._vif_driver = None
|
||||
self._load_vif_driver_class()
|
||||
|
||||
@ -157,6 +160,13 @@ class VMOps(object):
|
||||
num_cpu=info['NumberOfProcessors'],
|
||||
cpu_time_ns=info['UpTime'])
|
||||
|
||||
def _create_root_device(self, context, instance, root_disk_info, vm_gen):
|
||||
path = None
|
||||
if root_disk_info['type'] == constants.DISK:
|
||||
path = self._create_root_vhd(context, instance)
|
||||
self.check_vm_image_type(instance.uuid, vm_gen, path)
|
||||
root_disk_info['path'] = path
|
||||
|
||||
def _create_root_vhd(self, context, instance, rescue_image_id=None):
|
||||
is_rescue_vhd = rescue_image_id is not None
|
||||
|
||||
@ -223,15 +233,17 @@ class VMOps(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_ephemeral_vhd(self, instance):
|
||||
eph_vhd_size = instance.get('ephemeral_gb', 0) * units.Gi
|
||||
if eph_vhd_size:
|
||||
vhd_format = self._vhdutils.get_best_supported_vhd_format()
|
||||
def _create_ephemerals(self, instance, ephemerals):
|
||||
for index, eph in enumerate(ephemerals):
|
||||
eph['format'] = self._vhdutils.get_best_supported_vhd_format()
|
||||
eph_name = "eph%s" % index
|
||||
eph['path'] = self._pathutils.get_ephemeral_vhd_path(
|
||||
instance.name, eph['format'], eph_name)
|
||||
self.create_ephemeral_disk(instance.name, eph)
|
||||
|
||||
eph_vhd_path = self._pathutils.get_ephemeral_vhd_path(
|
||||
instance.name, vhd_format)
|
||||
self._vhdutils.create_dynamic_vhd(eph_vhd_path, eph_vhd_size)
|
||||
return eph_vhd_path
|
||||
def create_ephemeral_disk(self, instance_name, eph_info):
|
||||
self._vhdutils.create_dynamic_vhd(eph_info['path'],
|
||||
eph_info['size'] * units.Gi)
|
||||
|
||||
@check_admin_permissions
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
@ -246,19 +258,17 @@ class VMOps(object):
|
||||
# Make sure we're starting with a clean slate.
|
||||
self._delete_disk_files(instance_name)
|
||||
|
||||
if self._volumeops.ebs_root_in_block_devices(block_device_info):
|
||||
root_vhd_path = None
|
||||
else:
|
||||
root_vhd_path = self._create_root_vhd(context, instance)
|
||||
vm_gen = self.get_image_vm_generation(instance.uuid, image_meta)
|
||||
|
||||
eph_vhd_path = self.create_ephemeral_vhd(instance)
|
||||
vm_gen = self.get_image_vm_generation(
|
||||
instance.uuid, root_vhd_path, image_meta)
|
||||
self._block_dev_man.validate_and_update_bdi(
|
||||
instance, image_meta, vm_gen, block_device_info)
|
||||
root_device = block_device_info['root_disk']
|
||||
self._create_root_device(context, instance, root_device, vm_gen)
|
||||
self._create_ephemerals(instance, block_device_info['ephemerals'])
|
||||
|
||||
try:
|
||||
self.create_instance(instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path,
|
||||
vm_gen)
|
||||
self.create_instance(instance, network_info, root_device,
|
||||
block_device_info, vm_gen)
|
||||
|
||||
if configdrive.required_by(instance):
|
||||
configdrive_path = self._create_config_drive(instance,
|
||||
@ -273,8 +283,8 @@ class VMOps(object):
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.destroy(instance)
|
||||
|
||||
def create_instance(self, instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path, vm_gen):
|
||||
def create_instance(self, instance, network_info, root_device,
|
||||
block_device_info, vm_gen):
|
||||
instance_name = instance.name
|
||||
instance_path = os.path.join(CONF.instances_path, instance_name)
|
||||
|
||||
@ -290,24 +300,10 @@ class VMOps(object):
|
||||
self._configure_remotefx(instance, vm_gen)
|
||||
|
||||
self._vmutils.create_scsi_controller(instance_name)
|
||||
controller_type = VM_GENERATIONS_CONTROLLER_TYPES[vm_gen]
|
||||
|
||||
ctrl_disk_addr = 0
|
||||
if root_vhd_path:
|
||||
self._attach_drive(instance_name, root_vhd_path, 0, ctrl_disk_addr,
|
||||
controller_type)
|
||||
|
||||
ctrl_disk_addr = 1
|
||||
if eph_vhd_path:
|
||||
self._attach_drive(instance_name, eph_vhd_path, 0, ctrl_disk_addr,
|
||||
controller_type)
|
||||
|
||||
# If ebs_root is False, the first volume will be attached to SCSI
|
||||
# controller. Generation 2 VMs only has a SCSI controller.
|
||||
ebs_root = vm_gen is not constants.VM_GEN_2 and root_vhd_path is None
|
||||
self._volumeops.attach_volumes(block_device_info,
|
||||
instance_name,
|
||||
ebs_root)
|
||||
self._attach_root_device(instance_name, root_device)
|
||||
self._attach_ephemerals(instance_name, block_device_info['ephemerals'])
|
||||
self._volumeops.attach_volumes(
|
||||
block_device_info['block_device_mapping'], instance_name)
|
||||
|
||||
# For the moment, we use COM port 1 when getting the serial console
|
||||
# log as well as interactive sessions. In the future, the way in which
|
||||
@ -368,6 +364,29 @@ class VMOps(object):
|
||||
remotefx_max_resolution,
|
||||
vram_bytes)
|
||||
|
||||
def _attach_root_device(self, instance_name, root_dev_info):
|
||||
if root_dev_info['type'] == constants.VOLUME:
|
||||
self._volumeops.attach_volume(root_dev_info['connection_info'],
|
||||
instance_name,
|
||||
disk_bus=root_dev_info['disk_bus'])
|
||||
else:
|
||||
self._attach_drive(instance_name, root_dev_info['path'],
|
||||
root_dev_info['drive_addr'],
|
||||
root_dev_info['ctrl_disk_addr'],
|
||||
root_dev_info['disk_bus'],
|
||||
root_dev_info['type'])
|
||||
|
||||
def _attach_ephemerals(self, instance_name, ephemerals):
|
||||
for eph in ephemerals:
|
||||
# if an ephemeral doesn't have a path, it might have been removed
|
||||
# during resize.
|
||||
if eph.get('path'):
|
||||
self._attach_drive(
|
||||
instance_name, eph['path'], eph['drive_addr'],
|
||||
eph['ctrl_disk_addr'], eph['disk_bus'],
|
||||
constants.BDI_DEVICE_TYPE_TO_DRIVE_TYPE[
|
||||
eph['device_type']])
|
||||
|
||||
def _attach_drive(self, instance_name, path, drive_addr, ctrl_disk_addr,
|
||||
controller_type, drive_type=constants.DISK):
|
||||
if controller_type == constants.CTRL_TYPE_SCSI:
|
||||
@ -376,18 +395,19 @@ class VMOps(object):
|
||||
self._vmutils.attach_ide_drive(instance_name, path, drive_addr,
|
||||
ctrl_disk_addr, drive_type)
|
||||
|
||||
def get_image_vm_generation(self, instance_id, root_vhd_path, image_meta):
|
||||
def get_image_vm_generation(self, instance_id, image_meta):
|
||||
default_vm_gen = self._hostutils.get_default_vm_generation()
|
||||
image_prop_vm = image_meta.properties.get(
|
||||
'hw_machine_type', default_vm_gen)
|
||||
image_prop_vm = image_meta.properties.get('hw_machine_type',
|
||||
default_vm_gen)
|
||||
if image_prop_vm not in self._hostutils.get_supported_vm_types():
|
||||
reason = _LE('Requested VM Generation %s is not supported on '
|
||||
'this OS.') % image_prop_vm
|
||||
raise exception.InstanceUnacceptable(instance_id=instance_id,
|
||||
reason=reason)
|
||||
|
||||
vm_gen = VM_GENERATIONS[image_prop_vm]
|
||||
return VM_GENERATIONS[image_prop_vm]
|
||||
|
||||
def check_vm_image_type(self, instance_id, vm_gen, root_vhd_path):
|
||||
if (vm_gen != constants.VM_GEN_1 and root_vhd_path and
|
||||
self._vhdutils.get_vhd_format(
|
||||
root_vhd_path) == constants.DISK_FORMAT_VHD):
|
||||
@ -396,8 +416,6 @@ class VMOps(object):
|
||||
raise exception.InstanceUnacceptable(instance_id=instance_id,
|
||||
reason=reason)
|
||||
|
||||
return vm_gen
|
||||
|
||||
def _create_config_drive(self, instance, injected_files, admin_password,
|
||||
network_info, rescue=False):
|
||||
if CONF.config_drive_format != 'iso9660':
|
||||
@ -748,7 +766,6 @@ class VMOps(object):
|
||||
context, instance, rescue_image_id=rescue_image_id)
|
||||
|
||||
rescue_vm_gen = self.get_image_vm_generation(instance.uuid,
|
||||
rescue_vhd_path,
|
||||
image_meta)
|
||||
vm_gen = self._vmutils.get_vm_generation(instance.name)
|
||||
if rescue_vm_gen != vm_gen:
|
||||
|
@ -28,12 +28,12 @@ from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from six.moves import range
|
||||
|
||||
from nova import block_device
|
||||
import nova.conf
|
||||
from nova import exception
|
||||
from nova.i18n import _, _LE, _LW
|
||||
from nova import utils
|
||||
from nova.virt import driver
|
||||
from nova.virt.hyperv import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -59,14 +59,8 @@ class VolumeOps(object):
|
||||
raise exception.VolumeDriverNotFound(driver_type=driver_type)
|
||||
return self.volume_drivers[driver_type]
|
||||
|
||||
def attach_volumes(self, block_device_info, instance_name, ebs_root):
|
||||
mapping = driver.block_device_info_get_mapping(block_device_info)
|
||||
|
||||
if ebs_root:
|
||||
self.attach_volume(mapping[0]['connection_info'],
|
||||
instance_name, True)
|
||||
mapping = mapping[1:]
|
||||
for vol in mapping:
|
||||
def attach_volumes(self, volumes, instance_name):
|
||||
for vol in volumes:
|
||||
self.attach_volume(vol['connection_info'], instance_name)
|
||||
|
||||
def disconnect_volumes(self, block_device_info):
|
||||
@ -77,24 +71,18 @@ class VolumeOps(object):
|
||||
volume_driver = self._get_volume_driver(driver_type)
|
||||
volume_driver.disconnect_volumes(block_device_mapping)
|
||||
|
||||
def attach_volume(self, connection_info, instance_name, ebs_root=False):
|
||||
def attach_volume(self, connection_info, instance_name,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI):
|
||||
volume_driver = self._get_volume_driver(
|
||||
connection_info=connection_info)
|
||||
volume_driver.attach_volume(connection_info, instance_name, ebs_root)
|
||||
volume_driver.attach_volume(connection_info, instance_name,
|
||||
disk_bus=disk_bus)
|
||||
|
||||
def detach_volume(self, connection_info, instance_name):
|
||||
volume_driver = self._get_volume_driver(
|
||||
connection_info=connection_info)
|
||||
volume_driver.detach_volume(connection_info, instance_name)
|
||||
|
||||
def ebs_root_in_block_devices(self, block_device_info):
|
||||
if block_device_info:
|
||||
root_device = block_device_info.get('root_device_name')
|
||||
if not root_device:
|
||||
root_device = self._default_root_device
|
||||
return block_device.volume_in_mapping(root_device,
|
||||
block_device_info)
|
||||
|
||||
def fix_instance_volume_disk_paths(self, instance_name, block_device_info):
|
||||
# Mapping containing the current disk paths for each volume.
|
||||
actual_disk_mapping = self.get_disk_path_mapping(block_device_info)
|
||||
@ -230,7 +218,8 @@ class ISCSIVolumeDriver(object):
|
||||
return self._get_mounted_disk_from_lun(target_iqn, target_lun,
|
||||
wait_for_device=True)
|
||||
|
||||
def attach_volume(self, connection_info, instance_name, ebs_root=False):
|
||||
def attach_volume(self, connection_info, instance_name,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI):
|
||||
"""Attach a volume to the SCSI controller or to the IDE controller if
|
||||
ebs_root is True
|
||||
"""
|
||||
@ -246,7 +235,7 @@ class ISCSIVolumeDriver(object):
|
||||
mounted_disk_path = self.get_mounted_disk_path_from_volume(
|
||||
connection_info)
|
||||
|
||||
if ebs_root:
|
||||
if disk_bus == constants.CTRL_TYPE_IDE:
|
||||
# Find the IDE controller for the vm.
|
||||
ctrller_path = self._vmutils.get_vm_ide_controller(
|
||||
instance_name, 0)
|
||||
@ -359,13 +348,14 @@ class SMBFSVolumeDriver(object):
|
||||
return self._get_disk_path(connection_info)
|
||||
|
||||
@export_path_synchronized
|
||||
def attach_volume(self, connection_info, instance_name, ebs_root=False):
|
||||
def attach_volume(self, connection_info, instance_name,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI):
|
||||
self.ensure_share_mounted(connection_info)
|
||||
|
||||
disk_path = self._get_disk_path(connection_info)
|
||||
|
||||
try:
|
||||
if ebs_root:
|
||||
if disk_bus == constants.CTRL_TYPE_IDE:
|
||||
ctrller_path = self._vmutils.get_vm_ide_controller(
|
||||
instance_name, 0)
|
||||
slot = 0
|
||||
|
Loading…
Reference in New Issue
Block a user