message = _("No target id found for volume %(volume_id)s.")
+class ISCSITargetCreateFailed(CinderException):
+ message = _("Failed to create iscsi target for volume %(volume_id)s.")
+
+
+class ISCSITargetRemoveFailed(CinderException):
+ message = _("Failed to remove iscsi target for volume %(volume_id)s.")
+
+
+class ISCSITargetAttachFailed(CinderException):
+ message = _("Failed to attach iSCSI target for volume %(volume_id)s.")
+
+
class InvalidImageRef(Invalid):
message = _("Invalid image href %(image_href)s.")
intended that these drivers ONLY implement Control Path
details (create, delete, extend...), while transport or
data path related implementation should be a *member object*
- that we call a connector. The point here is that for example
+ that we call a target. The point here is that for example
don't allow the LVM driver to implement iSCSI methods, instead
call whatever connector it has configued via conf file
(iSCSI{LIO, TGT, IET}, FC, etc).
In the base class and for example the LVM driver we do this via a has-a
- relationship and just provide an interface to the specific connector
+ relationship and just provide an interface to the specific target
methods. How you do this in your own driver is of course up to you.
"""
--- /dev/null
+# 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.
+
+
+class Target(object):
+ """Target object for block storage devices.
+
+ Base class for target object, where target
+ is data transport mechanism (target) specific calls.
+ This includes things like create targets, attach, detach
+ etc.
+
+ Base class here does nothing more than set an executor and db as
+ well as force implementation of required methods.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.db = kwargs.get('db')
+ self.configuration = kwargs.get('configuration')
+ self._execute = kwargs.get('executor')
+
+ def ensure_export(self, context, volume,
+ iscsi_name, volume_path,
+ volume_group, config):
+ raise NotImplementedError()
+
+ def create_export(self, context, volume):
+ raise NotImplementedError()
+
+ def remove_export(self, context, volume):
+ raise NotImplementedError()
+
+ def attach_volume(self, context,
+ volume, instance_uuid,
+ host_name, mountpoint):
+ raise NotImplementedError()
+
+ def detach_volume(self, context, volume):
+ raise NotImplementedError()
+
+ def initialize_connection(self, volume, **kwargs):
+ raise NotImplementedError()
+
+ def terminate_connection(self, volume, **kwargs):
+ raise NotImplementedError()
--- /dev/null
+# 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.
+
+
+class FakeTarget(object):
+ VERSION = '0.1'
+
+ def __init__(self, *args, **kwargs):
+ super(FakeTarget, self).__init__(*args, **kwargs)
+
+ def ensure_export(self, context, volume,
+ iscsi_name, volume_path,
+ volume_group, config):
+ pass
+
+ def create_export(self, context, volume):
+ pass
+
+ def remove_export(self, context, volume):
+ pass
+
+ def attach_volume(self, context,
+ volume, instance_uuid,
+ host_name, mountpoint):
+ pass
+
+ def detach_volume(self, context, volume):
+ pass
+
+ def initialize_connection(self, volume, **kwargs):
+ pass
+
+ def terminate_connection(self, volume, **kwargs):
+ pass
--- /dev/null
+# 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.
+
+
+class IetAdm(object):
+ VERSION = '0.1'
+
+ def __init__(self, *args, **kwargs):
+ super(IetAdm, self).__init__(*args, **kwargs)
+
+ def ensure_export(self, context, volume,
+ iscsi_name, volume_path,
+ volume_group, config):
+ pass
+
+ def create_export(self, context, volume):
+ pass
+
+ def remove_export(self, context, volume):
+ pass
+
+ def attach_volume(self, context, volume,
+ instance_uuid, host_name, mountpoint):
+ pass
+
+ def detach_volume(self, context, volume):
+ pass
+
+ def initialize_connection(self, volume, **kwargs):
+ pass
+
+ def terminate_connection(self, volume, **kwargs):
+ pass
--- /dev/null
+# 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.
+
+
+from cinder import exception
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+from cinder.volume.targets import driver
+
+LOG = logging.getLogger(__name__)
+
+
+class ISCSITarget(driver.Target):
+ """Target object for block storage devices.
+
+ Base class for target object, where target
+ is data transport mechanism (target) specific calls.
+ This includes things like create targets, attach, detach
+ etc.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(ISCSITarget, self).__init__(*args, **kwargs)
+ self.iscsi_target_prefix = \
+ self.configuration.safe_get('iscsi_target_prefix')
+ self.protocol = 'iSCSI'
+
+ def _get_iscsi_properties(self, volume):
+ """Gets iscsi configuration
+
+ We ideally get saved information in the volume entity, but fall back
+ to discovery if need be. Discovery may be completely removed in the
+ future.
+
+ The properties are:
+
+ :target_discovered: boolean indicating whether discovery was used
+
+ :target_iqn: the IQN of the iSCSI target
+
+ :target_portal: the portal of the iSCSI target
+
+ :target_lun: the lun of the iSCSI target
+
+ :volume_id: the uuid of the volume
+
+ :auth_method:, :auth_username:, :auth_password:
+
+ the authentication details. Right now, either auth_method is not
+ present meaning no authentication, or auth_method == `CHAP`
+ meaning use CHAP with the specified credentials.
+
+ :access_mode: the volume access mode allow client used
+ ('rw' or 'ro' currently supported)
+ """
+
+ properties = {}
+
+ location = volume['provider_location']
+
+ if location:
+ # provider_location is the same format as iSCSI discovery output
+ properties['target_discovered'] = False
+ else:
+ location = self._do_iscsi_discovery(volume)
+
+ if not location:
+ msg = (_("Could not find iSCSI export for volume %s") %
+ (volume['name']))
+ raise exception.InvalidVolume(reason=msg)
+
+ LOG.debug(("ISCSI Discovery: Found %s") % (location))
+ properties['target_discovered'] = True
+
+ results = location.split(" ")
+ properties['target_portal'] = results[0].split(",")[0]
+ properties['target_iqn'] = results[1]
+ try:
+ properties['target_lun'] = int(results[2])
+ except (IndexError, ValueError):
+ # NOTE(jdg): The following is carried over from the existing
+ # code. The trick here is that different targets use different
+ # default lun numbers, the base driver with tgtadm uses 1
+ # others like LIO use 0.
+ if (self.configuration.volume_driver in
+ ['cinder.volume.drivers.lvm.LVMISCSIDriver',
+ 'cinder.volume.drivers.lvm.ThinLVMVolumeDriver'] and
+ self.configuration.iscsi_helper == 'tgtadm'):
+ properties['target_lun'] = 1
+ else:
+ properties['target_lun'] = 0
+
+ properties['volume_id'] = volume['id']
+
+ auth = volume['provider_auth']
+ if auth:
+ (auth_method, auth_username, auth_secret) = auth.split()
+
+ properties['auth_method'] = auth_method
+ properties['auth_username'] = auth_username
+ properties['auth_password'] = auth_secret
+
+ geometry = volume.get('provider_geometry', None)
+ if geometry:
+ (physical_block_size, logical_block_size) = geometry.split()
+ properties['physical_block_size'] = physical_block_size
+ properties['logical_block_size'] = logical_block_size
+
+ encryption_key_id = volume.get('encryption_key_id', None)
+ properties['encrypted'] = encryption_key_id is not None
+
+ return properties
+
+ def _iscsi_authentication(self, chap, name, password):
+ return "%s %s %s" % (chap, name, password)
+
+ def _do_iscsi_discovery(self, volume):
+ # TODO(justinsb): Deprecate discovery and use stored info
+ # NOTE(justinsb): Discovery won't work with CHAP-secured targets (?)
+ LOG.warn(_("ISCSI provider_location not stored, using discovery"))
+
+ volume_name = volume['name']
+
+ try:
+ # NOTE(griff) We're doing the split straight away which should be
+ # safe since using '@' in hostname is considered invalid
+
+ (out, _err) = self._execute('iscsiadm', '-m', 'discovery',
+ '-t', 'sendtargets', '-p',
+ volume['host'].split('@')[0],
+ run_as_root=True)
+ except processutils.ProcessExecutionError as ex:
+ LOG.error(_("ISCSI discovery attempt failed for:%s") %
+ volume['host'].split('@')[0])
+ LOG.debug(("Error from iscsiadm -m discovery: %s") % ex.stderr)
+ return None
+
+ for target in out.splitlines():
+ if (self.configuration.safe_get('iscsi_ip_address') in target
+ and volume_name in target):
+ return target
+ return None
+
+ def detach_volume(self, context, volume):
+ self._get_iscsi_properties(volume)
+
+ def initialize_connection(self, volume, **kwargs):
+ """Initializes the connection and returns connection info.
+
+ The iscsi driver returns a driver_volume_type of 'iscsi'.
+ The format of the driver data is defined in _get_iscsi_properties.
+ Example return value::
+
+ {
+ 'driver_volume_type': 'iscsi'
+ 'data': {
+ 'target_discovered': True,
+ 'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
+ 'target_portal': '127.0.0.0.1:3260',
+ 'volume_id': '9a0d35d0-175a-11e4-8c21-0800200c9a66',
+ 'access_mode': 'rw'
+ }
+ }
+ """
+
+ iscsi_properties = self._get_iscsi_properties(volume)
+ return {
+ 'driver_volume_type': 'iscsi',
+ 'data': iscsi_properties
+ }
+
+ def validate_connector(self, connector):
+ # NOTE(jdg): api passes in connector which is initiator info
+ if 'initiator' not in connector:
+ err_msg = (_('The volume driver requires the iSCSI initiator '
+ 'name in the connector.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
--- /dev/null
+# 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.
+
+
+from cinder.openstack.common import log as logging
+from cinder.volume.targets.tgt import TgtAdm
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ISERTgtAdm(TgtAdm):
+ VERSION = '0.2'
+
+ VOLUME_CONF = """
+ <target %s>
+ driver iser
+ backing-store %s
+ </target>
+ """
+ VOLUME_CONF_WITH_CHAP_AUTH = """
+ <target %s>
+ driver iser
+ backing-store %s
+ %s
+ </target>
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(ISERTgtAdm, self).__init__(*args, **kwargs)
+ self.volumes_dir = self.configuration.safe_get('volumes_dir')
+ self.protocol = 'iSER'
+
+ # backward compatability mess
+ self.configuration.num_volume_device_scan_tries = \
+ self.configuration.num_iser_scan_tries
+ self.configuration.iscsi_num_targets = \
+ self.configuration.iser_num_targets
+ self.configuration.iscsi_target_prefix = \
+ self.configuration.iser_target_prefix
+ self.configuration.iscsi_ip_address = \
+ self.configuration.iser_ip_address
+ self.configuration.iscsi_port = self.configuration.iser_port
--- /dev/null
+# 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.
+
+from cinder import exception
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils as putils
+from cinder.volume.targets.tgt import TgtAdm
+
+LOG = logging.getLogger(__name__)
+
+
+class LioAdm(TgtAdm):
+ """iSCSI target administration for LIO using python-rtslib."""
+ def __init__(self, *args, **kwargs):
+ super(LioAdm, self).__init__(*args, **kwargs)
+
+ # FIXME(jdg): modify executor to use the cinder-rtstool
+ self.iscsi_target_prefix =\
+ self.configuration.safe_get('iscsi_target_prefix')
+ self.lio_initiator_iqns =\
+ self.configuration.safe_get('lio_initiator_iqns')
+ self._verify_rtstool()
+
+ def remove_export(self, context, volume):
+ 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.remove_iscsi_target(iscsi_target, 0, volume['id'], volume['name'])
+
+ def ensure_export(self, context, volume,
+ iscsi_name, volume_path,
+ volume_group, config):
+ 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:%s"), volume_info)
+ LOG.info(_("Skipping ensure_export. No iscsi_target "
+ "provision for volume: %s"), volume['id'])
+
+ iscsi_target = 1
+
+ self.create_iscsi_target(iscsi_name, iscsi_target, 0, volume_path,
+ chap_auth, check_exit_code=False)
+
+ def _verify_rtstool(self):
+ try:
+ self._execute('cinder-rtstool', 'verify')
+ except (OSError, putils.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 iscsi_target for volume: %s') % vol_id)
+
+ # rtstool requires chap_auth, but unit tests don't provide it
+ chap_auth_userid = 'test_id'
+ chap_auth_password = 'test_pass'
+
+ if chap_auth is not None:
+ (chap_auth_userid, chap_auth_password) = chap_auth.split(' ')[1:]
+
+ extra_args = []
+ if self.lio_initiator_iqns:
+ extra_args.append(self.lio_initiator_iqns)
+
+ try:
+ command_args = ['cinder-rtstool',
+ 'create',
+ path,
+ name,
+ chap_auth_userid,
+ chap_auth_password]
+ if extra_args:
+ command_args.extend(extra_args)
+ self._execute(*command_args, run_as_root=True)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_("Failed to create iscsi target for volume "
+ "id:%s.") % vol_id)
+ LOG.error(_("%s") % e)
+
+ raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
+
+ iqn = '%s%s' % (self.iscsi_target_prefix, vol_id)
+ tid = self._get_target(iqn)
+ if tid is None:
+ LOG.error(_("Failed to create iscsi target for volume "
+ "id:%s.") % vol_id)
+ raise exception.NotFound()
+
+ return tid
+
+ def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
+ LOG.info(_('Removing iscsi_target: %s') % vol_id)
+ vol_uuid_name = vol_name
+ iqn = '%s%s' % (self.iscsi_target_prefix, vol_uuid_name)
+
+ try:
+ self._execute('cinder-rtstool',
+ 'delete',
+ iqn,
+ run_as_root=True)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_("Failed to remove iscsi target for volume "
+ "id:%s.") % vol_id)
+ LOG.error(_("%s") % 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 initialize_connection(self, volume, connector):
+ volume_iqn = volume['provider_location'].split(' ')[1]
+
+ (auth_method, auth_user, auth_pass) = \
+ volume['provider_auth'].split(' ', 3)
+
+ # Add initiator iqns to target ACL
+ try:
+ self._execute('cinder-rtstool', 'add-initiator',
+ volume_iqn,
+ auth_user,
+ auth_pass,
+ connector['initiator'],
+ run_as_root=True)
+ except putils.ProcessExecutionError:
+ LOG.error(_("Failed to add initiator iqn %s to target") %
+ connector['initiator'])
+ raise exception.ISCSITargetAttachFailed(
+ volume_id=volume['id'])
+
+ iscsi_properties = self._get_iscsi_properties(volume)
+
+ # FIXME(jdg): For LIO the target_lun is 0, other than that all data
+ # is the same as it is for tgtadm, just modify it here
+ iscsi_properties['target_lun'] = 0
+
+ return {
+ 'driver_volume_type': 'iscsi',
+ 'data': iscsi_properties
+ }
--- /dev/null
+# 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 time
+
+from cinder import exception
+from cinder.openstack.common import fileutils
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils as putils
+from cinder.volume.targets import iscsi
+from cinder.volume import utils as vutils
+
+LOG = logging.getLogger(__name__)
+
+
+class TgtAdm(iscsi.ISCSITarget):
+ """Target object for block storage devices.
+
+ Base class for target object, where target
+ is data transport mechanism (target) specific calls.
+ This includes things like create targets, attach, detach
+ etc.
+ """
+ VOLUME_CONF = """
+ <target %s>
+ backing-store %s
+ lld iscsi
+ </target>
+ """
+ VOLUME_CONF_WITH_CHAP_AUTH = """
+ <target %s>
+ backing-store %s
+ lld iscsi
+ %s
+ </target>
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(TgtAdm, self).__init__(*args, **kwargs)
+ self.volumes_dir = self.configuration.safe_get('volumes_dir')
+
+ def _get_target(self, iqn):
+ (out, err) = self._execute('tgt-admin', '--show', run_as_root=True)
+ lines = out.split('\n')
+ for line in lines:
+ if iqn in line:
+ parsed = line.split()
+ tid = parsed[1]
+ return tid[:-1]
+
+ return None
+
+ def _verify_backing_lun(self, iqn, tid):
+ backing_lun = True
+ capture = False
+ target_info = []
+
+ (out, err) = self._execute('tgt-admin', '--show', run_as_root=True)
+ lines = out.split('\n')
+
+ for line in lines:
+ if iqn in line and "Target %s" % tid in line:
+ capture = True
+ if capture:
+ target_info.append(line)
+ if iqn not in line and 'Target ' in line:
+ capture = False
+
+ if ' LUN: 1' not in target_info:
+ backing_lun = False
+
+ return backing_lun
+
+ def _recreate_backing_lun(self, iqn, tid, name, path):
+ LOG.warning(_('Attempting recreate of backing lun...'))
+
+ # Since we think the most common case of this is a dev busy
+ # (create vol from snapshot) we're going to add a sleep here
+ # this will hopefully give things enough time to stabilize
+ # how long should we wait?? I have no idea, let's go big
+ # and error on the side of caution
+ time.sleep(10)
+ try:
+ (out, err) = self._execute('tgtadm', '--lld', 'iscsi',
+ '--op', 'new', '--mode',
+ 'logicalunit', '--tid',
+ tid, '--lun', '1', '-b',
+ path, run_as_root=True)
+ LOG.debug('StdOut from recreate backing lun: %s' % out)
+ LOG.debug('StdErr from recreate backing lun: %s' % err)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_("Failed to recover attempt to create "
+ "iscsi backing lun for volume "
+ "id:%(vol_id)s: %(e)s")
+ % {'vol_id': name, 'e': e})
+
+ def _iscsi_location(self, ip, target, iqn, lun=None):
+ return "%s:%s,%s %s %s" % (ip, self.configuration.iscsi_port,
+ target, iqn, lun)
+
+ def _get_iscsi_target(self, context, vol_id):
+ return 0
+
+ def _get_target_and_lun(self, context, volume):
+ lun = 1 # For tgtadm the controller is lun 0, dev starts at lun 1
+ iscsi_target = 0 # NOTE(jdg): Not used by tgtadm
+ return iscsi_target, lun
+
+ def _ensure_iscsi_targets(self, context, host):
+ """Ensure that target ids have been created in datastore."""
+ # 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
+ host_iscsi_targets = self.db.iscsi_target_count_by_host(context,
+ host)
+ if host_iscsi_targets >= self.configuration.iscsi_num_targets:
+ return
+
+ # NOTE(vish): Target ids start at 1, not 0.
+ 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 ensure_export(self, context, volume,
+ iscsi_name, volume_path,
+ volume_group, config):
+ chap_auth = None
+ old_name = None
+
+ # FIXME (jdg): This appears to be broken in existing code
+ # we recreate the iscsi target but we pass in None
+ # for CHAP, so we just recreated without CHAP even if
+ # we had it set on initial create
+
+ iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
+ volume['name'])
+ self.create_iscsi_target(
+ iscsi_name,
+ 1, 0, volume_path,
+ chap_auth, check_exit_code=False,
+ old_name=old_name)
+
+ def create_iscsi_target(self, name, tid, lun, path,
+ chap_auth=None, **kwargs):
+ # Note(jdg) tid and lun aren't used by TgtAdm but remain for
+ # compatibility
+ fileutils.ensure_tree(self.volumes_dir)
+
+ vol_id = name.split(':')[1]
+ if chap_auth is None:
+ volume_conf = self.VOLUME_CONF % (name, path)
+ else:
+ volume_conf = self.VOLUME_CONF_WITH_CHAP_AUTH % (name,
+ path, chap_auth)
+
+ LOG.info(_('Creating iscsi_target for: %s') % vol_id)
+ volumes_dir = self.volumes_dir
+ volume_path = os.path.join(volumes_dir, vol_id)
+
+ 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 is not None:
+ 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) = self._execute('tgt-admin', '--update', name,
+ run_as_root=True)
+ LOG.debug("StdOut from tgt-admin --update: %s", out)
+ LOG.debug("StdErr from tgt-admin --update: %s", err)
+
+ # Grab targets list for debug
+ # Consider adding a check for lun 0 and 1 for tgtadm
+ # before considering this as valid
+ (out, err) = self._execute('tgtadm',
+ '--lld',
+ 'iscsi',
+ '--op',
+ 'show',
+ '--mode',
+ 'target',
+ run_as_root=True)
+ LOG.debug("Targets after update: %s" % out)
+ except putils.ProcessExecutionError as e:
+ LOG.warning(_("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)
+
+ iqn = '%s%s' % (self.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. Please ensure your tgtd config file "
+ "contains 'include %(volumes_dir)s/*'") % {
+ 'vol_id': vol_id,
+ 'volumes_dir': volumes_dir, })
+ raise exception.NotFound()
+
+ # NOTE(jdg): Sometimes we have some issues with the backing lun
+ # not being created, believe this is due to a device busy
+ # or something related, so we're going to add some code
+ # here that verifies the backing lun (lun 1) was created
+ # and we'll try and recreate it if it's not there
+ if not self._verify_backing_lun(iqn, tid):
+ try:
+ self._recreate_backing_lun(iqn, tid, name, path)
+ except putils.ProcessExecutionError:
+ os.unlink(volume_path)
+ raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
+
+ # Finally check once more and if no go, fail and punt
+ if not self._verify_backing_lun(iqn, tid):
+ os.unlink(volume_path)
+ raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
+
+ if old_persist_file is not None and os.path.exists(old_persist_file):
+ os.unlink(old_persist_file)
+
+ return tid
+
+ def create_export(self, context, volume, volume_path):
+ """Creates an export for a logical volume."""
+ iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
+ volume['name'])
+ iscsi_target, lun = self._get_target_and_lun(context, volume)
+ chap_username = vutils.generate_username()
+ chap_password = vutils.generate_password()
+ chap_auth = self._iscsi_authentication('IncomingUser', chap_username,
+ chap_password)
+ # NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
+ # should clean this all up at some point in the future
+ tid = self.create_iscsi_target(iscsi_name,
+ iscsi_target,
+ 0,
+ volume_path,
+ chap_auth)
+ data = {}
+ data['location'] = self._iscsi_location(
+ self.configuration.iscsi_ip_address, tid, iscsi_name, lun)
+ data['auth'] = self._iscsi_authentication(
+ 'CHAP', chap_username, chap_password)
+ return data
+
+ def remove_export(self, context, volume):
+ try:
+ iscsi_target = self._get_iscsi_target(context, volume['id'])
+ except exception.NotFound:
+ LOG.info(_("Skipping remove_export. No iscsi_target "
+ "provisioned for volume: %s"), volume['id'])
+ return
+ try:
+
+ # NOTE: provider_location may be unset if the volume hasn't
+ # been exported
+ location = volume['provider_location'].split(' ')
+ iqn = location[1]
+
+ # ietadm show will exit with an error
+ # this export has already been removed
+ self.show_target(iscsi_target, iqn=iqn)
+
+ except Exception:
+ LOG.info(_("Skipping remove_export. No iscsi_target "
+ "is presently exported for volume: %s"), volume['id'])
+ return
+
+ self.remove_iscsi_target(iscsi_target, 0, volume['id'], volume['name'])
+
+ def initialize_connection(self, volume, connector):
+ iscsi_properties = self._get_iscsi_properties(volume)
+ return {
+ 'driver_volume_type': 'iscsi',
+ 'data': iscsi_properties
+ }
+
+ def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
+ LOG.info(_('Removing iscsi_target for: %s') % vol_id)
+ vol_uuid_file = vol_name
+ volume_path = os.path.join(self.volumes_dir, vol_uuid_file)
+ if not os.path.exists(volume_path):
+ LOG.warning(_('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)
+ try:
+ # NOTE(vish): --force is a workaround for bug:
+ # https://bugs.launchpad.net/cinder/+bug/1159948
+ self._execute('tgt-admin',
+ '--force',
+ '--delete',
+ iqn,
+ run_as_root=True)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_("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)
+ # NOTE(jdg): There's a bug in some versions of tgt that
+ # will sometimes fail silently when using the force flag
+ # https://bugs.launchpad.net/ubuntu/+source/tgt/+bug/1305343
+ # For now work-around by checking if the target was deleted,
+ # if it wasn't, try again without the force.
+
+ # This will NOT do any good for the case of mutliple sessions
+ # which the force was aded for but it will however address
+ # the cases pointed out in bug:
+ # https://bugs.launchpad.net/cinder/+bug/1304122
+ if self._get_target(iqn):
+ try:
+ LOG.warning(_('Silent failure of target removal '
+ 'detected, retry....'))
+ self._execute('tgt-admin',
+ '--delete',
+ iqn,
+ run_as_root=True)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_("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)
+
+ # 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)
+
+ 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()