Merge "Add LIO iSCSI backend support using python-rtslib"
This commit is contained in:
commit
f623cdcc82
177
bin/cinder-rtstool
Executable file
177
bin/cinder-rtstool
Executable file
@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: et tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 - 2013 Red Hat, 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 gettext
|
||||
import re
|
||||
import sys
|
||||
|
||||
import rtslib
|
||||
|
||||
gettext.install('cinder-rtstool', unicode=1)
|
||||
|
||||
|
||||
class RtstoolError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RtstoolImportError(RtstoolError):
|
||||
pass
|
||||
|
||||
|
||||
def create(backing_device, name, userid, password):
|
||||
try:
|
||||
rtsroot = rtslib.root.RTSRoot()
|
||||
except rtslib.utils.RTSLibError:
|
||||
print _('Ensure that configfs is mounted at /sys/kernel/config.')
|
||||
raise
|
||||
|
||||
# Look to see if BlockStorageObject already exists
|
||||
for x in rtsroot.storage_objects:
|
||||
if x.dump()['name'] == name:
|
||||
# Already exists, use this one
|
||||
return
|
||||
|
||||
so_new = rtslib.BlockStorageObject(name=name,
|
||||
dev=backing_device)
|
||||
|
||||
target_new = rtslib.Target(rtslib.FabricModule('iscsi'), name, 'create')
|
||||
|
||||
tpg_new = rtslib.TPG(target_new, mode='create')
|
||||
tpg_new.set_attribute('authentication', '1')
|
||||
|
||||
lun_new = rtslib.LUN(tpg_new, storage_object=so_new)
|
||||
|
||||
initiator_name = None
|
||||
name_file = '/etc/iscsi/initiatorname.iscsi'
|
||||
|
||||
try:
|
||||
with open(name_file, 'r') as f:
|
||||
for line in f:
|
||||
m = re.match('InitiatorName=(.+)', line)
|
||||
if m != None:
|
||||
initiator_name = m.group(1)
|
||||
break
|
||||
except IOError:
|
||||
raise RtstoolError(_('Could not open %s') % name_file)
|
||||
|
||||
if initiator_name == None:
|
||||
raise RtstoolError(_('Could not read InitiatorName from %s') %
|
||||
name_file)
|
||||
|
||||
acl_new = rtslib.NodeACL(tpg_new, initiator_name, mode='create')
|
||||
|
||||
acl_new.chap_userid = userid
|
||||
acl_new.chap_password = password
|
||||
|
||||
tpg_new.enable = 1
|
||||
|
||||
m = rtslib.MappedLUN(acl_new, lun_new.lun, lun_new.lun)
|
||||
|
||||
try:
|
||||
rtslib.NetworkPortal(tpg_new, '0.0.0.0', 3260, mode='any')
|
||||
except rtslib.utils.RTSLibError:
|
||||
print _('Error creating NetworkPortal: ensure port 3260 '
|
||||
'is not in use by another service.')
|
||||
raise
|
||||
|
||||
try:
|
||||
rtslib.NetworkPortal(tpg_new, '::0', 3260, mode='any')
|
||||
except rtslib.utils.RTSLibError:
|
||||
# TODO(emh): Binding to IPv6 fails sometimes -- let pass for now.
|
||||
pass
|
||||
|
||||
|
||||
def get_targets():
|
||||
rtsroot = rtslib.root.RTSRoot()
|
||||
for x in rtsroot.targets:
|
||||
print(x.dump()['wwn'])
|
||||
|
||||
|
||||
def delete(iqn):
|
||||
rtsroot = rtslib.root.RTSRoot()
|
||||
for x in rtsroot.targets:
|
||||
if x.dump()['wwn'] == iqn:
|
||||
x.delete()
|
||||
break
|
||||
|
||||
for x in rtsroot.storage_objects:
|
||||
if x.dump()['name'] == iqn:
|
||||
x.delete()
|
||||
break
|
||||
|
||||
|
||||
def verify_rtslib():
|
||||
for member in ['BlockStorageObject', 'FabricModule', 'LUN',
|
||||
'MappedLUN', 'NetworkPortal', 'NodeACL', 'root',
|
||||
'Target', 'TPG']:
|
||||
if not hasattr(rtslib, member):
|
||||
raise RtstoolImportError(_("rtslib is missing member %s: "
|
||||
"You may need a newer python-rtslib.") %
|
||||
member)
|
||||
|
||||
|
||||
def usage():
|
||||
print "Usage:"
|
||||
print sys.argv[0], "create [device] [name] [userid] [password]"
|
||||
print sys.argv[0], "get-targets"
|
||||
print sys.argv[0], "delete [iqn]"
|
||||
print sys.argv[0], "verify"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
if len(argv) < 2:
|
||||
usage()
|
||||
|
||||
if argv[1] == 'create':
|
||||
if len(argv) < 6:
|
||||
usage()
|
||||
|
||||
backing_device = argv[2]
|
||||
name = argv[3]
|
||||
userid = argv[4]
|
||||
password = argv[5]
|
||||
|
||||
create(backing_device, name, userid, password)
|
||||
|
||||
elif argv[1] == 'get-targets':
|
||||
get_targets()
|
||||
|
||||
elif argv[1] == 'delete':
|
||||
if len(argv) < 3:
|
||||
usage()
|
||||
|
||||
iqn = argv[2]
|
||||
delete(iqn)
|
||||
|
||||
elif argv[1] == 'verify':
|
||||
# This is used to verify that this script can be called by cinder,
|
||||
# and that rtslib is new enough to work.
|
||||
verify_rtslib()
|
||||
return 0
|
||||
|
||||
else:
|
||||
usage()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -38,6 +38,11 @@ class TargetAdminTestCase(object):
|
||||
self.stubs.Set(os.path, 'isfile', lambda _: True)
|
||||
self.stubs.Set(os, 'unlink', lambda _: '')
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
self.stubs.Set(iscsi.LioAdm, '_get_target', self.fake_get_target)
|
||||
self.stubs.Set(iscsi.LioAdm, '__init__', self.fake_init)
|
||||
|
||||
def fake_init(obj):
|
||||
return
|
||||
|
||||
def fake_get_target(obj, iqn):
|
||||
return 1
|
||||
@ -119,3 +124,16 @@ class IetAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
||||
|
||||
|
||||
class LioAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LioAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.persist_tempdir = tempfile.mkdtemp()
|
||||
self.flags(iscsi_helper='lioadm')
|
||||
self.script_template = "\n".join([
|
||||
'cinder-rtstool create '
|
||||
'/foo iqn.2011-09.org.foo.bar:blaa test_id test_pass',
|
||||
'cinder-rtstool delete iqn.2010-10.org.openstack:volume-blaa'])
|
||||
|
@ -298,6 +298,31 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
||||
# NOTE(jdg): tgtadm doesn't use the iscsi_targets table
|
||||
# TODO(jdg): In the future move all of the dependent stuff into the
|
||||
# cooresponding target admin class
|
||||
|
||||
if isinstance(self.tgtadm, iscsi.LioAdm):
|
||||
try:
|
||||
volume_info = self.db.volume_get(context, volume['id'])
|
||||
(auth_method,
|
||||
auth_user,
|
||||
auth_pass) = volume_info['provider_auth'].split(' ', 3)
|
||||
chap_auth = self._iscsi_authentication(auth_method,
|
||||
auth_user,
|
||||
auth_pass)
|
||||
except exception.NotFound:
|
||||
LOG.debug("volume_info:", volume_info)
|
||||
LOG.info(_("Skipping ensure_export. No iscsi_target "
|
||||
"provision for volume: %s"), volume['id'])
|
||||
return
|
||||
|
||||
iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
|
||||
volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
|
||||
iscsi_target = 1
|
||||
|
||||
self.tgtadm.create_iscsi_target(iscsi_name, iscsi_target,
|
||||
0, volume_path, chap_auth,
|
||||
check_exit_code=False)
|
||||
return
|
||||
|
||||
if not isinstance(self.tgtadm, iscsi.TgtAdm):
|
||||
try:
|
||||
iscsi_target = self.db.volume_get_iscsi_target_num(
|
||||
@ -310,6 +335,8 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
||||
else:
|
||||
iscsi_target = 1 # dummy value when using TgtAdm
|
||||
|
||||
chap_auth = None
|
||||
|
||||
# Check for https://bugs.launchpad.net/cinder/+bug/1065702
|
||||
old_name = None
|
||||
volume_name = volume['name']
|
||||
@ -329,7 +356,7 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
|
||||
# should clean this all up at some point in the future
|
||||
self.tgtadm.create_iscsi_target(iscsi_name, iscsi_target,
|
||||
0, volume_path,
|
||||
0, volume_path, chap_auth,
|
||||
check_exit_code=False,
|
||||
old_name=old_name)
|
||||
|
||||
@ -435,7 +462,22 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
||||
# NOTE(jdg): tgtadm doesn't use the iscsi_targets table
|
||||
# TODO(jdg): In the future move all of the dependent stuff into the
|
||||
# cooresponding target admin class
|
||||
if not isinstance(self.tgtadm, iscsi.TgtAdm):
|
||||
|
||||
if isinstance(self.tgtadm, iscsi.LioAdm):
|
||||
try:
|
||||
iscsi_target = self.db.volume_get_iscsi_target_num(
|
||||
context,
|
||||
volume['id'])
|
||||
except exception.NotFound:
|
||||
LOG.info(_("Skipping remove_export. No iscsi_target "
|
||||
"provisioned for volume: %s"), volume['id'])
|
||||
return
|
||||
|
||||
self.tgtadm.remove_iscsi_target(iscsi_target, 0, volume['id'])
|
||||
|
||||
return
|
||||
|
||||
elif not isinstance(self.tgtadm, iscsi.TgtAdm):
|
||||
try:
|
||||
iscsi_target = self.db.volume_get_iscsi_target_num(
|
||||
context,
|
||||
|
@ -315,10 +315,99 @@ class FakeIscsiHelper(object):
|
||||
return self.tid
|
||||
|
||||
|
||||
class LioAdm(TargetAdmin):
|
||||
"""iSCSI target administration for LIO using python-rtslib."""
|
||||
def __init__(self, execute=utils.execute):
|
||||
super(LioAdm, self).__init__('cinder-rtstool', execute)
|
||||
|
||||
try:
|
||||
self._execute('cinder-rtstool', 'verify')
|
||||
except (OSError, exception.ProcessExecutionError):
|
||||
LOG.error(_('cinder-rtstool is not installed correctly'))
|
||||
raise
|
||||
|
||||
def _get_target(self, iqn):
|
||||
(out, err) = self._execute('cinder-rtstool',
|
||||
'get-targets',
|
||||
run_as_root=True)
|
||||
lines = out.split('\n')
|
||||
for line in lines:
|
||||
if iqn in line:
|
||||
return line
|
||||
|
||||
return None
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
# tid and lun are not used
|
||||
|
||||
vol_id = name.split(':')[1]
|
||||
|
||||
LOG.info(_('Creating volume: %s') % vol_id)
|
||||
|
||||
# cinder-rtstool requires chap_auth, but unit tests don't provide it
|
||||
chap_auth_userid = 'test_id'
|
||||
chap_auth_password = 'test_pass'
|
||||
|
||||
if chap_auth != None:
|
||||
(chap_auth_userid, chap_auth_password) = chap_auth.split(' ')[1:]
|
||||
|
||||
try:
|
||||
self._execute('cinder-rtstool',
|
||||
'create',
|
||||
path,
|
||||
name,
|
||||
chap_auth_userid,
|
||||
chap_auth_password,
|
||||
run_as_root=True)
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.error(_("Failed to create iscsi target for volume "
|
||||
"id:%(vol_id)s.") % locals())
|
||||
LOG.error("%s" % str(e))
|
||||
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
|
||||
iqn = '%s%s' % (FLAGS.iscsi_target_prefix, vol_id)
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
LOG.error(_("Failed to create iscsi target for volume "
|
||||
"id:%(vol_id)s.") % locals())
|
||||
raise exception.NotFound()
|
||||
|
||||
return tid
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, **kwargs):
|
||||
LOG.info(_('Removing volume: %s') % vol_id)
|
||||
vol_uuid_name = 'volume-%s' % vol_id
|
||||
iqn = '%s%s' % (FLAGS.iscsi_target_prefix, vol_uuid_name)
|
||||
|
||||
try:
|
||||
self._execute('cinder-rtstool',
|
||||
'delete',
|
||||
iqn,
|
||||
run_as_root=True)
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.error(_("Failed to remove iscsi target for volume "
|
||||
"id:%(vol_id)s.") % locals())
|
||||
LOG.error("%s" % str(e))
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
def show_target(self, tid, iqn=None, **kwargs):
|
||||
if iqn is None:
|
||||
raise exception.InvalidParameterValue(
|
||||
err=_('valid iqn needed for show_target'))
|
||||
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
raise exception.NotFound()
|
||||
|
||||
|
||||
def get_target_admin():
|
||||
if FLAGS.iscsi_helper == 'tgtadm':
|
||||
return TgtAdm()
|
||||
elif FLAGS.iscsi_helper == 'fake':
|
||||
return FakeIscsiHelper()
|
||||
elif FLAGS.iscsi_helper == 'lioadm':
|
||||
return LioAdm()
|
||||
else:
|
||||
return IetAdm()
|
||||
|
@ -6,6 +6,7 @@
|
||||
ietadm: CommandFilter, /usr/sbin/ietadm, root
|
||||
tgtadm: CommandFilter, /usr/sbin/tgtadm, root
|
||||
tgt-admin: CommandFilter, /usr/sbin/tgt-admin, root
|
||||
cinder-rtstool: CommandFilter, cinder-rtstool, root
|
||||
|
||||
# cinder/volume/driver.py: 'vgs', '--noheadings', '-o', 'name'
|
||||
vgs: CommandFilter, /sbin/vgs, root
|
||||
|
1
setup.py
1
setup.py
@ -74,6 +74,7 @@ setuptools.setup(
|
||||
'bin/cinder-clear-rabbit-queues',
|
||||
'bin/cinder-manage',
|
||||
'bin/cinder-rootwrap',
|
||||
'bin/cinder-rtstool',
|
||||
'bin/cinder-scheduler',
|
||||
'bin/cinder-volume',
|
||||
'bin/cinder-volume-usage-audit'],
|
||||
|
@ -9,6 +9,7 @@ openstack.nose_plugin
|
||||
nosehtmloutput
|
||||
pep8==1.3.3
|
||||
pylint==0.25.2
|
||||
rtslib>=2.1.fb27
|
||||
sphinx>=1.1.2
|
||||
MySQL-python
|
||||
psycopg2
|
||||
|
Loading…
x
Reference in New Issue
Block a user