--- /dev/null
+#!/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())
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
'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'])
# 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(
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']
# 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)
# 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,
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()
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
'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'],
nosehtmloutput
pep8==1.3.3
pylint==0.25.2
+rtslib>=2.1.fb27
sphinx>=1.1.2
MySQL-python
hp3parclient>=1.0.0