Merge "Remove IET iSCSI target"

changes/22/866222/1
Zuul 2 months ago committed by Gerrit Code Review
commit a084c1f1d4

@ -1,85 +0,0 @@
# Copyright 2018 Red Hat, Inc
# Copyright 2017 Rackspace Australia
# Copyright 2018 Michael Still and Aptira
#
# 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.
"""
Helpers for ietadm related routines.
"""
from oslo_concurrency import processutils
import cinder.privsep
@cinder.privsep.sys_admin_pctxt.entrypoint
def new_target(name, tid):
"""Create new scsi target using specified parameters.
If the target already exists, ietadm returns
'Invalid argument' and error code '234'.
This should be ignored for ensure export case.
"""
processutils.execute('ietadm', '--op', 'new',
'--tid=%s' % tid,
'--params', 'Name=%s' % name,
check_exit_code=[0, 234])
@cinder.privsep.sys_admin_pctxt.entrypoint
def delete_target(tid):
processutils.execute('ietadm', '--op', 'delete',
'--tid=%s' % tid)
@cinder.privsep.sys_admin_pctxt.entrypoint
def force_delete_target(tid, sid, cid):
processutils.execute('ietadm', '--op', 'delete',
'--tid=%s' % tid,
'--sid=%s' % sid,
'--cid=%s' % cid)
@cinder.privsep.sys_admin_pctxt.entrypoint
def new_logicalunit(tid, lun, path, iotype):
"""Attach a new volume to scsi target as a logical unit.
If a logical unit exists on the specified target lun,
ietadm returns 'File exists' and error code '239'.
This should be ignored for ensure export case.
"""
processutils.execute('ietadm', '--op', 'new',
'--tid=%s' % tid,
'--lun=%d' % lun,
'--params',
'Path=%s,Type=%s' % (path, iotype),
check_exit_code=[0, 239])
@cinder.privsep.sys_admin_pctxt.entrypoint
def delete_logicalunit(tid, lun):
processutils.execute('ietadm', '--op', 'delete',
'--tid=%s' % tid,
'--lun=%d' % lun)
@cinder.privsep.sys_admin_pctxt.entrypoint
def new_auth(tid, type, username, password):
processutils.execute('ietadm', '--op', 'new',
'--tid=%s' % tid,
'--user',
'--params=%s=%s,Password=%s' % (type,
username,
password))

