# 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 oslo_concurrency import processutils as putils from oslo_log import log as logging from cinder import exception from cinder import utils from cinder.volume.targets import iscsi LOG = logging.getLogger(__name__) class LioAdm(iscsi.ISCSITarget): """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('target_prefix') self._verify_rtstool() def _verify_rtstool(self): try: # This call doesn't need locking utils.execute('cinder-rtstool', 'verify') except (OSError, putils.ProcessExecutionError): LOG.error('cinder-rtstool is not installed correctly') raise @staticmethod @utils.synchronized('lioadm', external=True) def _execute(*args, **kwargs): """Locked execution to prevent racing issues. Racing issues are derived from a bug in RTSLib: https://github.com/agrover/rtslib-fb/issues/36 """ return utils.execute(*args, **kwargs) 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 _get_targets(self): (out, err) = self._execute('cinder-rtstool', 'get-targets', run_as_root=True) return out def _get_iscsi_target(self, context, vol_id): return 0 def _get_target_and_lun(self, context, volume): lun = 0 # For lio, the lun starts at lun 0. iscsi_target = 0 # NOTE: Not used by lio. return iscsi_target, lun def _persist_configuration(self, vol_id): try: self._execute('cinder-rtstool', 'save', run_as_root=True) # On persistence failure we don't raise an exception, as target has # been successfully created. except putils.ProcessExecutionError: LOG.warning("Failed to save iscsi LIO configuration when " "modifying volume id: %(vol_id)s.", {'vol_id': vol_id}) def _restore_configuration(self): try: self._execute('cinder-rtstool', 'restore', run_as_root=True) # On persistence failure we don't raise an exception, as target has # been successfully created. except putils.ProcessExecutionError: LOG.warning("Failed to restore iscsi LIO configuration.") 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) chap_auth_userid = "" chap_auth_password = "" if chap_auth is not None: (chap_auth_userid, chap_auth_password) = chap_auth optional_args = [] if 'portals_port' in kwargs: optional_args.append('-p%s' % kwargs['portals_port']) if 'portals_ips' in kwargs: optional_args.append('-a' + ','.join(kwargs['portals_ips'])) try: command_args = ['cinder-rtstool', 'create', path, name, chap_auth_userid, chap_auth_password, self.iscsi_protocol == 'iser'] + optional_args self._execute(*command_args, run_as_root=True) except putils.ProcessExecutionError: LOG.exception("Failed to create iscsi target for volume " "id:%s.", vol_id) 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() # We make changes persistent self._persist_configuration(vol_id) 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: LOG.exception("Failed to remove iscsi target for volume id:%s.", vol_id) raise exception.ISCSITargetRemoveFailed(volume_id=vol_id) # We make changes persistent self._persist_configuration(vol_id) 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.exception("Failed to add initiator iqn %s to target", connector['initiator']) raise exception.ISCSITargetAttachFailed( volume_id=volume['id']) # We make changes persistent self._persist_configuration(volume['id']) return super(LioAdm, self).initialize_connection(volume, connector) def terminate_connection(self, volume, connector, **kwargs): if volume['provider_location'] is None: LOG.debug('No provider_location for volume %s.', volume['id']) return volume_iqn = volume['provider_location'].split(' ')[1] # Delete initiator iqns from target ACL try: self._execute('cinder-rtstool', 'delete-initiator', volume_iqn, connector['initiator'], run_as_root=True) except putils.ProcessExecutionError: LOG.exception( "Failed to delete initiator iqn %s from target.", connector['initiator']) raise exception.ISCSITargetDetachFailed(volume_id=volume['id']) # We make changes persistent self._persist_configuration(volume['id']) def ensure_export(self, context, volume, volume_path): """Recreate exports for logical volumes.""" # Restore saved configuration file if no target exists. if not self._get_targets(): LOG.info('Restoring iSCSI target from configuration file') self._restore_configuration() return LOG.info("Skipping ensure_export. Found existing iSCSI target.")