cinder/cinder/tests/unit/targets/test_tgt_driver.py

412 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 volume_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.privsep.targets.tgt.tgtadmin_show',
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.privsep.targets.tgt.tgtadmin_show',
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.privsep.targets.tgt.tgtadmin_show',
return_value=(bad_scan, None)):
self.assertFalse(self.target._verify_backing_lun(iqn, '1'))
@mock.patch.object(time, 'sleep')
@mock.patch('cinder.privsep.targets.tgt.tgtadm_create')
def test_recreate_backing_lun(self, mock_privsep, mock_sleep):
mock_privsep.return_value = ('out', 'err')
self.target._recreate_backing_lun(self.test_vol, '1',
self.testvol['name'],
self.testvol_path)
mock_privsep.assert_called_once_with('1', self.testvol_path)
# Test the failure case
mock_privsep.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.privsep.targets.tgt.tgtadm_show',
return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=lambda x: 1),\
mock.patch('cinder.privsep.targets.tgt.tgtadmin_update',
return_value=('', '')), \
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.privsep.targets.tgt.tgtadm_show',
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.privsep.targets.tgt.tgtadmin_update',
return_value=('', '')), \
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.privsep.targets.tgt.tgtadmin_update',
return_value=('', '')), \
mock.patch('cinder.privsep.targets.tgt.tgtadm_show',
_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.privsep.targets.tgt.tgtadmin_delete')
@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.privsep.targets.tgt.tgtadmin_delete')
@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')
@mock.patch('cinder.privsep.targets.tgt.tgtadmin_delete')
def test_remove_iscsi_target(self,
mock_delete,
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'])
mock_delete.assert_called_with(
self.iscsi_target_prefix + self.testvol['name'])
@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.privsep.targets.tgt.tgtadm_show',
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('cinder.privsep.targets.tgt.tgtadmin_update',
return_value=('', '')), \
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.target_ip_address],
portals_port=self.configuration.target_port)
@test.testtools.skipIf(sys.platform == "darwin", "SKIP on OSX")
def test_create_iscsi_target_retry(self):
with mock.patch('cinder.privsep.targets.tgt.tgtadm_show',
return_value=('', '')),\
mock.patch.object(self.target, '_get_target',
side_effect=[None, None, 1]) as get_target,\
mock.patch('cinder.privsep.targets.tgt.tgtadmin_update',
return_value=('', '')), \
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)