"Fossies" - the Fresh Open Source Software Archive

Member "cinder-14.0.2/cinder/volume/targets/iscsi.py" (4 Oct 2019, 15599 Bytes) of package /linux/misc/openstack/cinder-14.0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "iscsi.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.0.1_vs_14.0.2.

    1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 #    not use this file except in compliance with the License. You may obtain
    3 #    a copy of the License at
    4 #
    5 #         http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 #    Unless required by applicable law or agreed to in writing, software
    8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 #    License for the specific language governing permissions and limitations
   11 #    under the License.
   12 
   13 import abc
   14 
   15 from oslo_concurrency import processutils
   16 from oslo_log import log as logging
   17 
   18 from cinder import exception
   19 from cinder.i18n import _
   20 from cinder import utils
   21 from cinder.volume.targets import driver
   22 from cinder.volume import utils as vutils
   23 
   24 LOG = logging.getLogger(__name__)
   25 
   26 
   27 class ISCSITarget(driver.Target):
   28     """Target object for block storage devices.
   29 
   30     Base class for target object, where target
   31     is data transport mechanism (target) specific calls.
   32     This includes things like create targets, attach, detach
   33     etc.
   34     """
   35 
   36     def __init__(self, *args, **kwargs):
   37         super(ISCSITarget, self).__init__(*args, **kwargs)
   38         self.iscsi_target_prefix = \
   39             self.configuration.safe_get('target_prefix')
   40         self.iscsi_protocol = \
   41             self.configuration.safe_get('target_protocol')
   42         self.protocol = 'iSCSI'
   43         self.volumes_dir = self.configuration.safe_get('volumes_dir')
   44 
   45     def _get_iscsi_properties(self, volume, multipath=False):
   46         """Gets iscsi configuration
   47 
   48         We ideally get saved information in the volume entity, but fall back
   49         to discovery if need be. Discovery may be completely removed in the
   50         future.
   51 
   52         The properties are:
   53 
   54         :target_discovered:    boolean indicating whether discovery was used
   55 
   56         :target_iqn:    the IQN of the iSCSI target
   57 
   58         :target_portal:    the portal of the iSCSI target
   59 
   60         :target_lun:    the lun of the iSCSI target
   61 
   62         :volume_id:    the uuid of the volume
   63 
   64         :auth_method:, :auth_username:, :auth_password:
   65 
   66             the authentication details. Right now, either auth_method is not
   67             present meaning no authentication, or auth_method == `CHAP`
   68             meaning use CHAP with the specified credentials.
   69 
   70         :discard:    boolean indicating if discard is supported
   71 
   72         In some of drivers that support multiple connections (for multipath
   73         and for single path with failover on connection failure), it returns
   74         :target_iqns, :target_portals, :target_luns, which contain lists of
   75         multiple values. The main portal information is also returned in
   76         :target_iqn, :target_portal, :target_lun for backward compatibility.
   77 
   78         Note that some of drivers don't return :target_portals even if they
   79         support multipath. Then the connector should use sendtargets discovery
   80         to find the other portals if it supports multipath.
   81         """
   82 
   83         properties = {}
   84 
   85         location = volume['provider_location']
   86 
   87         if location:
   88             # provider_location is the same format as iSCSI discovery output
   89             properties['target_discovered'] = False
   90         else:
   91             location = self._do_iscsi_discovery(volume)
   92 
   93             if not location:
   94                 msg = (_("Could not find iSCSI export for volume %s") %
   95                         (volume['name']))
   96                 raise exception.InvalidVolume(reason=msg)
   97 
   98             LOG.debug("ISCSI Discovery: Found %s", location)
   99             properties['target_discovered'] = True
  100 
  101         results = location.split(" ")
  102         portals = results[0].split(",")[0].split(";")
  103         iqn = results[1]
  104         nr_portals = len(portals)
  105         try:
  106             lun = int(results[2])
  107         except (IndexError, ValueError):
  108             # NOTE(jdg): The following is carried over from the existing
  109             # code.  The trick here is that different targets use different
  110             # default lun numbers, the base driver with tgtadm uses 1
  111             # others like LIO use 0.
  112             if (self.configuration.volume_driver ==
  113                     'cinder.volume.drivers.lvm.ThinLVMVolumeDriver' and
  114                     self.configuration.target_helper == 'tgtadm'):
  115                 lun = 1
  116             else:
  117                 lun = 0
  118 
  119         if nr_portals > 1 or multipath:
  120             properties['target_portals'] = portals
  121             properties['target_iqns'] = [iqn] * nr_portals
  122             properties['target_luns'] = [lun] * nr_portals
  123         properties['target_portal'] = portals[0]
  124         properties['target_iqn'] = iqn
  125         properties['target_lun'] = lun
  126 
  127         properties['volume_id'] = volume['id']
  128 
  129         auth = volume['provider_auth']
  130         if auth:
  131             (auth_method, auth_username, auth_secret) = auth.split()
  132 
  133             properties['auth_method'] = auth_method
  134             properties['auth_username'] = auth_username
  135             properties['auth_password'] = auth_secret
  136 
  137         geometry = volume.get('provider_geometry', None)
  138         if geometry:
  139             (physical_block_size, logical_block_size) = geometry.split()
  140             properties['physical_block_size'] = physical_block_size
  141             properties['logical_block_size'] = logical_block_size
  142 
  143         encryption_key_id = volume.get('encryption_key_id', None)
  144         properties['encrypted'] = encryption_key_id is not None
  145 
  146         return properties
  147 
  148     def _iscsi_authentication(self, chap, name, password):
  149         return "%s %s %s" % (chap, name, password)
  150 
  151     def _do_iscsi_discovery(self, volume):
  152         # TODO(justinsb): Deprecate discovery and use stored info
  153         # NOTE(justinsb): Discovery won't work with CHAP-secured targets (?)
  154         LOG.warning("ISCSI provider_location not stored, using discovery")
  155 
  156         volume_id = volume['id']
  157 
  158         try:
  159             # NOTE(griff) We're doing the split straight away which should be
  160             # safe since using '@' in hostname is considered invalid
  161 
  162             (out, _err) = utils.execute('iscsiadm', '-m', 'discovery',
  163                                         '-t', 'sendtargets', '-p',
  164                                         volume['host'].split('@')[0],
  165                                         run_as_root=True)
  166         except processutils.ProcessExecutionError as ex:
  167             LOG.error("ISCSI discovery attempt failed for: %s",
  168                       volume['host'].split('@')[0])
  169             LOG.debug("Error from iscsiadm -m discovery: %s", ex.stderr)
  170             return None
  171 
  172         for target in out.splitlines():
  173             if (self.configuration.safe_get('target_ip_address') in target
  174                     and volume_id in target):
  175                 return target
  176         return None
  177 
  178     def _get_portals_config(self):
  179         # Prepare portals configuration
  180         portals_ips = ([self.configuration.target_ip_address]
  181                        + self.configuration.iscsi_secondary_ip_addresses or [])
  182 
  183         return {'portals_ips': portals_ips,
  184                 'portals_port': self.configuration.target_port}
  185 
  186     def create_export(self, context, volume, volume_path):
  187         """Creates an export for a logical volume."""
  188         # 'iscsi_name': 'iqn.2010-10.org.openstack:volume-00000001'
  189         iscsi_name = "%s%s" % (self.configuration.target_prefix,
  190                                volume['name'])
  191         iscsi_target, lun = self._get_target_and_lun(context, volume)
  192 
  193         # Verify we haven't setup a CHAP creds file already
  194         # if DNE no big deal, we'll just create it
  195         chap_auth = self._get_target_chap_auth(context, volume)
  196         if not chap_auth:
  197             chap_auth = (vutils.generate_username(),
  198                          vutils.generate_password())
  199 
  200         # Get portals ips and port
  201         portals_config = self._get_portals_config()
  202 
  203         # NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
  204         # should clean this all up at some point in the future
  205         tid = self.create_iscsi_target(iscsi_name,
  206                                        iscsi_target,
  207                                        lun,
  208                                        volume_path,
  209                                        chap_auth,
  210                                        **portals_config)
  211         data = {}
  212         data['location'] = self._iscsi_location(
  213             self.configuration.target_ip_address, tid, iscsi_name, lun,
  214             self.configuration.iscsi_secondary_ip_addresses)
  215         LOG.debug('Set provider_location to: %s', data['location'])
  216         data['auth'] = self._iscsi_authentication(
  217             'CHAP', *chap_auth)
  218         return data
  219 
  220     def remove_export(self, context, volume):
  221         try:
  222             iscsi_target, lun = self._get_target_and_lun(context, volume)
  223         except exception.NotFound:
  224             LOG.info("Skipping remove_export. No iscsi_target "
  225                      "provisioned for volume: %s", volume['id'])
  226             return
  227         try:
  228 
  229             # NOTE: provider_location may be unset if the volume hasn't
  230             # been exported
  231             location = volume['provider_location'].split(' ')
  232             iqn = location[1]
  233 
  234             # ietadm show will exit with an error
  235             # this export has already been removed
  236             self.show_target(iscsi_target, iqn=iqn)
  237 
  238         except Exception:
  239             LOG.info("Skipping remove_export. No iscsi_target "
  240                      "is presently exported for volume: %s", volume['id'])
  241             return
  242 
  243         # NOTE: For TgtAdm case volume['id'] is the ONLY param we need
  244         self.remove_iscsi_target(iscsi_target, lun, volume['id'],
  245                                  volume['name'])
  246 
  247     def ensure_export(self, context, volume, volume_path):
  248         """Recreates an export for a logical volume."""
  249         iscsi_name = "%s%s" % (self.configuration.target_prefix,
  250                                volume['name'])
  251 
  252         chap_auth = self._get_target_chap_auth(context, volume)
  253 
  254         # Get portals ips and port
  255         portals_config = self._get_portals_config()
  256 
  257         iscsi_target, lun = self._get_target_and_lun(context, volume)
  258         self.create_iscsi_target(
  259             iscsi_name, iscsi_target, lun, volume_path,
  260             chap_auth, check_exit_code=False,
  261             old_name=None, **portals_config)
  262 
  263     def initialize_connection(self, volume, connector):
  264         """Initializes the connection and returns connection info.
  265 
  266         The iscsi driver returns a driver_volume_type of 'iscsi'.
  267         The format of the driver data is defined in _get_iscsi_properties.
  268         Example return value::
  269 
  270             {
  271                 'driver_volume_type': 'iscsi'
  272                 'data': {
  273                     'target_discovered': True,
  274                     'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
  275                     'target_portal': '127.0.0.0.1:3260',
  276                     'volume_id': '9a0d35d0-175a-11e4-8c21-0800200c9a66',
  277                     'discard': False,
  278                 }
  279             }
  280         """
  281 
  282         iscsi_properties = self._get_iscsi_properties(volume,
  283                                                       connector.get(
  284                                                           'multipath'))
  285         return {
  286             'driver_volume_type': self.iscsi_protocol,
  287             'data': iscsi_properties
  288         }
  289 
  290     def terminate_connection(self, volume, connector, **kwargs):
  291         pass
  292 
  293     def validate_connector(self, connector):
  294         # NOTE(jdg): api passes in connector which is initiator info
  295         if 'initiator' not in connector:
  296             err_msg = ('The volume driver requires the iSCSI initiator '
  297                        'name in the connector.')
  298             LOG.error(err_msg)
  299             raise exception.InvalidConnectorException(missing='initiator')
  300         return True
  301 
  302     def _iscsi_location(self, ip, target, iqn, lun=None, ip_secondary=None):
  303         ip_secondary = ip_secondary or []
  304         port = self.configuration.target_port
  305         portals = map(lambda x: "%s:%s" % (vutils.sanitize_host(x), port),
  306                       [ip] + ip_secondary)
  307         return ("%(portals)s,%(target)s %(iqn)s %(lun)s"
  308                 % ({'portals': ";".join(portals),
  309                     'target': target, 'iqn': iqn, 'lun': lun}))
  310 
  311     def show_target(self, iscsi_target, iqn, **kwargs):
  312         if iqn is None:
  313             raise exception.InvalidParameterValue(
  314                 err=_('valid iqn needed for show_target'))
  315 
  316         tid = self._get_target(iqn)
  317         if tid is None:
  318             raise exception.NotFound()
  319 
  320     def _get_target_chap_auth(self, context, volume):
  321         """Get the current chap auth username and password."""
  322         try:
  323             # Query DB to get latest state of volume
  324             volume_info = self.db.volume_get(context, volume['id'])
  325             # 'provider_auth': 'CHAP user_id password'
  326             if volume_info['provider_auth']:
  327                 return tuple(volume_info['provider_auth'].split(' ', 3)[1:])
  328         except exception.NotFound:
  329             LOG.debug('Failed to get CHAP auth from DB for %s.', volume['id'])
  330 
  331     def extend_target(self, volume):
  332         """Reinitializes a target after the LV has been extended.
  333 
  334         Note: This will cause IO disruption in most cases.
  335         """
  336         iscsi_name = "%s%s" % (self.configuration.target_prefix,
  337                                volume['name'])
  338 
  339         if volume.volume_attachment:
  340             self._do_tgt_update(iscsi_name, force=True)
  341 
  342     @abc.abstractmethod
  343     def _get_target_and_lun(self, context, volume):
  344         """Get iscsi target and lun."""
  345         pass
  346 
  347     @abc.abstractmethod
  348     def create_iscsi_target(self, name, tid, lun, path,
  349                             chap_auth, **kwargs):
  350         pass
  351 
  352     @abc.abstractmethod
  353     def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
  354         pass
  355 
  356     @abc.abstractmethod
  357     def _get_iscsi_target(self, context, vol_id):
  358         pass
  359 
  360     @abc.abstractmethod
  361     def _get_target(self, iqn):
  362         pass
  363 
  364     def _do_tgt_update(self, name, force=False):
  365         pass
  366 
  367 
  368 class SanISCSITarget(ISCSITarget):
  369     """iSCSI target for san devices.
  370 
  371     San devices are slightly different, they don't need to implement
  372     all of the same things that we need to implement locally fro LVM
  373     and local block devices when we create and manage our own targets.
  374 
  375     """
  376     @abc.abstractmethod
  377     def create_export(self, context, volume, volume_path):
  378         pass
  379 
  380     @abc.abstractmethod
  381     def remove_export(self, context, volume):
  382         pass
  383 
  384     @abc.abstractmethod
  385     def ensure_export(self, context, volume, volume_path):
  386         pass
  387 
  388     @abc.abstractmethod
  389     def terminate_connection(self, volume, connector, **kwargs):
  390         pass
  391 
  392     # NOTE(jdg): Items needed for local iSCSI target drivers,
  393     # but NOT sans Stub them out here to make abc happy
  394 
  395     # Use care when looking at these to make sure something
  396     # that's inheritted isn't dependent on one of
  397     # these.
  398     def _get_target_and_lun(self, context, volume):
  399         pass
  400 
  401     def _get_target_chap_auth(self, context, volume):
  402         pass
  403 
  404     def create_iscsi_target(self, name, tid, lun, path,
  405                             chap_auth, **kwargs):
  406         pass
  407 
  408     def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
  409         pass
  410 
  411     def _get_iscsi_target(self, context, vol_id):
  412         pass
  413 
  414     def _get_target(self, iqn):
  415         pass