@ -1,214 +0,0 @@
# 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 contextlib
import io
from unittest import mock
from oslo_concurrency import processutils as putils
from cinder import context
from cinder import exception
from cinder.tests.unit.targets import targets_fixture as tf
from cinder import utils
from cinder.volume.targets import iet
class TestIetAdmDriver(tf.TargetDriverFixture):
def setUp(self):
super(TestIetAdmDriver, self).setUp()
self.target = iet.IetAdm(root_helper=utils.get_root_helper(),
configuration=self.configuration)
def test_get_target(self):
tmp_file = io.StringIO()
tmp_file.write(
'tid:1 name:iqn.2010-10.org.openstack:'
'volume-83c2e877-feed-46be-8435-77884fe55b45\n'
' sid:844427031282176 initiator:'
'iqn.1994-05.com.redhat:5a6894679665\n'
' cid:0 ip:10.9.8.7 state:active hd:none dd:none')
tmp_file.seek(0)
with mock.patch('builtins.open') as mock_open:
mock_open.return_value = contextlib.closing(tmp_file)
self.assertEqual('1',
self.target._get_target(
'iqn.2010-10.org.openstack:'
'volume-83c2e877-feed-46be-8435-77884fe55b45'
))
# Test the failure case: Failed to handle the config file
mock_open.side_effect = MemoryError()
self.assertRaises(MemoryError,
self.target._get_target,
'')
@mock.patch('cinder.volume.targets.iet.IetAdm._get_target',
return_value=0)
@mock.patch('cinder.privsep.targets.iet.new_target')
@mock.patch('cinder.privsep.targets.iet.new_logicalunit')
@mock.patch('os.path.exists', return_value=True)
@mock.patch('cinder.utils.temporary_chown')
@mock.patch.object(iet, 'LOG')
def test_create_iscsi_target(self, mock_log, mock_chown, mock_exists,
mock_new_logical_unit, mock_new_target,
mock_get_targ):
tmp_file = io.StringIO()
with mock.patch('builtins.open') as mock_open:
mock_open.return_value = contextlib.closing(tmp_file)
self.assertEqual(
0,
self.target.create_iscsi_target(
self.test_vol,
0,
0,
self.fake_volumes_dir))
self.assertTrue(mock_new_target.called)
self.assertTrue(mock_open.called)
self.assertTrue(mock_get_targ.called)
self.assertTrue(mock_new_logical_unit.called)
# Test the failure case: Failed to chown the config file
mock_open.side_effect = putils.ProcessExecutionError
self.assertRaises(exception.ISCSITargetCreateFailed,
self.target.create_iscsi_target,
self.test_vol,
0,
0,
self.fake_volumes_dir)
# Test the failure case: Failed to set new auth
mock_new_target.side_effect = putils.ProcessExecutionError
self.assertRaises(exception.ISCSITargetCreateFailed,
self.target.create_iscsi_target,
self.test_vol,
0,
0,
self.fake_volumes_dir)
@mock.patch('cinder.utils.execute')
@mock.patch('os.path.exists', return_value=True)
def test_update_config_file_failure(self, mock_exists, mock_execute):
# Test the failure case: conf file does not exist
mock_exists.return_value = False
mock_execute.side_effect = putils.ProcessExecutionError
self.assertRaises(exception.ISCSITargetCreateFailed,
self.target.update_config_file,
self.test_vol,
0,
self.fake_volumes_dir,
"foo bar")
@mock.patch('cinder.volume.targets.iet.IetAdm._get_target',
return_value=1)
@mock.patch('cinder.privsep.targets.iet.new_target')
@mock.patch('cinder.privsep.targets.iet.new_logicalunit')
def test_create_iscsi_target_already_exists(
self, mock_new_logical_unit, mock_new_target, mock_get_targ):
self.assertEqual(
1,
self.target.create_iscsi_target(
self.test_vol,
1,
0,
self.fake_volumes_dir))
self.assertTrue(mock_get_targ.called)
self.assertTrue(mock_new_target.called)
self.assertTrue(mock_new_logical_unit.called)
@mock.patch('cinder.volume.targets.iet.IetAdm._find_sid_cid_for_target',
return_value=None)
@mock.patch('os.path.exists', return_value=False)
@mock.patch('cinder.privsep.targets.iet.delete_logicalunit')
@mock.patch('cinder.privsep.targets.iet.delete_target')
def test_remove_iscsi_target(
self, mock_delete_target,
mock_delete_logicalunit, mock_exists, mock_find):
# Test the normal case
self.target.remove_iscsi_target(1,
0,
self.testvol['id'],
self.testvol['name'])
mock_delete_logicalunit.assert_called_once_with(1, 0)
mock_delete_target.assert_called_once_with(1)
# Test the failure case: putils.ProcessExecutionError
mock_delete_logicalunit.side_effect = putils.ProcessExecutionError
self.assertRaises(exception.ISCSITargetRemoveFailed,
self.target.remove_iscsi_target,
1,
0,
self.testvol['id'],
self.testvol['name'])
@mock.patch('cinder.privsep.targets.iet.delete_target')
def test_find_sid_cid_for_target(self, mock_delete_target):
tmp_file = io.StringIO()
tmp_file.write(
'tid:1 name:iqn.2010-10.org.openstack:'
'volume-83c2e877-feed-46be-8435-77884fe55b45\n'
' sid:844427031282176 initiator:'
'iqn.1994-05.com.redhat:5a6894679665\n'
' cid:0 ip:10.9.8.7 state:active hd:none dd:none')
tmp_file.seek(0)
with mock.patch('builtins.open') as mock_open:
mock_open.return_value = contextlib.closing(tmp_file)
self.assertEqual(('844427031282176', '0'),
self.target._find_sid_cid_for_target(
'1',
'iqn.2010-10.org.openstack:'
'volume-83c2e877-feed-46be-8435-77884fe55b45',
'volume-83c2e877-feed-46be-8435-77884fe55b45'
))
@mock.patch('cinder.volume.targets.iet.IetAdm._get_target',
return_value=1)
@mock.patch('cinder.privsep.targets.iet.new_target')
@mock.patch('cinder.privsep.targets.iet.new_logicalunit')
@mock.patch('cinder.privsep.targets.iet.new_auth')
@mock.patch.object(iet.IetAdm, '_get_target_chap_auth')
def test_create_export(
self, mock_get_chap, mock_new_auth, mock_new_logicalunit,
mock_new_target, mock_get_targ):
mock_get_chap.return_value = ('QZJbisGmn9AL954FNF4D',
'P68eE7u9eFqDGexd28DQ')
expected_result = {'location': '10.9.8.7:3260,1 '
'iqn.2010-10.org.openstack:testvol 0',
'auth': 'CHAP '
'QZJbisGmn9AL954FNF4D P68eE7u9eFqDGexd28DQ'}
ctxt = context.get_admin_context()
self.assertEqual(expected_result,
self.target.create_export(ctxt,
self.testvol,
self.fake_volumes_dir))
self.assertTrue(mock_new_logicalunit.called)
self.assertTrue(mock_new_target.called)
@mock.patch('cinder.volume.targets.iet.IetAdm._get_target_chap_auth',
return_value=None)
@mock.patch('cinder.volume.targets.iet.IetAdm._get_target',
return_value=1)
def test_ensure_export(self, mock_get_targetm, mock_get_chap):
ctxt = context.get_admin_context()
with mock.patch.object(self.target, 'create_iscsi_target'):
self.target.ensure_export(ctxt,
self.testvol,
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,
portals_ips=[self.configuration.target_ip_address],
portals_port=int(self.configuration.target_port),
check_exit_code=False,
old_name=None)

