
Currently, nova-compute and brick support multipath for iSCSI volume data path. It depends on response to targets discovery of the main iSCSI portal, expecting multiple portal addresses to be contained. However, some arrays only respond to discovery with a single portal address, even if secondary portals are available. In this case, the connector cannot know secondary portals and corresponding iSCSI target IQN, so cannot establish multiple sessions for the target(s). To enable the connector to login to secondary portals, cinder should tell all the portal addresses and corresponding target iqns/luns. With this patch initialize_connection API will return connection_info with multiple portal addresses/iqns/luns when multipath=True is specified in the connector info. For example: {"driver_volume_type": "iscsi", "data": {"target_portals": ["10.0.2.15:3260", "10.0.3.15:3260"], "target_iqns": ["iqn.2010-10.org.example:target-01", "iqn.2010-10.org.example:target-02"], "target_luns": [1, 2], ...}} This also modify brick/iscsi/connector.py to make the parameter recognized and to login to every target. In addition, this adds an config option 'iscsi_secondary_ip_addresses' to specify a list of secondary portal addresses for LVM iSCSI Driver. Change-Id: I2a650c8ae70189b738937eaea7e5cd28f896257c Implements: blueprint iscsi-multipath-enhancement
311 lines
12 KiB
Python
311 lines
12 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import mock
|
|
from oslo_concurrency import processutils as putils
|
|
from oslo_utils import timeutils
|
|
|
|
from cinder import context
|
|
from cinder import test
|
|
from cinder import utils
|
|
from cinder.volume import configuration as conf
|
|
from cinder.volume.targets import tgt
|
|
from cinder.volume import utils as vutils
|
|
|
|
|
|
class TestTgtAdmDriver(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestTgtAdmDriver, self).setUp()
|
|
self.configuration = conf.Configuration(None)
|
|
self.configuration.append_config_values = mock.Mock(return_value=0)
|
|
self.configuration.iscsi_ip_address = '10.9.8.7'
|
|
self.fake_volumes_dir = tempfile.mkdtemp()
|
|
self.fake_id_1 = 'ed2c1fd4-5fc0-11e4-aa15-123b93f75cba'
|
|
self.fake_id_2 = 'ed2c2222-5fc0-11e4-aa15-123b93f75cba'
|
|
self.stubs.Set(self.configuration, 'safe_get', self.fake_safe_get)
|
|
self.target = tgt.TgtAdm(root_helper=utils.get_root_helper(),
|
|
configuration=self.configuration)
|
|
self.testvol_1 =\
|
|
{'project_id': self.fake_id_1,
|
|
'name': 'testvol',
|
|
'size': 1,
|
|
'id': self.fake_id_2,
|
|
'volume_type_id': None,
|
|
'provider_location': '10.9.8.7:3260 '
|
|
'iqn.2010-10.org.openstack:'
|
|
'volume-%s 0' % self.fake_id_2,
|
|
'provider_auth': 'CHAP stack-1-a60e2611875f40199931f2'
|
|
'c76370d66b 2FE0CQ8J196R',
|
|
'provider_geometry': '512 512',
|
|
'created_at': timeutils.utcnow(),
|
|
'host': 'fake_host@lvm#lvm'}
|
|
|
|
self.expected_iscsi_properties = \
|
|
{'auth_method': 'CHAP',
|
|
'auth_password': '2FE0CQ8J196R',
|
|
'auth_username': 'stack-1-a60e2611875f40199931f2c76370d66b',
|
|
'encrypted': False,
|
|
'logical_block_size': '512',
|
|
'physical_block_size': '512',
|
|
'target_discovered': False,
|
|
'target_iqn': 'iqn.2010-10.org.openstack:volume-%s' %
|
|
self.fake_id_2,
|
|
'target_lun': 0,
|
|
'target_portal': '10.9.8.7:3260',
|
|
'volume_id': self.fake_id_2}
|
|
|
|
self.fake_iscsi_scan =\
|
|
('Target 1: iqn.2010-10.org.openstack:volume-83c2e877-feed-46be-8435-77884fe55b45\n' # noqa
|
|
' System information:\n'
|
|
' Driver: iscsi\n'
|
|
' State: ready\n'
|
|
' I_T nexus information:\n'
|
|
' LUN information:\n'
|
|
' LUN: 0\n'
|
|
' Type: controller\n'
|
|
' SCSI ID: IET 00010000\n'
|
|
' SCSI SN: beaf10\n'
|
|
' Size: 0 MB, Block size: 1\n'
|
|
' Online: Yes\n'
|
|
' Removable media: No\n'
|
|
' Prevent removal: No\n'
|
|
' Readonly: No\n'
|
|
' SWP: No\n'
|
|
' Thin-provisioning: No\n'
|
|
' Backing store type: null\n'
|
|
' Backing store path: None\n'
|
|
' Backing store flags:\n'
|
|
' LUN: 1\n'
|
|
' Type: disk\n'
|
|
' SCSI ID: IET 00010001\n'
|
|
' SCSI SN: beaf11\n'
|
|
' Size: 1074 MB, Block size: 512\n'
|
|
' Online: Yes\n'
|
|
' Removable media: No\n'
|
|
' Prevent removal: No\n'
|
|
' Readonly: No\n'
|
|
' SWP: No\n'
|
|
' Thin-provisioning: No\n'
|
|
' Backing store type: rdwr\n'
|
|
' Backing store path: /dev/stack-volumes-lvmdriver-1/volume-83c2e877-feed-46be-8435-77884fe55b45\n' # noqa
|
|
' Backing store flags:\n'
|
|
' Account information:\n'
|
|
' mDVpzk8cZesdahJC9h73\n'
|
|
' ACL information:\n'
|
|
' ALL"\n')
|
|
|
|
def fake_safe_get(self, value):
|
|
if value == 'volumes_dir':
|
|
return self.fake_volumes_dir
|
|
elif value == 'iscsi_protocol':
|
|
return self.configuration.iscsi_protocol
|
|
|
|
def test_iscsi_protocol(self):
|
|
self.assertEqual(self.target.iscsi_protocol, 'iscsi')
|
|
|
|
def test_get_target(self):
|
|
|
|
def _fake_execute(*args, **kwargs):
|
|
return self.fake_iscsi_scan, None
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute)
|
|
|
|
self.assertEqual('1',
|
|
self.target._get_target('iqn.2010-10.org.openstack:'
|
|
'volume-83c2e877-feed-46be-'
|
|
'8435-77884fe55b45'))
|
|
|
|
def test_verify_backing_lun(self):
|
|
|
|
def _fake_execute(*args, **kwargs):
|
|
return self.fake_iscsi_scan, None
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute)
|
|
|
|
self.assertTrue(self.target._verify_backing_lun(
|
|
'iqn.2010-10.org.openstack:'
|
|
'volume-83c2e877-feed-46be-'
|
|
'8435-77884fe55b45', '1'))
|
|
|
|
# Test the failure case
|
|
bad_scan = self.fake_iscsi_scan.replace('LUN: 1', 'LUN: 3')
|
|
|
|
def _fake_execute_bad_lun(*args, **kwargs):
|
|
return bad_scan, None
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute_bad_lun)
|
|
|
|
self.assertFalse(self.target._verify_backing_lun(
|
|
'iqn.2010-10.org.openstack:'
|
|
'volume-83c2e877-feed-46be-'
|
|
'8435-77884fe55b45', '1'))
|
|
|
|
def test_get_target_chap_auth(self):
|
|
persist_file =\
|
|
'<target iqn.2010-10.org.openstack:volume-83c2e877-feed-46be-8435-77884fe55b45>\n'\
|
|
' backing-store /dev/stack-volumes-lvmdriver-1/volume-83c2e877-feed-46be-8435-77884fe55b45\n'\
|
|
' driver iscsi\n'\
|
|
' incominguser otzLy2UYbYfnP4zXLG5z 234Zweo38VGBBvrpK9nt\n'\
|
|
' write-cache on\n'\
|
|
'</target>'
|
|
test_vol =\
|
|
'iqn.2010-10.org.openstack:'\
|
|
'volume-83c2e877-feed-46be-8435-77884fe55b45'
|
|
with open(os.path.join(self.fake_volumes_dir,
|
|
test_vol.split(':')[1]),
|
|
'wb') as tmp_file:
|
|
tmp_file.write(persist_file)
|
|
expected = ('otzLy2UYbYfnP4zXLG5z', '234Zweo38VGBBvrpK9nt')
|
|
self.assertEqual(expected, self.target._get_target_chap_auth(test_vol))
|
|
|
|
def test_create_iscsi_target(self):
|
|
|
|
def _fake_execute(*args, **kwargs):
|
|
return '', ''
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_get_target',
|
|
lambda x: 1)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_verify_backing_lun',
|
|
lambda x, y: True)
|
|
|
|
test_vol = 'iqn.2010-10.org.openstack:'\
|
|
'volume-83c2e877-feed-46be-8435-77884fe55b45'
|
|
self.assertEqual(
|
|
1,
|
|
self.target.create_iscsi_target(
|
|
test_vol,
|
|
1,
|
|
0,
|
|
self.fake_volumes_dir))
|
|
|
|
def test_create_iscsi_target_already_exists(self):
|
|
def _fake_execute(*args, **kwargs):
|
|
if 'update' in args:
|
|
raise putils.ProcessExecutionError(
|
|
exit_code=1,
|
|
stdout='',
|
|
stderr='target already exists',
|
|
cmd='tgtad --lld iscsi --op show --mode target')
|
|
else:
|
|
return 'fake out', 'fake err'
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_get_target',
|
|
lambda x: 1)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_verify_backing_lun',
|
|
lambda x, y: True)
|
|
|
|
test_vol = 'iqn.2010-10.org.openstack:'\
|
|
'volume-83c2e877-feed-46be-8435-77884fe55b45'
|
|
self.assertEqual(
|
|
1,
|
|
self.target.create_iscsi_target(
|
|
test_vol,
|
|
1,
|
|
0,
|
|
self.fake_volumes_dir))
|
|
|
|
def test_create_export(self):
|
|
|
|
def _fake_execute(*args, **kwargs):
|
|
return '', ''
|
|
|
|
self.stubs.Set(utils,
|
|
'execute',
|
|
_fake_execute)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_get_target',
|
|
lambda x: 1)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_verify_backing_lun',
|
|
lambda x, y: True)
|
|
|
|
self.stubs.Set(self.target,
|
|
'_get_target_chap_auth',
|
|
lambda x: None)
|
|
self.stubs.Set(vutils,
|
|
'generate_username',
|
|
lambda: 'QZJbisGmn9AL954FNF4D')
|
|
self.stubs.Set(vutils,
|
|
'generate_password',
|
|
lambda: 'P68eE7u9eFqDGexd28DQ')
|
|
|
|
expected_result = {'location': '10.9.8.7:3260,1 '
|
|
'iqn.2010-10.org.openstack:testvol 1',
|
|
'auth': 'CHAP '
|
|
'QZJbisGmn9AL954FNF4D P68eE7u9eFqDGexd28DQ'}
|
|
|
|
ctxt = context.get_admin_context()
|
|
self.assertEqual(expected_result,
|
|
self.target.create_export(ctxt,
|
|
self.testvol_1,
|
|
self.fake_volumes_dir))
|
|
|
|
self.stubs.Set(self.target,
|
|
'_get_target_chap_auth',
|
|
lambda x: ('otzLy2UYbYfnP4zXLG5z',
|
|
'234Zweo38VGBBvrpK9nt'))
|
|
|
|
expected_result['auth'] = ('CHAP '
|
|
'otzLy2UYbYfnP4zXLG5z 234Zweo38VGBBvrpK9nt')
|
|
|
|
self.assertEqual(expected_result,
|
|
self.target.create_export(ctxt,
|
|
self.testvol_1,
|
|
self.fake_volumes_dir))
|
|
|
|
def test_ensure_export(self):
|
|
ctxt = context.get_admin_context()
|
|
with mock.patch.object(self.target, 'create_iscsi_target'):
|
|
self.target.ensure_export(ctxt,
|
|
self.testvol_1,
|
|
self.fake_volumes_dir)
|
|
self.target.create_iscsi_target.assert_called_once_with(
|
|
'iqn.2010-10.org.openstack:testvol',
|
|
1, 0, self.fake_volumes_dir, None,
|
|
iscsi_write_cache='on',
|
|
check_exit_code=False,
|
|
old_name=None)
|
|
|
|
def test_iscsi_location(self):
|
|
location = self.target._iscsi_location('portal', 1, 'target', 2)
|
|
self.assertEqual('portal:3260,1 target 2', location)
|
|
|
|
location = self.target._iscsi_location('portal', 1, 'target', 2,
|
|
['portal2'])
|
|
self.assertEqual('portal:3260;portal2:3260,1 target 2', location)
|