Add support for chiscsi iscsi helper
The chiscsi target works as a drop in replacement for IET/TGT with minor configuration differences. This patch implements support for this as the 'cxtadm' iscsi_helper. Certification results : https://bugs.launchpad.net/cinder/+bug/1417499 DocImpact Implements: blueprint chiscsi-iscsi-helper Change-Id: Ib8e94f532cd07fea44aaeeac266e7f6750bf00c1
This commit is contained in:
parent
7cd5fe6bb5
commit
b43b36e9fa
208
cinder/tests/targets/test_cxt_driver.py
Normal file
208
cinder/tests/targets/test_cxt_driver.py
Normal file
@ -0,0 +1,208 @@
|
||||
# Copyright 2015 Chelsio Communications Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 os
|
||||
import shutil
|
||||
import StringIO
|
||||
import tempfile
|
||||
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from cinder import context
|
||||
from cinder.openstack.common import fileutils
|
||||
from cinder import test
|
||||
from cinder import utils
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.targets import cxt
|
||||
|
||||
|
||||
class TestCxtAdmDriver(test.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TestCxtAdmDriver, self).__init__(*args, **kwargs)
|
||||
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.cxt_subdir = cxt.CxtAdm.cxt_subdir
|
||||
self.fake_id_1 = 'ed2c1fd4-5fc0-11e4-aa15-123b93f75cba'
|
||||
self.fake_id_2 = 'ed2c2222-5fc0-11e4-aa15-123b93f75cba'
|
||||
self.target = cxt.CxtAdm(root_helper=utils.get_root_helper(),
|
||||
configuration=self.configuration)
|
||||
self.fake_volume = 'volume-83c2e877-feed-46be-8435-77884fe55b45'
|
||||
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.10.7.1:3260',
|
||||
'volume_id': self.fake_id_2}
|
||||
|
||||
self.fake_iscsi_scan =\
|
||||
('\n'
|
||||
'TARGET: iqn.2010-10.org.openstack:%s, id=1, login_ip=0\n' # noqa
|
||||
' PortalGroup=1@10.9.8.7:3260,timeout=0\n'
|
||||
' TargetDevice=/dev/stack-volumes-lvmdriver-1/%s,BLK,PROD=CHISCSI Target,SN=0N0743000000000,ID=0D074300000000000000000,WWN=:W00743000000000\n' # noqa
|
||||
% (self.fake_volume, self.fake_volume))
|
||||
|
||||
def setUp(self):
|
||||
super(TestCxtAdmDriver, self).setUp()
|
||||
self.fake_base_dir = tempfile.mkdtemp()
|
||||
self.fake_volumes_dir = os.path.join(self.fake_base_dir,
|
||||
self.cxt_subdir)
|
||||
fileutils.ensure_tree(self.fake_volumes_dir)
|
||||
self.addCleanup(self._cleanup)
|
||||
|
||||
self.exec_patcher = mock.patch.object(utils, 'execute')
|
||||
self.mock_execute = self.exec_patcher.start()
|
||||
self.addCleanup(self.exec_patcher.stop)
|
||||
|
||||
def _cleanup(self):
|
||||
if os.path.exists(self.fake_base_dir):
|
||||
shutil.rmtree(self.fake_base_dir)
|
||||
|
||||
@mock.patch('cinder.utils.execute')
|
||||
def test_get_target(self, mock_execute):
|
||||
mock_execute.return_value = (self.fake_iscsi_scan, None)
|
||||
with mock.patch.object(self.target, '_get_volumes_dir') as mock_get:
|
||||
mock_get.return_value = self.fake_volumes_dir
|
||||
self.assertEqual('1',
|
||||
self.target._get_target(
|
||||
'iqn.2010-10.org.openstack:volume-83c2e877-feed-46be-8435-77884fe55b45' # noqa
|
||||
))
|
||||
self.assertTrue(mock_execute.called)
|
||||
|
||||
def test_get_target_chap_auth(self):
|
||||
tmp_file = StringIO.StringIO()
|
||||
tmp_file.write(
|
||||
'target:\n'
|
||||
' TargetName=iqn.2010-10.org.openstack:volume-83c2e877-feed-46be-8435-77884fe55b45\n' # noqa
|
||||
' TargetDevice=/dev/stack-volumes-lvmdriver-1/volume-83c2e877-feed-46be-8435-77884fe55b45\n' # noqa
|
||||
' PortalGroup=1@10.9.8.7:3260\n'
|
||||
' AuthMethod=CHAP\n'
|
||||
' Auth_CHAP_Policy=Oneway\n'
|
||||
' Auth_CHAP_Initiator="otzLy2UYbYfnP4zXLG5z":"234Zweo38VGBBvrpK9nt"\n' # noqa
|
||||
)
|
||||
tmp_file.seek(0)
|
||||
test_vol = ('iqn.2010-10.org.openstack:'
|
||||
'volume-83c2e877-feed-46be-8435-77884fe55b45')
|
||||
expected = ('otzLy2UYbYfnP4zXLG5z', '234Zweo38VGBBvrpK9nt')
|
||||
with mock.patch('__builtin__.open') as mock_open:
|
||||
ctx = context.get_admin_context()
|
||||
mock_open.return_value = contextlib.closing(tmp_file)
|
||||
self.assertEqual(expected,
|
||||
self.target._get_target_chap_auth(ctx, test_vol))
|
||||
self.assertTrue(mock_open.called)
|
||||
|
||||
@mock.patch('cinder.volume.targets.cxt.CxtAdm._get_target',
|
||||
return_value=1)
|
||||
@mock.patch('cinder.utils.execute')
|
||||
def test_create_iscsi_target(self, mock_execute, mock_get_targ):
|
||||
mock_execute.return_value = ('', '')
|
||||
with mock.patch.object(self.target, '_get_volumes_dir') as mock_get:
|
||||
mock_get.return_value = self.fake_volumes_dir
|
||||
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))
|
||||
self.assertTrue(mock_get.called)
|
||||
self.assertTrue(mock_execute.called)
|
||||
self.assertTrue(mock_get_targ.called)
|
||||
|
||||
@mock.patch('cinder.volume.targets.cxt.CxtAdm._get_target',
|
||||
return_value=1)
|
||||
@mock.patch('cinder.utils.execute')
|
||||
def test_create_iscsi_target_already_exists(self, mock_execute,
|
||||
mock_get_targ):
|
||||
mock_execute.return_value = ('fake out', 'fake err')
|
||||
with mock.patch.object(self.target, '_get_volumes_dir') as mock_get:
|
||||
mock_get.return_value = self.fake_volumes_dir
|
||||
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))
|
||||
self.assertTrue(mock_get.called)
|
||||
self.assertTrue(mock_get_targ.called)
|
||||
self.assertTrue(mock_execute.called)
|
||||
|
||||
@mock.patch('cinder.volume.targets.cxt.CxtAdm._get_target',
|
||||
return_value=1)
|
||||
@mock.patch('cinder.utils.execute')
|
||||
@mock.patch('cinder.volume.utils.generate_password',
|
||||
return_value="P68eE7u9eFqDGexd28DQ")
|
||||
@mock.patch('cinder.volume.utils.generate_username',
|
||||
return_value="QZJbisGmn9AL954FNF4D")
|
||||
def test_create_export(self, mock_user, mock_pass, mock_execute,
|
||||
mock_get_targ):
|
||||
mock_execute.return_value = ('', '')
|
||||
with mock.patch.object(self.target, '_get_volumes_dir') as mock_get:
|
||||
mock_get.return_value = self.fake_volumes_dir
|
||||
|
||||
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_1,
|
||||
self.fake_volumes_dir))
|
||||
self.assertTrue(mock_get.called)
|
||||
self.assertTrue(mock_execute.called)
|
||||
|
||||
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,
|
||||
check_exit_code=False,
|
||||
old_name=None)
|
@ -91,7 +91,8 @@ volume_opts = [
|
||||
default='tgtadm',
|
||||
help='iSCSI target user-land tool to use. tgtadm is default, '
|
||||
'use lioadm for LIO iSCSI support, iseradm for the ISER '
|
||||
'protocol, or fake for testing.'),
|
||||
'protocol, iscsictl for Chelsio iSCSI Target or fake for '
|
||||
'testing.'),
|
||||
cfg.StrOpt('volumes_dir',
|
||||
default='$state_path/volumes',
|
||||
help='Volume configuration file storage '
|
||||
@ -99,6 +100,9 @@ volume_opts = [
|
||||
cfg.StrOpt('iet_conf',
|
||||
default='/etc/iet/ietd.conf',
|
||||
help='IET configuration file'),
|
||||
cfg.StrOpt('chiscsi_conf',
|
||||
default='/etc/chelsio-iscsi/chiscsi.conf',
|
||||
help='Chiscsi (CXT) global defaults configuration file'),
|
||||
cfg.StrOpt('lio_initiator_iqns',
|
||||
default='',
|
||||
help='This option is deprecated and unused. '
|
||||
@ -252,7 +256,8 @@ class BaseVD(object):
|
||||
'iseradm': 'cinder.volume.targets.iser.ISERTgtAdm',
|
||||
'lioadm': 'cinder.volume.targets.lio.LioAdm',
|
||||
'tgtadm': 'cinder.volume.targets.tgt.TgtAdm',
|
||||
'scstadmin': 'cinder.volume.targets.scst.SCSTAdm', }
|
||||
'scstadmin': 'cinder.volume.targets.scst.SCSTAdm',
|
||||
'iscsictl': 'cinder.volume.targets.cxt.CxtAdm'}
|
||||
|
||||
# set True by manager after successful check_for_setup
|
||||
self._initialized = False
|
||||
|
291
cinder/volume/targets/cxt.py
Normal file
291
cinder/volume/targets/cxt.py
Normal file
@ -0,0 +1,291 @@
|
||||
# Copyright 2015 Chelsio Communications Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_utils import netutils
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import fileutils
|
||||
from cinder.i18n import _LI, _LW, _LE
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
from cinder.volume.targets import iscsi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CxtAdm(iscsi.ISCSITarget):
|
||||
"""Chiscsi target configuration for block storage devices.
|
||||
This includes things like create targets, attach, detach
|
||||
etc.
|
||||
"""
|
||||
|
||||
TARGET_FMT = """
|
||||
target:
|
||||
TargetName=%s
|
||||
TargetDevice=%s
|
||||
PortalGroup=1@%s
|
||||
"""
|
||||
TARGET_FMT_WITH_CHAP = """
|
||||
target:
|
||||
TargetName=%s
|
||||
TargetDevice=%s
|
||||
PortalGroup=1@%s
|
||||
AuthMethod=CHAP
|
||||
Auth_CHAP_Policy=Oneway
|
||||
Auth_CHAP_Initiator=%s
|
||||
"""
|
||||
|
||||
cxt_subdir = 'cxt'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CxtAdm, self).__init__(*args, **kwargs)
|
||||
self.volumes_dir = self.configuration.safe_get('volumes_dir')
|
||||
self.volumes_dir = os.path.join(self.volumes_dir, self.cxt_subdir)
|
||||
self.config = self.configuration.safe_get('chiscsi_conf')
|
||||
|
||||
def _get_volumes_dir(self):
|
||||
return self.volumes_dir
|
||||
|
||||
def _get_target(self, iqn):
|
||||
# We can use target=iqn here, but iscsictl has no --brief mode, and
|
||||
# this way we save on a lot of unnecessary parsing
|
||||
(out, err) = utils.execute('iscsictl',
|
||||
'-c',
|
||||
'target=ALL',
|
||||
run_as_root=True)
|
||||
lines = out.split('\n')
|
||||
for line in lines:
|
||||
if iqn in line:
|
||||
parsed = line.split()
|
||||
tid = parsed[2]
|
||||
return tid[3:].rstrip(',')
|
||||
|
||||
return None
|
||||
|
||||
def _get_iscsi_target(self, context, vol_id):
|
||||
return 0
|
||||
|
||||
def _get_target_and_lun(self, context, volume):
|
||||
lun = 0 # For chiscsi dev starts at lun 0
|
||||
iscsi_target = 1
|
||||
return iscsi_target, lun
|
||||
|
||||
def _ensure_iscsi_targets(self, context, host):
|
||||
"""Ensure that target ids have been created in datastore."""
|
||||
# NOTE : This is probably not required for chiscsi
|
||||
# TODO(jdg): In the future move all of the dependent stuff into the
|
||||
# cooresponding target admin class
|
||||
host_iscsi_targets = self.db.iscsi_target_count_by_host(context,
|
||||
host)
|
||||
if host_iscsi_targets >= self.configuration.iscsi_num_targets:
|
||||
return
|
||||
|
||||
# NOTE Chiscsi target ids start at 1.
|
||||
target_end = self.configuration.iscsi_num_targets + 1
|
||||
for target_num in xrange(1, target_end):
|
||||
target = {'host': host, 'target_num': target_num}
|
||||
self.db.iscsi_target_create_safe(context, target)
|
||||
|
||||
def _get_target_chap_auth(self, context, name):
|
||||
volumes_dir = self._get_volumes_dir()
|
||||
vol_id = name.split(':')[1]
|
||||
volume_path = os.path.join(volumes_dir, vol_id)
|
||||
|
||||
try:
|
||||
with open(volume_path, 'r') as f:
|
||||
volume_conf = f.read()
|
||||
except IOError as e_fnf:
|
||||
LOG.debug('Failed to open config for %(vol_id)s: %(e)s',
|
||||
{'vol_id': vol_id, 'e':
|
||||
six.text_type(e_fnf)})
|
||||
# We don't run on anything non-linux
|
||||
if e_fnf.errno == 2:
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
except Exception as e_vol:
|
||||
LOG.debug('Failed to open config for %(vol_id)s: %(e)s',
|
||||
{'vol_id': vol_id, 'e':
|
||||
six.text_type(e_vol)})
|
||||
raise
|
||||
|
||||
m = re.search('Auth_CHAP_Initiator="(\w+)":"(\w+)"', volume_conf)
|
||||
if m:
|
||||
return (m.group(1), m.group(2))
|
||||
LOG.debug('Failed to find CHAP auth from config for %s', vol_id)
|
||||
return None
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
|
||||
(out, err) = utils.execute('iscsictl',
|
||||
'-c',
|
||||
'target=ALL',
|
||||
run_as_root=True)
|
||||
LOG.debug("Targets prior to update: %s", out)
|
||||
volumes_dir = self._get_volumes_dir()
|
||||
fileutils.ensure_tree(volumes_dir)
|
||||
|
||||
vol_id = name.split(':')[1]
|
||||
|
||||
if netutils.is_valid_ipv4(self.configuration.iscsi_ip_address):
|
||||
portal = "%s:%s" % (self.configuration.iscsi_ip_address,
|
||||
self.configuration.iscsi_port)
|
||||
else:
|
||||
# ipv6 addresses use [ip]:port format, ipv4 use ip:port
|
||||
portal = "[%s]:%s" % (self.configuration.iscsi_ip_address,
|
||||
self.configuration.iscsi_port)
|
||||
|
||||
if chap_auth is None:
|
||||
volume_conf = self.TARGET_FMT % (name, path, portal)
|
||||
else:
|
||||
volume_conf = self.TARGET_FMT_WITH_CHAP % (name,
|
||||
path, portal,
|
||||
'"%s":"%s"' % chap_auth)
|
||||
LOG.debug('Creating iscsi_target for: %s', vol_id)
|
||||
volume_path = os.path.join(volumes_dir, vol_id)
|
||||
|
||||
if os.path.exists(volume_path):
|
||||
LOG.warning(_LW('Persistence file already exists for volume, '
|
||||
'found file at: %s'), volume_path)
|
||||
f = open(volume_path, 'w+')
|
||||
f.write(volume_conf)
|
||||
f.close()
|
||||
LOG.debug('Created volume path %(vp)s,\n'
|
||||
'content: %(vc)s',
|
||||
{'vp': volume_path, 'vc': volume_conf})
|
||||
|
||||
old_persist_file = None
|
||||
old_name = kwargs.get('old_name', None)
|
||||
if old_name:
|
||||
LOG.debug('Detected old persistence file for volume '
|
||||
'%{vol}s at %{old_name}s',
|
||||
{'vol': vol_id, 'old_name': old_name})
|
||||
old_persist_file = os.path.join(volumes_dir, old_name)
|
||||
|
||||
try:
|
||||
# With the persistent tgts we create them
|
||||
# by creating the entry in the persist file
|
||||
# and then doing an update to get the target
|
||||
# created.
|
||||
(out, err) = utils.execute('iscsictl', '-S', 'target=%s' % name,
|
||||
'-f', volume_path,
|
||||
'-x', self.config,
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to create iscsi target for volume "
|
||||
"id:%(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
|
||||
# Don't forget to remove the persistent file we created
|
||||
os.unlink(volume_path)
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
finally:
|
||||
LOG.debug("StdOut from iscsictl -S: %s", out)
|
||||
LOG.debug("StdErr from iscsictl -S: %s", err)
|
||||
|
||||
# Grab targets list for debug
|
||||
(out, err) = utils.execute('iscsictl',
|
||||
'-c',
|
||||
'target=ALL',
|
||||
run_as_root=True)
|
||||
LOG.debug("Targets after update: %s", out)
|
||||
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix, vol_id)
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
LOG.error(_LE("Failed to create iscsi target for volume "
|
||||
"id:%(vol_id)s. Please verify your configuration "
|
||||
"in %(volumes_dir)'"), {
|
||||
'vol_id': vol_id,
|
||||
'volumes_dir': volumes_dir, })
|
||||
raise exception.NotFound()
|
||||
|
||||
if old_persist_file is not None and os.path.exists(old_persist_file):
|
||||
os.unlink(old_persist_file)
|
||||
|
||||
return tid
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
|
||||
LOG.info(_LI('Removing iscsi_target for: %s'), vol_id)
|
||||
vol_uuid_file = vol_name
|
||||
volume_path = os.path.join(self._get_volumes_dir(), vol_uuid_file)
|
||||
if not os.path.exists(volume_path):
|
||||
LOG.warning(_LW('Volume path %s does not exist, '
|
||||
'nothing to remove.'), volume_path)
|
||||
return
|
||||
|
||||
if os.path.isfile(volume_path):
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix,
|
||||
vol_uuid_file)
|
||||
else:
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
target_exists = False
|
||||
try:
|
||||
(out, err) = utils.execute('iscsictl',
|
||||
'-c',
|
||||
'target=%s' % iqn,
|
||||
run_as_root=True)
|
||||
LOG.debug("StdOut from iscsictl -c: %s", out)
|
||||
LOG.debug("StdErr from iscsictl -c: %s", err)
|
||||
except putils.ProcessExecutionError as e:
|
||||
if "NOT found" in e.stdout:
|
||||
LOG.info(_LI("No iscsi target present for volume "
|
||||
"id:%(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
return
|
||||
else:
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
else:
|
||||
target_exists = True
|
||||
|
||||
try:
|
||||
utils.execute('iscsictl',
|
||||
'-s',
|
||||
'target=%s' % iqn,
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
# There exists a race condition where multiple calls to
|
||||
# remove_iscsi_target come in simultaneously. If we can poll
|
||||
# for a target successfully but it is gone before we can remove
|
||||
# it, fail silently
|
||||
if "is not found" in e.stderr and target_exists:
|
||||
LOG.info(_LI("No iscsi target present for volume "
|
||||
"id:%(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
return
|
||||
else:
|
||||
LOG.error(_LE("Failed to remove iscsi target for volume "
|
||||
"id:%(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
# Carried over from tgt
|
||||
# NOTE(jdg): This *should* be there still but incase
|
||||
# it's not we don't care, so just ignore it if was
|
||||
# somehow deleted between entry of this method
|
||||
# and here
|
||||
if os.path.exists(volume_path):
|
||||
os.unlink(volume_path)
|
||||
else:
|
||||
LOG.debug('Volume path %s not found at end, '
|
||||
'of remove_iscsi_target.', volume_path)
|
@ -5,6 +5,7 @@
|
||||
# cinder/volume/iscsi.py: iscsi_helper '--op' ...
|
||||
ietadm: CommandFilter, ietadm, root
|
||||
tgtadm: CommandFilter, tgtadm, root
|
||||
iscsictl: CommandFilter, iscsictl, root
|
||||
tgt-admin: CommandFilter, tgt-admin, root
|
||||
cinder-rtstool: CommandFilter, cinder-rtstool, root
|
||||
scstadmin: CommandFilter, scstadmin, root
|
||||
|
Loading…
x
Reference in New Issue
Block a user