Brick os-brick up to par with cinder brick

This brick's brick up to the same codebase as
cinder brick's patch
number: ca97a7461f0f56835a0f2dceb9afaebac756f7c3
"Enhance iSCSI multipath support"
This commit is contained in:
Walter A. Boring IV 2015-02-06 19:50:34 +00:00
parent 88afc6c17e
commit 4f4ced7d8f
2 changed files with 553 additions and 55 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import os
import socket
import time
@ -32,12 +33,35 @@ from os_brick.openstack.common import loopingcall
LOG = logging.getLogger(__name__)
synchronized = lockutils.synchronized_with_prefix('brick-')
synchronized = lockutils.synchronized_with_prefix('os-brick-')
DEVICE_SCAN_ATTEMPTS_DEFAULT = 3
def get_connector_properties(root_helper, my_ip):
"""Get the connection properties for all protocols."""
def _check_multipathd_running(root_helper, enforce_multipath):
try:
putils.execute('multipathd', 'show', 'status',
run_as_root=True, root_helper=root_helper)
except putils.ProcessExecutionError as err:
LOG.error(_LE('multipathd is not running: exit code %(err)s'),
{'err': err.exit_code})
if enforce_multipath:
raise
return False
return True
def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath):
"""Get the connection properties for all protocols.
When the connector wants to use multipath, multipath=True should be
specified. If enforce_multipath=True is specified too, an exception is
thrown when multipathd is not running. Otherwise, it falls back to
multipath=False and only the first path shown up is used.
For the compatibility reason, even if multipath=False is specified,
some cinder storage drivers may export the target for multipath, which
can be found via sendtargets discovery.
"""
iscsi = ISCSIConnector(root_helper=root_helper)
fc = linuxfc.LinuxFibreChannel(root_helper=root_helper)
@ -54,7 +78,9 @@ def get_connector_properties(root_helper, my_ip):
wwnns = fc.get_fc_wwnns()
if wwnns:
props['wwnns'] = wwnns
props['multipath'] = (multipath and
_check_multipathd_running(root_helper,
enforce_multipath))
return props
@ -191,65 +217,97 @@ class ISCSIConnector(InitiatorConnector):
super(ISCSIConnector, self).set_execute(execute)
self._linuxscsi.set_execute(execute)
def _iterate_multiple_targets(self, connection_properties, ips_iqns_luns):
for ip, iqn, lun in ips_iqns_luns:
props = copy.deepcopy(connection_properties)
props['target_portal'] = ip
props['target_iqn'] = iqn
props['target_lun'] = lun
yield props
def _multipath_targets(self, connection_properties):
return zip(connection_properties.get('target_portals', []),
connection_properties.get('target_iqns', []),
connection_properties.get('target_luns', []))
def _discover_iscsi_portals(self, connection_properties):
if all([key in connection_properties for key in ('target_portals',
'target_iqns')]):
# Use targets specified by connection_properties
return zip(connection_properties['target_portals'],
connection_properties['target_iqns'])
# Discover and return every available target
out = self._run_iscsiadm_bare(['-m',
'discovery',
'-t',
'sendtargets',
'-p',
connection_properties['target_portal']],
check_exit_code=[0, 255])[0] \
or ""
return self._get_target_portals_from_iscsiadm_output(out)
@synchronized('connect_volume')
def connect_volume(self, connection_properties):
"""Attach the volume to instance_name.
connection_properties for iSCSI must include:
target_portal - ip and optional port
target_iqn - iSCSI Qualified Name
target_lun - LUN id of the volume
target_portal(s) - ip and optional port
target_iqn(s) - iSCSI Qualified Name
target_lun(s) - LUN id of the volume
Note that plural keys may be used when use_multipath=True
"""
device_info = {'type': 'block'}
if self.use_multipath:
#multipath installed, discovering other targets if available
target_portal = connection_properties['target_portal']
out = self._run_iscsiadm_bare(['-m',
'discovery',
'-t',
'sendtargets',
'-p',
target_portal],
check_exit_code=[0, 255])[0] \
or ""
for ip, iqn in self._get_target_portals_from_iscsiadm_output(out):
props = connection_properties.copy()
for ip, iqn in self._discover_iscsi_portals(connection_properties):
props = copy.deepcopy(connection_properties)
props['target_portal'] = ip
props['target_iqn'] = iqn
self._connect_to_iscsi_portal(props)
self._rescan_iscsi()
host_devices = self._get_device_path(connection_properties)
else:
self._connect_to_iscsi_portal(connection_properties)
host_device = self._get_device_path(connection_properties)
host_devices = self._get_device_path(connection_properties)
# The /dev/disk/by-path/... node is not always present immediately
# TODO(justinsb): This retry-with-delay is a pattern, move to utils?
tries = 0
while not os.path.exists(host_device):
# Loop until at least 1 path becomes available
while all(map(lambda x: not os.path.exists(x), host_devices)):
if tries >= self.device_scan_attempts:
raise exception.VolumeDeviceNotFound(device=host_device)
raise exception.VolumeDeviceNotFound(device=host_devices)
LOG.warn(_LW("ISCSI volume not yet found at: %(host_device)s. "
LOG.warn(_LW("ISCSI volume not yet found at: %(host_devices)s. "
"Will rescan & retry. Try number: %(tries)s"),
{'host_device': host_device,
{'host_devices': host_devices,
'tries': tries})
# The rescan isn't documented as being necessary(?), but it helps
self._run_iscsiadm(connection_properties, ("--rescan",))
if self.use_multipath:
self._rescan_iscsi()
else:
self._run_iscsiadm(connection_properties, ("--rescan",))
tries = tries + 1
if not os.path.exists(host_device):
if all(map(lambda x: not os.path.exists(x), host_devices)):
time.sleep(tries ** 2)
else:
break
if tries != 0:
LOG.debug("Found iSCSI node %(host_device)s "
LOG.debug("Found iSCSI node %(host_devices)s "
"(after %(tries)s rescans)",
{'host_device': host_device, 'tries': tries})
{'host_devices': host_devices, 'tries': tries})
# Choose an accessible host device
host_device = next(dev for dev in host_devices if os.path.exists(dev))
if self.use_multipath:
#we use the multipath device instead of the single path device
@ -266,9 +324,9 @@ class ISCSIConnector(InitiatorConnector):
"""Detach the volume from instance_name.
connection_properties for iSCSI must include:
target_portal - IP and optional port
target_iqn - iSCSI Qualified Name
target_lun - LUN id of the volume
target_portal(s) - IP and optional port
target_iqn(s) - iSCSI Qualified Name
target_lun(s) - LUN id of the volume
"""
# Moved _rescan_iscsi and _rescan_multipath
# from _disconnect_volume_multipath_iscsi to here.
@ -276,19 +334,43 @@ class ISCSIConnector(InitiatorConnector):
# but before logging out, the removed devices under /dev/disk/by-path
# will reappear after rescan.
self._rescan_iscsi()
host_device = self._get_device_path(connection_properties)
multipath_device = None
if self.use_multipath:
self._rescan_multipath()
multipath_device = self._get_multipath_device_name(host_device)
host_device = multipath_device = None
host_devices = self._get_device_path(connection_properties)
# Choose an accessible host device
for dev in host_devices:
if os.path.exists(dev):
host_device = dev
multipath_device = self._get_multipath_device_name(dev)
if multipath_device:
break
if not host_device:
LOG.error(_LE("No accessible volume device: %(host_devices)s"),
{'host_devices': host_devices})
raise exception.VolumeDeviceNotFound(device=host_devices)
if multipath_device:
device_realpath = os.path.realpath(host_device)
self._linuxscsi.remove_multipath_device(device_realpath)
return self._disconnect_volume_multipath_iscsi(
connection_properties, multipath_device)
# When multiple portals/iqns/luns are specified, we need to remove
# unused devices created by logging into other LUNs' session.
ips_iqns_luns = self._multipath_targets(connection_properties)
if not ips_iqns_luns:
ips_iqns_luns = [[connection_properties['target_portal'],
connection_properties['target_iqn'],
connection_properties.get('target_lun', 0)]]
for props in self._iterate_multiple_targets(connection_properties,
ips_iqns_luns):
self._disconnect_volume_iscsi(props)
def _disconnect_volume_iscsi(self, connection_properties):
# remove the device from the scsi subsystem
# this eliminates any stale entries until logout
host_device = self._get_device_path(connection_properties)[0]
dev_name = self._linuxscsi.get_name_from_path(host_device)
if dev_name:
self._linuxscsi.remove_scsi_device(dev_name)
@ -306,11 +388,16 @@ class ISCSIConnector(InitiatorConnector):
self._disconnect_from_iscsi_portal(connection_properties)
def _get_device_path(self, connection_properties):
multipath_targets = self._multipath_targets(connection_properties)
if multipath_targets:
return ["/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" % x for x in
multipath_targets]
path = ("/dev/disk/by-path/ip-%(portal)s-iscsi-%(iqn)s-lun-%(lun)s" %
{'portal': connection_properties['target_portal'],
'iqn': connection_properties['target_iqn'],
'lun': connection_properties.get('target_lun', 0)})
return path
return [path]
def get_initiator(self):
"""Secure helper to read file as root."""
@ -370,16 +457,7 @@ class ISCSIConnector(InitiatorConnector):
# Do a discovery to find all targets.
# Targets for multiple paths for the same multipath device
# may not be the same.
out = self._run_iscsiadm_bare(['-m',
'discovery',
'-t',
'sendtargets',
'-p',
connection_properties['target_portal']],
check_exit_code=[0, 255])[0] \
or ""
ips_iqns = self._get_target_portals_from_iscsiadm_output(out)
ips_iqns = self._discover_iscsi_portals(connection_properties)
if not devices:
# disconnect if no other multipath devices
@ -499,7 +577,7 @@ class ISCSIConnector(InitiatorConnector):
def _disconnect_mpath(self, connection_properties, ips_iqns):
for ip, iqn in ips_iqns:
props = connection_properties.copy()
props = copy.deepcopy(connection_properties)
props['target_portal'] = ip
props['target_iqn'] = iqn
self._disconnect_from_iscsi_portal(props)

View File

@ -13,7 +13,9 @@
# under the License.
import os.path
import socket
import string
import tempfile
import time
import mock
@ -21,16 +23,68 @@ from oslo_concurrency import processutils as putils
import testtools
from os_brick import exception
from os_brick.i18n import _
from os_brick.i18n import _LE
from os_brick.initiator import connector
from os_brick.initiator import host_driver
from os_brick.initiator import linuxfc
from os_brick.openstack.common import log as logging
from os_brick.openstack.common import loopingcall
from os_brick.tests import base
LOG = logging.getLogger(__name__)
MY_IP = '10.0.0.1'
class ConnectorUtilsTestCase(base.TestCase):
@mock.patch.object(socket, 'gethostname', return_value='fakehost')
@mock.patch.object(connector.ISCSIConnector, 'get_initiator',
return_value='fakeinitiator')
@mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_wwpns',
return_value=None)
@mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_wwnns',
return_value=None)
def _test_brick_get_connector_properties(self, multipath,
enforce_multipath,
multipath_result,
mock_wwnns, mock_wwpns,
mock_initiator, mock_gethostname):
props_actual = connector.get_connector_properties('sudo',
MY_IP,
multipath,
enforce_multipath)
props = {'initiator': 'fakeinitiator',
'host': 'fakehost',
'ip': MY_IP,
'multipath': multipath_result}
self.assertEqual(props, props_actual)
def test_brick_get_connector_properties(self):
self._test_brick_get_connector_properties(False, False, False)
@mock.patch.object(putils, 'execute')
def test_brick_get_connector_properties_multipath(self, mock_execute):
self._test_brick_get_connector_properties(True, True, True)
mock_execute.assert_called_once_with('multipathd', 'show', 'status',
run_as_root=True,
root_helper='sudo')
@mock.patch.object(putils, 'execute',
side_effect=putils.ProcessExecutionError)
def test_brick_get_connector_properties_fallback(self, mock_execute):
self._test_brick_get_connector_properties(True, False, False)
mock_execute.assert_called_once_with('multipathd', 'show', 'status',
run_as_root=True,
root_helper='sudo')
@mock.patch.object(putils, 'execute',
side_effect=putils.ProcessExecutionError)
def test_brick_get_connector_properties_raise(self, mock_execute):
self.assertRaises(putils.ProcessExecutionError,
self._test_brick_get_connector_properties,
True, True, None)
class ConnectorTestCase(base.TestCase):
@ -123,6 +177,8 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
super(ISCSIConnectorTestCase, self).setUp()
self.connector = connector.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=False)
self.connector_with_multipath = connector.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=True)
get_name_mock = mock.Mock()
get_name_mock.return_value = "/dev/sdb"
@ -139,6 +195,17 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
}
}
def iscsi_connection_multipath(self, volume, locations, iqns, luns):
return {
'driver_volume_type': 'iscsi',
'data': {
'volume_id': volume['id'],
'target_portals': locations,
'target_iqns': iqns,
'target_luns': luns,
}
}
def test_get_initiator(self):
def initiator_no_file(*args, **kwargs):
raise putils.ProcessExecutionError('No file')
@ -166,7 +233,9 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@testtools.skipUnless(os.path.exists('/dev/disk/by-path'),
'Test requires /dev/disk/by-path')
def test_connect_volume(self):
self.stubs.Set(os.path, 'exists', lambda x: True)
exists_mock = mock.Mock()
exists_mock.return_value = True
os.path.exists = exists_mock
location = '10.0.2.15:3260'
name = 'volume-00000001'
iqn = 'iqn.2010-10.org.openstack:%s' % name
@ -234,18 +303,129 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
exists_mock = mock.Mock()
exists_mock.return_value = True
os.path.exists = exists_mock
result = self.connector_with_multipath.connect_volume(
connection_properties['data'])
expected_result = {'path': 'iqn.2010-10.org.openstack:volume-00000001',
'type': 'block'}
self.assertEqual(result, expected_result)
@mock.patch.object(os.path, 'exists', return_value=True)
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_run_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_device_name')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_iqn')
def test_connect_volume_with_multiple_portals(
self, mock_get_iqn, mock_device_name, mock_run_multipath,
mock_rescan_multipath, mock_devices, mock_exists):
location1 = '10.0.2.15:3260'
location2 = '10.0.3.15:3260'
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
fake_multipath_dev = '/dev/mapper/fake-multipath-dev'
vol = {'id': 1, 'name': name1}
connection_properties = self.iscsi_connection_multipath(
vol, [location1, location2], [iqn1, iqn2], [1, 2])
devs = ['/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location1, iqn1),
'/dev/disk/by-path/ip-%s-iscsi-%s-lun-2' % (location2, iqn2)]
mock_devices.return_value = devs
mock_device_name.return_value = fake_multipath_dev
mock_get_iqn.return_value = [iqn1, iqn2]
result = self.connector_with_multipath.connect_volume(
connection_properties['data'])
expected_result = {'path': fake_multipath_dev, 'type': 'block'}
cmd_format = 'iscsiadm -m node -T %s -p %s --%s'
expected_commands = [cmd_format % (iqn1, location1, 'login'),
cmd_format % (iqn2, location2, 'login')]
self.assertEqual(expected_result, result)
for command in expected_commands:
self.assertIn(command, self.cmds)
mock_device_name.assert_called_once_with(devs[0])
self.cmds = []
self.connector_with_multipath.disconnect_volume(
connection_properties['data'], result)
expected_commands = [cmd_format % (iqn1, location1, 'logout'),
cmd_format % (iqn2, location2, 'logout')]
for command in expected_commands:
self.assertIn(command, self.cmds)
@mock.patch.object(os.path, 'exists')
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_run_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_device_name')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_iqn')
@mock.patch.object(connector.ISCSIConnector, '_run_iscsiadm')
def test_connect_volume_with_multiple_portals_primary_error(
self, mock_iscsiadm, mock_get_iqn, mock_device_name,
mock_run_multipath, mock_rescan_multipath, mock_devices,
mock_exists):
location1 = '10.0.2.15:3260'
location2 = '10.0.3.15:3260'
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
fake_multipath_dev = '/dev/mapper/fake-multipath-dev'
vol = {'id': 1, 'name': name1}
connection_properties = self.iscsi_connection_multipath(
vol, [location1, location2], [iqn1, iqn2], [1, 2])
dev1 = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location1, iqn1)
dev2 = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-2' % (location2, iqn2)
def fake_run_iscsiadm(iscsi_properties, iscsi_command, **kwargs):
if iscsi_properties['target_portal'] == location1:
if iscsi_command == ('--login',):
raise putils.ProcessExecutionError(None, None, 21)
return mock.DEFAULT
mock_exists.side_effect = lambda x: x != dev1
mock_devices.return_value = [dev2]
mock_device_name.return_value = fake_multipath_dev
mock_get_iqn.return_value = [iqn2]
mock_iscsiadm.side_effect = fake_run_iscsiadm
props = connection_properties['data'].copy()
result = self.connector_with_multipath.connect_volume(
connection_properties['data'])
expected_result = {'path': fake_multipath_dev, 'type': 'block'}
self.assertEqual(expected_result, result)
mock_device_name.assert_called_once_with(dev2)
props['target_portal'] = location1
props['target_iqn'] = iqn1
mock_iscsiadm.assert_any_call(props, ('--login',),
check_exit_code=[0, 255])
props['target_portal'] = location2
props['target_iqn'] = iqn2
mock_iscsiadm.assert_any_call(props, ('--login',),
check_exit_code=[0, 255])
mock_iscsiadm.reset_mock()
self.connector_with_multipath.disconnect_volume(
connection_properties['data'], result)
props = connection_properties['data'].copy()
props['target_portal'] = location1
props['target_iqn'] = iqn1
mock_iscsiadm.assert_any_call(props, ('--logout',),
check_exit_code=[0, 21, 255])
props['target_portal'] = location2
props['target_iqn'] = iqn2
mock_iscsiadm.assert_any_call(props, ('--logout',),
check_exit_code=[0, 21, 255])
def test_connect_volume_with_not_found_device(self):
exists_mock = mock.Mock()
exists_mock.return_value = False
os.path.exists = exists_mock
sleep_mock = mock.Mock()
sleep_mock.return_value = None
sleep_mock.return_value = False
time.sleep = sleep_mock
location = '10.0.2.15:3260'
name = 'volume-00000001'
@ -313,7 +493,6 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
def fake_disconnect_from_iscsi_portal(properties):
result.append(properties)
iqn1 = 'iqn.2013-01.ro.com.netapp:node.netapp01'
iqn2 = 'iqn.2013-01.ro.com.netapp:node.netapp02'
iqns = [iqn1, iqn2]
@ -380,6 +559,48 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
# Target not in use by other mp devices, disconnect
self.assertEqual([fake_property], result)
def test_disconnect_volume_multipath_iscsi_with_invalid_symlink(self):
result = []
def fake_disconnect_from_iscsi_portal(properties):
result.append(properties)
portal = '10.0.0.1:3260'
name = 'volume-00000001'
iqn = 'iqn.2010-10.org.openstack:%s' % name
dev = ('ip-%s-iscsi-%s-lun-0' % (portal, iqn))
get_portals_mock = mock.Mock()
get_portals_mock.return_value = [[portal, iqn]]
self.connector._get_target_portals_from_iscsiadm_output = \
get_portals_mock
rescan_iscsi_mock = mock.Mock()
rescan_iscsi_mock.return_value = None
self.connector._rescan_iscsi = rescan_iscsi_mock
rescan_multipath_mock = mock.Mock()
rescan_multipath_mock.return_value = None
self.connector._rescan_multipath = rescan_multipath_mock
get_all_devices_mock = mock.Mock()
get_all_devices_mock.return_value = [dev, '/dev/mapper/md-1']
self.connector.driver.get_all_block_devices = get_all_devices_mock
self.connector._disconnect_from_iscsi_portal = \
fake_disconnect_from_iscsi_portal
# Simulate a broken symlink by returning False for os.path.exists(dev)
mock_exists = mock.Mock()
mock_exists.return_value = False
os.path.exists = mock_exists
fake_property = {'target_portal': portal,
'target_iqn': iqn}
self.connector._disconnect_volume_multipath_iscsi(fake_property,
'fake/multipath')
# Target not in use by other mp devices, disconnect
self.assertEqual([fake_property], result)
class FibreChannelConnectorTestCase(ConnectorTestCase):
def setUp(self):
@ -458,6 +679,7 @@ class FibreChannelConnectorTestCase(ConnectorTestCase):
get_device_info_mock = mock.Mock()
get_device_info_mock.return_value = devices['devices'][0]
self.connector._linuxscsi.get_device_info = get_device_info_mock
location = '10.0.2.15:3260'
name = 'volume-00000001'
vol = {'id': 1, 'name': name}
@ -515,7 +737,7 @@ class FakeFixedIntervalLoopingCall(object):
except loopingcall.LoopingCallDone:
return self
except Exception:
LOG.exception(_('in fixed duration looping call'))
LOG.exception(_LE('in fixed duration looping call'))
raise
@ -569,7 +791,7 @@ class AoEConnectorTestCase(ConnectorTestCase):
self.assertDictMatch(volume_info, expected_info)
def test_connect_volume_could_not_discover_path(self):
aoe_device, aoe_path = self.connector._get_aoe_info(
_aoe_device, aoe_path = self.connector._get_aoe_info(
self.connection_properties)
exists_mock = mock.Mock()
@ -615,7 +837,7 @@ class RemoteFsConnectorTestCase(ConnectorTestCase):
client = self.connector._remotefsclient
client.mount = mock.Mock()
client.get_mount_point = mock.Mock()
client.get_mount_point.return_value = "ass"
client.get_mount_point.return_value = "something"
self.connector.connect_volume(self.connection_properties)
@ -643,3 +865,201 @@ class LocalConnectorTestCase(base.TestCase):
cprops = {}
self.assertRaises(ValueError,
self.connector.connect_volume, cprops)
class HuaweiStorHyperConnectorTestCase(ConnectorTestCase):
"""Test cases for StorHyper initiator class."""
attached = False
def setUp(self):
super(HuaweiStorHyperConnectorTestCase, self).setUp()
self.fake_sdscli_file = tempfile.mktemp()
self.addCleanup(os.remove, self.fake_sdscli_file)
newefile = open(self.fake_sdscli_file, 'w')
newefile.write('test')
newefile.close()
self.connector = connector.HuaweiStorHyperConnector(
None, execute=self.fake_execute)
self.connector.cli_path = self.fake_sdscli_file
self.connector.iscliexist = True
self.connector_fail = connector.HuaweiStorHyperConnector(
None, execute=self.fake_execute_fail)
self.connector_fail.cli_path = self.fake_sdscli_file
self.connector_fail.iscliexist = True
self.connector_nocli = connector.HuaweiStorHyperConnector(
None, execute=self.fake_execute_fail)
self.connector_nocli.cli_path = self.fake_sdscli_file
self.connector_nocli.iscliexist = False
self.connection_properties = {
'access_mode': 'rw',
'qos_specs': None,
'volume_id': 'volume-b2911673-863c-4380-a5f2-e1729eecfe3f'
}
self.device_info = {'type': 'block',
'path': '/dev/vdxxx'}
HuaweiStorHyperConnectorTestCase.attached = False
def fake_execute(self, *cmd, **kwargs):
method = cmd[2]
self.cmds.append(string.join(cmd))
if 'attach' == method:
HuaweiStorHyperConnectorTestCase.attached = True
return 'ret_code=0', None
if 'querydev' == method:
if HuaweiStorHyperConnectorTestCase.attached:
return 'ret_code=0\ndev_addr=/dev/vdxxx', None
else:
return 'ret_code=1\ndev_addr=/dev/vdxxx', None
if 'detach' == method:
HuaweiStorHyperConnectorTestCase.attached = False
return 'ret_code=0', None
def fake_execute_fail(self, *cmd, **kwargs):
method = cmd[2]
self.cmds.append(string.join(cmd))
if 'attach' == method:
HuaweiStorHyperConnectorTestCase.attached = False
return 'ret_code=330151401', None
if 'querydev' == method:
if HuaweiStorHyperConnectorTestCase.attached:
return 'ret_code=0\ndev_addr=/dev/vdxxx', None
else:
return 'ret_code=1\ndev_addr=/dev/vdxxx', None
if 'detach' == method:
HuaweiStorHyperConnectorTestCase.attached = True
return 'ret_code=330155007', None
def test_connect_volume(self):
"""Test the basic connect volume case."""
retval = self.connector.connect_volume(self.connection_properties)
self.assertEqual(self.device_info, retval)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)
self.assertEqual(expected_commands, self.cmds)
def test_disconnect_volume(self):
"""Test the basic disconnect volume case."""
self.connector.connect_volume(self.connection_properties)
self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
self.connector.disconnect_volume(self.connection_properties,
self.device_info)
self.assertEqual(False, HuaweiStorHyperConnectorTestCase.attached)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c detach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)
self.assertEqual(expected_commands, self.cmds)
def test_is_volume_connected(self):
"""Test if volume connected to host case."""
self.connector.connect_volume(self.connection_properties)
self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
is_connected = self.connector.is_volume_connected(
'volume-b2911673-863c-4380-a5f2-e1729eecfe3f')
self.assertEqual(HuaweiStorHyperConnectorTestCase.attached,
is_connected)
self.connector.disconnect_volume(self.connection_properties,
self.device_info)
self.assertEqual(False, HuaweiStorHyperConnectorTestCase.attached)
is_connected = self.connector.is_volume_connected(
'volume-b2911673-863c-4380-a5f2-e1729eecfe3f')
self.assertEqual(HuaweiStorHyperConnectorTestCase.attached,
is_connected)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c detach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)
self.assertEqual(expected_commands, self.cmds)
def test__analyze_output(self):
cliout = 'ret_code=0\ndev_addr=/dev/vdxxx\nret_desc="success"'
analyze_result = {'dev_addr': '/dev/vdxxx',
'ret_desc': '"success"',
'ret_code': '0'}
result = self.connector._analyze_output(cliout)
self.assertEqual(analyze_result, result)
def test_connect_volume_fail(self):
"""Test the fail connect volume case."""
self.assertRaises(exception.BrickException,
self.connector_fail.connect_volume,
self.connection_properties)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)
self.assertEqual(expected_commands, self.cmds)
def test_disconnect_volume_fail(self):
"""Test the fail disconnect volume case."""
self.connector.connect_volume(self.connection_properties)
self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
self.assertRaises(exception.BrickException,
self.connector_fail.disconnect_volume,
self.connection_properties,
self.device_info)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c detach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)
self.assertEqual(expected_commands, self.cmds)
def test_connect_volume_nocli(self):
"""Test the fail connect volume case."""
self.assertRaises(exception.BrickException,
self.connector_nocli.connect_volume,
self.connection_properties)
def test_disconnect_volume_nocli(self):
"""Test the fail disconnect volume case."""
self.connector.connect_volume(self.connection_properties)
self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
self.assertRaises(exception.BrickException,
self.connector_nocli.disconnect_volume,
self.connection_properties,
self.device_info)
expected_commands = [self.fake_sdscli_file + ' -c attach'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
self.fake_sdscli_file + ' -c querydev'
' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
LOG.debug("self.cmds = %s." % self.cmds)
LOG.debug("expected = %s." % expected_commands)