@ -1,230 +0,0 @@
# 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 re
import stat
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from oslo_log import versionutils
from cinder import exception
import cinder.privsep.targets.iet
from cinder import utils
from cinder.volume.targets import iscsi
LOG = logging.getLogger(__name__)
class IetAdm(iscsi.ISCSITarget):
VERSION = '0.1'
def __init__(self, *args, **kwargs):
super(IetAdm, self).__init__(*args, **kwargs)
self.iet_conf = self.configuration.safe_get('iet_conf')
self.iscsi_iotype = self.configuration.safe_get('iscsi_iotype')
self.auth_type = 'IncomingUser'
self.iet_sessions = '/proc/net/iet/session'
versionutils.report_deprecated_feature(
LOG,
'The IET iSCSI target is deprecated and will be removed in the '
'"V" release. It is recommended to use the LIO or TGT targets '
'instead.')
def _get_target(self, iqn):
# Find existing iSCSI target session from /proc/net/iet/session
#
# tid:2 name:iqn.2010-10.org:volume-222
# sid:562950561399296 initiator:iqn.1994-05.com:5a6894679665
# cid:0 ip:192.168.122.1 state:active hd:none dd:none
# tid:1 name:iqn.2010-10.org:volume-111
# sid:281475567911424 initiator:iqn.1994-05.com:5a6894679665
# cid:0 ip:192.168.122.1 state:active hd:none dd:none
iscsi_target = 0
try:
with open(self.iet_sessions, 'r') as f:
sessions = f.read()
except Exception:
LOG.exception("Failed to open iet session list for %s", iqn)
raise
session_list = re.split('(?m)^tid:', sessions)[1:]
for ses in session_list:
m = re.match(r'(\d+) name:(\S+)\s+', ses)
if m and iqn in m.group(2):
return m.group(1)
return iscsi_target
def _get_iscsi_target(self, context, vol_id):
pass
def _get_target_and_lun(self, context, volume):
# For ietadm dev starts at lun 0
lun = 0
# Using 0, ietadm tries to search empty tid for creating
# new iSCSI target
iscsi_target = 0
# Find existing iSCSI target based on iqn
iqn = '%svolume-%s' % (self.iscsi_target_prefix, volume['id'])
iscsi_target = self._get_target(iqn)
return iscsi_target, lun
def create_iscsi_target(self, name, tid, lun, path,
chap_auth=None, **kwargs):
config_auth = None
vol_id = name.split(':')[1]
# Check the target is already existing.
tmp_tid = self._get_target(name)
# Create a new iSCSI target. If a target already exists,
# the command returns 234, but we ignore it.
try:
cinder.privsep.targets.iet.new_target(name, tid)
tid = self._get_target(name)
cinder.privsep.targets.iet.new_logicalunit(
tid, lun, path, self._iotype(path))
if chap_auth is not None:
(username, password) = chap_auth
config_auth = ' '.join((self.auth_type,) + chap_auth)
cinder.privsep.targets.iet.new_auth(
tid, self.auth_type, username, password)
except putils.ProcessExecutionError:
LOG.exception("Failed to create iscsi target for volume "
"id:%s", vol_id)
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
# Update config file only if new scsi target is created.
if not tmp_tid:
self.update_config_file(name, tid, path, config_auth)
return tid
def update_config_file(self, name, tid, path, config_auth):
conf_file = self.iet_conf
vol_id = name.split(':')[1]
# If config file does not exist, create a blank conf file and
# add configuration for the volume on the new file.
if not os.path.exists(conf_file):
try:
utils.execute("truncate", conf_file, "--size=0",
run_as_root=True)
except putils.ProcessExecutionError:
LOG.exception("Failed to create %(conf)s for volume "
"id:%(vol_id)s",
{'conf': conf_file, 'vol_id': vol_id})
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
try:
volume_conf = """
Target %s
%s
Lun 0 Path=%s,Type=%s
""" % (name, config_auth, path, self._iotype(path))
with utils.temporary_chown(conf_file):
with open(conf_file, 'a+') as f:
f.write(volume_conf)
except Exception:
LOG.exception("Failed to update %(conf)s for volume "
"id:%(vol_id)s",
{'conf': conf_file, 'vol_id': vol_id})
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
LOG.info("Removing iscsi_target for volume: %s", vol_id)
try:
cinder.privsep.targets.iet.delete_logicalunit(tid, lun)
session_info = self._find_sid_cid_for_target(tid, vol_name, vol_id)
if session_info:
sid, cid = session_info
cinder.privsep.targets.iet.force_delete_target(tid, sid, cid)
cinder.privsep.targets.iet.delete_target(tid)
except putils.ProcessExecutionError:
LOG.exception("Failed to remove iscsi target for volume "
"id:%s", vol_id)
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
vol_uuid_file = vol_name
conf_file = self.iet_conf
if os.path.exists(conf_file):
try:
with utils.temporary_chown(conf_file):
with open(conf_file, 'r+') as iet_conf_text:
full_txt = iet_conf_text.readlines()
new_iet_conf_txt = []
count = 0
for line in full_txt:
if count > 0:
count -= 1
continue
elif vol_uuid_file in line:
count = 2
continue
else:
new_iet_conf_txt.append(line)
iet_conf_text.seek(0)
iet_conf_text.truncate(0)
iet_conf_text.writelines(new_iet_conf_txt)
except Exception:
LOG.exception("Failed to update %(conf)s for volume id "
"%(vol_id)s after removing iscsi target",
{'conf': conf_file, 'vol_id': vol_id})
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
else:
LOG.warning("Failed to update %(conf)s for volume id "
"%(vol_id)s after removing iscsi target. "
"%(conf)s does not exist.",
{'conf': conf_file, 'vol_id': vol_id})
def _find_sid_cid_for_target(self, tid, name, vol_id):
"""Find sid, cid for existing iscsi target"""
try:
with open(self.iet_sessions, 'r') as f:
sessions = f.read()
except Exception as e:
LOG.info("Failed to open iet session list for "
"%(vol_id)s: %(e)s",
{'vol_id': vol_id, 'e': e})
return None
session_list = re.split('(?m)^tid:', sessions)[1:]
for ses in session_list:
m = re.match(r'(\d+) name:(\S+)\s+sid:(\d+).+\s+cid:(\d+)', ses)
if m and tid in m.group(1) and name in m.group(2):
return m.group(3), m.group(4)
def _is_block(self, path):
mode = os.stat(path).st_mode
return stat.S_ISBLK(mode)
def _iotype(self, path):
if self.iscsi_iotype == 'auto':
return 'blockio' if self._is_block(path) else 'fileio'
else:
return self.iscsi_iotype

@ -0,0 +1,4 @@
---
upgrade:
- |
IET iSCSI target removed. IET iSCSI target was deprecated in the V release.
Loading…
Cancel
Save