text: # Copyright 2016 Violin Memory, 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.
  
  """
  Violin 7000 Series All-Flash Array iSCSI Volume Driver
  
  Provides ISCSI specific LUN services for V7000 series flash arrays.
  
  This driver requires Concerto v7.5.4 or newer software on the array.
  
  You will need to install the python VMEM REST client:
  sudo pip install vmemclient
  
  Set the following in the cinder.conf file to enable the VMEM V7000
  ISCSI Driver along with the required flags:
  
  volume_driver=cinder.volume.drivers.violin.v7000_iscsi.V7000ISCSIDriver
  """
  
  import random
  import uuid
  
  from oslo_log import log as logging
  
  from cinder import exception
  from cinder import interface
  from cinder.i18n import _, _LE, _LI, _LW
  from cinder.volume import driver
  from cinder.volume.drivers.san import san
  from cinder.volume.drivers.violin import v7000_common
  
  LOG = logging.getLogger(__name__)
  
  
  @interface.volumedriver
  class V7000ISCSIDriver(driver.ISCSIDriver):
      """Executes commands relating to iscsi based Violin Memory arrays.
  
      Version history:
          1.0 - Initial driver
      """
  
      VERSION = '1.0'
  
      def __init__(self, *args, **kwargs):
          super(V7000ISCSIDriver, self).__init__(*args, **kwargs)
          self.stats = {}
          self.gateway_iscsi_ip_addresses = []
          self.configuration.append_config_values(v7000_common.violin_opts)
          self.configuration.append_config_values(san.san_opts)
          self.common = v7000_common.V7000Common(self.configuration)
  
          LOG.info(_LI("Initialized driver %(name)s version: %(vers)s"),
                   {'name': self.__class__.__name__, 'vers': self.VERSION})
  
      def do_setup(self, context):
          """Any initialization the driver does while starting."""
          super(V7000ISCSIDriver, self).do_setup(context)
  
          self.common.do_setup(context)
  
          # Register the client with the storage array
          iscsi_version = self.VERSION + "-ISCSI"
          self.common.vmem_mg.utility.set_managed_by_openstack_version(
              iscsi_version, protocol="iSCSI")
  
          # Getting iscsi IPs from the array is incredibly expensive,
          # so only do it once.
          if not self.configuration.violin_iscsi_target_ips:
              LOG.warning(_LW("iSCSI target ip addresses not configured "))
              self.gateway_iscsi_ip_addresses = (
                  self.common.vmem_mg.utility.get_iscsi_interfaces())
          else:
              self.gateway_iscsi_ip_addresses = (
                  self.configuration.violin_iscsi_target_ips)
  
      def check_for_setup_error(self):
          """Returns an error if prerequisites aren't met."""
          self.common.check_for_setup_error()
          if len(self.gateway_iscsi_ip_addresses) == 0:
              msg = _('No iSCSI IPs configured on SAN gateway')
              raise exception.ViolinInvalidBackendConfig(reason=msg)
  
      def create_volume(self, volume):
          """Creates a volume."""
          self.common._create_lun(volume)
  
      def create_volume_from_snapshot(self, volume, snapshot):
          """Creates a volume from a snapshot."""
          self.common._create_volume_from_snapshot(snapshot, volume)
  
      def create_cloned_volume(self, volume, src_vref):
          """Creates a clone of the specified volume."""
          self.common._create_lun_from_lun(src_vref, volume)
  
      def delete_volume(self, volume):
          """Deletes a volume."""
          self.common._delete_lun(volume)
  
      def extend_volume(self, volume, new_size):
          """Extend an existing volume's size."""
          self.common._extend_lun(volume, new_size)
  
      def create_snapshot(self, snapshot):
          """Creates a snapshot."""
          self.common._create_lun_snapshot(snapshot)
  
      def delete_snapshot(self, snapshot):
          """Deletes a snapshot."""
          self.common._delete_lun_snapshot(snapshot)
  
      def ensure_export(self, context, volume):
          """Synchronously checks and re-exports volumes at cinder start time."""
          pass
  
      def create_export(self, context, volume, connector):
          """Exports the volume."""
          pass
  
      def remove_export(self, context, volume):
          """Removes an export for a logical volume."""
          pass
  
      def initialize_connection(self, volume, connector):
          """Allow connection to connector and return connection info."""
          resp = {}
  
          LOG.debug("Initialize_connection: initiator - %(initiator)s  host - "
                    "%(host)s ip - %(ip)s",
                    {'initiator': connector['initiator'],
                     'host': connector['host'],
                     'ip': connector['ip']})
  
          iqn = self._get_iqn(connector)
  
          # pick a random single target to give the connector since
          # there is no multipathing support
          tgt = self.gateway_iscsi_ip_addresses[random.randint(
              0, len(self.gateway_iscsi_ip_addresses) - 1)]
  
          resp = self.common.vmem_mg.client.create_client(
              name=connector['host'], proto='iSCSI',
              iscsi_iqns=connector['initiator'])
  
          # raise if we failed for any reason other than a 'client
          # already exists' error code
          if not resp['success'] and 'Error: 0x900100cd' not in resp['msg']:
              msg = _("Failed to create iscsi client")
              raise exception.ViolinBackendErr(message=msg)
  
          resp = self.common.vmem_mg.client.create_iscsi_target(
              name=iqn, client_name=connector['host'],
              ip=self.gateway_iscsi_ip_addresses, access_mode='ReadWrite')
  
          # same here, raise for any failure other than a 'target
          # already exists' error code
          if not resp['success'] and 'Error: 0x09024309' not in resp['msg']:
              msg = _("Failed to create iscsi target")
              raise exception.ViolinBackendErr(message=msg)
  
          lun_id = self._export_lun(volume, iqn, connector)
  
          properties = {}
          properties['target_discovered'] = False
          properties['target_iqn'] = iqn
          properties['target_portal'] = '%s:%s' % (tgt, '3260')
          properties['target_lun'] = lun_id
          properties['volume_id'] = volume['id']
  
          LOG.debug("Return ISCSI data: %(properties)s.",
                    {'properties': properties})
  
          return {'driver_volume_type': 'iscsi', 'data': properties}
  
      def terminate_connection(self, volume, connector, **kwargs):
          """Terminates the connection (target<-->initiator)."""
          iqn = self._get_iqn(connector)
          self._unexport_lun(volume, iqn, connector)
  
      def get_volume_stats(self, refresh=False):
          """Get volume stats.
  
          If 'refresh' is True, update the stats first.
          """
          if refresh or not self.stats:
              self._update_volume_stats()
          return self.stats
  
      def _update_volume_stats(self):
          """Gathers array stats and converts them to GB values."""
          data = self.common._get_volume_stats(self.configuration.san_ip)
  
          backend_name = self.configuration.volume_backend_name
          data['volume_backend_name'] = backend_name or self.__class__.__name__
          data['driver_version'] = self.VERSION
          data['storage_protocol'] = 'iSCSI'
          for i in data:
              LOG.debug("stat update: %(name)s=%(data)s",
                        {'name': i, 'data': data[i]})
  
          self.stats = data
  
      def _export_lun(self, volume, target, connector):
          """Generates the export configuration for the given volume.
  
          :param volume:  volume object provided by the Manager
          :param connector:  connector object provided by the Manager
          :returns: the LUN ID assigned by the backend
          """
          lun_id = ''
          v = self.common.vmem_mg
  
          LOG.debug("Exporting lun %(vol_id)s - initiator iqns %(i_iqns)s "
                    "- target iqns %(t_iqns)s.",
                    {'vol_id': volume['id'], 'i_iqns': connector['initiator'],
                     't_iqns': self.gateway_iscsi_ip_addresses})
  
          try:
              lun_id = self.common._send_cmd_and_verify(
                  v.lun.assign_lun_to_iscsi_target,
                  self._is_lun_id_ready,
                  "Assign device successfully",
                  [volume['id'], target],
                  [volume['id'], connector['host']])
  
          except exception.ViolinBackendErr:
              LOG.exception(_LE("Backend returned error for lun export."))
              raise
  
          except Exception:
              raise exception.ViolinInvalidBackendConfig(
                  reason=_('LUN export failed!'))
  
          lun_id = self._get_lun_id(volume['id'], connector['host'])
          LOG.info(_LI("Exported lun %(vol_id)s on lun_id %(lun_id)s."),
                   {'vol_id': volume['id'], 'lun_id': lun_id})
  
          return lun_id
  
      def _unexport_lun(self, volume, target, connector):
          """Removes the export configuration for the given volume.
  
          The equivalent CLI command is "no lun export container
          <container_name> name <lun_name>"
  
          Arguments:
              volume -- volume object provided by the Manager
          """
          v = self.common.vmem_mg
  
          LOG.info(_LI("Unexporting lun %(vol)s host is %(host)s"),
                   {'vol': volume['id'], 'host': connector['host']})
  
          try:
              self.common._send_cmd(v.lun.unassign_lun_from_iscsi_target,
                                    "Unassign device successfully",
                                    volume['id'], target, True)
  
          except exception.ViolinBackendErrNotFound:
              LOG.info(_LI("Lun %s already unexported, continuing"),
                       volume['id'])
  
          except Exception:
              LOG.exception(_LE("LUN unexport failed!"))
              msg = _("LUN unexport failed")
              raise exception.ViolinBackendErr(message=msg)
  
      def _is_lun_id_ready(self, volume_name, client_name):
          """Get the lun ID for an exported volume.
  
          If the lun is successfully assigned (exported) to a client, the
          client info has the lun_id.
  
          Note: The structure returned for iscsi is different from the
          one returned for FC. Therefore this function is here instead of
          common.
  
          Arguments:
              volume_name -- name of volume to query for lun ID
              client_name -- name of client associated with the volume
  
          Returns:
              lun_id -- Returns True or False
          """
  
          lun_id = -1
          lun_id = self._get_lun_id(volume_name, client_name)
  
          if lun_id is None:
              return False
          else:
              return True
  
      def _get_lun_id(self, volume_name, client_name):
          """Get the lun ID for an exported volume.
  
          If the lun is successfully assigned (exported) to a client, the
          client info has the lun_id.
  
          Note: The structure returned for iscsi is different from the
          one returned for FC. Therefore this function is here instead of
          common.
  
          Arguments:
              volume_name -- name of volume to query for lun ID
              client_name -- name of client associated with the volume
  
          Returns:
              lun_id -- integer value of lun ID
          """
          v = self.common.vmem_mg
          lun_id = None
  
          client_info = v.client.get_client_info(client_name)
  
          for x in client_info['ISCSIDevices']:
              if volume_name == x['name']:
                  lun_id = x['lun']
                  break
  
          if lun_id:
              lun_id = int(lun_id)
  
          return lun_id
  
      def _get_iqn(self, connector):
          # The vmemclient connection properties list hostname field may
          # change depending on failover cluster config.  Use a UUID
          # from the backend's IP address to avoid a potential naming
          # issue.
          host_uuid = uuid.uuid3(uuid.NAMESPACE_DNS, self.configuration.san_ip)
          return "%s%s.%s" % (self.configuration.iscsi_target_prefix,
                              connector['host'], host_uuid)
  