Merge "Update device metadata when attaching disks/NICs"
This commit is contained in:
commit
3ceacfdbbe
@ -18,19 +18,27 @@ Handling of block device information and mapping
|
||||
Module contains helper methods for dealing with block device information
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import os
|
||||
|
||||
from nova import block_device
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.virt import block_device as driver_block_device
|
||||
from nova.virt import configdrive
|
||||
from nova.virt import driver
|
||||
from os_win import constants as os_win_const
|
||||
from os_win import exceptions as os_win_exc
|
||||
from os_win import utilsfactory
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from compute_hyperv.i18n import _
|
||||
from compute_hyperv.nova import constants
|
||||
from compute_hyperv.nova import pathutils
|
||||
from compute_hyperv.nova import volumeops
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlockDeviceInfoManager(object):
|
||||
|
||||
@ -48,48 +56,133 @@ class BlockDeviceInfoManager(object):
|
||||
|
||||
def __init__(self):
|
||||
self._volops = volumeops.VolumeOps()
|
||||
self._pathutils = pathutils.PathUtils()
|
||||
|
||||
self._vmutils = utilsfactory.get_vmutils()
|
||||
|
||||
@staticmethod
|
||||
def _get_device_bus(bdm):
|
||||
def _get_device_bus(ctrl_type, ctrl_addr, ctrl_slot):
|
||||
"""Determines the device bus and it's hypervisor assigned address.
|
||||
"""
|
||||
if bdm['disk_bus'] == constants.CTRL_TYPE_SCSI:
|
||||
address = ':'.join(['0', '0', str(bdm['drive_addr']),
|
||||
str(bdm['ctrl_disk_addr'])])
|
||||
if ctrl_type == constants.CTRL_TYPE_SCSI:
|
||||
address = ':'.join(map(str, [0, 0, ctrl_addr, ctrl_slot]))
|
||||
return objects.SCSIDeviceBus(address=address)
|
||||
elif bdm['disk_bus'] == constants.CTRL_TYPE_IDE:
|
||||
address = ':'.join([str(bdm['drive_addr']),
|
||||
str(bdm['ctrl_disk_addr'])])
|
||||
elif ctrl_type == constants.CTRL_TYPE_IDE:
|
||||
address = ':'.join(map(str, [ctrl_addr, ctrl_slot]))
|
||||
return objects.IDEDeviceBus(address=address)
|
||||
|
||||
def get_bdm_metadata(self, context, instance, block_device_info):
|
||||
def _get_vol_bdm_attachment_info(self, bdm):
|
||||
drv_vol_bdm = driver_block_device.convert_volume(bdm)
|
||||
if not drv_vol_bdm:
|
||||
return
|
||||
|
||||
connection_info = drv_vol_bdm['connection_info']
|
||||
if not connection_info:
|
||||
LOG.warning("Missing connection info for volume %s.",
|
||||
bdm.volume_id)
|
||||
return
|
||||
|
||||
attachment_info = self._volops.get_disk_attachment_info(
|
||||
connection_info)
|
||||
attachment_info['serial'] = connection_info['serial']
|
||||
return attachment_info
|
||||
|
||||
def _get_eph_bdm_attachment_info(self, instance, bdm):
|
||||
# When attaching ephemeral disks, we're setting this field so that
|
||||
# we can map them with bdm objects.
|
||||
connection_info = self.get_bdm_connection_info(bdm)
|
||||
eph_filename = connection_info.get("eph_filename")
|
||||
if not eph_filename:
|
||||
LOG.warning("Missing ephemeral disk filename in "
|
||||
"BDM connection info. BDM: %s", bdm)
|
||||
return
|
||||
|
||||
eph_path = os.path.join(
|
||||
self._pathutils.get_instance_dir(instance.name), eph_filename)
|
||||
if not os.path.exists(eph_path):
|
||||
LOG.warning("Could not find ephemeral disk %s.", eph_path)
|
||||
return
|
||||
|
||||
return self._vmutils.get_disk_attachment_info(eph_path,
|
||||
is_physical=False)
|
||||
|
||||
def _get_disk_metadata(self, instance, bdm):
|
||||
attachment_info = None
|
||||
if bdm.is_volume:
|
||||
attachment_info = self._get_vol_bdm_attachment_info(bdm)
|
||||
elif block_device.new_format_is_ephemeral(bdm):
|
||||
attachment_info = self._get_eph_bdm_attachment_info(
|
||||
instance, bdm)
|
||||
|
||||
if not attachment_info:
|
||||
LOG.debug("No attachment info retrieved for bdm %s.", bdm)
|
||||
return
|
||||
|
||||
tags = [bdm.tag] if bdm.tag else []
|
||||
bus = self._get_device_bus(
|
||||
attachment_info['controller_type'],
|
||||
attachment_info['controller_addr'],
|
||||
attachment_info['controller_slot'])
|
||||
serial = attachment_info.get('serial')
|
||||
|
||||
return objects.DiskMetadata(bus=bus,
|
||||
tags=tags,
|
||||
serial=serial)
|
||||
|
||||
def get_bdm_metadata(self, context, instance):
|
||||
"""Builds a metadata object for instance devices, that maps the user
|
||||
provided tag to the hypervisor assigned device address.
|
||||
"""
|
||||
# block_device_info does not contain tags information.
|
||||
bdm_obj_list = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||
context, instance.uuid)
|
||||
|
||||
# create a map between BDM object and its device name.
|
||||
bdm_obj_map = {bdm.device_name: bdm for bdm in bdm_obj_list}
|
||||
|
||||
bdm_metadata = []
|
||||
for bdm in itertools.chain(block_device_info['block_device_mapping'],
|
||||
block_device_info['ephemerals'],
|
||||
[block_device_info['root_disk']]):
|
||||
# NOTE(claudiub): ephemerals have device_name instead of
|
||||
# mount_device.
|
||||
device_name = bdm.get('mount_device') or bdm.get('device_name')
|
||||
bdm_obj = bdm_obj_map.get(device_name)
|
||||
|
||||
if bdm_obj and 'tag' in bdm_obj and bdm_obj.tag:
|
||||
bus = self._get_device_bus(bdm)
|
||||
device = objects.DiskMetadata(bus=bus,
|
||||
tags=[bdm_obj.tag])
|
||||
bdm_metadata.append(device)
|
||||
for bdm in bdms:
|
||||
try:
|
||||
device_metadata = self._get_disk_metadata(instance, bdm)
|
||||
if device_metadata:
|
||||
bdm_metadata.append(device_metadata)
|
||||
except (exception.DiskNotFound, os_win_exc.DiskNotFound):
|
||||
LOG.debug("Could not find disk attachment while "
|
||||
"updating device metadata. It may have been "
|
||||
"detached. BDM: %s", bdm)
|
||||
|
||||
return bdm_metadata
|
||||
|
||||
def set_volume_bdm_connection_info(self, context, instance,
|
||||
connection_info):
|
||||
# When attaching volumes to already existing instances, the connection
|
||||
# info passed to the driver is not saved yet within the BDM table.
|
||||
#
|
||||
# Nova sets the volume id within the connection info using the
|
||||
# 'serial' key.
|
||||
volume_id = connection_info['serial']
|
||||
bdm = objects.BlockDeviceMapping.get_by_volume_and_instance(
|
||||
context, volume_id, instance.uuid)
|
||||
bdm.connection_info = jsonutils.dumps(connection_info)
|
||||
bdm.save()
|
||||
|
||||
@staticmethod
|
||||
def get_bdm_connection_info(bdm):
|
||||
# We're using the BDM 'connection_info' field to store ephemeral
|
||||
# image information so that we can map them. In order to do so,
|
||||
# we're using this helper.
|
||||
# The ephemeral bdm object wrapper does not currently expose this
|
||||
# field.
|
||||
try:
|
||||
conn_info = jsonutils.loads(bdm.connection_info)
|
||||
except TypeError:
|
||||
conn_info = {}
|
||||
|
||||
return conn_info
|
||||
|
||||
@staticmethod
|
||||
def update_bdm_connection_info(bdm, **kwargs):
|
||||
conn_info = BlockDeviceInfoManager.get_bdm_connection_info(bdm)
|
||||
conn_info.update(**kwargs)
|
||||
bdm.connection_info = jsonutils.dumps(conn_info)
|
||||
bdm.save()
|
||||
|
||||
def _initialize_controller_slot_counter(self, instance, vm_gen):
|
||||
# we have 2 IDE controllers, for a total of 4 slots
|
||||
free_slots_by_device_type = {
|
||||
|
@ -21,6 +21,7 @@ import functools
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova import image
|
||||
from nova.virt import driver
|
||||
@ -100,6 +101,8 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
"supports_migrate_to_same_host": False,
|
||||
"supports_attach_interface": True,
|
||||
"supports_device_tagging": True,
|
||||
"supports_tagged_attach_interface": True,
|
||||
"supports_tagged_attach_volume": True,
|
||||
}
|
||||
|
||||
def __init__(self, virtapi):
|
||||
@ -180,13 +183,20 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
|
||||
def attach_volume(self, context, connection_info, instance, mountpoint,
|
||||
disk_bus=None, device_type=None, encryption=None):
|
||||
return self._volumeops.attach_volume(connection_info,
|
||||
instance.name)
|
||||
self._volumeops.attach_volume(context,
|
||||
connection_info,
|
||||
instance,
|
||||
update_device_metadata=True)
|
||||
|
||||
def detach_volume(self, connection_info, instance, mountpoint,
|
||||
encryption=None):
|
||||
return self._volumeops.detach_volume(connection_info,
|
||||
instance.name)
|
||||
context = nova_context.get_admin_context()
|
||||
# The nova compute manager only updates the device metadata in
|
||||
# case of tagged devices. We're including untagged devices as well.
|
||||
self._volumeops.detach_volume(context,
|
||||
connection_info,
|
||||
instance,
|
||||
update_device_metadata=True)
|
||||
|
||||
def get_volume_connector(self, instance):
|
||||
return self._volumeops.get_volume_connector()
|
||||
@ -367,9 +377,10 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
self._imagecache.update(context, all_instances)
|
||||
|
||||
def attach_interface(self, context, instance, image_meta, vif):
|
||||
return self._vmops.attach_interface(instance, vif)
|
||||
self._vmops.attach_interface(context, instance, vif)
|
||||
|
||||
def detach_interface(self, context, instance, vif):
|
||||
# The device metadata gets updated outside the driver.
|
||||
return self._vmops.detach_interface(instance, vif)
|
||||
|
||||
def rescue(self, context, instance, network_info, image_meta,
|
||||
|
@ -252,20 +252,19 @@ class VMOps(object):
|
||||
|
||||
return vif_metadata
|
||||
|
||||
def _save_device_metadata(self, context, instance, block_device_info):
|
||||
def update_device_metadata(self, context, instance):
|
||||
"""Builds a metadata object for instance devices, that maps the user
|
||||
provided tag to the hypervisor assigned device address.
|
||||
"""
|
||||
metadata = []
|
||||
|
||||
metadata.extend(self._get_vif_metadata(context, instance.uuid))
|
||||
if block_device_info:
|
||||
metadata.extend(self._block_dev_man.get_bdm_metadata(
|
||||
context, instance, block_device_info))
|
||||
metadata.extend(self._block_dev_man.get_bdm_metadata(context,
|
||||
instance))
|
||||
|
||||
if metadata:
|
||||
instance.device_metadata = objects.InstanceDeviceMetadata(
|
||||
devices=metadata)
|
||||
instance.device_metadata = objects.InstanceDeviceMetadata(
|
||||
devices=metadata)
|
||||
instance.save()
|
||||
|
||||
def set_boot_order(self, instance_name, vm_gen, block_device_info):
|
||||
boot_order = self._block_dev_man.get_boot_order(
|
||||
@ -305,7 +304,7 @@ class VMOps(object):
|
||||
# This is supported starting from OVS version 2.5
|
||||
self.plug_vifs(instance, network_info)
|
||||
|
||||
self._save_device_metadata(context, instance, block_device_info)
|
||||
self.update_device_metadata(context, instance)
|
||||
|
||||
if configdrive.required_by(instance):
|
||||
configdrive_path = self._create_config_drive(context,
|
||||
@ -382,10 +381,10 @@ class VMOps(object):
|
||||
self.configure_remotefx(instance, vm_gen)
|
||||
|
||||
self._vmutils.create_scsi_controller(instance_name)
|
||||
self._attach_root_device(instance_name, root_device)
|
||||
self._attach_root_device(context, instance, root_device)
|
||||
self.attach_ephemerals(instance_name, block_device_info['ephemerals'])
|
||||
self._volumeops.attach_volumes(
|
||||
block_device_info['block_device_mapping'], instance_name)
|
||||
context, block_device_info['block_device_mapping'], instance)
|
||||
|
||||
serial_ports = self._get_image_serial_port_settings(image_meta)
|
||||
self._create_vm_com_port_pipes(instance, serial_ports)
|
||||
@ -588,13 +587,14 @@ class VMOps(object):
|
||||
remotefx_max_resolution,
|
||||
vram_bytes)
|
||||
|
||||
def _attach_root_device(self, instance_name, root_dev_info):
|
||||
def _attach_root_device(self, context, instance, root_dev_info):
|
||||
if root_dev_info['type'] == constants.VOLUME:
|
||||
self._volumeops.attach_volume(root_dev_info['connection_info'],
|
||||
instance_name,
|
||||
self._volumeops.attach_volume(context,
|
||||
root_dev_info['connection_info'],
|
||||
instance,
|
||||
disk_bus=root_dev_info['disk_bus'])
|
||||
else:
|
||||
self._attach_drive(instance_name, root_dev_info['path'],
|
||||
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'],
|
||||
@ -611,6 +611,10 @@ class VMOps(object):
|
||||
constants.BDI_DEVICE_TYPE_TO_DRIVE_TYPE[
|
||||
eph['device_type']])
|
||||
|
||||
filename = os.path.basename(eph['path'])
|
||||
self._block_dev_man.update_bdm_connection_info(
|
||||
eph._bdm_obj, eph_filename=filename)
|
||||
|
||||
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:
|
||||
@ -1073,7 +1077,7 @@ class VMOps(object):
|
||||
|
||||
return True
|
||||
|
||||
def attach_interface(self, instance, vif):
|
||||
def attach_interface(self, context, instance, vif):
|
||||
if not self._check_hotplug_available(instance):
|
||||
raise exception.InterfaceAttachFailed(instance_uuid=instance.uuid)
|
||||
|
||||
@ -1081,6 +1085,8 @@ class VMOps(object):
|
||||
self._vmutils.create_nic(instance.name, vif['id'], vif['address'])
|
||||
self._vif_driver.plug(instance, vif)
|
||||
|
||||
self.update_device_metadata(context, instance)
|
||||
|
||||
def detach_interface(self, instance, vif):
|
||||
try:
|
||||
if not self._check_hotplug_available(instance):
|
||||
|
@ -72,6 +72,8 @@ class VolumeOps(object):
|
||||
|
||||
def __init__(self):
|
||||
self._volume_api = cinder.API()
|
||||
self._vmops_prop = None
|
||||
self._block_dev_man_prop = None
|
||||
|
||||
self._vmutils = utilsfactory.get_vmutils()
|
||||
self._default_root_device = 'vda'
|
||||
@ -80,30 +82,49 @@ class VolumeOps(object):
|
||||
constants.STORAGE_PROTOCOL_ISCSI: ISCSIVolumeDriver(),
|
||||
constants.STORAGE_PROTOCOL_FC: FCVolumeDriver()}
|
||||
|
||||
@property
|
||||
def _vmops(self):
|
||||
# We have to avoid a circular dependency.
|
||||
if not self._vmops_prop:
|
||||
self._vmops_prop = importutils.import_class(
|
||||
'compute_hyperv.nova.vmops.VMOps')()
|
||||
return self._vmops_prop
|
||||
|
||||
@property
|
||||
def _block_dev_man(self):
|
||||
if not self._block_dev_man_prop:
|
||||
self._block_dev_man_prop = importutils.import_class(
|
||||
'compute_hyperv.nova.block_device_manager.'
|
||||
'BlockDeviceInfoManager')()
|
||||
return self._block_dev_man_prop
|
||||
|
||||
def _get_volume_driver(self, connection_info):
|
||||
driver_type = connection_info.get('driver_volume_type')
|
||||
if driver_type not in self.volume_drivers:
|
||||
raise exception.VolumeDriverNotFound(driver_type=driver_type)
|
||||
return self.volume_drivers[driver_type]
|
||||
|
||||
def attach_volumes(self, volumes, instance_name):
|
||||
def attach_volumes(self, context, volumes, instance):
|
||||
for vol in volumes:
|
||||
self.attach_volume(vol['connection_info'], instance_name)
|
||||
self.attach_volume(context, vol['connection_info'], instance)
|
||||
|
||||
def disconnect_volumes(self, block_device_info):
|
||||
mapping = driver.block_device_info_get_mapping(block_device_info)
|
||||
for vol in mapping:
|
||||
self.disconnect_volume(vol['connection_info'])
|
||||
|
||||
def attach_volume(self, connection_info, instance_name,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI):
|
||||
def attach_volume(self, context, connection_info, instance,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI,
|
||||
update_device_metadata=False):
|
||||
tries_left = CONF.hyperv.volume_attach_retry_count + 1
|
||||
|
||||
while tries_left:
|
||||
try:
|
||||
self._attach_volume(connection_info,
|
||||
instance_name,
|
||||
disk_bus)
|
||||
self._attach_volume(context,
|
||||
connection_info,
|
||||
instance,
|
||||
disk_bus,
|
||||
update_device_metadata)
|
||||
break
|
||||
except Exception as ex:
|
||||
tries_left -= 1
|
||||
@ -113,9 +134,15 @@ class VolumeOps(object):
|
||||
"to instance %(instance_name)s. ",
|
||||
{'connection_info': strutils.mask_dict_password(
|
||||
connection_info),
|
||||
'instance_name': instance_name})
|
||||
'instance_name': instance.name})
|
||||
|
||||
self.disconnect_volume(connection_info)
|
||||
# We're requesting a detach as the disk may have
|
||||
# been attached to the instance but one of the
|
||||
# post-attach operations failed.
|
||||
self.detach_volume(context,
|
||||
connection_info,
|
||||
instance,
|
||||
update_device_metadata)
|
||||
raise exception.VolumeAttachFailed(
|
||||
volume_id=connection_info['serial'],
|
||||
reason=ex)
|
||||
@ -126,22 +153,32 @@ class VolumeOps(object):
|
||||
"Tries left: %(tries_left)s.",
|
||||
{'connection_info': strutils.mask_dict_password(
|
||||
connection_info),
|
||||
'instance_name': instance_name,
|
||||
'instance_name': instance.name,
|
||||
'tries_left': tries_left})
|
||||
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
|
||||
def _attach_volume(self, connection_info, instance_name,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI):
|
||||
def _attach_volume(self, context, connection_info, instance,
|
||||
disk_bus=constants.CTRL_TYPE_SCSI,
|
||||
update_device_metadata=False):
|
||||
LOG.debug(
|
||||
"Attaching volume: %(connection_info)s to %(instance_name)s",
|
||||
{'connection_info': strutils.mask_dict_password(connection_info),
|
||||
'instance_name': instance_name})
|
||||
'instance_name': instance.name})
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.attach_volume(connection_info,
|
||||
instance_name,
|
||||
instance.name,
|
||||
disk_bus)
|
||||
|
||||
if update_device_metadata:
|
||||
# When attaching volumes to already existing instances,
|
||||
# the connection info passed to the driver is not saved
|
||||
# yet within the BDM table.
|
||||
self._block_dev_man.set_volume_bdm_connection_info(
|
||||
context, instance, connection_info)
|
||||
self._vmops.update_device_metadata(
|
||||
context, instance)
|
||||
|
||||
qos_specs = connection_info['data'].get('qos_specs') or {}
|
||||
if qos_specs:
|
||||
volume_driver.set_disk_qos_specs(connection_info,
|
||||
@ -151,16 +188,20 @@ class VolumeOps(object):
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.disconnect_volume(connection_info)
|
||||
|
||||
def detach_volume(self, connection_info, instance_name):
|
||||
def detach_volume(self, context, connection_info, instance,
|
||||
update_device_metadata=False):
|
||||
LOG.debug("Detaching volume: %(connection_info)s "
|
||||
"from %(instance_name)s",
|
||||
{'connection_info': strutils.mask_dict_password(
|
||||
connection_info),
|
||||
'instance_name': instance_name})
|
||||
'instance_name': instance.name})
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.detach_volume(connection_info, instance_name)
|
||||
volume_driver.detach_volume(connection_info, instance.name)
|
||||
volume_driver.disconnect_volume(connection_info)
|
||||
|
||||
if update_device_metadata:
|
||||
self._vmops.update_device_metadata(context, instance)
|
||||
|
||||
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)
|
||||
@ -319,6 +360,10 @@ class VolumeOps(object):
|
||||
instance.save(
|
||||
expected_task_state=[task_states.IMAGE_SNAPSHOT_PENDING])
|
||||
|
||||
def get_disk_attachment_info(self, connection_info):
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
return volume_driver.get_disk_attachment_info(connection_info)
|
||||
|
||||
|
||||
class BaseVolumeDriver(object):
|
||||
_is_block_dev = True
|
||||
@ -456,6 +501,19 @@ class BaseVolumeDriver(object):
|
||||
def delete_snapshot(self, connection_info, instance, delete_info):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_disk_attachment_info(self, connection_info):
|
||||
if self._is_block_dev:
|
||||
disk_path = None
|
||||
serial = connection_info['serial']
|
||||
else:
|
||||
disk_path = self.get_disk_resource_path(connection_info)
|
||||
serial = None
|
||||
|
||||
return self._vmutils.get_disk_attachment_info(
|
||||
disk_path,
|
||||
is_physical=self._is_block_dev,
|
||||
serial=serial)
|
||||
|
||||
|
||||
class ISCSIVolumeDriver(BaseVolumeDriver):
|
||||
_is_block_dev = True
|
||||
|
@ -62,7 +62,7 @@ def fake_db_instance(**updates):
|
||||
return db_instance
|
||||
|
||||
|
||||
def fake_instance_obj(context, **updates):
|
||||
def fake_instance_obj(context='fake-context', **updates):
|
||||
expected_attrs = updates.pop('expected_attrs', None)
|
||||
flavor = objects.Flavor(id=1, name='flavor1',
|
||||
memory_mb=256, vcpus=1,
|
||||
|
@ -12,81 +12,276 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from nova import block_device
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.virt import block_device as driver_block_device
|
||||
from os_win import constants as os_win_const
|
||||
from os_win import exceptions as os_win_exc
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from compute_hyperv.nova import block_device_manager
|
||||
from compute_hyperv.nova import constants
|
||||
from compute_hyperv.tests.unit import test_base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class BlockDeviceManagerTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for the Hyper-V BlockDeviceInfoManager class."""
|
||||
|
||||
_FAKE_CONN_INFO = {
|
||||
'serial': 'fake_volume_id'
|
||||
}
|
||||
|
||||
_FAKE_ATTACH_INFO = {
|
||||
'controller_type': constants.CTRL_TYPE_SCSI,
|
||||
'controller_addr': 0,
|
||||
'controller_slot': 1
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(BlockDeviceManagerTestCase, self).setUp()
|
||||
self._bdman = block_device_manager.BlockDeviceInfoManager()
|
||||
self._bdman._volops = mock.Mock()
|
||||
self._bdman._vmutils = mock.Mock()
|
||||
self._bdman._pathutils = mock.Mock()
|
||||
|
||||
def test_get_device_bus_scsi(self):
|
||||
bdm = {'disk_bus': constants.CTRL_TYPE_SCSI,
|
||||
'drive_addr': 0, 'ctrl_disk_addr': 2}
|
||||
self._volops = self._bdman._volops
|
||||
self._pathutils = self._bdman._pathutils
|
||||
|
||||
bus = self._bdman._get_device_bus(bdm)
|
||||
self.assertEqual('0:0:0:2', bus.address)
|
||||
@ddt.data(constants.CTRL_TYPE_SCSI, constants.CTRL_TYPE_IDE)
|
||||
def test_get_device_bus(self, controller_type):
|
||||
fake_ctrl_addr = self._FAKE_ATTACH_INFO['controller_addr']
|
||||
fake_ctrl_slot = self._FAKE_ATTACH_INFO['controller_slot']
|
||||
|
||||
def test_get_device_bus_ide(self):
|
||||
bdm = {'disk_bus': constants.CTRL_TYPE_IDE,
|
||||
'drive_addr': 0, 'ctrl_disk_addr': 1}
|
||||
bus = self._bdman._get_device_bus(
|
||||
controller_type, fake_ctrl_addr, fake_ctrl_slot)
|
||||
|
||||
bus = self._bdman._get_device_bus(bdm)
|
||||
self.assertEqual('0:1', bus.address)
|
||||
if controller_type == constants.CTRL_TYPE_SCSI:
|
||||
exp_addr = '0:0:%s:%s' % (fake_ctrl_addr, fake_ctrl_slot)
|
||||
exp_cls = objects.SCSIDeviceBus
|
||||
else:
|
||||
exp_addr = '%s:%s' % (fake_ctrl_addr, fake_ctrl_slot)
|
||||
exp_cls = objects.IDEDeviceBus
|
||||
|
||||
@staticmethod
|
||||
def _bdm_mock(**kwargs):
|
||||
bdm = mock.MagicMock(**kwargs)
|
||||
bdm.__contains__.side_effect = (
|
||||
lambda attr: getattr(bdm, attr, None) is not None)
|
||||
return bdm
|
||||
self.assertIsInstance(bus, exp_cls)
|
||||
self.assertEqual(exp_addr, bus.address)
|
||||
|
||||
@mock.patch.object(block_device_manager.objects, 'DiskMetadata')
|
||||
@ddt.data({},
|
||||
{'bdm_is_vol': False},
|
||||
{'conn_info_set': False})
|
||||
@ddt.unpack
|
||||
@mock.patch.object(driver_block_device, 'convert_volume')
|
||||
def test_get_vol_bdm_att_info(self, mock_convert_vol,
|
||||
bdm_is_vol=True,
|
||||
conn_info_set=True):
|
||||
mock_drv_bdm = (dict(connection_info=self._FAKE_CONN_INFO)
|
||||
if conn_info_set else {})
|
||||
mock_convert_vol.return_value = (mock_drv_bdm
|
||||
if bdm_is_vol
|
||||
else None)
|
||||
|
||||
self._volops.get_disk_attachment_info.return_value = (
|
||||
self._FAKE_ATTACH_INFO.copy())
|
||||
|
||||
attach_info = self._bdman._get_vol_bdm_attachment_info(
|
||||
mock.sentinel.bdm)
|
||||
|
||||
mock_convert_vol.assert_called_once_with(
|
||||
mock.sentinel.bdm)
|
||||
|
||||
if bdm_is_vol and conn_info_set:
|
||||
exp_attach_info = self._FAKE_ATTACH_INFO.copy()
|
||||
exp_attach_info['serial'] = self._FAKE_CONN_INFO['serial']
|
||||
|
||||
self._volops.get_disk_attachment_info.assert_called_once_with(
|
||||
self._FAKE_CONN_INFO)
|
||||
else:
|
||||
exp_attach_info = None
|
||||
|
||||
self._volops.get_disk_attachment_info.assert_not_called()
|
||||
|
||||
self.assertEqual(exp_attach_info, attach_info)
|
||||
|
||||
@ddt.data({},
|
||||
{'eph_name_set': False},
|
||||
{'eph_disk_exists': False})
|
||||
@ddt.unpack
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'get_bdm_connection_info')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_get_eph_bdm_attachment_info(self, mock_exists,
|
||||
mock_get_bdm_conn_info,
|
||||
eph_name_set=True,
|
||||
eph_disk_exists=True):
|
||||
fake_instance_dir = 'fake_instance_dir'
|
||||
fake_eph_name = 'eph0.vhdx'
|
||||
mock_instance = mock.Mock()
|
||||
|
||||
fake_conn_info = self._FAKE_CONN_INFO.copy()
|
||||
if eph_name_set:
|
||||
fake_conn_info['eph_filename'] = fake_eph_name
|
||||
|
||||
mock_get_bdm_conn_info.return_value = fake_conn_info
|
||||
mock_exists.return_value = eph_disk_exists
|
||||
mock_get_attach_info = self._bdman._vmutils.get_disk_attachment_info
|
||||
|
||||
self._pathutils.get_instance_dir.return_value = fake_instance_dir
|
||||
|
||||
attach_info = self._bdman._get_eph_bdm_attachment_info(
|
||||
mock_instance, mock.sentinel.bdm)
|
||||
|
||||
if eph_name_set and eph_disk_exists:
|
||||
exp_attach_info = mock_get_attach_info.return_value
|
||||
exp_eph_path = os.path.join(fake_instance_dir, fake_eph_name)
|
||||
|
||||
mock_exists.assert_called_once_with(exp_eph_path)
|
||||
mock_get_attach_info.assert_called_once_with(
|
||||
exp_eph_path,
|
||||
is_physical=False)
|
||||
else:
|
||||
exp_attach_info = None
|
||||
|
||||
mock_get_attach_info.assert_not_called()
|
||||
|
||||
self.assertEqual(exp_attach_info, attach_info)
|
||||
|
||||
mock_get_bdm_conn_info.assert_called_once_with(
|
||||
mock.sentinel.bdm)
|
||||
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_get_vol_bdm_attachment_info')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_get_eph_bdm_attachment_info')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_get_device_bus')
|
||||
@mock.patch.object(block_device_manager.objects.BlockDeviceMappingList,
|
||||
@mock.patch.object(block_device, 'new_format_is_ephemeral')
|
||||
@mock.patch.object(objects, 'DiskMetadata')
|
||||
def test_get_disk_metadata(self, mock_diskmetadata_cls,
|
||||
mock_is_eph,
|
||||
mock_get_device_bus,
|
||||
mock_get_vol_attach_info,
|
||||
mock_get_eph_attach_info,
|
||||
bdm_is_eph=False,
|
||||
bdm_is_vol=False,
|
||||
attach_info_retrieved=True):
|
||||
mock_instance = mock.Mock()
|
||||
mock_bdm = mock.Mock()
|
||||
mock_bdm.is_volume = bdm_is_vol
|
||||
|
||||
if attach_info_retrieved:
|
||||
attach_info = self._FAKE_ATTACH_INFO.copy()
|
||||
attach_info['serial'] = mock.sentinel.serial
|
||||
else:
|
||||
attach_info = None
|
||||
|
||||
mock_get_eph_attach_info.return_value = attach_info
|
||||
mock_get_vol_attach_info.return_value = attach_info
|
||||
mock_is_eph.return_value = bdm_is_eph
|
||||
|
||||
disk_metadata = self._bdman._get_disk_metadata(
|
||||
mock_instance, mock_bdm)
|
||||
|
||||
if (bdm_is_vol or bdm_is_eph) and attach_info_retrieved:
|
||||
exp_disk_meta = mock_diskmetadata_cls.return_value
|
||||
|
||||
mock_get_device_bus.assert_called_once_with(
|
||||
self._FAKE_ATTACH_INFO['controller_type'],
|
||||
self._FAKE_ATTACH_INFO['controller_addr'],
|
||||
self._FAKE_ATTACH_INFO['controller_slot'])
|
||||
mock_diskmetadata_cls.assert_called_once_with(
|
||||
bus=mock_get_device_bus.return_value,
|
||||
tags=[mock_bdm.tag],
|
||||
serial=mock.sentinel.serial)
|
||||
else:
|
||||
exp_disk_meta = None
|
||||
|
||||
mock_get_device_bus.assert_not_called()
|
||||
|
||||
self.assertEqual(exp_disk_meta, disk_metadata)
|
||||
|
||||
if bdm_is_vol:
|
||||
mock_get_vol_attach_info.assert_called_once_with(mock_bdm)
|
||||
elif bdm_is_eph:
|
||||
mock_get_eph_attach_info.assert_called_once_with(mock_instance,
|
||||
mock_bdm)
|
||||
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'_get_disk_metadata')
|
||||
@mock.patch.object(objects.BlockDeviceMappingList,
|
||||
'get_by_instance_uuid')
|
||||
def test_get_bdm_metadata(self, mock_get_by_inst_uuid, mock_get_device_bus,
|
||||
mock_DiskMetadata):
|
||||
mock_instance = mock.MagicMock()
|
||||
root_disk = {'mount_device': mock.sentinel.dev0}
|
||||
ephemeral = {'device_name': mock.sentinel.dev1}
|
||||
block_device_info = {
|
||||
'root_disk': root_disk,
|
||||
'block_device_mapping': [
|
||||
{'mount_device': mock.sentinel.dev2},
|
||||
{'mount_device': mock.sentinel.dev3},
|
||||
],
|
||||
'ephemerals': [ephemeral],
|
||||
}
|
||||
def test_get_bdm_metadata(self, mock_get_bdm_list,
|
||||
mock_get_disk_meta):
|
||||
bdms = [mock.Mock()] * 4
|
||||
disk_meta = mock.Mock()
|
||||
mock_instance = mock.Mock()
|
||||
|
||||
bdm = self._bdm_mock(device_name=mock.sentinel.dev0, tag='taggy')
|
||||
eph = self._bdm_mock(device_name=mock.sentinel.dev1, tag='ephy')
|
||||
mock_get_by_inst_uuid.return_value = [
|
||||
bdm, eph, self._bdm_mock(device_name=mock.sentinel.dev2, tag=None),
|
||||
]
|
||||
mock_get_bdm_list.return_value = bdms
|
||||
mock_get_disk_meta.side_effect = [
|
||||
None,
|
||||
exception.DiskNotFound(message='fake_err'),
|
||||
os_win_exc.DiskNotFound(message='fake_err'),
|
||||
disk_meta]
|
||||
|
||||
bdm_metadata = self._bdman.get_bdm_metadata(mock.sentinel.context,
|
||||
mock_instance,
|
||||
block_device_info)
|
||||
bdm_meta = self._bdman.get_bdm_metadata(mock.sentinel.context,
|
||||
mock_instance)
|
||||
|
||||
mock_get_by_inst_uuid.assert_called_once_with(mock.sentinel.context,
|
||||
mock_instance.uuid)
|
||||
mock_get_device_bus.assert_has_calls(
|
||||
[mock.call(root_disk), mock.call(ephemeral)], any_order=True)
|
||||
mock_DiskMetadata.assert_has_calls(
|
||||
[mock.call(bus=mock_get_device_bus.return_value, tags=[bdm.tag]),
|
||||
mock.call(bus=mock_get_device_bus.return_value, tags=[eph.tag])],
|
||||
any_order=True)
|
||||
self.assertEqual([mock_DiskMetadata.return_value] * 2, bdm_metadata)
|
||||
self.assertEqual([disk_meta], bdm_meta)
|
||||
|
||||
mock_get_bdm_list.assert_called_once_with(mock.sentinel.context,
|
||||
mock_instance.uuid)
|
||||
mock_get_disk_meta.assert_has_calls(
|
||||
[mock.call(mock_instance, bdm) for bdm in bdms])
|
||||
|
||||
@mock.patch.object(objects.BlockDeviceMapping,
|
||||
'get_by_volume_and_instance')
|
||||
def test_set_vol_bdm_conn_info(self, mock_get_bdm):
|
||||
mock_instance = mock.Mock()
|
||||
mock_bdm = mock_get_bdm.return_value
|
||||
|
||||
self._bdman.set_volume_bdm_connection_info(
|
||||
mock.sentinel.context, mock_instance, self._FAKE_CONN_INFO)
|
||||
|
||||
mock_get_bdm.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
self._FAKE_CONN_INFO['serial'],
|
||||
mock_instance.uuid)
|
||||
|
||||
self.assertEqual(self._FAKE_CONN_INFO,
|
||||
jsonutils.loads(mock_bdm.connection_info))
|
||||
mock_bdm.save.assert_called_once_with()
|
||||
|
||||
def test_get_bdm_connection_info(self):
|
||||
bdm = mock.Mock(connection_info=None)
|
||||
self.assertEqual({}, self._bdman.get_bdm_connection_info(bdm))
|
||||
|
||||
bdm = mock.Mock()
|
||||
bdm.connection_info = jsonutils.dumps(self._FAKE_CONN_INFO)
|
||||
self.assertEqual(self._FAKE_CONN_INFO,
|
||||
self._bdman.get_bdm_connection_info(bdm))
|
||||
|
||||
def test_update_bdm_conn_info(self):
|
||||
connection_info = self._FAKE_CONN_INFO.copy()
|
||||
|
||||
mock_bdm = mock.Mock()
|
||||
mock_bdm.connection_info = jsonutils.dumps(connection_info)
|
||||
|
||||
updates = dict(some_key='some_val',
|
||||
some_other_key='some_other_val')
|
||||
|
||||
self._bdman.update_bdm_connection_info(
|
||||
mock_bdm, **updates)
|
||||
|
||||
exp_connection_info = connection_info.copy()
|
||||
exp_connection_info.update(**updates)
|
||||
|
||||
self.assertEqual(exp_connection_info,
|
||||
jsonutils.loads(mock_bdm.connection_info))
|
||||
mock_bdm.save.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
def test_init_controller_slot_counter_gen1_no_configdrive(
|
||||
|
@ -207,9 +207,13 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.device_type, mock.sentinel.encryption)
|
||||
|
||||
self.driver._volumeops.attach_volume.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
mock.sentinel.connection_info,
|
||||
mock_instance.name)
|
||||
mock_instance,
|
||||
update_device_metadata=True)
|
||||
|
||||
@mock.patch('nova.context.get_admin_context',
|
||||
lambda: mock.sentinel.admin_context)
|
||||
def test_detach_volume(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
self.driver.detach_volume(
|
||||
@ -217,8 +221,10 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.sentinel.mountpoint, mock.sentinel.encryption)
|
||||
|
||||
self.driver._volumeops.detach_volume.assert_called_once_with(
|
||||
mock.sentinel.admin_context,
|
||||
mock.sentinel.connection_info,
|
||||
mock_instance.name)
|
||||
mock_instance,
|
||||
update_device_metadata=True)
|
||||
|
||||
def test_get_volume_connector(self):
|
||||
self.driver.get_volume_connector(mock.sentinel.instance)
|
||||
@ -528,6 +534,15 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
self.driver._imagecache.update.assert_called_once_with(
|
||||
mock.sentinel.context, mock.sentinel.all_instances)
|
||||
|
||||
def test_attach_interface(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
self.driver.attach_interface(
|
||||
self.context, mock_instance, mock.sentinel.image_meta,
|
||||
mock.sentinel.vif)
|
||||
|
||||
self.driver._vmops.attach_interface.assert_called_once_with(
|
||||
self.context, mock_instance, mock.sentinel.vif)
|
||||
|
||||
def _check_recreate_image_meta(self, mock_image_meta, image_ref='',
|
||||
instance_img_ref=''):
|
||||
system_meta = {'image_base_image_ref': instance_img_ref}
|
||||
|
@ -382,20 +382,19 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
@mock.patch.object(vmops.objects, 'InstanceDeviceMetadata')
|
||||
@mock.patch.object(vmops.VMOps, '_get_vif_metadata')
|
||||
def test_save_device_metadata(self, mock_get_vif_metadata,
|
||||
mock_InstanceDeviceMetadata):
|
||||
def test_update_device_metadata(self, mock_get_vif_metadata,
|
||||
mock_InstanceDeviceMetadata):
|
||||
mock_instance = mock.MagicMock()
|
||||
mock_get_vif_metadata.return_value = [mock.sentinel.vif_metadata]
|
||||
self._vmops._block_dev_man.get_bdm_metadata.return_value = [
|
||||
mock.sentinel.bdm_metadata]
|
||||
|
||||
self._vmops._save_device_metadata(self.context, mock_instance,
|
||||
mock.sentinel.block_device_info)
|
||||
self._vmops.update_device_metadata(self.context, mock_instance)
|
||||
|
||||
mock_get_vif_metadata.assert_called_once_with(self.context,
|
||||
mock_instance.uuid)
|
||||
self._vmops._block_dev_man.get_bdm_metadata.assert_called_once_with(
|
||||
self.context, mock_instance, mock.sentinel.block_device_info)
|
||||
self.context, mock_instance)
|
||||
|
||||
expected_metadata = [mock.sentinel.vif_metadata,
|
||||
mock.sentinel.bdm_metadata]
|
||||
@ -422,7 +421,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps.attach_config_drive')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps._create_config_drive')
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps._save_device_metadata')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps.update_device_metadata')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps.create_instance')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps.get_image_vm_generation')
|
||||
@mock.patch('compute_hyperv.nova.vmops.VMOps._create_ephemerals')
|
||||
@ -437,7 +436,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_delete_disk_files,
|
||||
mock_create_root_device,
|
||||
mock_create_ephemerals, mock_get_image_vm_gen,
|
||||
mock_create_instance, mock_save_device_metadata,
|
||||
mock_create_instance, mock_update_device_metadata,
|
||||
mock_configdrive_required,
|
||||
mock_create_config_drive, mock_attach_config_drive,
|
||||
mock_set_boot_order,
|
||||
@ -494,8 +493,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
block_device_info, fake_vm_gen, mock_image_meta)
|
||||
mock_plug_vifs.assert_called_once_with(mock_instance,
|
||||
mock.sentinel.network_info)
|
||||
mock_save_device_metadata.assert_called_once_with(
|
||||
self.context, mock_instance, block_device_info)
|
||||
mock_update_device_metadata.assert_called_once_with(
|
||||
self.context, mock_instance)
|
||||
mock_configdrive_required.assert_called_once_with(mock_instance)
|
||||
if configdrive_required:
|
||||
mock_create_config_drive.assert_called_once_with(
|
||||
@ -634,12 +633,13 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_create_scsi_ctrl = self._vmops._vmutils.create_scsi_controller
|
||||
mock_create_scsi_ctrl.assert_called_once_with(mock_instance.name)
|
||||
|
||||
mock_attach_root_device.assert_called_once_with(mock_instance.name,
|
||||
root_device_info)
|
||||
mock_attach_root_device.assert_called_once_with(
|
||||
self.context, mock_instance, 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.context, block_device_info['block_device_mapping'],
|
||||
mock_instance)
|
||||
|
||||
mock_get_port_settings.assert_called_with(mock.sentinel.image_meta)
|
||||
mock_create_pipes.assert_called_once_with(
|
||||
@ -822,10 +822,12 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
'connection_info': mock.sentinel.CONN_INFO,
|
||||
'disk_bus': constants.CTRL_TYPE_IDE}
|
||||
|
||||
self._vmops._attach_root_device(mock_instance.name, root_device_info)
|
||||
self._vmops._attach_root_device(self.context,
|
||||
mock_instance, root_device_info)
|
||||
|
||||
mock_attach_volume.assert_called_once_with(
|
||||
root_device_info['connection_info'], mock_instance.name,
|
||||
self.context,
|
||||
root_device_info['connection_info'], mock_instance,
|
||||
disk_bus=root_device_info['disk_bus'])
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_attach_drive')
|
||||
@ -838,7 +840,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 1}
|
||||
|
||||
self._vmops._attach_root_device(mock_instance.name, root_device_info)
|
||||
self._vmops._attach_root_device(
|
||||
self.context, mock_instance, root_device_info)
|
||||
|
||||
mock_attach_drive.assert_called_once_with(
|
||||
mock_instance.name, root_device_info['path'],
|
||||
@ -849,28 +852,38 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
def test_attach_ephemerals(self, mock_attach_drive):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
|
||||
ephemerals = [{'path': mock.sentinel.PATH1,
|
||||
class FakeBDM(dict):
|
||||
_bdm_obj = mock.sentinel.bdm_obj
|
||||
|
||||
ephemerals = [{'path': os.path.join('eph_dir', 'eph0_path'),
|
||||
'boot_index': 1,
|
||||
'disk_bus': constants.CTRL_TYPE_IDE,
|
||||
'device_type': 'disk',
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 1},
|
||||
{'path': mock.sentinel.PATH2,
|
||||
{'path': os.path.join('eph_dir', 'eph1_path'),
|
||||
'boot_index': 2,
|
||||
'disk_bus': constants.CTRL_TYPE_SCSI,
|
||||
'device_type': 'disk',
|
||||
'drive_addr': 0,
|
||||
'ctrl_disk_addr': 0},
|
||||
{'path': None}]
|
||||
ephemerals = [FakeBDM(eph) for eph in ephemerals]
|
||||
|
||||
self._vmops.attach_ephemerals(mock_instance.name, ephemerals)
|
||||
|
||||
mock_attach_drive.assert_has_calls(
|
||||
[mock.call(mock_instance.name, mock.sentinel.PATH1, 0,
|
||||
[mock.call(mock_instance.name, ephemerals[0]['path'], 0,
|
||||
1, constants.CTRL_TYPE_IDE, constants.DISK),
|
||||
mock.call(mock_instance.name, mock.sentinel.PATH2, 0,
|
||||
mock.call(mock_instance.name, ephemerals[1]['path'], 0,
|
||||
0, constants.CTRL_TYPE_SCSI, constants.DISK)
|
||||
])
|
||||
mock_update_conn = (
|
||||
self._vmops._block_dev_man.update_bdm_connection_info)
|
||||
mock_update_conn.assert_has_calls(
|
||||
[mock.call(mock.sentinel.bdm_obj,
|
||||
eph_filename=os.path.basename(eph['path']))
|
||||
for eph in ephemerals if eph.get('path')])
|
||||
|
||||
def test_attach_drive_vm_to_scsi(self):
|
||||
self._vmops._attach_drive(
|
||||
@ -1717,25 +1730,31 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._test_check_hotplug_available(
|
||||
expected_result=False, windows_version=self._WIN_VERSION_6_3)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, 'update_device_metadata')
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
def test_attach_interface(self, mock_check_hotplug_available):
|
||||
def test_attach_interface(self, mock_check_hotplug_available,
|
||||
mock_update_dev_meta):
|
||||
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.attach_interface(fake_vm, fake_vif)
|
||||
self._vmops.attach_interface(
|
||||
mock.sentinel.context, 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_update_dev_meta.assert_called_once_with(
|
||||
mock.sentinel.context, fake_vm)
|
||||
|
||||
@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.sentinel.context,
|
||||
mock.MagicMock(), mock.sentinel.fake_vif)
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_check_hotplug_available')
|
||||
|
@ -27,10 +27,12 @@ from os_brick.initiator import connector
|
||||
from os_win import constants as os_win_const
|
||||
from oslo_utils import units
|
||||
|
||||
from compute_hyperv.nova import block_device_manager
|
||||
import compute_hyperv.nova.conf
|
||||
from compute_hyperv.nova import constants
|
||||
from compute_hyperv.nova import vmops
|
||||
from compute_hyperv.nova import volumeops
|
||||
from compute_hyperv.tests import fake_instance
|
||||
from compute_hyperv.tests.unit import test_base
|
||||
|
||||
CONF = compute_hyperv.nova.conf.CONF
|
||||
@ -56,6 +58,7 @@ def get_fake_connection_info(**kwargs):
|
||||
'serial': mock.sentinel.serial}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for VolumeOps class."""
|
||||
|
||||
@ -87,12 +90,14 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
|
||||
self._volumeops.attach_volumes(
|
||||
mock.sentinel.context,
|
||||
block_device_info['block_device_mapping'],
|
||||
mock.sentinel.instance_name)
|
||||
mock.sentinel.instance)
|
||||
|
||||
mock_attach_volume.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
block_device_info['block_device_mapping'][0]['connection_info'],
|
||||
mock.sentinel.instance_name)
|
||||
mock.sentinel.instance)
|
||||
|
||||
def test_fix_instance_volume_disk_paths_empty_bdm(self):
|
||||
self._volumeops.fix_instance_volume_disk_paths(
|
||||
@ -152,10 +157,24 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
fake_volume_driver.disconnect_volume.assert_called_once_with(
|
||||
block_device_mapping[0]['connection_info'])
|
||||
|
||||
@ddt.data({},
|
||||
{'attach_failed': True},
|
||||
{'update_device_metadata': True})
|
||||
@ddt.unpack
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch.object(volumeops.VolumeOps, 'detach_volume')
|
||||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def _test_attach_volume(self, mock_get_volume_driver, mock_sleep,
|
||||
attach_failed):
|
||||
@mock.patch.object(vmops.VMOps, 'update_device_metadata')
|
||||
@mock.patch.object(block_device_manager.BlockDeviceInfoManager,
|
||||
'set_volume_bdm_connection_info')
|
||||
def test_attach_volume(self, mock_set_bdm_conn_info,
|
||||
mock_update_dev_meta,
|
||||
mock_get_volume_driver,
|
||||
mock_detach,
|
||||
mock_sleep,
|
||||
attach_failed=False,
|
||||
update_device_metadata=False):
|
||||
mock_instance = fake_instance.fake_instance_obj()
|
||||
fake_conn_info = get_fake_connection_info(
|
||||
qos_specs=mock.sentinel.qos_specs)
|
||||
fake_volume_driver = mock_get_volume_driver.return_value
|
||||
@ -169,40 +188,53 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
self.assertRaises(exception.VolumeAttachFailed,
|
||||
self._volumeops.attach_volume,
|
||||
mock.sentinel.context,
|
||||
fake_conn_info,
|
||||
mock.sentinel.inst_name,
|
||||
mock.sentinel.disk_bus)
|
||||
mock_instance,
|
||||
mock.sentinel.disk_bus,
|
||||
update_device_metadata)
|
||||
else:
|
||||
self._volumeops.attach_volume(
|
||||
mock.sentinel.context,
|
||||
fake_conn_info,
|
||||
mock.sentinel.inst_name,
|
||||
mock.sentinel.disk_bus)
|
||||
mock_instance,
|
||||
mock.sentinel.disk_bus,
|
||||
update_device_metadata)
|
||||
|
||||
mock_get_volume_driver.assert_any_call(
|
||||
fake_conn_info)
|
||||
fake_volume_driver.attach_volume.assert_has_calls(
|
||||
[mock.call(fake_conn_info,
|
||||
mock.sentinel.inst_name,
|
||||
mock_instance.name,
|
||||
mock.sentinel.disk_bus)] * expected_try_count)
|
||||
fake_volume_driver.set_disk_qos_specs.assert_has_calls(
|
||||
[mock.call(fake_conn_info,
|
||||
mock.sentinel.qos_specs)] * expected_try_count)
|
||||
|
||||
if update_device_metadata:
|
||||
mock_set_bdm_conn_info.assert_has_calls(
|
||||
[mock.call(mock.sentinel.context,
|
||||
mock_instance,
|
||||
fake_conn_info)] * expected_try_count)
|
||||
mock_update_dev_meta.assert_has_calls(
|
||||
[mock.call(mock.sentinel.context,
|
||||
mock_instance)] * expected_try_count)
|
||||
else:
|
||||
mock_set_bdm_conn_info.assert_not_called()
|
||||
mock_update_dev_meta.assert_not_called()
|
||||
|
||||
if attach_failed:
|
||||
fake_volume_driver.disconnect_volume.assert_called_once_with(
|
||||
fake_conn_info)
|
||||
mock_detach.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
fake_conn_info,
|
||||
mock_instance,
|
||||
update_device_metadata)
|
||||
mock_sleep.assert_has_calls(
|
||||
[mock.call(CONF.hyperv.volume_attach_retry_interval)] *
|
||||
CONF.hyperv.volume_attach_retry_count)
|
||||
else:
|
||||
mock_sleep.assert_not_called()
|
||||
|
||||
def test_attach_volume(self):
|
||||
self._test_attach_volume(attach_failed=False)
|
||||
|
||||
def test_attach_volume_exc(self):
|
||||
self._test_attach_volume(attach_failed=True)
|
||||
|
||||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def test_disconnect_volume(self, mock_get_volume_driver):
|
||||
fake_volume_driver = mock_get_volume_driver.return_value
|
||||
@ -214,21 +246,34 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
fake_volume_driver.disconnect_volume.assert_called_once_with(
|
||||
mock.sentinel.conn_info)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def test_detach_volume(self, mock_get_volume_driver):
|
||||
@mock.patch.object(vmops.VMOps, 'update_device_metadata')
|
||||
def test_detach_volume(self, update_device_metadata,
|
||||
mock_update_dev_meta,
|
||||
mock_get_volume_driver):
|
||||
mock_instance = fake_instance.fake_instance_obj()
|
||||
fake_volume_driver = mock_get_volume_driver.return_value
|
||||
fake_conn_info = {'data': 'fake_conn_info_data'}
|
||||
|
||||
self._volumeops.detach_volume(fake_conn_info,
|
||||
mock.sentinel.inst_name)
|
||||
self._volumeops.detach_volume(mock.sentinel.context,
|
||||
fake_conn_info,
|
||||
mock_instance,
|
||||
update_device_metadata)
|
||||
|
||||
mock_get_volume_driver.assert_called_once_with(
|
||||
fake_conn_info)
|
||||
fake_volume_driver.detach_volume.assert_called_once_with(
|
||||
fake_conn_info, mock.sentinel.inst_name)
|
||||
fake_conn_info, mock_instance.name)
|
||||
fake_volume_driver.disconnect_volume.assert_called_once_with(
|
||||
fake_conn_info)
|
||||
|
||||
if update_device_metadata:
|
||||
mock_update_dev_meta.assert_called_once_with(
|
||||
mock.sentinel.context, mock_instance)
|
||||
else:
|
||||
mock_update_dev_meta.assert_not_called()
|
||||
|
||||
@mock.patch.object(connector, 'get_connector_properties')
|
||||
def test_get_volume_connector(self, mock_get_connector):
|
||||
conn = self._volumeops.get_volume_connector()
|
||||
@ -444,6 +489,19 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock.call(expected_task_state=[
|
||||
task_states.IMAGE_SNAPSHOT_PENDING])])
|
||||
|
||||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def test_get_disk_attachment_info(self, mock_get_volume_driver):
|
||||
fake_conn_info = get_fake_connection_info()
|
||||
ret_val = self._volumeops.get_disk_attachment_info(fake_conn_info)
|
||||
|
||||
mock_vol_driver = mock_get_volume_driver.return_value
|
||||
mock_vol_driver.get_disk_attachment_info.assert_called_once_with(
|
||||
fake_conn_info)
|
||||
|
||||
self.assertEqual(
|
||||
mock_vol_driver.get_disk_attachment_info.return_value,
|
||||
ret_val)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class BaseVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
@ -684,6 +742,31 @@ class BaseVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
self._base_vol_driver.set_disk_qos_specs(
|
||||
mock.sentinel.conn_info, mock.sentinel.disk_qos_spes)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(volumeops.BaseVolumeDriver,
|
||||
'get_disk_resource_path')
|
||||
def test_get_disk_attachment_info(self, is_block_dev,
|
||||
mock_get_disk_resource_path):
|
||||
connection_info = get_fake_connection_info()
|
||||
self._base_vol_driver._is_block_dev = is_block_dev
|
||||
|
||||
self._base_vol_driver.get_disk_attachment_info(connection_info)
|
||||
|
||||
if is_block_dev:
|
||||
exp_serial = connection_info['serial']
|
||||
exp_disk_res_path = None
|
||||
self.assertFalse(mock_get_disk_resource_path.called)
|
||||
else:
|
||||
exp_serial = None
|
||||
exp_disk_res_path = mock_get_disk_resource_path.return_value
|
||||
mock_get_disk_resource_path.assert_called_once_with(
|
||||
connection_info)
|
||||
|
||||
self._vmutils.get_disk_attachment_info.assert_called_once_with(
|
||||
exp_disk_res_path,
|
||||
is_physical=is_block_dev,
|
||||
serial=exp_serial)
|
||||
|
||||
|
||||
class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for Hyper-V BaseVolumeDriver class."""
|
||||
|
Loading…
Reference in New Issue
Block a user