"Fossies" - the Fresh Open Source Software Archive

Member "cinder-13.0.7/cinder/volume/drivers/ibm/flashsystem_iscsi.py" (4 Oct 2019, 15763 Bytes) of package /linux/misc/openstack/cinder-13.0.7.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 "flashsystem_iscsi.py" see the Fossies "Dox" file reference documentation.

    1 # Copyright 2015 IBM Corp.
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 #
   16 
   17 
   18 """
   19 Volume driver for IBM FlashSystem storage systems with iSCSI protocol.
   20 
   21 Limitations:
   22 1. Cinder driver only works when open_access_enabled=off.
   23 
   24 """
   25 
   26 import random
   27 import threading
   28 
   29 from oslo_config import cfg
   30 from oslo_log import log as logging
   31 from oslo_utils import excutils
   32 import six
   33 
   34 from cinder import exception
   35 from cinder.i18n import _
   36 from cinder import interface
   37 from cinder import utils
   38 from cinder.volume import configuration as conf
   39 from cinder.volume.drivers.ibm import flashsystem_common as fscommon
   40 from cinder.volume.drivers.san import san
   41 
   42 LOG = logging.getLogger(__name__)
   43 
   44 flashsystem_iscsi_opts = [
   45     cfg.IntOpt('flashsystem_iscsi_portid',
   46                default=0,
   47                help='Default iSCSI Port ID of FlashSystem. '
   48                     '(Default port is 0.)')
   49 ]
   50 
   51 CONF = cfg.CONF
   52 CONF.register_opts(flashsystem_iscsi_opts, group=conf.SHARED_CONF_GROUP)
   53 
   54 
   55 @interface.volumedriver
   56 class FlashSystemISCSIDriver(fscommon.FlashSystemDriver):
   57     """IBM FlashSystem iSCSI volume driver.
   58 
   59     Version history:
   60 
   61     .. code-block:: none
   62 
   63         1.0.0 - Initial driver
   64         1.0.1 - Code clean up
   65         1.0.2 - Add lock into vdisk map/unmap, connection
   66                 initialize/terminate
   67         1.0.3 - Initial driver for iSCSI
   68         1.0.4 - Split Flashsystem driver into common and FC
   69         1.0.5 - Report capability of volume multiattach
   70         1.0.6 - Fix bug #1469581, add I/T mapping check in
   71                 terminate_connection
   72         1.0.7 - Fix bug #1505477, add host name check in
   73                 _find_host_exhaustive for FC
   74         1.0.8 - Fix bug #1572743, multi-attach attribute
   75                 should not be hardcoded, only in iSCSI
   76         1.0.9 - Fix bug #1570574, Cleanup host resource
   77                 leaking, changes only in iSCSI
   78         1.0.10 - Fix bug #1585085, add host name check in
   79                  _find_host_exhaustive for iSCSI
   80         1.0.11 - Update driver to use ABC metaclasses
   81         1.0.12 - Update driver to support Manage/Unmanage
   82                  existing volume
   83     """
   84 
   85     VERSION = "1.0.12"
   86 
   87     # ThirdPartySystems wiki page
   88     CI_WIKI_NAME = "IBM_STORAGE_CI"
   89 
   90     def __init__(self, *args, **kwargs):
   91         super(FlashSystemISCSIDriver, self).__init__(*args, **kwargs)
   92         self.configuration.append_config_values(fscommon.flashsystem_opts)
   93         self.configuration.append_config_values(flashsystem_iscsi_opts)
   94         self.configuration.append_config_values(san.san_opts)
   95 
   96     def _check_vdisk_params(self, params):
   97         # Check that the requested protocol is enabled
   98         if not params['protocol'] in self._protocol:
   99             msg = (_("'%(prot)s' is invalid for "
  100                      "flashsystem_connection_protocol "
  101                      "in config file. valid value(s) are "
  102                      "%(enabled)s.")
  103                    % {'prot': params['protocol'],
  104                       'enabled': self._protocol})
  105             raise exception.InvalidInput(reason=msg)
  106 
  107         # Check if iscsi_ip is set when protocol is iSCSI
  108         if params['protocol'] == 'iSCSI' and params['iscsi_ip'] == 'None':
  109             msg = _("target_ip_address must be set in config file when "
  110                     "using protocol 'iSCSI'.")
  111             raise exception.InvalidInput(reason=msg)
  112 
  113     def _create_host(self, connector):
  114         """Create a new host on the storage system.
  115 
  116         We create a host and associate it with the given connection
  117         information.
  118         """
  119 
  120         LOG.debug('enter: _create_host: host %s.', connector['host'])
  121 
  122         rand_id = six.text_type(random.randint(0, 99999999)).zfill(8)
  123         host_name = '%s-%s' % (self._connector_to_hostname_prefix(connector),
  124                                rand_id)
  125 
  126         ports = []
  127 
  128         if 'iSCSI' == self._protocol and 'initiator' in connector:
  129             ports.append('-iscsiname %s' % connector['initiator'])
  130 
  131         self._driver_assert(ports,
  132                             (_('_create_host: No connector ports.')))
  133         port1 = ports.pop(0)
  134         arg_name, arg_val = port1.split()
  135         ssh_cmd = ['svctask', 'mkhost', '-force', arg_name, arg_val, '-name',
  136                    '"%s"' % host_name]
  137         out, err = self._ssh(ssh_cmd)
  138         self._assert_ssh_return('successfully created' in out,
  139                                 '_create_host', ssh_cmd, out, err)
  140 
  141         for port in ports:
  142             arg_name, arg_val = port.split()
  143             ssh_cmd = ['svctask', 'addhostport', '-force',
  144                        arg_name, arg_val, host_name]
  145             out, err = self._ssh(ssh_cmd)
  146             self._assert_ssh_return(
  147                 (not out.strip()),
  148                 '_create_host', ssh_cmd, out, err)
  149 
  150         LOG.debug(
  151             'leave: _create_host: host %(host)s - %(host_name)s.',
  152             {'host': connector['host'], 'host_name': host_name})
  153 
  154         return host_name
  155 
  156     def _find_host_exhaustive(self, connector, hosts):
  157         LOG.debug('enter: _find_host_exhaustive hosts: %s.', hosts)
  158         hname = connector['host']
  159         hnames = [ihost[0:ihost.rfind('-')] for ihost in hosts]
  160         if hname in hnames:
  161             host = hosts[hnames.index(hname)]
  162             ssh_cmd = ['svcinfo', 'lshost', '-delim', '!', host]
  163             out, err = self._ssh(ssh_cmd)
  164             self._assert_ssh_return(
  165                 out.strip(),
  166                 '_find_host_exhaustive', ssh_cmd, out, err)
  167             for attr_line in out.split('\n'):
  168                 attr_name, foo, attr_val = attr_line.partition('!')
  169                 if (attr_name == 'iscsi_name' and
  170                         'initiator' in connector and
  171                         attr_val == connector['initiator']):
  172                     LOG.debug(
  173                         'leave: _find_host_exhaustive connector: %s.',
  174                         connector)
  175                     return host
  176         else:
  177             LOG.warning('Host %(host)s was not found on backend storage.',
  178                         {'host': hname})
  179         return None
  180 
  181     def _get_vdisk_map_properties(
  182             self, connector, lun_id, vdisk_name, vdisk_id, vdisk_params):
  183         """Get the map properties of vdisk."""
  184 
  185         LOG.debug(
  186             'enter: _get_vdisk_map_properties: vdisk '
  187             '%(vdisk_name)s.', {'vdisk_name': vdisk_name})
  188 
  189         preferred_node = '0'
  190         IO_group = '0'
  191 
  192         # Get preferred node and other nodes in I/O group
  193         preferred_node_entry = None
  194         io_group_nodes = []
  195         for k, node in self._storage_nodes.items():
  196             if vdisk_params['protocol'] != node['protocol']:
  197                 continue
  198             if node['id'] == preferred_node:
  199                 preferred_node_entry = node
  200             if node['IO_group'] == IO_group:
  201                 io_group_nodes.append(node)
  202 
  203         if not io_group_nodes:
  204             msg = (_('No node found in I/O group %(gid)s for volume %(vol)s.')
  205                    % {'gid': IO_group, 'vol': vdisk_name})
  206             LOG.error(msg)
  207             raise exception.VolumeBackendAPIException(data=msg)
  208 
  209         if not preferred_node_entry:
  210             # Get 1st node in I/O group
  211             preferred_node_entry = io_group_nodes[0]
  212             LOG.warning('_get_vdisk_map_properties: Did not find a '
  213                         'preferred node for vdisk %s.', vdisk_name)
  214         properties = {
  215             'target_discovered': False,
  216             'target_lun': lun_id,
  217             'volume_id': vdisk_id,
  218         }
  219 
  220         type_str = 'iscsi'
  221         if preferred_node_entry['ipv4']:
  222             ipaddr = preferred_node_entry['ipv4'][0]
  223         else:
  224             ipaddr = preferred_node_entry['ipv6'][0]
  225         iscsi_port = self.configuration.target_port
  226         properties['target_portal'] = '%s:%s' % (ipaddr, iscsi_port)
  227         properties['target_iqn'] = preferred_node_entry['iscsi_name']
  228 
  229         LOG.debug(
  230             'leave: _get_vdisk_map_properties: vdisk '
  231             '%(vdisk_name)s.', {'vdisk_name': vdisk_name})
  232 
  233         return {'driver_volume_type': type_str, 'data': properties}
  234 
  235     @utils.synchronized('flashsystem-init-conn', external=True)
  236     def initialize_connection(self, volume, connector):
  237         """Perform work so that an iSCSI connection can be made.
  238 
  239         To be able to create an iSCSI connection from a given host to a
  240         volume, we must:
  241         1. Translate the given iSCSI name to a host name
  242         2. Create new host on the storage system if it does not yet exist
  243         3. Map the volume to the host if it is not already done
  244         4. Return the connection information for relevant nodes (in the
  245         proper I/O group)
  246 
  247         """
  248 
  249         LOG.debug(
  250             'enter: initialize_connection: volume %(vol)s with '
  251             'connector %(conn)s.', {'vol': volume, 'conn': connector})
  252 
  253         vdisk_name = volume['name']
  254         vdisk_id = volume['id']
  255         vdisk_params = self._get_vdisk_params(volume['volume_type_id'])
  256 
  257         self._wait_vdisk_copy_completed(vdisk_name)
  258 
  259         self._driver_assert(
  260             self._is_vdisk_defined(vdisk_name),
  261             (_('vdisk %s is not defined.')
  262              % vdisk_name))
  263 
  264         lun_id = self._map_vdisk_to_host(vdisk_name, connector)
  265 
  266         properties = {}
  267         try:
  268             properties = self._get_vdisk_map_properties(
  269                 connector, lun_id, vdisk_name, vdisk_id, vdisk_params)
  270         except exception.VolumeBackendAPIException:
  271             with excutils.save_and_reraise_exception():
  272                 self.terminate_connection(volume, connector)
  273                 LOG.error('Failed to collect return properties for '
  274                           'volume %(vol)s and connector %(conn)s.',
  275                           {'vol': volume, 'conn': connector})
  276 
  277         LOG.debug(
  278             'leave: initialize_connection:\n volume: %(vol)s\n connector '
  279             '%(conn)s\n properties: %(prop)s.',
  280             {'vol': volume,
  281              'conn': connector,
  282              'prop': properties})
  283 
  284         return properties
  285 
  286     @utils.synchronized('flashsystem-term-conn', external=True)
  287     def terminate_connection(self, volume, connector, **kwargs):
  288         """Cleanup after connection has been terminated.
  289 
  290         When we clean up a terminated connection between a given connector
  291         and volume, we:
  292         1. Translate the given connector to a host name
  293         2. Remove the volume-to-host mapping if it exists
  294         3. Delete the host if it has no more mappings (hosts are created
  295         automatically by this driver when mappings are created)
  296         """
  297         LOG.debug(
  298             'enter: terminate_connection: volume %(vol)s with '
  299             'connector %(conn)s.',
  300             {'vol': volume, 'conn': connector})
  301 
  302         vdisk_name = volume['name']
  303         self._wait_vdisk_copy_completed(vdisk_name)
  304         host_name = self._unmap_vdisk_from_host(vdisk_name, connector)
  305         # checking if host_name none, if not then, check if the host has
  306         # any mappings, if not the host gets deleted.
  307         if host_name:
  308             if not self._get_hostvdisk_mappings(host_name):
  309                 self._delete_host(host_name)
  310 
  311         LOG.debug(
  312             'leave: terminate_connection: volume %(vol)s with '
  313             'connector %(conn)s.', {'vol': volume, 'conn': connector})
  314 
  315         return {'driver_volume_type': 'iscsi'}
  316 
  317     def _get_iscsi_ip_addrs(self):
  318         """get ip address of iSCSI interface."""
  319 
  320         LOG.debug('enter: _get_iscsi_ip_addrs')
  321 
  322         cmd = ['svcinfo', 'lsportip']
  323         generator = self._port_conf_generator(cmd)
  324         header = next(generator, None)
  325         if not header:
  326             return
  327 
  328         for key in self._storage_nodes:
  329             if self._storage_nodes[key]['config_node'] == 'yes':
  330                 node = self._storage_nodes[key]
  331                 break
  332 
  333         if node is None:
  334             msg = _('No config node found.')
  335             LOG.error(msg)
  336             raise exception.VolumeBackendAPIException(data=msg)
  337         for port_data in generator:
  338             try:
  339                 port_ipv4 = port_data['IP_address']
  340                 port_ipv6 = port_data['IP_address_6']
  341                 state = port_data['state']
  342                 speed = port_data['speed']
  343             except KeyError:
  344                 self._handle_keyerror('lsportip', header)
  345             if port_ipv4 == self.configuration.target_ip_address and (
  346                     port_data['id'] == (
  347                         six.text_type(
  348                             self.configuration.flashsystem_iscsi_portid))):
  349                 if state not in ('configured', 'online'):
  350                     msg = (_('State of node is wrong. Current state is %s.')
  351                            % state)
  352                     LOG.error(msg)
  353                     raise exception.VolumeBackendAPIException(data=msg)
  354                 if state in ('configured', 'online') and speed != 'NONE':
  355                     if port_ipv4:
  356                         node['ipv4'].append(port_ipv4)
  357                     if port_ipv6:
  358                         node['ipv6'].append(port_ipv6)
  359                     break
  360         if not (len(node['ipv4']) or len(node['ipv6'])):
  361             msg = _('No ip address found.')
  362             LOG.error(msg)
  363             raise exception.VolumeBackendAPIException(data=msg)
  364 
  365         LOG.debug('leave: _get_iscsi_ip_addrs')
  366 
  367     def do_setup(self, ctxt):
  368         """Check that we have all configuration details from the storage."""
  369 
  370         LOG.debug('enter: do_setup')
  371 
  372         self._context = ctxt
  373 
  374         # Get data of configured node
  375         self._get_node_data()
  376 
  377         # Get the iSCSI IP addresses of the FlashSystem nodes
  378         self._get_iscsi_ip_addrs()
  379 
  380         for k, node in self._storage_nodes.items():
  381             if self.configuration.flashsystem_connection_protocol == 'iSCSI':
  382                 if (len(node['ipv4']) or len(node['ipv6']) and
  383                         len(node['iscsi_name'])):
  384                     node['protocol'] = 'iSCSI'
  385 
  386         self._protocol = 'iSCSI'
  387 
  388         # Set for vdisk synchronization
  389         self._vdisk_copy_in_progress = set()
  390         self._vdisk_copy_lock = threading.Lock()
  391         self._check_lock_interval = 5
  392 
  393         LOG.debug('leave: do_setup')
  394 
  395     def _build_default_params(self):
  396         protocol = self.configuration.flashsystem_connection_protocol
  397         if protocol.lower() == 'iscsi':
  398             protocol = 'iSCSI'
  399         return {
  400             'protocol': protocol,
  401             'iscsi_ip': self.configuration.target_ip_address,
  402             'iscsi_port': self.configuration.target_port,
  403             'iscsi_ported': self.configuration.flashsystem_iscsi_portid,
  404         }
  405 
  406     def validate_connector(self, connector):
  407         """Check connector for enabled protocol."""
  408         valid = False
  409         if 'iSCSI' == self._protocol and 'initiator' in connector:
  410             valid = True
  411         if not valid:
  412             LOG.error('The connector does not contain the '
  413                       'required information: initiator is missing')
  414             raise exception.InvalidConnectorException(missing=(
  415                                                       'initiator'))