cinder/cinder/tests/unit/targets/test_tgt_driver.py
Michael Dovgal d0abb60fe7 Attach/Delete volume for tgt driver race condition fix
We have race condition when remove target operation still
isn't completed and we try to run create target operation.
This patch fixed this race condition by retrying create
iscsi target operation run.

Change-Id: I151990cc5148eb3f56e27e01fce71d87da2d9759
Closes-Bug: #1335889
2016-12-26 10:15:21 +00:00

407 lines
17 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 sys
import time
import mock
from oslo_concurrency import processutils as putils
from cinder import context
from cinder import exception
from cinder import test
from cinder.tests.unit.targets import targets_fixture as tf
from cinder import utils
from cinder.volume.targets import tgt
from cinder.volume import utils as vutils
class TestTgtAdmDriver(tf.TargetDriverFixture):
def setUp(self):
super(TestTgtAdmDriver, self).setUp()
self.configuration.get = mock.Mock(side_effect=self.fake_get)
self.target = tgt.TgtAdm(root_helper=utils.get_root_helper(),
configuration=self.configuration)
self.testvol_path = \
'/dev/stack-volumes-lvmdriver-1/%s' % self.VOLUME_NAME
self.fake_iscsi_scan =\
('Target 1: %(test_vol)s\n'
' 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: %(bspath)s\n'
' Backing store flags:\n'
' Account information:\n'
' mDVpzk8cZesdahJC9h73\n'
' ACL information:\n'
' ALL"\n' % {'test_vol': self.test_vol,
'bspath': self.testvol_path})
self.patch('time.sleep')
def fake_get(self, value, default):
if value in ('iscsi_target_flags', 'iscsi_write_cache'):
return getattr(self, value, default)
def test_iscsi_protocol(self):
self.assertEqual('iscsi', self.target.iscsi_protocol)
def test_get_target(self):
with mock.patch('cinder.utils.execute',
return_value=(self.fake_iscsi_scan, None)):
iqn = self.test_vol
self.assertEqual('1', self.target._get_target(iqn))
def test_verify_backing_lun(self):
iqn = self.test_vol
with mock.patch('cinder.utils.execute',
return_value=(self.fake_iscsi_scan, None)):
self.assertTrue(self.target._verify_backing_lun(iqn, '1'))
# Test the failure case
bad_scan = self.fake_iscsi_scan.replace('LUN: 1', 'LUN: 3')
with mock.patch('cinder.utils.execute',
return_value=(bad_scan, None)):
self.assertFalse(self.target._verify_backing_lun(iqn, '1'))
@mock.patch.object(time, 'sleep')
@mock.patch('cinder.utils.execute')
def test_recreate_backing_lun(self, mock_execute, mock_sleep):
mock_execute.return_value = ('out', 'err')
self.target._recreate_backing_lun(self.test_vol, '1',
self.testvol['name'],
self.testvol_path)
expected_command = ('tgtadm', '--lld', 'iscsi', '--op', 'new',
'--mode', 'logicalunit', '--tid', '1',
'--lun', '1', '-b',
self.testvol_path)
mock_execute.assert_called_once_with(*expected_command,
run_as_root=True)
# Test the failure case
mock_execute.side_effect = putils.ProcessExecutionError
self.assertIsNone(
self.target._recreate_backing_lun(self.test_vol,
'1',
self.testvol['name'],
self.testvol_path))
def test_get_iscsi_target(self):
ctxt = context.get_admin_context()
expected = 0
self.assertEqual(expected,
self.target._get_iscsi_target(ctxt,
self.testvol['id']))
def test_get_target_and_lun(self):
lun = 1
iscsi_target = 0
ctxt = context.get_admin_context()
expected = (iscsi_target, lun)
self.assertEqual(expected,
self.target._get_target_and_lun(ctxt, self.testvol))
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
def test_create_iscsi_target(self):
with mock.patch('cinder.utils.execute', return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=lambda x: 1),\
mock.patch.object(self.target, '_verify_backing_lun',
side_effect=lambda x, y: True):
self.assertEqual(
1,
self.target.create_iscsi_target(
self.test_vol,
1,
0,
self.fake_volumes_dir))
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
def test_create_iscsi_target_content(self):
self.iscsi_target_flags = 'foo'
self.iscsi_write_cache = 'bar'
mock_open = mock.mock_open()
with mock.patch('cinder.utils.execute', return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=lambda x: 1),\
mock.patch.object(self.target, '_verify_backing_lun',
side_effect=lambda x, y: True),\
mock.patch('cinder.volume.targets.tgt.open',
mock_open, create=True):
self.assertEqual(
1,
self.target.create_iscsi_target(
self.test_vol,
1,
0,
self.testvol_path,
chap_auth=('chap_foo', 'chap_bar')))
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
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'
with mock.patch.object(self.target, '_get_target',
side_effect=lambda x: 1),\
mock.patch.object(self.target, '_verify_backing_lun',
side_effect=lambda x, y: True),\
mock.patch('cinder.utils.execute', _fake_execute):
self.assertEqual(
1,
self.target.create_iscsi_target(
self.test_vol,
1,
0,
self.fake_volumes_dir))
@mock.patch('os.path.isfile', return_value=True)
@mock.patch('os.path.exists', return_value=True)
@mock.patch('cinder.utils.execute')
@mock.patch('os.unlink', return_value=None)
def test_delete_target_not_found(self,
mock_unlink,
mock_exec,
mock_pathexists,
mock_isfile):
def _fake_execute(*args, **kwargs):
raise putils.ProcessExecutionError(
exit_code=1,
stdout='',
stderr='can\'t find the target',
cmd='tgt-admin --force --delete')
def _fake_execute_wrong_message(*args, **kwargs):
raise putils.ProcessExecutionError(
exit_code=1,
stdout='',
stderr='this is not the error you are looking for',
cmd='tgt-admin --force --delete')
mock_exec.side_effect = _fake_execute
with mock.patch.object(self.target, '_get_target', return_value=False):
self.assertIsNone(self.target.remove_iscsi_target(
1,
0,
self.VOLUME_ID,
self.VOLUME_NAME))
mock_exec.side_effect = _fake_execute_wrong_message
self.assertRaises(exception.ISCSITargetRemoveFailed,
self.target.remove_iscsi_target,
1,
0,
self.VOLUME_ID,
self.VOLUME_NAME)
@mock.patch('os.path.isfile', return_value=True)
@mock.patch('os.path.exists', return_value=True)
@mock.patch('cinder.utils.execute')
@mock.patch('os.unlink', return_value=None)
def test_delete_target_acl_not_found(self,
mock_unlink,
mock_exec,
mock_pathexists,
mock_isfile):
def _fake_execute(*args, **kwargs):
raise putils.ProcessExecutionError(
exit_code=1,
stdout='',
stderr='this access control rule does not exist',
cmd='tgt-admin --force --delete')
def _fake_execute_wrong_message(*args, **kwargs):
raise putils.ProcessExecutionError(
exit_code=1,
stdout='',
stderr='this is not the error you are looking for',
cmd='tgt-admin --force --delete')
mock_exec.side_effect = _fake_execute
with mock.patch.object(self.target, '_get_target', return_value=False):
self.assertIsNone(self.target.remove_iscsi_target(
1,
0,
self.VOLUME_ID,
self.VOLUME_NAME))
mock_exec.side_effect = _fake_execute_wrong_message
self.assertRaises(exception.ISCSITargetRemoveFailed,
self.target.remove_iscsi_target,
1,
0,
self.VOLUME_ID,
self.VOLUME_NAME)
@mock.patch.object(tgt.TgtAdm, '_get_iscsi_properties')
def test_initialize_connection(self, mock_get_iscsi):
connector = {'initiator': 'fake_init'}
# Test the normal case
mock_get_iscsi.return_value = 'foo bar'
expected_return = {'driver_volume_type': 'iscsi',
'data': 'foo bar'}
self.assertEqual(expected_return,
self.target.initialize_connection(self.testvol,
connector))
@mock.patch('cinder.utils.execute')
@mock.patch.object(tgt.TgtAdm, '_get_target')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'unlink')
def test_remove_iscsi_target(self,
mock_unlink,
mock_isfile,
mock_path_exists,
mock_get_target,
mock_execute):
# Test the failure case: path does not exist
mock_path_exists.return_value = None
self.assertIsNone(self.target.remove_iscsi_target(
0,
1,
self.testvol['id'],
self.testvol['name']))
# Test the normal case
mock_path_exists.return_value = True
mock_isfile.return_value = True
self.target.remove_iscsi_target(0,
1,
self.testvol['id'],
self.testvol['name'])
calls = [mock.call('tgt-admin', '--force', '--delete',
self.iscsi_target_prefix + self.testvol['name'],
run_as_root=True),
mock.call('tgt-admin', '--delete',
self.iscsi_target_prefix + self.testvol['name'],
run_as_root=True)]
mock_execute.assert_has_calls(calls)
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
def test_create_export(self):
expected_result = {'location': '10.9.8.7:3260,1 ' +
self.iscsi_target_prefix +
self.testvol['name'] + ' 1',
'auth': 'CHAP QZJb P68e'}
with mock.patch('cinder.utils.execute', return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=lambda x: 1),\
mock.patch.object(self.target, '_verify_backing_lun',
side_effect=lambda x, y: True),\
mock.patch.object(self.target, '_get_target_chap_auth',
side_effect=lambda x, y: None) as m_chap,\
mock.patch.object(vutils, 'generate_username',
side_effect=lambda: 'QZJb'),\
mock.patch.object(vutils, 'generate_password',
side_effect=lambda: 'P68e'):
ctxt = context.get_admin_context()
self.assertEqual(expected_result,
self.target.create_export(ctxt,
self.testvol,
self.fake_volumes_dir))
m_chap.side_effect = lambda x, y: ('otzL', '234Z')
expected_result['auth'] = ('CHAP otzL 234Z')
self.assertEqual(expected_result,
self.target.create_export(ctxt,
self.testvol,
self.fake_volumes_dir))
@mock.patch.object(tgt.TgtAdm, '_get_target_chap_auth')
@mock.patch.object(tgt.TgtAdm, 'create_iscsi_target')
def test_ensure_export(self, _mock_create, mock_get_chap):
ctxt = context.get_admin_context()
mock_get_chap.return_value = ('foo', 'bar')
self.target.ensure_export(ctxt,
self.testvol,
self.fake_volumes_dir)
_mock_create.assert_called_once_with(
self.iscsi_target_prefix + self.testvol['name'],
0, 1, self.fake_volumes_dir, ('foo', 'bar'),
check_exit_code=False,
old_name=None,
portals_ips=[self.configuration.iscsi_ip_address],
portals_port=self.configuration.iscsi_port)
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
def test_create_iscsi_target_retry(self):
with mock.patch('cinder.utils.execute', return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=[None, None, 1]) as get_target,\
mock.patch.object(self.target, '_verify_backing_lun',
side_effect=lambda x, y: True):
self.assertEqual(
1,
self.target.create_iscsi_target(
self.test_vol,
1,
0,
self.fake_volumes_dir))
# 3 - default retries count value for utils.retry
self.assertEqual(3, get_target.call_count)