Add iSCSI MPIO support
Until now, even if there were multiple paths on which we could access a volume, we were estabilishing a single iSCSI session. This patch changes this, so we may use all the targets/portals that we retrieve in the connection info. Also, a config option was added so that the deployer can request which initiators will be used for estabilishing the session, for example forcing some hardware initiators to be used. If none is explicitly requested, we let the iSCSI initiator service pick one. This will also require the MPIO service to be enabled and properly configured, in order to claim the disks. Change-Id: Ia5c0aa81d56bb862b3b30de4b782d9d1d15d44f5
This commit is contained in:
parent
4bbba46014
commit
af8f9928bd
|
@ -23,12 +23,12 @@ import os
|
|||
import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from nova import block_device
|
||||
from nova import exception
|
||||
from nova import utils
|
||||
from nova.virt import driver
|
||||
from os_win import exceptions as os_win_exc
|
||||
from os_win import utilsfactory
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
@ -36,9 +36,8 @@ from oslo_service import loopingcall
|
|||
from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
import six
|
||||
from six.moves import range
|
||||
|
||||
from hyperv.i18n import _, _LE, _LW
|
||||
from hyperv.i18n import _, _LI, _LE, _LW
|
||||
from hyperv.nova import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -64,6 +63,12 @@ hyper_volumeops_opts = [
|
|||
'FC disks. This requires the Multipath IO Windows '
|
||||
'feature to be enabled. MPIO must be configured to '
|
||||
'claim such devices.'),
|
||||
cfg.ListOpt('iscsi_initiator_list',
|
||||
default=[],
|
||||
help='List of iSCSI initiators that will be used for '
|
||||
'estabilishing iSCSI sessions. If none is specified, '
|
||||
'the Microsoft iSCSI initiator service will choose '
|
||||
'the initiator.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -102,9 +107,8 @@ class VolumeOps(object):
|
|||
"enabled. MPIO must be configured to claim such devices.")
|
||||
raise exception.ServiceUnavailable(err_msg)
|
||||
|
||||
def _get_volume_driver(self, driver_type=None, connection_info=None):
|
||||
if connection_info:
|
||||
driver_type = connection_info.get('driver_volume_type')
|
||||
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]
|
||||
|
@ -115,8 +119,7 @@ class VolumeOps(object):
|
|||
|
||||
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 = self._get_volume_driver(connection_info)
|
||||
|
||||
volume_connected = False
|
||||
try:
|
||||
|
@ -144,15 +147,13 @@ class VolumeOps(object):
|
|||
|
||||
def disconnect_volumes(self, block_device_info):
|
||||
mapping = driver.block_device_info_get_mapping(block_device_info)
|
||||
block_devices = self._group_block_devices_by_type(
|
||||
mapping)
|
||||
for driver_type, block_device_mapping in six.iteritems(block_devices):
|
||||
volume_driver = self._get_volume_driver(driver_type)
|
||||
volume_driver.disconnect_volumes(block_device_mapping)
|
||||
for volume in mapping:
|
||||
connection_info = volume['connection_info']
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.disconnect_volume(connection_info)
|
||||
|
||||
def detach_volume(self, connection_info, instance_name):
|
||||
volume_driver = self._get_volume_driver(
|
||||
connection_info=connection_info)
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.detach_volume(connection_info, instance_name)
|
||||
volume_driver.disconnect_volume(connection_info)
|
||||
|
||||
|
@ -201,8 +202,7 @@ class VolumeOps(object):
|
|||
mapping = driver.block_device_info_get_mapping(block_device_info)
|
||||
for vol in mapping:
|
||||
connection_info = vol['connection_info']
|
||||
volume_driver = self._get_volume_driver(
|
||||
connection_info=connection_info)
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
volume_driver.connect_volume(connection_info)
|
||||
|
||||
def parse_disk_qos_specs(self, qos_specs):
|
||||
|
@ -252,17 +252,8 @@ class VolumeOps(object):
|
|||
disk_path_mapping[disk_serial] = disk_path
|
||||
return disk_path_mapping
|
||||
|
||||
def _group_block_devices_by_type(self, block_device_mapping):
|
||||
block_devices = collections.defaultdict(list)
|
||||
for volume in block_device_mapping:
|
||||
connection_info = volume['connection_info']
|
||||
volume_type = connection_info.get('driver_volume_type')
|
||||
block_devices[volume_type].append(volume)
|
||||
return block_devices
|
||||
|
||||
def get_disk_resource_path(self, connection_info):
|
||||
volume_driver = self._get_volume_driver(
|
||||
connection_info=connection_info)
|
||||
volume_driver = self._get_volume_driver(connection_info)
|
||||
return volume_driver.get_disk_resource_path(connection_info)
|
||||
|
||||
|
||||
|
@ -270,14 +261,12 @@ class VolumeOps(object):
|
|||
class BaseVolumeDriver(object):
|
||||
def __init__(self):
|
||||
self._vmutils = utilsfactory.get_vmutils()
|
||||
self._diskutils = utilsfactory.get_diskutils()
|
||||
self._is_block_dev = True
|
||||
|
||||
def connect_volume(self, connection_info):
|
||||
pass
|
||||
|
||||
def disconnect_volumes(self, block_device_info):
|
||||
pass
|
||||
|
||||
def disconnect_volume(self, connection_info):
|
||||
pass
|
||||
|
||||
|
@ -346,133 +335,160 @@ class BaseVolumeDriver(object):
|
|||
LOG.warn(_LW("The %s Hyper-V volume driver does not support QoS. "
|
||||
"Ignoring QoS specs."), volume_type)
|
||||
|
||||
def _check_device_paths(self, device_paths):
|
||||
if len(device_paths) > 1:
|
||||
err_msg = _("Multiple disk paths were found: %s. This can "
|
||||
"occur if multipath is used and MPIO is not "
|
||||
"properly configured, thus not claiming the device "
|
||||
"paths. This issue must be addressed urgently as "
|
||||
"it can lead to data corruption.")
|
||||
raise exception.InvalidDevicePath(err_msg % device_paths)
|
||||
elif not device_paths:
|
||||
err_msg = _("Could not find the physical disk "
|
||||
"path for the requested volume.")
|
||||
raise exception.DiskNotFound(err_msg)
|
||||
|
||||
def _get_mounted_disk_path_by_dev_name(self, device_name):
|
||||
device_number = self._diskutils.get_device_number_from_device_name(
|
||||
device_name)
|
||||
mounted_disk_path = self._vmutils.get_mounted_disk_by_drive_number(
|
||||
device_number)
|
||||
return mounted_disk_path
|
||||
|
||||
|
||||
class ISCSIVolumeDriver(BaseVolumeDriver):
|
||||
def __init__(self):
|
||||
self._volutils = utilsfactory.get_iscsi_initiator_utils()
|
||||
self._initiator = self._volutils.get_iscsi_initiator()
|
||||
super(ISCSIVolumeDriver, self).__init__()
|
||||
self._iscsi_utils = utilsfactory.get_iscsi_initiator_utils()
|
||||
self._initiator_node_name = self._iscsi_utils.get_iscsi_initiator()
|
||||
|
||||
self.validate_initiators()
|
||||
|
||||
def get_volume_connector_props(self):
|
||||
props = {'initiator': self._initiator}
|
||||
props = {'initiator': self._initiator_node_name}
|
||||
return props
|
||||
|
||||
def login_storage_target(self, connection_info):
|
||||
data = connection_info['data']
|
||||
target_lun = data['target_lun']
|
||||
target_iqn = data['target_iqn']
|
||||
target_portal = data['target_portal']
|
||||
auth_method = data.get('auth_method')
|
||||
auth_username = data.get('auth_username')
|
||||
auth_password = data.get('auth_password')
|
||||
def validate_initiators(self):
|
||||
# The MS iSCSI initiator service can manage the software iSCSI
|
||||
# initiator as well as hardware initiators.
|
||||
initiator_list = CONF.hyperv.iscsi_initiator_list
|
||||
valid_initiators = True
|
||||
|
||||
if not initiator_list:
|
||||
LOG.info(_LI("No iSCSI initiator was explicitly requested. "
|
||||
"The Microsoft iSCSI initiator will choose the "
|
||||
"initiator when estabilishing sessions."))
|
||||
else:
|
||||
available_initiators = self._iscsi_utils.get_iscsi_initiators()
|
||||
for initiator in initiator_list:
|
||||
if initiator not in available_initiators:
|
||||
valid_initiators = False
|
||||
msg = _LW("The requested initiator %(req_initiator)s "
|
||||
"is not in the list of available initiators: "
|
||||
"%(avail_initiators)s.")
|
||||
LOG.warning(msg,
|
||||
dict(req_initiator=initiator,
|
||||
avail_initiators=available_initiators))
|
||||
|
||||
return valid_initiators
|
||||
|
||||
def _get_all_targets(self, connection_properties):
|
||||
if all([key in connection_properties for key in ('target_portals',
|
||||
'target_iqns',
|
||||
'target_luns')]):
|
||||
return zip(connection_properties['target_portals'],
|
||||
connection_properties['target_iqns'],
|
||||
connection_properties['target_luns'])
|
||||
|
||||
return [(connection_properties['target_portal'],
|
||||
connection_properties['target_iqn'],
|
||||
connection_properties.get('target_lun', 0))]
|
||||
|
||||
def _get_all_paths(self, connection_properties):
|
||||
initiator_list = CONF.hyperv.iscsi_initiator_list or [None]
|
||||
all_targets = self._get_all_targets(connection_properties)
|
||||
paths = [(initiator_name, target_portal, target_iqn, target_lun)
|
||||
for target_portal, target_iqn, target_lun in all_targets
|
||||
for initiator_name in initiator_list]
|
||||
return paths
|
||||
|
||||
def connect_volume(self, connection_info):
|
||||
connection_properties = connection_info['data']
|
||||
auth_method = connection_properties.get('auth_method')
|
||||
|
||||
if auth_method and auth_method.upper() != 'CHAP':
|
||||
LOG.error(_LE("Cannot log in target %(target_iqn)s. Unsupported "
|
||||
"iSCSI authentication method: %(auth_method)s."),
|
||||
{'target_iqn': target_iqn,
|
||||
'auth_method': auth_method})
|
||||
LOG.error(_LE("Unsupported iSCSI authentication "
|
||||
"method: %(auth_method)s."),
|
||||
dict(auth_method=auth_method))
|
||||
raise exception.UnsupportedBDMVolumeAuthMethod(
|
||||
auth_method=auth_method)
|
||||
|
||||
# Check if we already logged in
|
||||
if self._volutils.get_device_number_for_target(target_iqn, target_lun):
|
||||
LOG.debug("Already logged in on storage target. No need to "
|
||||
"login. Portal: %(target_portal)s, "
|
||||
"IQN: %(target_iqn)s, LUN: %(target_lun)s",
|
||||
{'target_portal': target_portal,
|
||||
'target_iqn': target_iqn, 'target_lun': target_lun})
|
||||
else:
|
||||
LOG.debug("Logging in on storage target. Portal: "
|
||||
"%(target_portal)s, IQN: %(target_iqn)s, "
|
||||
"LUN: %(target_lun)s",
|
||||
{'target_portal': target_portal,
|
||||
'target_iqn': target_iqn, 'target_lun': target_lun})
|
||||
self._volutils.login_storage_target(target_lun, target_iqn,
|
||||
target_portal, auth_username,
|
||||
auth_password)
|
||||
# Wait for the target to be mounted
|
||||
self._get_mounted_disk_from_lun(target_iqn, target_lun, True)
|
||||
volume_connected = False
|
||||
for (initiator_name,
|
||||
target_portal,
|
||||
target_iqn,
|
||||
target_lun) in self._get_all_paths(connection_properties):
|
||||
try:
|
||||
msg = _LI("Attempting to estabilish an iSCSI session to "
|
||||
"target %(target_iqn)s on portal %(target_portal)s "
|
||||
"acessing LUN %(target_lun)s using initiator "
|
||||
"%(initiator_name)s.")
|
||||
LOG.info(msg, dict(target_portal=target_portal,
|
||||
target_iqn=target_iqn,
|
||||
target_lun=target_lun,
|
||||
initiator_name=initiator_name))
|
||||
self._iscsi_utils.login_storage_target(
|
||||
target_lun=target_lun,
|
||||
target_iqn=target_iqn,
|
||||
target_portal=target_portal,
|
||||
auth_username=connection_properties.get('auth_username'),
|
||||
auth_password=connection_properties.get('auth_password'),
|
||||
mpio_enabled=CONF.hyperv.use_multipath_io,
|
||||
initiator_name=initiator_name)
|
||||
|
||||
def disconnect_volumes(self, block_device_mapping):
|
||||
iscsi_targets = collections.defaultdict(int)
|
||||
for vol in block_device_mapping:
|
||||
target_iqn = vol['connection_info']['data']['target_iqn']
|
||||
iscsi_targets[target_iqn] += 1
|
||||
volume_connected = True
|
||||
if not CONF.hyperv.use_multipath_io:
|
||||
break
|
||||
except os_win_exc.OSWinException:
|
||||
LOG.exception(_LE("Could not connect iSCSI target %s."),
|
||||
target_iqn)
|
||||
|
||||
for target_iqn, disconnected_luns in six.iteritems(iscsi_targets):
|
||||
self.logout_storage_target(target_iqn, disconnected_luns)
|
||||
if not volume_connected:
|
||||
raise exception.VolumeAttachFailed(
|
||||
_("Could not connect volume %s.") %
|
||||
connection_properties['volume_id'])
|
||||
|
||||
def disconnect_volume(self, connection_info):
|
||||
target_iqn = connection_info['data']['target_iqn']
|
||||
self.logout_storage_target(target_iqn)
|
||||
# We want to refresh the cached information first.
|
||||
self._diskutils.rescan_disks()
|
||||
|
||||
def logout_storage_target(self, target_iqn, disconnected_luns_count=1):
|
||||
total_available_luns = self._volutils.get_target_lun_count(
|
||||
target_iqn)
|
||||
for (target_portal,
|
||||
target_iqn,
|
||||
target_lun) in self._get_all_targets(connection_info['data']):
|
||||
|
||||
if total_available_luns == disconnected_luns_count:
|
||||
LOG.debug("Logging off storage target %s", target_iqn)
|
||||
self._volutils.logout_storage_target(target_iqn)
|
||||
else:
|
||||
LOG.debug("Skipping disconnecting target %s as there "
|
||||
"are LUNs still being used.", target_iqn)
|
||||
luns = self._iscsi_utils.get_target_luns(target_iqn)
|
||||
# We disconnect the target only if it does not expose other
|
||||
# luns which may be in use.
|
||||
if not luns or luns == [target_lun]:
|
||||
self._iscsi_utils.logout_storage_target(target_iqn)
|
||||
|
||||
def get_disk_resource_path(self, connection_info):
|
||||
data = connection_info['data']
|
||||
target_lun = data['target_lun']
|
||||
target_iqn = data['target_iqn']
|
||||
device_paths = set()
|
||||
connection_properties = connection_info['data']
|
||||
|
||||
# Getting the mounted disk
|
||||
return self._get_mounted_disk_from_lun(target_iqn, target_lun,
|
||||
wait_for_device=True)
|
||||
for (target_portal,
|
||||
target_iqn,
|
||||
target_lun) in self._get_all_targets(connection_properties):
|
||||
|
||||
def _get_mounted_disk_from_lun(self, target_iqn, target_lun,
|
||||
wait_for_device=False):
|
||||
# The WMI query in get_device_number_for_target can incorrectly
|
||||
# return no data when the system is under load. This issue can
|
||||
# be avoided by adding a retry.
|
||||
for i in range(CONF.hyperv.mounted_disk_query_retry_count):
|
||||
device_number = self._volutils.get_device_number_for_target(
|
||||
(device_number,
|
||||
device_path) = self._iscsi_utils.get_device_number_and_path(
|
||||
target_iqn, target_lun)
|
||||
if device_number in (None, -1):
|
||||
attempt = i + 1
|
||||
LOG.debug('Attempt %d to get device_number '
|
||||
'from get_device_number_for_target failed. '
|
||||
'Retrying...', attempt)
|
||||
time.sleep(CONF.hyperv.mounted_disk_query_retry_interval)
|
||||
else:
|
||||
break
|
||||
if device_path:
|
||||
device_paths.add(device_path)
|
||||
|
||||
if device_number in (None, -1):
|
||||
raise exception.NotFound(_('Unable to find a mounted disk for '
|
||||
'target_iqn: %s') % target_iqn)
|
||||
LOG.debug('Device number: %(device_number)s, '
|
||||
'target lun: %(target_lun)s',
|
||||
{'device_number': device_number, 'target_lun': target_lun})
|
||||
# Finding Mounted disk drive
|
||||
for i in range(0, CONF.hyperv.volume_attach_retry_count):
|
||||
mounted_disk_path = self._vmutils.get_mounted_disk_by_drive_number(
|
||||
device_number)
|
||||
if mounted_disk_path or not wait_for_device:
|
||||
break
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
|
||||
if not mounted_disk_path:
|
||||
raise exception.NotFound(_('Unable to find a mounted disk for '
|
||||
'target_iqn: %s. Please ensure that '
|
||||
'the host\'s SAN policy is set to '
|
||||
'"OfflineAll" or "OfflineShared"') %
|
||||
target_iqn)
|
||||
return mounted_disk_path
|
||||
|
||||
def get_target_from_disk_path(self, physical_drive_path):
|
||||
return self._volutils.get_target_from_disk_path(physical_drive_path)
|
||||
|
||||
def get_target_lun_count(self, target_iqn):
|
||||
return self._volutils.get_target_lun_count(target_iqn)
|
||||
|
||||
def connect_volume(self, connection_info):
|
||||
self.login_storage_target(connection_info)
|
||||
self._check_device_paths(device_paths)
|
||||
disk_path = list(device_paths)[0]
|
||||
return self._get_mounted_disk_path_by_dev_name(disk_path)
|
||||
|
||||
|
||||
def export_path_synchronized(f):
|
||||
|
@ -497,16 +513,6 @@ class SMBFSVolumeDriver(BaseVolumeDriver):
|
|||
def get_disk_resource_path(self, connection_info):
|
||||
return self._get_disk_path(connection_info)
|
||||
|
||||
def disconnect_volumes(self, block_device_mapping):
|
||||
export_paths = set()
|
||||
for vol in block_device_mapping:
|
||||
connection_info = vol['connection_info']
|
||||
export_path = self._get_export_path(connection_info)
|
||||
export_paths.add(export_path)
|
||||
|
||||
for export_path in export_paths:
|
||||
self._unmount_smb_share(export_path)
|
||||
|
||||
def disconnect_volume(self, connection_info):
|
||||
export_path = self._get_export_path(connection_info)
|
||||
self._unmount_smb_share(export_path)
|
||||
|
@ -584,6 +590,7 @@ class FCVolumeDriver(BaseVolumeDriver):
|
|||
def get_disk_resource_path(self, connection_info):
|
||||
@loopingcall.RetryDecorator(max_retry_count=10, max_sleep_time=0)
|
||||
def get_disk_path():
|
||||
disk_paths = set()
|
||||
volume_mappings = self._get_fc_volume_mappings(connection_info)
|
||||
if not volume_mappings:
|
||||
LOG.debug("Could not find FC mappings for volume "
|
||||
|
@ -598,8 +605,13 @@ class FCVolumeDriver(BaseVolumeDriver):
|
|||
for mapping in volume_mappings:
|
||||
device_name = mapping['device_name']
|
||||
if device_name:
|
||||
return self._get_mounted_disk_path_by_dev_name(
|
||||
device_name)
|
||||
disk_paths.add(device_name)
|
||||
|
||||
if disk_paths:
|
||||
self._check_device_paths(disk_paths)
|
||||
disk_path = list(disk_paths)[0]
|
||||
return self._get_mounted_disk_path_by_dev_name(
|
||||
disk_path)
|
||||
|
||||
err_msg = _("Could not find the physical disk "
|
||||
"path for the requested volume.")
|
||||
|
@ -607,13 +619,6 @@ class FCVolumeDriver(BaseVolumeDriver):
|
|||
|
||||
return get_disk_path()
|
||||
|
||||
def _get_mounted_disk_path_by_dev_name(self, device_name):
|
||||
device_number = self._vmutils.get_device_number_from_device_name(
|
||||
device_name)
|
||||
mounted_disk_path = self._vmutils.get_mounted_disk_by_drive_number(
|
||||
device_number)
|
||||
return mounted_disk_path
|
||||
|
||||
def _get_fc_volume_mappings(self, connection_info):
|
||||
# Note(lpetrut): All the WWNs returned by os-win are upper case.
|
||||
target_wwpns = [wwpn.upper()
|
||||
|
|
|
@ -18,11 +18,13 @@ import os
|
|||
import platform
|
||||
import sys
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from nova import exception
|
||||
from nova.tests.unit import fake_block_device
|
||||
from os_win import exceptions as os_win_exc
|
||||
from oslo_utils import units
|
||||
|
||||
from hyperv.nova import constants
|
||||
|
@ -32,12 +34,12 @@ from hyperv.tests.unit import test_base
|
|||
CONF = cfg.CONF
|
||||
|
||||
connection_data = {'volume_id': 'fake_vol_id',
|
||||
'target_lun': mock.sentinel.fake_lun,
|
||||
'target_iqn': mock.sentinel.fake_iqn,
|
||||
'target_portal': mock.sentinel.fake_portal,
|
||||
'target_lun': mock.sentinel.target_lun,
|
||||
'target_iqn': mock.sentinel.target_iqn,
|
||||
'target_portal': mock.sentinel.target_portal,
|
||||
'auth_method': 'chap',
|
||||
'auth_username': mock.sentinel.fake_user,
|
||||
'auth_password': mock.sentinel.fake_pass}
|
||||
'auth_username': mock.sentinel.auth_username,
|
||||
'auth_password': mock.sentinel.auth_password}
|
||||
|
||||
|
||||
def get_fake_block_dev_info():
|
||||
|
@ -167,8 +169,7 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
mock.sentinel.instance_name,
|
||||
mock.sentinel.fake_disk_bus)
|
||||
|
||||
mock_get_volume_driver.assert_called_once_with(
|
||||
connection_info=fake_conn_info)
|
||||
mock_get_volume_driver.assert_called_once_with(fake_conn_info)
|
||||
mock_volume_driver.attach_volume.assert_called_once_with(
|
||||
fake_conn_info,
|
||||
mock.sentinel.instance_name,
|
||||
|
@ -190,7 +191,7 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
mock.sentinel.instance_name)
|
||||
|
||||
mock_get_volume_driver.assert_called_once_with(
|
||||
connection_info=mock.sentinel.conn_info)
|
||||
mock.sentinel.conn_info)
|
||||
mock_volume_driver = mock_get_volume_driver.return_value
|
||||
mock_volume_driver.detach_volume.assert_called_once_with(
|
||||
mock.sentinel.conn_info, mock.sentinel.instance_name)
|
||||
|
@ -200,13 +201,16 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def test_disconnect_volumes(self, mock_get_volume_driver):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
block_device_mapping = block_device_info['block_device_mapping']
|
||||
block_device_mapping[0]['connection_info'] = {
|
||||
'driver_volume_type': mock.sentinel.fake_vol_type}
|
||||
fake_volume_driver = mock_get_volume_driver.return_value
|
||||
conn_info = block_device_info[
|
||||
'block_device_mapping'][0]['connection_info']
|
||||
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
fake_volume_driver.disconnect_volumes.assert_called_once_with(
|
||||
block_device_mapping)
|
||||
|
||||
mock_get_volume_driver.assert_called_once_with(conn_info)
|
||||
disconnect_volume = (
|
||||
mock_get_volume_driver.return_value.disconnect_volume)
|
||||
disconnect_volume.assert_called_once_with(
|
||||
conn_info)
|
||||
|
||||
@mock.patch('nova.block_device.volume_in_mapping')
|
||||
def test_ebs_root_in_block_devices(self, mock_vol_in_mapping):
|
||||
|
@ -244,13 +248,16 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
@mock.patch.object(volumeops.VolumeOps, '_get_volume_driver')
|
||||
def test_connect_volumes(self, mock_get_volume_driver):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
conn_info = block_device_info[
|
||||
'block_device_mapping'][0]['connection_info']
|
||||
|
||||
self._volumeops.connect_volumes(block_device_info)
|
||||
|
||||
init_vol_conn = (
|
||||
mock_get_volume_driver.assert_called_once_with(conn_info)
|
||||
connect_volume = (
|
||||
mock_get_volume_driver.return_value.connect_volume)
|
||||
init_vol_conn.assert_called_once_with(
|
||||
block_device_info['block_device_mapping'][0]['connection_info'])
|
||||
connect_volume.assert_called_once_with(
|
||||
conn_info)
|
||||
|
||||
@mock.patch.object(volumeops.VolumeOps,
|
||||
'get_disk_resource_path')
|
||||
|
@ -272,16 +279,6 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
self.assertEqual(expected_disk_path_mapping,
|
||||
resulted_disk_path_mapping)
|
||||
|
||||
def test_group_block_devices_by_type(self):
|
||||
block_device_map = get_fake_block_dev_info()['block_device_mapping']
|
||||
block_device_map[0]['connection_info'] = {
|
||||
'driver_volume_type': 'iscsi'}
|
||||
result = self._volumeops._group_block_devices_by_type(
|
||||
block_device_map)
|
||||
|
||||
expected = {'iscsi': [block_device_map[0]]}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_disk_qos_specs_using_iops(self):
|
||||
fake_qos_specs = {
|
||||
'total_iops_sec': 10,
|
||||
|
@ -323,8 +320,7 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase):
|
|||
resulted_disk_path = self._volumeops.get_disk_resource_path(
|
||||
fake_conn_info)
|
||||
|
||||
mock_get_volume_driver.assert_called_once_with(
|
||||
connection_info=fake_conn_info)
|
||||
mock_get_volume_driver.assert_called_once_with(fake_conn_info)
|
||||
fake_volume_driver.get_disk_resource_path.assert_called_once_with(
|
||||
fake_conn_info)
|
||||
self.assertEqual(
|
||||
|
@ -440,18 +436,52 @@ class BaseVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
mock.sentinel.instance_name,
|
||||
'fake bus')
|
||||
|
||||
def test_check_device_paths_multiple_found(self):
|
||||
device_paths = [mock.sentinel.dev_path_0, mock.sentinel.dev_path_1]
|
||||
self.assertRaises(exception.InvalidDevicePath,
|
||||
self._base_vol_driver._check_device_paths,
|
||||
device_paths)
|
||||
|
||||
def test_check_device_paths_none_found(self):
|
||||
self.assertRaises(exception.DiskNotFound,
|
||||
self._base_vol_driver._check_device_paths,
|
||||
[])
|
||||
|
||||
def test_check_device_paths_one_device_found(self):
|
||||
self._base_vol_driver._check_device_paths([mock.sentinel.dev_path])
|
||||
|
||||
def test_get_mounted_disk_by_dev_name(self):
|
||||
vmutils = self._base_vol_driver._vmutils
|
||||
diskutils = self._base_vol_driver._diskutils
|
||||
mock_get_dev_number = diskutils.get_device_number_from_device_name
|
||||
|
||||
mock_get_dev_number.return_value = mock.sentinel.dev_number
|
||||
vmutils.get_mounted_disk_by_drive_number.return_value = (
|
||||
mock.sentinel.disk_path)
|
||||
|
||||
disk_path = self._base_vol_driver._get_mounted_disk_path_by_dev_name(
|
||||
mock.sentinel.dev_name)
|
||||
|
||||
mock_get_dev_number.assert_called_once_with(mock.sentinel.dev_name)
|
||||
vmutils.get_mounted_disk_by_drive_number.assert_called_once_with(
|
||||
mock.sentinel.dev_number)
|
||||
|
||||
self.assertEqual(mock.sentinel.disk_path, disk_path)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for Hyper-V ISCSIVolumeDriver class."""
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSIVolumeDriverTestCase, self).setUp()
|
||||
self._volume_driver = volumeops.ISCSIVolumeDriver()
|
||||
self._volume_driver._vmutils = mock.MagicMock()
|
||||
self._volume_driver._volutils = mock.MagicMock()
|
||||
self._iscsi_utils = self._volume_driver._iscsi_utils
|
||||
self._diskutils = self._volume_driver._diskutils
|
||||
|
||||
def _test_get_volume_connector_props(self, initiator_present=True):
|
||||
expected_props = dict(initiator=self._volume_driver._initiator)
|
||||
expected_initiator = self._volume_driver._initiator_node_name
|
||||
expected_props = dict(initiator=expected_initiator)
|
||||
resulted_props = self._volume_driver.get_volume_connector_props()
|
||||
self.assertEqual(expected_props, resulted_props)
|
||||
|
||||
|
@ -461,149 +491,163 @@ class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
def test_get_vol_connector_props_without_initiator(self):
|
||||
self._test_get_volume_connector_props(initiator_present=False)
|
||||
|
||||
def test_login_storage_target_auth_exception(self):
|
||||
connection_info = get_fake_connection_info(
|
||||
auth_method='fake_auth_method')
|
||||
@ddt.data({'requested_initiators': [mock.sentinel.initiator_0],
|
||||
'available_initiators': [mock.sentinel.initiator_0,
|
||||
mock.sentinel.initiator_1]},
|
||||
{'requested_initiators': [mock.sentinel.initiator_0],
|
||||
'available_initiators': [mock.sentinel.initiator_1]})
|
||||
@ddt.unpack
|
||||
def test_validate_initiators(self, requested_initiators,
|
||||
available_initiators):
|
||||
self.flags(iscsi_initiator_list=requested_initiators, group='hyperv')
|
||||
self._iscsi_utils.get_iscsi_initiators.return_value = (
|
||||
available_initiators)
|
||||
|
||||
expected_valid_initiator = not (
|
||||
set(requested_initiators).difference(set(available_initiators)))
|
||||
valid_initiator = self._volume_driver.validate_initiators()
|
||||
|
||||
self.assertEqual(expected_valid_initiator, valid_initiator)
|
||||
|
||||
def test_get_all_targets_multipath(self):
|
||||
conn_props = {'target_portals': [mock.sentinel.portal0,
|
||||
mock.sentinel.portal1],
|
||||
'target_iqns': [mock.sentinel.target0,
|
||||
mock.sentinel.target1],
|
||||
'target_luns': [mock.sentinel.lun0,
|
||||
mock.sentinel.lun1]}
|
||||
expected_targets = zip(conn_props['target_portals'],
|
||||
conn_props['target_iqns'],
|
||||
conn_props['target_luns'])
|
||||
|
||||
resulted_targets = self._volume_driver._get_all_targets(conn_props)
|
||||
self.assertEqual(list(expected_targets), list(resulted_targets))
|
||||
|
||||
def test_get_all_targets_single_path(self):
|
||||
conn_props = dict(target_portal=mock.sentinel.portal,
|
||||
target_iqn=mock.sentinel.target,
|
||||
target_lun=mock.sentinel.lun)
|
||||
expected_targets = [
|
||||
(mock.sentinel.portal, mock.sentinel.target, mock.sentinel.lun)]
|
||||
resulted_targets = self._volume_driver._get_all_targets(conn_props)
|
||||
self.assertEqual(expected_targets, resulted_targets)
|
||||
|
||||
@ddt.data([mock.sentinel.initiator_1, mock.sentinel.initiator_2], [])
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_get_all_targets')
|
||||
def test_get_all_paths(self, requested_initiators, mock_get_all_targets):
|
||||
self.flags(iscsi_initiator_list=requested_initiators, group='hyperv')
|
||||
|
||||
target = (mock.sentinel.portal, mock.sentinel.target,
|
||||
mock.sentinel.lun)
|
||||
mock_get_all_targets.return_value = [target]
|
||||
|
||||
paths = self._volume_driver._get_all_paths(mock.sentinel.conn_props)
|
||||
|
||||
expected_initiators = requested_initiators or [None]
|
||||
expected_paths = [(initiator, ) + target
|
||||
for initiator in expected_initiators]
|
||||
|
||||
self.assertEqual(expected_paths, paths)
|
||||
mock_get_all_targets.assert_called_once_with(mock.sentinel.conn_props)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_get_all_paths')
|
||||
def test_connect_volume(self, use_multipath,
|
||||
mock_get_all_paths):
|
||||
self.flags(use_multipath_io=use_multipath, group='hyperv')
|
||||
fake_paths = [(mock.sentinel.initiator_name,
|
||||
mock.sentinel.target_portal,
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun)] * 3
|
||||
|
||||
mock_get_all_paths.return_value = fake_paths
|
||||
self._iscsi_utils.login_storage_target.side_effect = [
|
||||
os_win_exc.OSWinException, None, None]
|
||||
|
||||
conn_info = get_fake_connection_info()
|
||||
conn_props = conn_info['data']
|
||||
|
||||
self._volume_driver.connect_volume(conn_info)
|
||||
|
||||
mock_get_all_paths.assert_called_once_with(conn_props)
|
||||
expected_login_attempts = 3 if use_multipath else 2
|
||||
self._iscsi_utils.login_storage_target.assert_has_calls(
|
||||
[mock.call(target_lun=mock.sentinel.target_lun,
|
||||
target_iqn=mock.sentinel.target_iqn,
|
||||
target_portal=mock.sentinel.target_portal,
|
||||
auth_username=conn_props['auth_username'],
|
||||
auth_password=conn_props['auth_password'],
|
||||
mpio_enabled=use_multipath,
|
||||
initiator_name=mock.sentinel.initiator_name)] *
|
||||
expected_login_attempts)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_get_all_paths')
|
||||
def test_connect_volume_failed(self, mock_get_all_paths):
|
||||
self.flags(use_multipath_io=True, group='hyperv')
|
||||
fake_paths = [(mock.sentinel.initiator_name,
|
||||
mock.sentinel.target_portal,
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun)] * 3
|
||||
|
||||
mock_get_all_paths.return_value = fake_paths
|
||||
self._iscsi_utils.login_storage_target.side_effect = (
|
||||
os_win_exc.OSWinException)
|
||||
|
||||
self.assertRaises(exception.VolumeAttachFailed,
|
||||
self._volume_driver.connect_volume,
|
||||
get_fake_connection_info())
|
||||
|
||||
def test_connect_volume_invalid_auth_method(self):
|
||||
conn_info = get_fake_connection_info(auth_method='fake_auth')
|
||||
self.assertRaises(exception.UnsupportedBDMVolumeAuthMethod,
|
||||
self._volume_driver.login_storage_target,
|
||||
connection_info)
|
||||
self._volume_driver.connect_volume,
|
||||
conn_info)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_get_all_targets')
|
||||
def test_disconnect_volume(self, mock_get_all_targets):
|
||||
targets = [
|
||||
(mock.sentinel.portal_0, mock.sentinel.tg_0, mock.sentinel.lun_0),
|
||||
(mock.sentinel.portal_1, mock.sentinel.tg_1, mock.sentinel.lun_1)]
|
||||
|
||||
mock_get_all_targets.return_value = targets
|
||||
self._iscsi_utils.get_target_luns.return_value = [mock.sentinel.lun_0]
|
||||
|
||||
conn_info = get_fake_connection_info()
|
||||
self._volume_driver.disconnect_volume(conn_info)
|
||||
|
||||
self._diskutils.rescan_disks.assert_called_once_with()
|
||||
mock_get_all_targets.assert_called_once_with(conn_info['data'])
|
||||
self._iscsi_utils.logout_storage_target.assert_called_once_with(
|
||||
mock.sentinel.tg_0)
|
||||
self._iscsi_utils.get_target_luns.assert_has_calls(
|
||||
[mock.call(mock.sentinel.tg_0), mock.call(mock.sentinel.tg_1)])
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_get_all_targets')
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, '_check_device_paths')
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver,
|
||||
'_get_mounted_disk_from_lun')
|
||||
def _check_login_storage_target(self, mock_get_mounted_disk_from_lun,
|
||||
dev_number):
|
||||
connection_info = get_fake_connection_info()
|
||||
login_target = self._volume_driver._volutils.login_storage_target
|
||||
get_number = self._volume_driver._volutils.get_device_number_for_target
|
||||
get_number.return_value = dev_number
|
||||
'_get_mounted_disk_path_by_dev_name')
|
||||
def test_get_disk_resource_path(self, mock_get_mounted_disk,
|
||||
mock_check_dev_paths,
|
||||
mock_get_all_targets):
|
||||
targets = [
|
||||
(mock.sentinel.portal_0, mock.sentinel.tg_0, mock.sentinel.lun_0),
|
||||
(mock.sentinel.portal_1, mock.sentinel.tg_1, mock.sentinel.lun_1)]
|
||||
|
||||
self._volume_driver.login_storage_target(connection_info)
|
||||
mock_get_all_targets.return_value = targets
|
||||
self._iscsi_utils.get_device_number_and_path.return_value = [
|
||||
mock.sentinel.dev_num, mock.sentinel.dev_path]
|
||||
|
||||
get_number.assert_called_once_with(mock.sentinel.fake_iqn,
|
||||
mock.sentinel.fake_lun)
|
||||
if not dev_number:
|
||||
login_target.assert_called_once_with(
|
||||
mock.sentinel.fake_lun, mock.sentinel.fake_iqn,
|
||||
mock.sentinel.fake_portal, mock.sentinel.fake_user,
|
||||
mock.sentinel.fake_pass)
|
||||
mock_get_mounted_disk_from_lun.assert_called_once_with(
|
||||
mock.sentinel.fake_iqn, mock.sentinel.fake_lun, True)
|
||||
else:
|
||||
self.assertFalse(login_target.called)
|
||||
conn_info = get_fake_connection_info()
|
||||
volume_paths = self._volume_driver.get_disk_resource_path(conn_info)
|
||||
self.assertEqual(mock_get_mounted_disk.return_value, volume_paths)
|
||||
|
||||
def test_login_storage_target_already_logged(self):
|
||||
self._check_login_storage_target(dev_number=1)
|
||||
|
||||
def test_login_storage_target(self):
|
||||
self._check_login_storage_target(dev_number=0)
|
||||
|
||||
def _check_logout_storage_target(self, disconnected_luns_count=0):
|
||||
self._volume_driver._volutils.get_target_lun_count.return_value = 1
|
||||
|
||||
self._volume_driver.logout_storage_target(
|
||||
target_iqn=mock.sentinel.fake_iqn,
|
||||
disconnected_luns_count=disconnected_luns_count)
|
||||
|
||||
logout_storage = self._volume_driver._volutils.logout_storage_target
|
||||
|
||||
if disconnected_luns_count:
|
||||
logout_storage.assert_called_once_with(mock.sentinel.fake_iqn)
|
||||
else:
|
||||
self.assertFalse(logout_storage.called)
|
||||
|
||||
def test_logout_storage_target_skip(self):
|
||||
self._check_logout_storage_target()
|
||||
|
||||
def test_logout_storage_target(self):
|
||||
self._check_logout_storage_target(disconnected_luns_count=1)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver,
|
||||
'_get_mounted_disk_from_lun')
|
||||
def test_get_disk_resource_path(self, mock_get_mounted_disk_from_lun):
|
||||
connection_info = get_fake_connection_info()
|
||||
resulted_disk_path = self._volume_driver.get_disk_resource_path(
|
||||
connection_info)
|
||||
|
||||
mock_get_mounted_disk_from_lun.assert_called_once_with(
|
||||
connection_info['data']['target_iqn'],
|
||||
connection_info['data']['target_lun'],
|
||||
wait_for_device=True)
|
||||
self.assertEqual(mock_get_mounted_disk_from_lun.return_value,
|
||||
resulted_disk_path)
|
||||
|
||||
def test_get_mounted_disk_from_lun(self):
|
||||
mock_get_device_number_for_target = (
|
||||
self._volume_driver._volutils.get_device_number_for_target)
|
||||
mock_get_device_number_for_target.return_value = 0
|
||||
|
||||
mock_get_mounted_disk = (
|
||||
self._volume_driver._vmutils.get_mounted_disk_by_drive_number)
|
||||
mock_get_mounted_disk.return_value = mock.sentinel.disk_path
|
||||
|
||||
disk = self._volume_driver._get_mounted_disk_from_lun(
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun)
|
||||
self.assertEqual(mock.sentinel.disk_path, disk)
|
||||
|
||||
def test_get_target_from_disk_path(self):
|
||||
result = self._volume_driver.get_target_from_disk_path(
|
||||
mock.sentinel.physical_drive_path)
|
||||
|
||||
mock_get_target = (
|
||||
self._volume_driver._volutils.get_target_from_disk_path)
|
||||
mock_get_target.assert_called_once_with(
|
||||
mock.sentinel.physical_drive_path)
|
||||
self.assertEqual(mock_get_target.return_value, result)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def test_get_mounted_disk_from_lun_failure(self, fake_sleep):
|
||||
self.flags(mounted_disk_query_retry_count=1, group='hyperv')
|
||||
|
||||
with mock.patch.object(self._volume_driver._volutils,
|
||||
'get_device_number_for_target') as m_device_num:
|
||||
m_device_num.side_effect = [None, -1]
|
||||
|
||||
self.assertRaises(exception.NotFound,
|
||||
self._volume_driver._get_mounted_disk_from_lun,
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, 'logout_storage_target')
|
||||
def test_disconnect_volumes(self, mock_logout_storage_target):
|
||||
block_device_info = get_fake_block_dev_info()
|
||||
connection_info = get_fake_connection_info()
|
||||
block_device_mapping = block_device_info['block_device_mapping']
|
||||
block_device_mapping[0]['connection_info'] = connection_info
|
||||
|
||||
self._volume_driver.disconnect_volumes(block_device_mapping)
|
||||
|
||||
mock_logout_storage_target.assert_called_once_with(
|
||||
mock.sentinel.fake_iqn, 1)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, 'logout_storage_target')
|
||||
def test_disconnect_volume(self, mock_logout_storage_target):
|
||||
connection_info = get_fake_connection_info()
|
||||
|
||||
self._volume_driver.disconnect_volume(connection_info)
|
||||
|
||||
mock_logout_storage_target.assert_called_once_with(
|
||||
mock.sentinel.fake_iqn)
|
||||
|
||||
def test_get_target_lun_count(self):
|
||||
result = self._volume_driver.get_target_lun_count(
|
||||
mock.sentinel.target_iqn)
|
||||
|
||||
mock_get_lun_count = self._volume_driver._volutils.get_target_lun_count
|
||||
mock_get_lun_count.assert_called_once_with(mock.sentinel.target_iqn)
|
||||
self.assertEqual(mock_get_lun_count.return_value, result)
|
||||
|
||||
@mock.patch.object(volumeops.ISCSIVolumeDriver, 'login_storage_target')
|
||||
def test_connect_volume(self, mock_login_storage_target):
|
||||
self._volume_driver.connect_volume(
|
||||
mock.sentinel.connection_info)
|
||||
mock_login_storage_target.assert_called_once_with(
|
||||
mock.sentinel.connection_info)
|
||||
mock_get_all_targets.assert_called_once_with(conn_info['data'])
|
||||
self._iscsi_utils.get_device_number_and_path.assert_has_calls(
|
||||
[mock.call(mock.sentinel.tg_0, mock.sentinel.lun_0),
|
||||
mock.call(mock.sentinel.tg_1, mock.sentinel.lun_1)])
|
||||
mock_check_dev_paths.assert_called_once_with(
|
||||
set([mock.sentinel.dev_path]))
|
||||
mock_get_mounted_disk.assert_called_once_with(mock.sentinel.dev_path)
|
||||
|
||||
|
||||
class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
||||
|
@ -684,15 +728,6 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
def test_ensure_already_mounted(self):
|
||||
self._test_ensure_mounted(is_mounted=True)
|
||||
|
||||
def test_disconnect_volumes(self):
|
||||
mock_unmount_smb_share = (
|
||||
self._volume_driver._smbutils.unmount_smb_share)
|
||||
block_device_mapping = [
|
||||
{'connection_info': self._FAKE_CONNECTION_INFO}]
|
||||
self._volume_driver.disconnect_volumes(block_device_mapping)
|
||||
mock_unmount_smb_share.assert_called_once_with(
|
||||
self._FAKE_SHARE_NORMALIZED)
|
||||
|
||||
@mock.patch.object(volumeops.SMBFSVolumeDriver, '_get_disk_path')
|
||||
def test_set_disk_qos_specs(self, mock_get_disk_path):
|
||||
self._volume_driver.set_disk_qos_specs(mock.sentinel.connection_info,
|
||||
|
@ -765,7 +800,9 @@ class FCVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
@mock.patch.object(volumeops.FCVolumeDriver,
|
||||
'_get_mounted_disk_path_by_dev_name')
|
||||
@mock.patch.object(volumeops.FCVolumeDriver, '_get_fc_volume_mappings')
|
||||
def _test_get_disk_resource_path(self, mock_get_fc_mappings,
|
||||
@mock.patch.object(volumeops.FCVolumeDriver, '_check_device_paths')
|
||||
def _test_get_disk_resource_path(self, mock_check_dev_paths,
|
||||
mock_get_fc_mappings,
|
||||
mock_get_disk_path_by_dev,
|
||||
fc_mappings_side_effect,
|
||||
expected_rescan_count,
|
||||
|
@ -777,6 +814,8 @@ class FCVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
disk_path = self._fc_driver.get_disk_resource_path(
|
||||
mock.sentinel.conn_info)
|
||||
self.assertEqual(mock.sentinel.disk_path, disk_path)
|
||||
mock_check_dev_paths.assert_called_once_with(
|
||||
set([retrieved_dev_name]))
|
||||
mock_get_disk_path_by_dev.assert_called_once_with(
|
||||
retrieved_dev_name)
|
||||
else:
|
||||
|
@ -807,23 +846,6 @@ class FCVolumeDriverTestCase(test_base.HyperVBaseTestCase):
|
|||
expected_rescan_count=0,
|
||||
retrieved_dev_name=dev_name)
|
||||
|
||||
def test_get_mounted_disk_by_dev_name(self):
|
||||
vmutils = self._fc_driver._vmutils
|
||||
vmutils.get_device_number_from_device_name.return_value = (
|
||||
mock.sentinel.dev_number)
|
||||
vmutils.get_mounted_disk_by_drive_number.return_value = (
|
||||
mock.sentinel.disk_path)
|
||||
|
||||
disk_path = self._fc_driver._get_mounted_disk_path_by_dev_name(
|
||||
mock.sentinel.dev_name)
|
||||
|
||||
vmutils.get_device_number_from_device_name.assert_called_once_with(
|
||||
mock.sentinel.dev_name)
|
||||
vmutils.get_mounted_disk_by_drive_number.assert_called_once_with(
|
||||
mock.sentinel.dev_number)
|
||||
|
||||
self.assertEqual(mock.sentinel.disk_path, disk_path)
|
||||
|
||||
@mock.patch.object(volumeops.FCVolumeDriver, '_get_fc_hba_mapping')
|
||||
def test_get_fc_volume_mappings(self, mock_get_fc_hba_mapping):
|
||||
fake_target_wwpn = 'FAKE_TARGET_WWPN'
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
hacking<0.11,>=0.10.0
|
||||
|
||||
coverage>=3.6 # Apache-2.0
|
||||
ddt>=1.0.1 # MIT
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue