diff --git a/cinder/exception.py b/cinder/exception.py index f53fdceda9a..60c571c11b4 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -294,18 +294,6 @@ class ISCSITargetNotFoundForVolume(NotFound): 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.") diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index 04887e782a7..60892239bd6 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -173,13 +173,13 @@ class VolumeDriver(object): 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 target. The point here is that for example + that we call a connector. 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 target + relationship and just provide an interface to the specific connector methods. How you do this in your own driver is of course up to you. """ diff --git a/cinder/volume/targets/__init__.py b/cinder/volume/targets/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/cinder/volume/targets/driver.py b/cinder/volume/targets/driver.py deleted file mode 100644 index 346837a384e..00000000000 --- a/cinder/volume/targets/driver.py +++ /dev/null @@ -1,55 +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. - - -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() diff --git a/cinder/volume/targets/fake.py b/cinder/volume/targets/fake.py deleted file mode 100644 index e2a31816900..00000000000 --- a/cinder/volume/targets/fake.py +++ /dev/null @@ -1,43 +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. - - -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 diff --git a/cinder/volume/targets/iet.py b/cinder/volume/targets/iet.py deleted file mode 100644 index e7eba1fa3dc..00000000000 --- a/cinder/volume/targets/iet.py +++ /dev/null @@ -1,42 +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. - - -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 diff --git a/cinder/volume/targets/iscsi.py b/cinder/volume/targets/iscsi.py deleted file mode 100644 index fe66def4e02..00000000000 --- a/cinder/volume/targets/iscsi.py +++ /dev/null @@ -1,188 +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. - - -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) diff --git a/cinder/volume/targets/iser.py b/cinder/volume/targets/iser.py deleted file mode 100644 index 0676d6fbd04..00000000000 --- a/cinder/volume/targets/iser.py +++ /dev/null @@ -1,52 +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. - - -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 = """ - - driver iser - backing-store %s - - """ - VOLUME_CONF_WITH_CHAP_AUTH = """ - - driver iser - backing-store %s - %s - - """ - - 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 diff --git a/cinder/volume/targets/lio.py b/cinder/volume/targets/lio.py deleted file mode 100644 index 3ada19bf197..00000000000 --- a/cinder/volume/targets/lio.py +++ /dev/null @@ -1,183 +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. - -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 - } diff --git a/cinder/volume/targets/tgt.py b/cinder/volume/targets/tgt.py deleted file mode 100644 index 351e04d4394..00000000000 --- a/cinder/volume/targets/tgt.py +++ /dev/null @@ -1,371 +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 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 = """ - - backing-store %s - lld iscsi - - """ - VOLUME_CONF_WITH_CHAP_AUTH = """ - - backing-store %s - lld iscsi - %s - - """ - - 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()