text: # Copyright (c) 2015 Infortrend Technology, Inc.
  # All Rights Reserved.
  #
  #    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.
  """
  Infortrend Common CLI.
  """
  import math
  import time
  
  from oslo_concurrency import lockutils
  from oslo_config import cfg
  from oslo_log import log as logging
  from oslo_service import loopingcall
  from oslo_utils import timeutils
  from oslo_utils import units
  
  from cinder import exception
  from cinder.i18n import _
  from cinder.volume.drivers.infortrend.raidcmd_cli import cli_factory as cli
  from cinder.volume.drivers.san import san
  from cinder.volume import volume_types
  from cinder.zonemanager import utils as fczm_utils
  
  LOG = logging.getLogger(__name__)
  
  infortrend_esds_opts = [
      cfg.StrOpt('infortrend_pools_name',
                 default='',
                 help='Infortrend raid pool name list. '
                 'It is separated with comma.'),
      cfg.StrOpt('infortrend_cli_path',
                 default='/opt/bin/Infortrend/raidcmd_ESDS10.jar',
                 help='The Infortrend CLI absolute path. '
                 'By default, it is at '
                 '/opt/bin/Infortrend/raidcmd_ESDS10.jar'),
      cfg.IntOpt('infortrend_cli_max_retries',
                 default=5,
                 help='Maximum retry time for cli. Default is 5.'),
      cfg.IntOpt('infortrend_cli_timeout',
                 default=30,
                 help='Default timeout for CLI copy operations in minutes. '
                 'Support: migrate volume, create cloned volume and '
                 'create volume from snapshot. '
                 'By Default, it is 30 minutes.'),
      cfg.StrOpt('infortrend_slots_a_channels_id',
                 default='0,1,2,3,4,5,6,7',
                 help='Infortrend raid channel ID list on Slot A '
                 'for OpenStack usage. It is separated with comma. '
                 'By default, it is the channel 0~7.'),
      cfg.StrOpt('infortrend_slots_b_channels_id',
                 default='0,1,2,3,4,5,6,7',
                 help='Infortrend raid channel ID list on Slot B '
                 'for OpenStack usage. It is separated with comma. '
                 'By default, it is the channel 0~7.'),
  ]
  
  infortrend_esds_extra_opts = [
      cfg.StrOpt('infortrend_provisioning',
                 default='full',
                 help='Let the volume use specific provisioning. '
                 'By default, it is the full provisioning. '
                 'The supported options are full or thin.'),
      cfg.StrOpt('infortrend_tiering',
                 default='0',
                 help='Let the volume use specific tiering level. '
                 'By default, it is the level 0. '
                 'The supported levels are 0,2,3,4.'),
  ]
  
  CONF = cfg.CONF
  CONF.register_opts(infortrend_esds_opts)
  CONF.register_opts(infortrend_esds_extra_opts)
  
  CLI_RC_FILTER = {
      'CreatePartition': {'error': _('Failed to create partition.')},
      'DeletePartition': {'error': _('Failed to delete partition.')},
      'SetPartition': {'error': _('Failed to set partition.')},
      'CreateMap': {
          'warning': {20: 'The MCS Channel is grouped.'},
          'error': _('Failed to create map.'),
      },
      'DeleteMap': {
          'warning': {11: 'No mapping.'},
          'error': _('Failed to delete map.'),
      },
      'CreateSnapshot': {'error': _('Failed to create snapshot.')},
      'DeleteSnapshot': {'error': _('Failed to delete snapshot.')},
      'CreateReplica': {'error': _('Failed to create replica.')},
      'DeleteReplica': {'error': _('Failed to delete replica.')},
      'CreateIQN': {
          'warning': {20: 'IQN already existed.'},
          'error': _('Failed to create iqn.'),
      },
      'DeleteIQN': {
          'warning': {
              20: 'IQN has been used to create map.',
              11: 'No such host alias name.',
          },
          'error': _('Failed to delete iqn.'),
      },
      'ShowLV': {'error': _('Failed to get lv info.')},
      'ShowPartition': {'error': _('Failed to get partition info.')},
      'ShowSnapshot': {'error': _('Failed to get snapshot info.')},
      'ShowDevice': {'error': _('Failed to get device info.')},
      'ShowChannel': {'error': _('Failed to get channel info.')},
      'ShowMap': {'error': _('Failed to get map info.')},
      'ShowNet': {'error': _('Failed to get network info.')},
      'ShowLicense': {'error': _('Failed to get license info.')},
      'ShowReplica': {'error': _('Failed to get replica info.')},
      'ShowWWN': {'error': _('Failed to get wwn info.')},
      'ShowIQN': {'error': _('Failed to get iqn info.')},
      'ExecuteCommand': {'error': _('Failed to execute common command.')},
  }
  
  
  def log_func(func):
      def inner(self, *args, **kwargs):
          LOG.debug('Entering: %(method)s', {'method': func.__name__})
          start = timeutils.utcnow()
          ret = func(self, *args, **kwargs)
          end = timeutils.utcnow()
          LOG.debug(
              'Leaving: %(method)s, '
              'Spent: %(time)s sec, '
              'Return: %(ret)s.', {
                  'method': func.__name__,
                  'time': timeutils.delta_seconds(start, end),
                  'ret': ret})
          return ret
      return inner
  
  
  def mi_to_gi(mi_size):
      return mi_size * units.Mi / units.Gi
  
  
  def gi_to_mi(gi_size):
      return gi_size * units.Gi / units.Mi
  
  
  class InfortrendCommon(object):
  
      """The Infortrend's Common Command using CLI.
  
      Version history:
          1.0.0 - Initial driver
          1.0.1 - Support DS4000
          1.0.2 - Support GS Series
      """
  
      VERSION = '1.0.2'
  
      constants = {
          'ISCSI_PORT': 3260,
          'MAX_LUN_MAP_PER_CHL': 128
      }
  
      provisioning_values = ['thin', 'full']
  
      tiering_values = ['0', '2', '3', '4']
  
      def __init__(self, protocol, configuration=None):
  
          self.protocol = protocol
          self.configuration = configuration
          self.configuration.append_config_values(san.san_opts)
          self.configuration.append_config_values(infortrend_esds_opts)
          self.configuration.append_config_values(infortrend_esds_extra_opts)
  
          self.iscsi_multipath = self.configuration.use_multipath_for_image_xfer
          self.path = self.configuration.infortrend_cli_path
          self.password = self.configuration.san_password
          self.ip = self.configuration.san_ip
          self.cli_retry_time = self.configuration.infortrend_cli_max_retries
          self.cli_timeout = self.configuration.infortrend_cli_timeout * 60
          self.iqn = 'iqn.2002-10.com.infortrend:raid.uid%s.%s%s%s'
          self.unmanaged_prefix = 'cinder-unmanaged-%s'
  
          if self.ip == '':
              msg = _('san_ip is not set.')
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          self.fc_lookup_service = fczm_utils.create_lookup_service()
  
          self._volume_stats = None
          self._model_type = 'R'
          self._replica_timeout = self.cli_timeout
  
          self.map_dict = {
              'slot_a': {},
              'slot_b': {},
          }
          self.map_dict_init = False
  
          self.target_dict = {
              'slot_a': {},
              'slot_b': {},
          }
  
          if self.protocol == 'iSCSI':
              self.mcs_dict = {
                  'slot_a': {},
                  'slot_b': {},
              }
  
          self._init_pool_list()
          self._init_channel_list()
  
          self.cli_conf = {
              'path': self.path,
              'password': self.password,
              'ip': self.ip,
              'cli_retry_time': int(self.cli_retry_time),
          }
  
      def _init_pool_list(self):
          pools_name = self.configuration.infortrend_pools_name
          if pools_name == '':
              msg = _('Pools name is not set.')
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          tmp_pool_list = pools_name.split(',')
          self.pool_list = [pool.strip() for pool in tmp_pool_list]
  
      def _init_channel_list(self):
          self.channel_list = {
              'slot_a': [],
              'slot_b': [],
          }
          tmp_channel_list = (
              self.configuration.infortrend_slots_a_channels_id.split(',')
          )
          self.channel_list['slot_a'] = (
              [channel.strip() for channel in tmp_channel_list]
          )
          tmp_channel_list = (
              self.configuration.infortrend_slots_b_channels_id.split(',')
          )
          self.channel_list['slot_b'] = (
              [channel.strip() for channel in tmp_channel_list]
          )
  
      def _execute_command(self, cli_type, *args, **kwargs):
          command = getattr(cli, cli_type)
          return command(self.cli_conf).execute(*args, **kwargs)
  
      def _execute(self, cli_type, *args, **kwargs):
          LOG.debug('Executing command type: %(type)s.', {'type': cli_type})
  
          rc, out = self._execute_command(cli_type, *args, **kwargs)
  
          if rc != 0:
              if ('warning' in CLI_RC_FILTER[cli_type] and
                      rc in CLI_RC_FILTER[cli_type]['warning']):
                  LOG.warning(CLI_RC_FILTER[cli_type]['warning'][rc])
              else:
                  msg = CLI_RC_FILTER[cli_type]['error']
                  LOG.error(msg)
                  raise exception.InfortrendCliException(
                      err=msg, param=args, rc=rc, out=out)
          return rc, out
  
      @log_func
      def _init_map_info(self, multipath=False):
          if not self.map_dict_init:
  
              rc, channel_info = self._execute('ShowChannel')
  
              if 'BID' in channel_info[0]:
                  self._model_type = 'R'
              else:
                  self._model_type = 'G'
  
              self._set_channel_id(channel_info, 'slot_a', multipath)
  
              if multipath and self._model_type == 'R':
                  self._set_channel_id(channel_info, 'slot_b', multipath)
  
              self.map_dict_init = True
  
      @log_func
      def _update_map_info(self, multipath=False):
          """Record the driver mapping information.
  
          map_dict = {
              'slot_a': {
                  '0': [1, 2, 3, 4]  # Slot A Channel 0 map lun 1, 2, 3, 4
              },
              'slot_b' : {
                  '1': [0, 1, 3]     # Slot B Channel 1 map lun 0, 1, 3
              }
          }
          """
          rc, map_info = self._execute('ShowMap')
  
          self._update_map_info_by_slot(map_info, 'slot_a')
  
          if multipath and self._model_type == 'R':
              self._update_map_info_by_slot(map_info, 'slot_b')
          return map_info
  
      @log_func
      def _update_map_info_by_slot(self, map_info, slot_key):
          for key, value in self.map_dict[slot_key].items():
              self.map_dict[slot_key][key] = list(
                  range(self.constants['MAX_LUN_MAP_PER_CHL']))
  
          if len(map_info) > 0 and isinstance(map_info, list):
              for entry in map_info:
                  ch = entry['Ch']
                  lun = entry['LUN']
                  if ch not in self.map_dict[slot_key].keys():
                      continue
  
                  target_id = self.target_dict[slot_key][ch]
                  if (entry['Target'] == target_id and
                          int(lun) in self.map_dict[slot_key][ch]):
                      self.map_dict[slot_key][ch].remove(int(lun))
  
      def _check_initiator_has_lun_map(self, initiator_info, map_info):
          if not isinstance(initiator_info, list):
              initiator_info = (initiator_info,)
          for initiator_name in initiator_info:
              for entry in map_info:
                  if initiator_name.lower() == entry['Host-ID'].lower():
                      return True
          return False
  
      @log_func
      def _set_channel_id(
              self, channel_info, controller='slot_a', multipath=False):
  
          if self.protocol == 'iSCSI':
              check_channel_type = ('NETWORK', 'LAN')
          else:
              check_channel_type = ('FIBRE', 'Fibre')
  
          for entry in channel_info:
              if entry['Type'] in check_channel_type:
                  if entry['Ch'] in self.channel_list[controller]:
                      self.map_dict[controller][entry['Ch']] = []
  
                      if self.protocol == 'iSCSI':
                          self._update_mcs_dict(
                              entry['Ch'], entry['MCS'], controller)
  
                      self._update_target_dict(entry, controller)
  
      @log_func
      def _update_target_dict(self, channel, controller):
          """Record the target id for mapping.
  
          # R model
          target_dict = {
              'slot_a': {
                  '0': '0',
                  '1': '0',
              },
              'slot_b': {
                  '0': '1',
                  '1': '1',
              },
          }
  
          # G model
          target_dict = {
              'slot_a': {
                  '2': '32',
                  '3': '112',
              }
          }
          """
          if self._model_type == 'G':
              self.target_dict[controller][channel['Ch']] = channel['ID']
          else:
              if controller == 'slot_a':
                  self.target_dict[controller][channel['Ch']] = channel['AID']
              else:
                  self.target_dict[controller][channel['Ch']] = channel['BID']
  
      def _update_mcs_dict(self, channel_id, mcs_id, controller):
          """Record the iSCSI MCS topology.
  
          # R model with mcs, but it not working with iSCSI multipath
          mcs_dict = {
              'slot_a': {
                  '0': ['0', '1'],
                  '1': ['2']
              },
              'slot_b': {
                  '0': ['0', '1'],
                  '1': ['2']
              }
          }
  
          # G model with mcs
          mcs_dict = {
              'slot_a': {
                  '0': ['0', '1'],
                  '1': ['2']
              },
              'slot_b': {}
          }
          """
          if mcs_id not in self.mcs_dict[controller]:
              self.mcs_dict[controller][mcs_id] = []
          self.mcs_dict[controller][mcs_id].append(channel_id)
  
      def _check_tiers_setup(self):
          tiering = self.configuration.infortrend_tiering
          if tiering != '0':
              self._check_extraspec_value(
                  tiering, self.tiering_values)
              tier_levels_list = list(range(int(tiering)))
              tier_levels_list = list(map(str, tier_levels_list))
  
              rc, lv_info = self._execute('ShowLV', 'tier')
  
              for pool in self.pool_list:
                  support_tier_levels = tier_levels_list[:]
                  for entry in lv_info:
                      if (entry['LV-Name'] == pool and
                              entry['Tier'] in support_tier_levels):
                          support_tier_levels.remove(entry['Tier'])
                      if len(support_tier_levels) == 0:
                          break
                  if len(support_tier_levels) != 0:
                      msg = _('Please create %(tier_levels)s '
                              'tier in pool %(pool)s in advance!') % {
                                  'tier_levels': support_tier_levels,
                                  'pool': pool}
                      LOG.error(msg)
                      raise exception.VolumeDriverException(message=msg)
  
      def _check_pools_setup(self):
          pool_list = self.pool_list[:]
  
          rc, lv_info = self._execute('ShowLV')
  
          for lv in lv_info:
              if lv['Name'] in pool_list:
                  pool_list.remove(lv['Name'])
              if len(pool_list) == 0:
                  break
  
          if len(pool_list) != 0:
              msg = _('Please create %(pool_list)s pool in advance!') % {
                  'pool_list': pool_list}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      def check_for_setup_error(self):
          self._check_pools_setup()
          self._check_tiers_setup()
  
      def create_volume(self, volume):
          """Create a Infortrend partition."""
          volume_id = volume['id'].replace('-', '')
  
          self._create_partition_by_default(volume)
          part_id = self._get_part_id(volume_id)
  
          system_id = self._get_system_id(self.ip)
  
          model_dict = {
              'system_id': system_id,
              'partition_id': part_id,
          }
  
          model_update = {
              "provider_location": self._concat_provider_location(model_dict),
          }
          LOG.info('Create Volume %(volume_id)s completed.', {
              'volume_id': volume_id})
          return model_update
  
      def _create_partition_by_default(self, volume):
          pool_id = self._get_target_pool_id(volume)
          self._create_partition_with_pool(volume, pool_id)
  
      def _create_partition_with_pool(
              self, volume, pool_id, extraspecs=None):
  
          volume_id = volume['id'].replace('-', '')
          volume_size = gi_to_mi(volume['size'])
  
          if extraspecs is None:
              extraspecs = self._get_extraspecs_dict(volume['volume_type_id'])
  
          provisioning = self._get_extraspecs_value(extraspecs, 'provisioning')
          tiering = self._get_extraspecs_value(extraspecs, 'tiering')
  
          extraspecs_dict = {}
          cmd = ''
          if provisioning == 'thin':
              provisioning = int(volume_size * 0.2)
              extraspecs_dict['provisioning'] = provisioning
              extraspecs_dict['init'] = 'disable'
          else:
              self._check_extraspec_value(
                  provisioning, self.provisioning_values)
  
          if tiering != '0':
              self._check_extraspec_value(
                  tiering, self.tiering_values)
              tier_levels_list = list(range(int(tiering)))
              tier_levels_list = list(map(str, tier_levels_list))
              self._check_tiering_existing(tier_levels_list, pool_id)
              extraspecs_dict['provisioning'] = 0
              extraspecs_dict['init'] = 'disable'
  
          if extraspecs_dict:
              cmd = self._create_part_parameters_str(extraspecs_dict)
  
          commands = (pool_id, volume_id, 'size=%s' % int(volume_size), cmd)
          self._execute('CreatePartition', *commands)
  
      def _create_part_parameters_str(self, extraspecs_dict):
          parameters_list = []
          parameters = {
              'provisioning': 'min=%sMB',
              'tiering': 'tier=%s',
              'init': 'init=%s',
          }
          for extraspec in sorted(extraspecs_dict.keys()):
              value = parameters[extraspec] % (extraspecs_dict[extraspec])
              parameters_list.append(value)
  
          cmd = ' '.join(parameters_list)
          return cmd
  
      def _check_tiering_existing(self, tier_levels, pool_id):
          rc, lv_info = self._execute('ShowLV', 'tier')
  
          for entry in lv_info:
              if entry['LV-ID'] == pool_id and entry['Tier'] in tier_levels:
                  tier_levels.remove(entry['Tier'])
                  if len(tier_levels) == 0:
                      break
          if len(tier_levels) != 0:
              msg = _('Have not created %(tier_levels)s tier(s).') % {
                  'tier_levels': tier_levels}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      @log_func
      def _create_map_with_lun_filter(
              self, part_id, channel_id, lun_id, host, controller='slot_a'):
  
          host_filter = self._create_target_id_and_host_filter(
              controller, host)
          target_id = self.target_dict[controller][channel_id]
  
          commands = (
              'part', part_id, channel_id, target_id, lun_id, host_filter
          )
          self._execute('CreateMap', *commands)
  
      @log_func
      def _create_map_with_mcs(
              self, part_id, channel_list, lun_id, host, controller='slot_a'):
  
          map_channel_id = None
          for channel_id in channel_list:
  
              host_filter = self._create_target_id_and_host_filter(
                  controller, host)
              target_id = self.target_dict[controller][channel_id]
  
              commands = (
                  'part', part_id, channel_id, target_id, lun_id,
                  host_filter
              )
              rc, out = self._execute('CreateMap', *commands)
              if rc == 0:
                  map_channel_id = channel_id
                  break
  
          if map_channel_id is None:
              msg = _('Failed to create map on mcs, no channel can map.')
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          return map_channel_id
  
      def _create_target_id_and_host_filter(self, controller, host):
          if self.protocol == 'iSCSI':
              host_filter = 'iqn=%s' % host
          else:
              host_filter = 'wwn=%s' % host
  
          return host_filter
  
      def _get_extraspecs_dict(self, volume_type_id):
          extraspecs = {}
          if volume_type_id:
              extraspecs = volume_types.get_volume_type_extra_specs(
                  volume_type_id)
  
          return extraspecs
  
      def _get_extraspecs_value(self, extraspecs, key):
          value = None
          if key == 'provisioning':
              if (extraspecs and
                      'infortrend_provisioning' in extraspecs.keys()):
                  value = extraspecs['infortrend_provisioning'].lower()
              else:
                  value = self.configuration.infortrend_provisioning.lower()
          elif key == 'tiering':
              value = self.configuration.infortrend_tiering
          return value
  
      def _select_most_free_capacity_pool_id(self, lv_info):
          largest_free_capacity_gb = 0.0
          dest_pool_id = None
  
          for lv in lv_info:
              if lv['Name'] in self.pool_list:
                  available_space = float(lv['Available'].split(' ', 1)[0])
                  free_capacity_gb = round(mi_to_gi(available_space))
                  if free_capacity_gb > largest_free_capacity_gb:
                      largest_free_capacity_gb = free_capacity_gb
                      dest_pool_id = lv['ID']
          return dest_pool_id
  
      def _get_target_pool_id(self, volume):
          extraspecs = self._get_extraspecs_dict(volume['volume_type_id'])
          pool_id = None
          rc, lv_info = self._execute('ShowLV')
  
          if 'pool_name' in extraspecs.keys():
              poolname = extraspecs['pool_name']
  
              for entry in lv_info:
                  if entry['Name'] == poolname:
                      pool_id = entry['ID']
          else:
              pool_id = self._select_most_free_capacity_pool_id(lv_info)
  
          if pool_id is None:
              msg = _('Failed to get pool id with volume %(volume_id)s.') % {
                  'volume_id': volume['id']}
              LOG.error(msg)
              raise exception.VolumeBackendAPIException(data=msg)
  
          return pool_id
  
      def _get_system_id(self, system_ip):
          rc, device_info = self._execute('ShowDevice')
  
          for entry in device_info:
              if system_ip == entry['Connected-IP']:
                  return str(int(entry['ID'], 16))
          return
  
      @log_func
      def _get_lun_id(self, ch_id, controller='slot_a'):
          lun_id = -1
  
          if len(self.map_dict[controller][ch_id]) > 0:
              lun_id = self.map_dict[controller][ch_id][0]
              self.map_dict[controller][ch_id].remove(lun_id)
  
          if lun_id == -1:
              msg = _('LUN number is out of bound '
                      'on channel id: %(ch_id)s.') % {'ch_id': ch_id}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
          else:
              return lun_id
  
      @log_func
      def _get_mapping_info(self, multipath):
          if self.iscsi_multipath or multipath:
              return self._get_mapping_info_with_mcs()
          else:
              return self._get_mapping_info_with_normal()
  
      def _get_mapping_info_with_mcs(self):
          """Get the minimun mapping channel id and multi lun id mapping info.
  
          # R model with mcs
          map_chl = {
              'slot_a': ['0', '1']
          }
          map_lun = ['0']
  
          # G model with mcs
          map_chl = {
              'slot_a': ['1', '2']
          }
          map_lun = ['0']
  
          :returns: minimun mapping channel id per slot and multi lun id
          """
          map_chl = {
              'slot_a': []
          }
  
          min_lun_num = 0
          map_mcs_group = None
          for mcs in self.mcs_dict['slot_a']:
              if len(self.mcs_dict['slot_a'][mcs]) > 1:
                  if min_lun_num < self._get_mcs_channel_lun_map_num(mcs):
                      min_lun_num = self._get_mcs_channel_lun_map_num(mcs)
                      map_mcs_group = mcs
  
          if map_mcs_group is None:
              msg = _('Raid did not have MCS Channel.')
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          map_chl['slot_a'] = self.mcs_dict['slot_a'][map_mcs_group]
          map_lun = self._get_mcs_channel_lun_map(map_chl['slot_a'])
          return map_chl, map_lun, map_mcs_group
  
      def _get_mcs_channel_lun_map_num(self, mcs_id):
          lun_num = 0
          for channel in self.mcs_dict['slot_a'][mcs_id]:
              lun_num += len(self.map_dict['slot_a'][channel])
          return lun_num
  
      def _get_mcs_channel_lun_map(self, channel_list):
          """Find the common lun id in mcs channel."""
  
          map_lun = []
          for lun_id in range(self.constants['MAX_LUN_MAP_PER_CHL']):
              check_map = True
              for channel_id in channel_list:
                  if lun_id not in self.map_dict['slot_a'][channel_id]:
                      check_map = False
              if check_map:
                  map_lun.append(str(lun_id))
                  break
          return map_lun
  
      @log_func
      def _get_mapping_info_with_normal(self):
          """Get the minimun mapping channel id and lun id mapping info.
  
          # G model and R model
          map_chl = {
              'slot_a': ['1']
          }
          map_lun = ['0']
  
          :returns: minimun mapping channel id per slot and lun id
          """
          map_chl = {
              'slot_a': []
          }
          map_lun = []
  
          ret_chl = self._get_minimun_mapping_channel_id('slot_a')
          lun_id = self._get_lun_id(ret_chl, 'slot_a')
          mcs_id = self._get_mcs_id_by_channel_id(ret_chl)
  
          map_chl['slot_a'].append(ret_chl)
          map_lun.append(str(lun_id))
  
          return map_chl, map_lun, mcs_id
  
      @log_func
      def _get_minimun_mapping_channel_id(self, controller):
          empty_lun_num = 0
          min_map_chl = -1
  
          # Sort items to get a reliable behaviour. Dictionary items
          # are iterated in a random order because of hash randomization.
          for key, value in sorted(self.map_dict[controller].items()):
              if empty_lun_num < len(value):
                  min_map_chl = key
                  empty_lun_num = len(value)
  
          if int(min_map_chl) < 0:
              msg = _('LUN map overflow on every channel.')
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
          else:
              return min_map_chl
  
      def _get_common_lun_map_id(self, wwpn_channel_info):
          map_lun = None
  
          for lun_id in range(self.constants['MAX_LUN_MAP_PER_CHL']):
              lun_id_exist = False
              for slot_name in ['slot_a', 'slot_b']:
                  for wwpn in wwpn_channel_info:
                      channel_id = wwpn_channel_info[wwpn]['channel']
                      if channel_id not in self.map_dict[slot_name]:
                          continue
                      elif lun_id not in self.map_dict[slot_name][channel_id]:
                          lun_id_exist = True
              if not lun_id_exist:
                  map_lun = str(lun_id)
                  break
          return map_lun
  
      def _get_mcs_id_by_channel_id(self, channel_id):
          mcs_id = None
  
          for mcs in self.mcs_dict['slot_a']:
              if channel_id in self.mcs_dict['slot_a'][mcs]:
                  mcs_id = mcs
                  break
  
          if mcs_id is None:
              msg = _('Cannot get mcs_id by channel id: %(channel_id)s.') % {
                  'channel_id': channel_id}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          return mcs_id
  
      def _concat_provider_location(self, model_dict):
          keys = sorted(model_dict.keys())
          return '@'.join([i + '^' + str(model_dict[i]) for i in keys])
  
      def delete_volume(self, volume):
          """Delete the specific volume."""
  
          volume_id = volume['id'].replace('-', '')
          has_pair = False
          have_map = False
  
          part_id = self._extract_specific_provider_location(
              volume['provider_location'], 'partition_id')
  
          (check_exist, have_map, part_id) = (
              self._check_volume_exist(volume_id, part_id)
          )
  
          if not check_exist:
              LOG.warning('Volume %(volume_id)s already deleted.', {
                  'volume_id': volume_id})
              return
  
          rc, replica_list = self._execute('ShowReplica', '-l')
  
          for entry in replica_list:
              if (volume_id == entry['Source-Name'] and
                      part_id == entry['Source']):
                  if not self._check_replica_completed(entry):
                      has_pair = True
                      LOG.warning('Volume still %(status)s '
                                  'Cannot delete volume.',
                                  {'status': entry['Status']})
                  else:
                      have_map = entry['Source-Mapped'] == 'Yes'
                      self._execute('DeleteReplica', entry['Pair-ID'], '-y')
  
              elif (volume_id == entry['Target-Name'] and
                      part_id == entry['Target']):
                  have_map = entry['Target-Mapped'] == 'Yes'
                  self._execute('DeleteReplica', entry['Pair-ID'], '-y')
  
          if not has_pair:
  
              rc, snapshot_list = self._execute(
                  'ShowSnapshot', 'part=%s' % part_id)
  
              for snapshot in snapshot_list:
                  si_has_pair = self._delete_pair_with_snapshot(
                      snapshot['SI-ID'], replica_list)
  
                  if si_has_pair:
                      msg = _('Failed to delete SI '
                              'for volume_id: %(volume_id)s '
                              'because it has pair.') % {
                                  'volume_id': volume_id}
                      LOG.error(msg)
                      raise exception.VolumeDriverException(message=msg)
  
                  self._execute('DeleteSnapshot', snapshot['SI-ID'], '-y')
  
              rc, map_info = self._execute('ShowMap', 'part=%s' % part_id)
  
              if have_map or len(map_info) > 0:
                  self._execute('DeleteMap', 'part', part_id, '-y')
  
              self._execute('DeletePartition', part_id, '-y')
  
              LOG.info('Delete Volume %(volume_id)s completed.', {
                  'volume_id': volume_id})
          else:
              msg = _('Failed to delete volume '
                      'for volume_id: %(volume_id)s '
                      'because it has pair.') % {
                          'volume_id': volume_id}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      def _check_replica_completed(self, replica):
          if ((replica['Type'] == 'Copy' and replica['Status'] == 'Completed') or
                  (replica['Type'] == 'Mirror' and
                      replica['Status'] == 'Mirror')):
              return True
  
          return False
  
      def _check_volume_exist(self, volume_id, part_id):
          check_exist = False
          have_map = False
          result_part_id = part_id
  
          rc, part_list = self._execute('ShowPartition', '-l')
  
          for entry in part_list:
              if entry['Name'] == volume_id:
                  check_exist = True
  
                  if part_id is None:
                      result_part_id = entry['ID']
                  if entry['Mapped'] == 'true':
                      have_map = True
  
          if check_exist:
              return (check_exist, have_map, result_part_id)
          else:
              return (False, False, None)
  
      def create_cloned_volume(self, volume, src_vref):
          """Create a clone of the volume by volume copy."""
  
          volume_id = volume['id'].replace('-', '')
          #  Step1 create a snapshot of the volume
          src_part_id = self._extract_specific_provider_location(
              src_vref['provider_location'], 'partition_id')
  
          if src_part_id is None:
              src_part_id = self._get_part_id(volume_id)
  
          model_update = self._create_volume_from_volume(volume, src_part_id)
  
          LOG.info('Create Cloned Volume %(volume_id)s completed.', {
              'volume_id': volume['id']})
          return model_update
  
      def _create_volume_from_volume(self, dst_volume, src_part_id):
          # create the target volume for volume copy
          dst_volume_id = dst_volume['id'].replace('-', '')
  
          self._create_partition_by_default(dst_volume)
  
          dst_part_id = self._get_part_id(dst_volume_id)
          # prepare return value
          system_id = self._get_system_id(self.ip)
          model_dict = {
              'system_id': system_id,
              'partition_id': dst_part_id,
          }
  
          model_info = self._concat_provider_location(model_dict)
          model_update = {"provider_location": model_info}
  
          # clone the volume from the origin partition
          commands = (
              'Cinder-Cloned', 'part', src_part_id, 'part', dst_part_id
          )
          self._execute('CreateReplica', *commands)
          self._wait_replica_complete(dst_part_id)
  
          return model_update
  
      def _extract_specific_provider_location(self, provider_location, key):
          provider_location_dict = self._extract_all_provider_location(
              provider_location)
  
          result = provider_location_dict.get(key, None)
          return result
  
      @log_func
      def _extract_all_provider_location(self, provider_location):
          provider_location_dict = {}
          dict_entry = provider_location.split("@")
          for entry in dict_entry:
              key, value = entry.split('^', 1)
              if value == 'None':
                  value = None
              provider_location_dict[key] = value
  
          return provider_location_dict
  
      def create_export(self, context, volume):
          model_update = volume['provider_location']
  
          LOG.info('Create export done from Volume %(volume_id)s.', {
              'volume_id': volume['id']})
  
          return {'provider_location': model_update}
  
      def get_volume_stats(self, refresh=False):
          """Get volume status.
  
          If refresh is True, update the status first.
          """
          if self._volume_stats is None or refresh:
              self._update_volume_stats()
  
          LOG.info(
              'Successfully update volume stats. '
              'backend: %(volume_backend_name)s, '
              'vendor: %(vendor_name)s, '
              'driver version: %(driver_version)s, '
              'storage protocol: %(storage_protocol)s.', self._volume_stats)
  
          return self._volume_stats
  
      def _update_volume_stats(self):
  
          backend_name = self.configuration.safe_get('volume_backend_name')
  
          data = {
              'volume_backend_name': backend_name,
              'vendor_name': 'Infortrend',
              'driver_version': self.VERSION,
              'storage_protocol': self.protocol,
              'pools': self._update_pools_stats(),
          }
          self._volume_stats = data
  
      def _update_pools_stats(self):
          enable_specs_dict = self._get_enable_specs_on_array()
  
          if 'Thin Provisioning' in enable_specs_dict.keys():
              provisioning = 'thin'
              provisioning_support = True
          else:
              provisioning = 'full'
              provisioning_support = False
  
          rc, part_list = self._execute('ShowPartition', '-l')
          rc, pools_info = self._execute('ShowLV')
          pools = []
  
          for pool in pools_info:
              if pool['Name'] in self.pool_list:
                  total_space = float(pool['Size'].split(' ', 1)[0])
                  available_space = float(pool['Available'].split(' ', 1)[0])
  
                  total_capacity_gb = round(mi_to_gi(total_space), 2)
                  free_capacity_gb = round(mi_to_gi(available_space), 2)
                  provisioning_factor = self.configuration.safe_get(
                      'max_over_subscription_ratio')
                  provisioned_space = self._get_provisioned_space(
                      pool['ID'], part_list)
                  provisioned_capacity_gb = round(mi_to_gi(provisioned_space), 2)
  
                  new_pool = {
                      'pool_name': pool['Name'],
                      'pool_id': pool['ID'],
                      'total_capacity_gb': total_capacity_gb,
                      'free_capacity_gb': free_capacity_gb,
                      'reserved_percentage': 0,
                      'QoS_support': False,
                      'provisioned_capacity_gb': provisioned_capacity_gb,
                      'max_over_subscription_ratio': provisioning_factor,
                      'thin_provisioning_support': provisioning_support,
                      'thick_provisioning_support': True,
                      'infortrend_provisioning': provisioning,
                  }
                  pools.append(new_pool)
          return pools
  
      def _get_provisioned_space(self, pool_id, part_list):
          provisioning_space = 0
          for entry in part_list:
              if entry['LV-ID'] == pool_id:
                  provisioning_space += int(entry['Size'])
          return provisioning_space
  
      def create_snapshot(self, snapshot):
          """Creates a snapshot."""
  
          snapshot_id = snapshot['id'].replace('-', '')
          volume_id = snapshot['volume_id'].replace('-', '')
  
          LOG.debug('Create Snapshot %(snapshot)s volume %(volume)s.',
                    {'snapshot': snapshot_id, 'volume': volume_id})
  
          model_update = {}
          part_id = self._get_part_id(volume_id)
  
          if part_id is None:
              msg = _('Failed to get Partition ID for volume %(volume_id)s.') % {
                  'volume_id': volume_id}
              LOG.error(msg)
              raise exception.VolumeBackendAPIException(data=msg)
  
          @lockutils.synchronized(
              'snapshot-' + part_id, 'infortrend-', True)
          def do_create_snapshot():
              self._execute('CreateSnapshot', 'part', part_id)
              rc, tmp_snapshot_list = self._execute(
                  'ShowSnapshot', 'part=%s' % part_id)
              return tmp_snapshot_list
  
          snapshot_list = do_create_snapshot()
  
          LOG.info(
              'Create success. '
              'Snapshot: %(snapshot)s, '
              'Snapshot ID in raid: %(raid_snapshot_id)s, '
              'volume: %(volume)s.', {
                  'snapshot': snapshot_id,
                  'raid_snapshot_id': snapshot_list[-1]['SI-ID'],
                  'volume': volume_id})
          model_update['provider_location'] = snapshot_list[-1]['SI-ID']
          return model_update
  
      def delete_snapshot(self, snapshot):
          """Delete the snapshot."""
  
          snapshot_id = snapshot['id'].replace('-', '')
          volume_id = snapshot['volume_id'].replace('-', '')
  
          LOG.debug('Delete Snapshot %(snapshot)s volume %(volume)s.',
                    {'snapshot': snapshot_id, 'volume': volume_id})
  
          raid_snapshot_id = self._get_raid_snapshot_id(snapshot)
  
          if raid_snapshot_id:
  
              rc, replica_list = self._execute('ShowReplica', '-l')
  
              has_pair = self._delete_pair_with_snapshot(
                  raid_snapshot_id, replica_list)
  
              if not has_pair:
                  self._execute('DeleteSnapshot', raid_snapshot_id, '-y')
  
                  LOG.info('Delete Snapshot %(snapshot_id)s completed.', {
                      'snapshot_id': snapshot_id})
              else:
                  msg = _('Failed to delete snapshot '
                          'for snapshot_id: %s '
                          'because it has pair.') % snapshot_id
                  LOG.error(msg)
                  raise exception.VolumeDriverException(message=msg)
          else:
              msg = _(
                  'Failed to get Raid Snapshot ID '
                  'from Snapshot %(snapshot_id)s.') % {
                      'snapshot_id': snapshot_id}
              LOG.error(msg)
              raise exception.VolumeBackendAPIException(data=msg)
  
      def _get_raid_snapshot_id(self, snapshot):
          if 'provider_location' not in snapshot:
              LOG.warning(
                  'Failed to get Raid Snapshot ID and '
                  'did not store in snapshot.')
              return
          return snapshot['provider_location']
  
      def _delete_pair_with_snapshot(self, snapshot_id, replica_list):
          has_pair = False
          for entry in replica_list:
              if entry['Source'] == snapshot_id:
  
                  if not self._check_replica_completed(entry):
                      has_pair = True
                      LOG.warning(
                          'Snapshot still %(status)s Cannot delete snapshot.',
                          {'status': entry['Status']})
                  else:
                      self._execute('DeleteReplica', entry['Pair-ID'], '-y')
          return has_pair
  
      def _get_part_id(self, volume_id, pool_id=None, part_list=None):
          if part_list is None:
              rc, part_list = self._execute('ShowPartition')
          for entry in part_list:
              if pool_id is None:
                  if entry['Name'] == volume_id:
                      return entry['ID']
              else:
                  if entry['Name'] == volume_id and entry['LV-ID'] == pool_id:
                      return entry['ID']
          return
  
      def create_volume_from_snapshot(self, volume, snapshot):
          raid_snapshot_id = self._get_raid_snapshot_id(snapshot)
  
          if raid_snapshot_id is None:
              msg = _('Failed to get Raid Snapshot ID '
                      'from snapshot: %(snapshot_id)s.') % {
                          'snapshot_id': snapshot['id']}
              LOG.error(msg)
              raise exception.VolumeBackendAPIException(data=msg)
  
          src_part_id = self._check_snapshot_filled_block(raid_snapshot_id)
  
          model_update = self._create_volume_from_snapshot_id(
              volume, raid_snapshot_id, src_part_id)
  
          LOG.info(
              'Create Volume %(volume_id)s from '
              'snapshot %(snapshot_id)s completed.', {
                  'volume_id': volume['id'],
                  'snapshot_id': snapshot['id']})
  
          return model_update
  
      def _check_snapshot_filled_block(self, raid_snapshot_id):
          rc, snapshot_list = self._execute(
              'ShowSnapshot', 'si=%s' % raid_snapshot_id, '-l')
  
          if snapshot_list and snapshot_list[0]['Total-filled-block'] == '0':
              return snapshot_list[0]['Partition-ID']
          return
  
      def _create_volume_from_snapshot_id(
              self, dst_volume, raid_snapshot_id, src_part_id):
          # create the target volume for volume copy
          dst_volume_id = dst_volume['id'].replace('-', '')
  
          self._create_partition_by_default(dst_volume)
  
          dst_part_id = self._get_part_id(dst_volume_id)
          # prepare return value
          system_id = self._get_system_id(self.ip)
          model_dict = {
              'system_id': system_id,
              'partition_id': dst_part_id,
          }
  
          model_info = self._concat_provider_location(model_dict)
          model_update = {"provider_location": model_info}
  
          if src_part_id:
              # clone the volume from the origin partition
              commands = (
                  'Cinder-Snapshot', 'part', src_part_id, 'part', dst_part_id
              )
              self._execute('CreateReplica', *commands)
              self._wait_replica_complete(dst_part_id)
  
          # clone the volume from the snapshot
          commands = (
              'Cinder-Snapshot', 'si', raid_snapshot_id, 'part', dst_part_id
          )
          self._execute('CreateReplica', *commands)
          self._wait_replica_complete(dst_part_id)
  
          return model_update
  
      @lockutils.synchronized('connection', 'infortrend-', True)
      def initialize_connection(self, volume, connector):
          if self.protocol == 'iSCSI':
              multipath = connector.get('multipath', False)
              return self._initialize_connection_iscsi(
                  volume, connector, multipath)
          elif self.protocol == 'FC':
              return self._initialize_connection_fc(
                  volume, connector)
          else:
              msg = _('Unknown protocol: %(protocol)s.') % {
                  'protocol': self.protocol}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      def _initialize_connection_fc(self, volume, connector):
          self._init_map_info(True)
          self._update_map_info(True)
  
          map_lun, target_wwpns, initiator_target_map = (
              self._do_fc_connection(volume, connector)
          )
  
          properties = self._generate_fc_connection_properties(
              map_lun, target_wwpns, initiator_target_map)
  
          LOG.info('Successfully initialized connection. '
                   'target_wwn: %(target_wwn)s, '
                   'initiator_target_map: %(initiator_target_map)s, '
                   'lun: %(target_lun)s.', properties['data'])
          return properties
  
      def _do_fc_connection(self, volume, connector):
          volume_id = volume['id'].replace('-', '')
          target_wwpns = []
  
          partition_data = self._extract_all_provider_location(
              volume['provider_location'])
          part_id = partition_data['partition_id']
  
          if part_id is None:
              part_id = self._get_part_id(volume_id)
  
          wwpn_list, wwpn_channel_info = self._get_wwpn_list()
  
          initiator_target_map, target_wwpns = self._build_initiator_target_map(
              connector, wwpn_list)
  
          map_lun = self._get_common_lun_map_id(wwpn_channel_info)
  
          # Sort items to get a reliable behaviour. Dictionary items
          # are iterated in a random order because of hash randomization.
          for initiator_wwpn in sorted(initiator_target_map):
              for target_wwpn in initiator_target_map[initiator_wwpn]:
                  channel_id = wwpn_channel_info[target_wwpn.upper()]['channel']
                  controller = wwpn_channel_info[target_wwpn.upper()]['slot']
                  self._create_map_with_lun_filter(
                      part_id, channel_id, map_lun, initiator_wwpn,
                      controller=controller)
  
          return map_lun, target_wwpns, initiator_target_map
  
      def _build_initiator_target_map(self, connector, all_target_wwpns):
          initiator_target_map = {}
          target_wwpns = []
  
          if self.fc_lookup_service:
              lookup_map = (
                  self.fc_lookup_service.get_device_mapping_from_network(
                      connector['wwpns'], all_target_wwpns)
              )
              for fabric_name in lookup_map:
                  fabric = lookup_map[fabric_name]
                  target_wwpns.extend(fabric['target_port_wwn_list'])
                  for initiator in fabric['initiator_port_wwn_list']:
                      initiator_target_map[initiator] = (
                          fabric['target_port_wwn_list']
                      )
          else:
              initiator_wwns = connector['wwpns']
              target_wwpns = all_target_wwpns
              for initiator in initiator_wwns:
                  initiator_target_map[initiator] = all_target_wwpns
  
          return initiator_target_map, target_wwpns
  
      def _generate_fc_connection_properties(
              self, lun_id, target_wwpns, initiator_target_map):
  
          return {
              'driver_volume_type': 'fibre_channel',
              'data': {
                  'target_discovered': True,
                  'target_lun': int(lun_id),
                  'target_wwn': target_wwpns,
                  'initiator_target_map': initiator_target_map,
              },
          }
  
      @log_func
      def _initialize_connection_iscsi(self, volume, connector, multipath):
          self._init_map_info(multipath)
          self._update_map_info(multipath)
  
          volume_id = volume['id'].replace('-', '')
  
          partition_data = self._extract_all_provider_location(
              volume['provider_location'])  # system_id, part_id
  
          part_id = partition_data['partition_id']
  
          if part_id is None:
              part_id = self._get_part_id(volume_id)
  
          self._set_host_iqn(connector['initiator'])
  
          map_chl, map_lun, mcs_id = self._get_mapping_info(multipath)
  
          lun_id = map_lun[0]
  
          if self.iscsi_multipath or multipath:
              channel_id = self._create_map_with_mcs(
                  part_id, map_chl['slot_a'], lun_id, connector['initiator'])
          else:
              channel_id = map_chl['slot_a'][0]
  
              self._create_map_with_lun_filter(
                  part_id, channel_id, lun_id, connector['initiator'])
  
          rc, net_list = self._execute('ShowNet')
          ip = self._get_ip_by_channel(channel_id, net_list)
  
          if ip is None:
              msg = _(
                  'Failed to get ip on Channel %(channel_id)s '
                  'with volume: %(volume_id)s.') % {
                      'channel_id': channel_id, 'volume_id': volume_id}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          partition_data = self._combine_channel_lun_target_id(
              partition_data, mcs_id, lun_id, channel_id)
  
          property_value = [{
              'lun_id': partition_data['lun_id'],
              'iqn': self._generate_iqn(partition_data),
              'ip': ip,
              'port': self.constants['ISCSI_PORT'],
          }]
  
          properties = self._generate_iscsi_connection_properties(
              property_value, volume)
          LOG.info('Successfully initialized connection '
                   'with volume: %(volume_id)s.', properties['data'])
          return properties
  
      @log_func
      def _combine_channel_lun_target_id(
              self, partition_data, mcs_id, lun_id, channel_id):
  
          target_id = self.target_dict['slot_a'][channel_id]
  
          partition_data['mcs_id'] = mcs_id
          partition_data['lun_id'] = lun_id
          partition_data['target_id'] = target_id
          partition_data['slot_id'] = 1
  
          return partition_data
  
      def _set_host_iqn(self, host_iqn):
  
          rc, iqn_list = self._execute('ShowIQN')
  
          check_iqn_exist = False
          for entry in iqn_list:
              if entry['IQN'] == host_iqn:
                  check_iqn_exist = True
  
          if not check_iqn_exist:
              self._execute(
                  'CreateIQN', host_iqn, self._truncate_host_name(host_iqn))
  
      def _truncate_host_name(self, iqn):
          if len(iqn) > 16:
              return iqn[-16:]
          else:
              return iqn
  
      @log_func
      def _generate_iqn(self, partition_data):
          return self.iqn % (
              partition_data['system_id'],
              partition_data['mcs_id'],
              partition_data['target_id'],
              partition_data['slot_id'])
  
      @log_func
      def _get_ip_by_channel(
              self, channel_id, net_list, controller='slot_a'):
  
          slot_name = 'slotA' if controller == 'slot_a' else 'slotB'
  
          for entry in net_list:
              if entry['ID'] == channel_id and entry['Slot'] == slot_name:
                  return entry['IPv4']
          return
  
      def _get_wwpn_list(self):
          rc, wwn_list = self._execute('ShowWWN')
  
          wwpn_list = []
          wwpn_channel_info = {}
  
          for entry in wwn_list:
              channel_id = entry['CH']
              if 'BID' in entry['ID']:
                  slot_name = 'slot_b'
              else:
                  slot_name = 'slot_a'
  
              if channel_id in self.map_dict[slot_name]:
                  wwpn_list.append(entry['WWPN'])
  
                  wwpn_channel_info[entry['WWPN']] = {
                      'channel': channel_id,
                      'slot': slot_name,
                  }
  
          return wwpn_list, wwpn_channel_info
  
      @log_func
      def _generate_iscsi_connection_properties(
              self, property_value, volume):
  
          properties = {}
          discovery_exist = False
  
          specific_property = property_value[0]
  
          discovery_ip = '%s:%s' % (
              specific_property['ip'], specific_property['port'])
          discovery_iqn = specific_property['iqn']
  
          if self._do_iscsi_discovery(discovery_iqn, discovery_ip):
              properties['target_portal'] = discovery_ip
              properties['target_iqn'] = discovery_iqn
              properties['target_lun'] = int(specific_property['lun_id'])
              discovery_exist = True
  
          if not discovery_exist:
              msg = _(
                  'Could not find iSCSI target '
                  'for volume: %(volume_id)s.') % {
                      'volume_id': volume['id']}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          properties['target_discovered'] = discovery_exist
          properties['volume_id'] = volume['id']
  
          if 'provider_auth' in volume:
              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
  
          return {
              'driver_volume_type': 'iscsi',
              'data': properties,
          }
  
      @log_func
      def _do_iscsi_discovery(self, target_iqn, target_ip):
          rc, out = self._execute(
              'ExecuteCommand',
              'iscsiadm', '-m', 'discovery',
              '-t', 'sendtargets', '-p',
              target_ip,
              run_as_root=True)
  
          if rc != 0:
              LOG.error(
                  'Can not discovery in %(target_ip)s with %(target_iqn)s.',
                  {'target_ip': target_ip, 'target_iqn': target_iqn})
              return False
          else:
              for target in out.splitlines():
                  if target_iqn in target and target_ip in target:
                      return True
          return False
  
      def extend_volume(self, volume, new_size):
          volume_id = volume['id'].replace('-', '')
  
          part_id = self._extract_specific_provider_location(
              volume['provider_location'], 'partition_id')
  
          if part_id is None:
              part_id = self._get_part_id(volume_id)
  
          expand_size = new_size - volume['size']
  
          if '.' in ('%s' % expand_size):
              expand_size = round(gi_to_mi(float(expand_size)))
              expand_command = 'size=%sMB' % expand_size
          else:
              expand_command = 'size=%sGB' % expand_size
  
          self._execute('SetPartition', 'expand', part_id, expand_command)
  
          LOG.info(
              'Successfully extended volume %(volume_id)s to size %(size)s.', {
                  'volume_id': volume['id'], 'size': new_size})
  
      @lockutils.synchronized('connection', 'infortrend-', True)
      def terminate_connection(self, volume, connector):
          volume_id = volume['id'].replace('-', '')
          multipath = connector.get('multipath', False)
          conn_info = None
  
          part_id = self._extract_specific_provider_location(
              volume['provider_location'], 'partition_id')
  
          if part_id is None:
              part_id = self._get_part_id(volume_id)
  
          self._execute('DeleteMap', 'part', part_id, '-y')
          map_info = self._update_map_info(multipath)
  
          if self.protocol == 'iSCSI':
              initiator_iqn = self._truncate_host_name(connector['initiator'])
              lun_map_exist = self._check_initiator_has_lun_map(
                  initiator_iqn, map_info)
  
              if not lun_map_exist:
                  self._execute('DeleteIQN', initiator_iqn)
  
          elif self.protocol == 'FC':
              conn_info = {'driver_volume_type': 'fibre_channel',
                           'data': {}}
              lun_map_exist = self._check_initiator_has_lun_map(
                  connector['wwpns'], map_info)
  
              if not lun_map_exist:
  
                  wwpn_list, wwpn_channel_info = self._get_wwpn_list()
                  init_target_map, target_wwpns = (
                      self._build_initiator_target_map(connector, wwpn_list)
                  )
                  conn_info['data']['initiator_target_map'] = init_target_map
  
          LOG.info(
              'Successfully terminated connection for volume: %(volume_id)s.',
              {'volume_id': volume['id']})
  
          return conn_info
  
      def migrate_volume(self, volume, host, new_extraspecs=None):
          is_valid, dst_pool_id = (
              self._is_valid_for_storage_assisted_migration(host)
          )
          if not is_valid:
              return (False, None)
  
          model_dict = self._migrate_volume_with_pool(
              volume, dst_pool_id, new_extraspecs)
  
          model_update = {
              "provider_location": self._concat_provider_location(model_dict),
          }
  
          LOG.info('Migrate Volume %(volume_id)s completed.', {
              'volume_id': volume['id']})
  
          return (True, model_update)
  
      def _is_valid_for_storage_assisted_migration(self, host):
          if 'pool_id' not in host['capabilities']:
              LOG.warning('Failed to get target pool id.')
              return (False, None)
  
          dst_pool_id = host['capabilities']['pool_id']
          if dst_pool_id is None:
              return (False, None)
  
          return (True, dst_pool_id)
  
      def _migrate_volume_with_pool(self, volume, dst_pool_id, extraspecs=None):
          volume_id = volume['id'].replace('-', '')
  
          # Get old partition data for delete map
          partition_data = self._extract_all_provider_location(
              volume['provider_location'])
  
          src_part_id = partition_data['partition_id']
  
          if src_part_id is None:
              src_part_id = self._get_part_id(volume_id)
  
          # Create New Partition
          self._create_partition_with_pool(volume, dst_pool_id, extraspecs)
  
          dst_part_id = self._get_part_id(
              volume_id, pool_id=dst_pool_id)
  
          if dst_part_id is None:
              msg = _('Failed to get new part id in new pool: %(pool_id)s.') % {
                  'pool_id': dst_pool_id}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
          # Volume Mirror from old partition into new partition
          commands = (
              'Cinder-Migrate', 'part', src_part_id, 'part', dst_part_id,
              'type=mirror'
          )
          self._execute('CreateReplica', *commands)
  
          self._wait_replica_complete(dst_part_id)
  
          self._execute('DeleteMap', 'part', src_part_id, '-y')
          self._execute('DeletePartition', src_part_id, '-y')
  
          model_dict = {
              'system_id': partition_data['system_id'],
              'partition_id': dst_part_id,
          }
  
          return model_dict
  
      def _wait_replica_complete(self, part_id):
          start_time = int(time.time())
          timeout = self._replica_timeout
  
          def _inner():
              check_done = False
              try:
                  rc, replica_list = self._execute('ShowReplica', '-l')
                  for entry in replica_list:
                      if (entry['Target'] == part_id and
                              self._check_replica_completed(entry)):
                          check_done = True
                          self._execute('DeleteReplica', entry['Pair-ID'], '-y')
              except Exception:
                  check_done = False
                  LOG.exception('Cannot detect replica status.')
  
              if check_done:
                  raise loopingcall.LoopingCallDone()
  
              if int(time.time()) - start_time > timeout:
                  msg = _('Wait replica complete timeout.')
                  LOG.error(msg)
                  raise exception.VolumeDriverException(message=msg)
  
          timer = loopingcall.FixedIntervalLoopingCall(_inner)
          timer.start(interval=10).wait()
  
      def _check_extraspec_value(self, extraspec, validvalues):
          if not extraspec:
              LOG.debug("The given extraspec is None.")
          elif extraspec not in validvalues:
              msg = _("The extraspec: %(extraspec)s is not valid.") % {
                  'extraspec': extraspec}
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      def _get_enable_specs_on_array(self):
          enable_specs = {}
          rc, license_list = self._execute('ShowLicense')
  
          for key, value in license_list.items():
              if value['Support']:
                  enable_specs[key] = value
  
          return enable_specs
  
      def manage_existing_get_size(self, volume, ref):
          """Return size of volume to be managed by manage_existing."""
  
          volume_name = self._get_existing_volume_ref_name(ref)
          part_entry = self._get_latter_volume_dict(volume_name)
  
          if part_entry is None:
              msg = _('Specified logical volume does not exist.')
              LOG.error(msg)
              raise exception.ManageExistingInvalidReference(
                  existing_ref=ref, reason=msg)
  
          rc, map_info = self._execute('ShowMap', 'part=%s' % part_entry['ID'])
  
          if len(map_info) != 0:
              msg = _('The specified volume is mapped to a host.')
              LOG.error(msg)
              raise exception.VolumeBackendAPIException(data=msg)
  
          return int(math.ceil(mi_to_gi(float(part_entry['Size']))))
  
      def manage_existing(self, volume, ref):
          volume_name = self._get_existing_volume_ref_name(ref)
          volume_id = volume['id'].replace('-', '')
  
          part_entry = self._get_latter_volume_dict(volume_name)
  
          if part_entry is None:
              msg = _('Specified logical volume does not exist.')
              LOG.error(msg)
              raise exception.ManageExistingInvalidReference(
                  existing_ref=ref, reason=msg)
  
          self._execute('SetPartition', part_entry['ID'], 'name=%s' % volume_id)
  
          model_dict = {
              'system_id': self._get_system_id(self.ip),
              'partition_id': part_entry['ID'],
          }
          model_update = {
              "provider_location": self._concat_provider_location(model_dict),
          }
  
          LOG.info('Rename Volume %(volume_id)s completed.', {
              'volume_id': volume['id']})
  
          return model_update
  
      def _get_existing_volume_ref_name(self, ref):
          volume_name = None
          if 'source-name' in ref:
              volume_name = ref['source-name']
          elif 'source-id' in ref:
              volume_name = self._get_unmanaged_volume_name(
                  ref['source-id'].replace('-', ''))
          else:
              msg = _('Reference must contain source-id or source-name.')
              LOG.error(msg)
              raise exception.ManageExistingInvalidReference(
                  existing_ref=ref, reason=msg)
  
          return volume_name
  
      def unmanage(self, volume):
          volume_id = volume['id'].replace('-', '')
          part_id = self._extract_specific_provider_location(
              volume['provider_location'], 'partition_id')
  
          if part_id is None:
              part_id = self._get_part_id(volume_id)
  
          new_vol_name = self._get_unmanaged_volume_name(volume_id)
          self._execute('SetPartition', part_id, 'name=%s' % new_vol_name)
  
          LOG.info('Unmanage volume %(volume_id)s completed.', {
              'volume_id': volume_id})
  
      def _get_unmanaged_volume_name(self, volume_id):
          return self.unmanaged_prefix % volume_id[:-17]
  
      def _get_specific_volume_dict(self, volume_id):
          ref_dict = {}
          rc, part_list = self._execute('ShowPartition')
  
          for entry in part_list:
              if entry['Name'] == volume_id:
                  ref_dict = entry
                  break
  
          return ref_dict
  
      def _get_latter_volume_dict(self, volume_name):
          rc, part_list = self._execute('ShowPartition', '-l')
  
          latest_timestamps = 0
          ref_dict = None
  
          for entry in part_list:
              if entry['Name'] == volume_name:
  
                  timestamps = self._get_part_timestamps(
                      entry['Creation-time'])
  
                  if timestamps > latest_timestamps:
                      ref_dict = entry
                      latest_timestamps = timestamps
  
          return ref_dict
  
      def _get_part_timestamps(self, time_string):
          """Transform 'Sat, Jan 11 22:18:40 2020' into timestamps with sec."""
  
          first, value = time_string.split(',')
          timestamps = time.mktime(
              time.strptime(value, " %b %d %H:%M:%S %Y"))
  
          return timestamps
  
      def _check_volume_attachment(self, volume):
          if not volume['volume_attachment']:
              return False
          return True
  
      def _check_volume_has_snapshot(self, volume):
          part_id = self._extract_specific_provider_location(
              volume['provider_location'], 'partition_id')
  
          rc, snapshot_list = self._execute('ShowSnapshot', 'part=%s' % part_id)
  
          if len(snapshot_list) > 0:
              return True
          return False
  
      def retype(self, ctxt, volume, new_type, diff, host):
          """Convert the volume to be of the new type."""
  
          if volume['host'] != host['host']:
              if self._check_volume_attachment(volume):
                  LOG.warning(
                      'Volume %(volume_id)s cannot be retyped '
                      'during attachment.', {
                          'volume_id': volume['id']})
                  return False
  
              if self._check_volume_has_snapshot(volume):
                  LOG.warning(
                      'Volume %(volume_id)s cannot be retyped '
                      'because it has snapshot.', {
                          'volume_id': volume['id']})
                  return False
  
              new_extraspecs = new_type['extra_specs']
              rc, model_update = self.migrate_volume(
                  volume, host, new_extraspecs)
  
              if rc:
                  LOG.info(
                      'Retype Volume %(volume_id)s is done '
                      'and migrated to pool %(pool_id)s.', {
                          'volume_id': volume['id'],
                          'pool_id': host['capabilities']['pool_id']})
  
              return (rc, model_update)
          else:
              if ('infortrend_provisioning' in diff['extra_specs'] and
                      (diff['extra_specs']['infortrend_provisioning'][0] !=
                          diff['extra_specs']['infortrend_provisioning'][1])):
  
                  LOG.warning(
                      'The provisioning: %(provisioning)s is not valid.',
                      {'provisioning':
                       diff['extra_specs']['infortrend_provisioning'][1]})
                  return False
  
              LOG.info('Retype Volume %(volume_id)s is completed.', {
                  'volume_id': volume['id']})
              return True
  
      def update_migrated_volume(self, ctxt, volume, new_volume,
                                 original_volume_status):
          """Return model update for migrated volume."""
  
          src_volume_id = volume['id'].replace('-', '')
          dst_volume_id = new_volume['id'].replace('-', '')
          part_id = self._extract_specific_provider_location(
              new_volume['provider_location'], 'partition_id')
  
          if part_id is None:
              part_id = self._get_part_id(dst_volume_id)
  
          LOG.debug(
              'Rename partition %(part_id)s '
              'into new volume %(new_volume)s.', {
                  'part_id': part_id, 'new_volume': dst_volume_id})
          try:
              self._execute('SetPartition', part_id, 'name=%s' % src_volume_id)
          except exception.InfortrendCliException:
              LOG.exception('Failed to rename %(new_volume)s into '
                            '%(volume)s.', {'new_volume': new_volume['id'],
                                            'volume': volume['id']})
              return {'_name_id': new_volume['_name_id'] or new_volume['id']}
  
          LOG.info('Update migrated volume %(new_volume)s completed.', {
              'new_volume': new_volume['id']})
  
          model_update = {
              '_name_id': None,
              'provider_location': new_volume['provider_location'],
          }
          return model_update
  