text: # Nimble Storage, Inc. (c) 2013-2014
  # 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.
  """
  Volume driver for Nimble Storage.
  
  This driver supports Nimble Storage controller CS-Series and Nimble AF Arrays.
  
  """
  
  import abc
  import functools
  import json
  import random
  import re
  import string
  import sys
  
  import eventlet
  from oslo_config import cfg
  from oslo_log import log as logging
  from oslo_utils import units
  import requests
  import six
  
  from cinder import exception
  from cinder.i18n import _
  from cinder import interface
  from cinder.objects import volume
  from cinder import utils
  from cinder.volume import configuration
  from cinder.volume import driver
  from cinder.volume.drivers.san import san
  from cinder.volume import volume_types
  from cinder.zonemanager import utils as fczm_utils
  
  DRIVER_VERSION = "4.0.1"
  AES_256_XTS_CIPHER = 'aes_256_xts'
  DEFAULT_CIPHER = 'none'
  EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
  EXTRA_SPEC_PERF_POLICY = 'nimble:perfpol-name'
  EXTRA_SPEC_DEDUPE = 'nimble:dedupe'
  EXTRA_SPEC_IOPS_LIMIT = 'nimble:iops-limit'
  EXTRA_SPEC_FOLDER = 'nimble:folder'
  DEFAULT_PERF_POLICY_SETTING = 'default'
  DEFAULT_ENCRYPTION_SETTING = 'no'
  DEFAULT_DEDUPE_SETTING = 'false'
  DEFAULT_IOPS_LIMIT_SETTING = None
  DEFAULT_FOLDER_SETTING = None
  DEFAULT_SNAP_QUOTA = sys.maxsize
  BACKUP_VOL_PREFIX = 'backup-vol-'
  AGENT_TYPE_OPENSTACK = 'openstack'
  AGENT_TYPE_OPENSTACK_GST = 'openstackv2'
  AGENT_TYPE_NONE = 'none'
  SM_SUBNET_DATA = 'data'
  SM_SUBNET_MGMT_PLUS_DATA = 'mgmt-data'
  SM_STATE_MSG = "is already in requested state"
  SM_OBJ_EXIST_MSG = "Object exists"
  SM_OBJ_ENOENT_MSG = "No such object"
  SM_OBJ_HAS_CLONE = "has a clone"
  IOPS_ERR_MSG = "Please set valid IOPS limit in the range"
  LUN_ID = '0'
  WARN_LEVEL = 80
  DEFAULT_SLEEP = 5
  MIN_IOPS = 256
  MAX_IOPS = 4294967294
  NimbleDefaultVersion = 1
  
  
  LOG = logging.getLogger(__name__)
  
  nimble_opts = [
      cfg.StrOpt('nimble_pool_name',
                 default='default',
                 help='Nimble Controller pool name'),
      cfg.StrOpt('nimble_subnet_label',
                 default='*',
                 help='Nimble Subnet Label'),
      cfg.BoolOpt('nimble_verify_certificate',
                  default=False,
                  help='Whether to verify Nimble SSL Certificate'),
      cfg.StrOpt('nimble_verify_cert_path',
                 help='Path to Nimble Array SSL certificate'), ]
  
  CONF = cfg.CONF
  CONF.register_opts(nimble_opts, group=configuration.SHARED_CONF_GROUP)
  
  
  class NimbleDriverException(exception.VolumeDriverException):
      message = _("Nimble Cinder Driver exception")
  
  
  class NimbleAPIException(exception.VolumeBackendAPIException):
      message = _("Unexpected response from Nimble API")
  
  
  class NimbleVolumeBusyException(exception.VolumeIsBusy):
      message = _("Nimble Cinder Driver: Volume Busy")
  
  
  class NimbleBaseVolumeDriver(san.SanDriver):
      """OpenStack driver to enable Nimble Controller.
  
      Version history:
  
      .. code-block:: none
  
  
          1.0 - Initial driver
          1.1.1 - Updated VERSION to Nimble driver version
          1.1.2 - Update snap-quota to unlimited
          2.0.0 - Added Extra Spec Capability
                  Correct capacity reporting
                  Added Manage/Unmanage volume support
          2.0.1 - Added multi-initiator support through extra-specs
          2.0.2 - Fixed supporting extra specs while cloning vols
          3.0.0 - Newton Support for Force Backup
          3.1.0 - Fibre Channel Support
          4.0.0 - Migrate from SOAP to REST API
                  Add support for Group Scoped Target
          4.0.1 - Add QoS and dedupe support
      """
      VERSION = DRIVER_VERSION
  
      # ThirdPartySystems wiki page
      CI_WIKI_NAME = "Nimble_Storage_CI"
  
      def __init__(self, *args, **kwargs):
          super(NimbleBaseVolumeDriver, self).__init__(*args, **kwargs)
          self.APIExecutor = None
          self.group_stats = {}
          self.api_protocol = None
          self._storage_protocol = None
          self._group_target_enabled = False
          self.configuration.append_config_values(nimble_opts)
          self.verify = False
          if self.configuration.nimble_verify_certificate is True:
              self.verify = self.configuration.nimble_verify_cert_path or True
  
      @staticmethod
      def get_driver_options():
          return nimble_opts
  
      def _check_config(self):
          """Ensure that the flags we care about are set."""
          required_config = ['san_ip', 'san_login', 'san_password']
          for attr in required_config:
              if not getattr(self.configuration, attr, None):
                  raise exception.InvalidInput(reason=_('%s is not set.') %
                                               attr)
  
      def create_volume(self, volume):
          """Create a new volume."""
          reserve = not self.configuration.san_thin_provision
          self.APIExecutor.create_vol(
              volume,
              self.configuration.nimble_pool_name, reserve,
              self._storage_protocol,
              self._group_target_enabled)
          return self._get_model_info(volume['name'])
  
      def is_volume_backup_clone(self, volume):
          """check if the volume is created through cinder-backup workflow.
  
          :param volume
          """
          vol_info = self.APIExecutor.get_vol_info(volume['name'])
          LOG.debug("is_clone: %(is_clone)s base_snap_id: %(snap)s, "
                    "parent_vol_id: %(vol)s",
                    {'is_clone': vol_info['clone'],
                     'snap': vol_info['base_snap_id'],
                     'vol': vol_info['parent_vol_id']})
  
          if vol_info['base_snap_id'] and (
             vol_info['parent_vol_id'] is not None):
              LOG.debug("Nimble base-snap exists for volume %(vol)s",
                        {'vol': volume['name']})
              volume_name_prefix = volume['name'].replace(volume['id'], "")
              LOG.debug("volume_name_prefix : %(prefix)s",
                        {'prefix': volume_name_prefix})
              snap_id = self.APIExecutor.get_snap_info_by_id(
                  vol_info['base_snap_id'],
                  vol_info['parent_vol_id'])
              snap_info = self.APIExecutor.get_snap_info_detail(snap_id['id'])
              LOG.debug("snap_info description %(snap_info)s",
                        {'snap_info': snap_info['description']})
              if snap_info['description'] and BACKUP_VOL_PREFIX in (
                      snap_info['description']):
                  # TODO(rkumar): get parent vol id from parent volume name
                  parent_vol_name = self.APIExecutor.get_volume_name(
                      vol_info['parent_vol_id'])
                  parent_vol_id = parent_vol_name. replace(
                      volume_name_prefix, "")
                  if BACKUP_VOL_PREFIX + parent_vol_id in snap_info[
                     'description']:
                      LOG.info('Nimble backup-snapshot exists name=%('
                               'name)s', {'name': snap_info['name']})
                      snap_vol_name = self.APIExecutor.get_volume_name(
                          snap_info['vol_id'])
                      LOG.debug("snap_vol_name %(snap)s",
                                {'snap': snap_vol_name})
                      return snap_info['name'], snap_vol_name
          return "", ""
  
      def delete_volume(self, volume):
          """Delete the specified volume."""
          backup_snap_name, backup_vol_name = self.is_volume_backup_clone(volume)
          eventlet.sleep(DEFAULT_SLEEP)
          self.APIExecutor.online_vol(volume['name'], False)
          LOG.debug("Deleting volume %(vol)s", {'vol': volume['name']})
  
          @utils.retry(NimbleAPIException, retries=3)
          def _retry_remove_vol(volume):
              self.APIExecutor.delete_vol(volume['name'])
          try:
              _retry_remove_vol(volume)
          except NimbleAPIException as ex:
              LOG.debug("delete volume exception: %s", ex)
              if SM_OBJ_HAS_CLONE in six.text_type(ex):
                  LOG.warning('Volume %(vol)s : %(state)s',
                              {'vol': volume['name'],
                               'state': SM_OBJ_HAS_CLONE})
                  # set the volume back to be online and raise busy exception
                  self.APIExecutor.online_vol(volume['name'], True)
                  raise exception.VolumeIsBusy(volume_name=volume['name'])
              raise
          # Nimble backend does not delete the snapshot from the parent volume
          # if there is a dependent clone. So the deletes need to be in reverse
          # order i.e.
          # 1. First delete the clone volume used for backup
          # 2. Delete the base snapshot used for clone from the parent volume.
          # This is only done for the force backup clone operation as it is
          # a temporary operation in which we are certain that the snapshot does
          # not need to be preserved after the backup is completed.
  
          if (backup_snap_name != "" and backup_vol_name != "") and (
             backup_snap_name is not None):
              LOG.debug("Delete volume backup vol: %(vol)s snap: %(snap)s",
                        {'vol': backup_vol_name,
                         'snap': backup_snap_name})
              self.APIExecutor.online_snap(backup_vol_name,
                                           False,
                                           backup_snap_name)
  
              self.APIExecutor.delete_snap(backup_vol_name,
                                           backup_snap_name)
  
      def _generate_random_string(self, length):
          """Generates random_string."""
          char_set = string.ascii_lowercase
          return ''.join(random.sample(char_set, length))
  
      def _clone_volume_from_snapshot(self, volume, snapshot):
          """Clone volume from snapshot.
  
          Extend the volume if the size of the volume is more than the snapshot.
          """
          reserve = not self.configuration.san_thin_provision
          pool_name = self.configuration.nimble_pool_name
          self.APIExecutor.clone_vol(volume, snapshot, reserve,
                                     self._group_target_enabled,
                                     self._storage_protocol,
                                     pool_name)
          if(volume['size'] > snapshot['volume_size']):
              vol_size = volume['size'] * units.Ki
              reserve_size = 100 if reserve else 0
              data = {"data": {'size': vol_size,
                               'reserve': reserve_size,
                               'warn_level': int(WARN_LEVEL),
                               'limit': 100,
                               'snap_limit': DEFAULT_SNAP_QUOTA}}
              LOG.debug("Edit Vol request %(data)s", {'data': data})
              self.APIExecutor.edit_vol(volume['name'], data)
          return self._get_model_info(volume['name'])
  
      def create_cloned_volume(self, volume, src_vref):
          """Create a clone of the specified volume."""
          snapshot_name = ('openstack-clone-' +
                           volume['name'] + '-' +
                           self._generate_random_string(12))
          snapshot = {'volume_name': src_vref['name'],
                      'name': snapshot_name,
                      'volume_size': src_vref['size'],
                      'display_name': volume.display_name,
                      'display_description': ''}
          self.APIExecutor.snap_vol(snapshot)
          self._clone_volume_from_snapshot(volume, snapshot)
          return self._get_model_info(volume['name'])
  
      def create_export(self, context, volume, connector):
          """Driver entry point to get the export info for a new volume."""
          return self._get_model_info(volume['name'])
  
      def ensure_export(self, context, volume):
          """Driver entry point to get the export info for an existing volume."""
          return self._get_model_info(volume['name'])
  
      def create_snapshot(self, snapshot):
          """Create a snapshot."""
          self.APIExecutor.snap_vol(snapshot)
  
      def delete_snapshot(self, snapshot):
          """Delete a snapshot."""
          self.APIExecutor.online_snap(
              snapshot['volume_name'],
              False,
              snapshot['name'])
          self.APIExecutor.delete_snap(snapshot['volume_name'],
                                       snapshot['name'])
  
      def create_volume_from_snapshot(self, volume, snapshot):
          """Create a volume from a snapshot."""
          self._clone_volume_from_snapshot(volume, snapshot)
          return self._get_model_info(volume['name'])
  
      def _enable_group_scoped_target(self, group_info):
          if 'version_current' in group_info:
              current_version = group_info['version_current']
              major_minor = current_version.split(".")
              if len(major_minor) >= 3:
                  major = major_minor[0]
                  minor = major_minor[1]
                  # TODO(rkumar): Fix the major version
                  if int(major) >= 4 and int(minor) >= 0:
                      # Enforce group scoped target
                      if 'group_target_enabled' in group_info:
                          if group_info['group_target_enabled'] is False:
                              try:
                                  self.APIExecutor.enable_group_scoped_target()
                              except Exception:
                                  raise NimbleAPIException(_("Unable to enable"
                                                           " GST"))
                          self._group_target_enabled = True
                          LOG.info("Group Scoped Target enabled for "
                                   "group %(group)s: %(ip)s",
                                   {'group': group_info['name'],
                                    'ip': self.configuration.san_ip})
                      elif 'group_target_enabled' not in group_info:
                          LOG.info("Group Scoped Target NOT "
                                   "present for group %(group)s: "
                                   "%(ip)s",
                                   {'group': group_info['name'],
                                    'ip': self.configuration.san_ip})
              else:
                  raise NimbleAPIException(_("Unable to get current software "
                                             "version for %s") %
                                           self.configuration.san_ip)
  
      def get_volume_stats(self, refresh=False):
          """Get volume stats. This is more of getting group stats."""
          if refresh:
              group_info = self.APIExecutor.get_group_info()
              if 'usage_valid' not in group_info:
                  raise NimbleDriverException(_('SpaceInfo returned by '
                                                'array is invalid'))
              total_capacity = (group_info['usable_capacity_bytes'] /
                                float(units.Gi))
              used_space = ((group_info['compressed_vol_usage_bytes'] +
                            group_info['compressed_snap_usage_bytes'] +
                            group_info['unused_reserve_bytes']) /
                            float(units.Gi))
  
              free_space = total_capacity - used_space
              LOG.debug('total_capacity=%(capacity)f '
                        'used_space=%(used)f free_space=%(free)f',
                        {'capacity': total_capacity,
                         'used': used_space,
                         'free': free_space})
  
              backend_name = self.configuration.safe_get(
                  'volume_backend_name') or self.__class__.__name__
              self.group_stats = {'volume_backend_name': backend_name,
                                  'vendor_name': 'Nimble',
                                  'driver_version': DRIVER_VERSION,
                                  'storage_protocol': self._storage_protocol}
              # Just use a single pool for now, FIXME to support multiple
              # pools
              single_pool = dict(
                  pool_name=backend_name,
                  total_capacity_gb=total_capacity,
                  free_capacity_gb=free_space,
                  reserved_percentage=0,
                  QoS_support=False,
                  multiattach=True)
              self.group_stats['pools'] = [single_pool]
          return self.group_stats
  
      def extend_volume(self, volume, new_size):
          """Extend an existing volume."""
          volume_name = volume['name']
          LOG.info('Entering extend_volume volume=%(vol)s '
                   'new_size=%(size)s',
                   {'vol': volume_name, 'size': new_size})
          vol_size = int(new_size) * units.Ki
          reserve = not self.configuration.san_thin_provision
          reserve_size = 100 if reserve else 0
          LOG.debug("new volume size in MB (size)s", {'size': vol_size})
          data = {"data": {'size': vol_size,
                           'reserve': reserve_size,
                           'warn_level': int(WARN_LEVEL),
                           'limit': 100,
                           'snap_limit': DEFAULT_SNAP_QUOTA}}
          self.APIExecutor.edit_vol(volume_name, data)
  
      def _get_existing_volume_ref_name(self, existing_ref):
          """Returns the volume name of an existing ref"""
          vol_name = None
          if 'source-name' in existing_ref:
              vol_name = existing_ref['source-name']
          else:
              reason = _("Reference must contain source-name.")
              raise exception.ManageExistingInvalidReference(
                  existing_ref=existing_ref,
                  reason=reason)
  
          return vol_name
  
      def _get_volumetype_extraspecs_with_type(self, type_id):
          specs = {}
          if type_id is not None:
              specs = volume_types.get_volume_type_extra_specs(type_id)
          return specs
  
      def retype(self, context, volume, new_type, diff, host):
          """Retype from one volume type to another.
  
          At this point HPE Nimble Storage does not differentiate between
          volume types on the same array. This is a no-op for us if there are
          no extra specs else honor the extra-specs.
          """
          if new_type is None:
              return True, None
          LOG.debug("retype called with volume_type %s", new_type)
  
          volume_type_id = new_type['id']
          if volume_type_id is None:
              raise NimbleAPIException(_("No volume_type_id present in"
                                         " %(type)s") % {'type': new_type})
  
          LOG.debug("volume_type id is %s", volume_type_id)
          specs_map = self._get_volumetype_extraspecs_with_type(
              volume_type_id)
          if specs_map is None:
              # no extra specs to retype
              LOG.debug("volume_type %s has no extra specs", volume_type_id)
              return True, None
          vol_info = self.APIExecutor.get_vol_info(volume['name'])
          LOG.debug("new extra specs %s", specs_map)
          data = self.APIExecutor.get_valid_nimble_extraspecs(specs_map,
                                                              vol_info)
          if data is None:
              # return if there is no update
              LOG.debug("no data to update for %s", new_type)
              return True, None
          try:
              # offline the volume before edit
              self.APIExecutor.online_vol(volume['name'], False)
              # modify the volume
              LOG.debug("updated volume %s", data)
              self.APIExecutor.edit_vol(volume['name'], data)
              # make the volume online after changing the specs
              self.APIExecutor.online_vol(volume['name'], True)
          except NimbleAPIException as ex:
              raise NimbleAPIException(_("Unable to retype %(vol)s to "
                                         "%(type)s: %(err)s") %
                                       {'vol': volume['name'],
                                        'type': new_type,
                                        'err': ex.message})
          return True, None
  
      def manage_existing(self, volume, external_ref):
          """Manage an existing nimble volume (import to cinder)"""
  
          # Get the volume name from the external reference
          target_vol_name = self._get_existing_volume_ref_name(external_ref)
          LOG.debug('Entering manage_existing. '
                    'Target_volume_name =%s', target_vol_name)
  
          # Get vol info from the volume name obtained from the reference
          vol_info = self.APIExecutor.get_vol_info(target_vol_name)
  
          # Check if volume is already managed by OpenStack
          if vol_info['agent_type'] == AGENT_TYPE_OPENSTACK or (
             vol_info['agent_type'] == AGENT_TYPE_OPENSTACK_GST):
              raise exception.ManageExistingAlreadyManaged(
                  volume_ref=volume['id'])
  
          # If agent-type is not None then raise exception
          if vol_info['agent_type'] != AGENT_TYPE_NONE:
              msg = (_('Volume should have agent-type set as None.'))
              raise exception.InvalidVolume(reason=msg)
  
          new_vol_name = volume['name']
          LOG.info("Volume status before managing it : %(status)s",
                   {'status': vol_info['online']})
          if vol_info['online'] is True:
              msg = (_('Volume %s is online. Set volume to offline for '
                       'managing using OpenStack.') % target_vol_name)
              raise exception.InvalidVolume(reason=msg)
  
          # edit the volume
          data = {'data': {'name': new_vol_name}}
          if self._group_target_enabled is True:
              # check if any ACL's are attached to this volume
              if 'access_control_records' in vol_info and (
                 vol_info['access_control_records'] is not None):
                  msg = (_('Volume %s has ACL associated with it. Remove ACL '
                           'for managing using OpenStack') % target_vol_name)
                  raise exception.InvalidVolume(reason=msg)
              data['data']['agent_type'] = AGENT_TYPE_OPENSTACK_GST
          else:
              data['data']['agent_type'] = AGENT_TYPE_OPENSTACK
  
          LOG.debug("Data for edit %(data)s", {'data': data})
          self.APIExecutor.edit_vol(target_vol_name, data)
  
          # make the volume online after rename
          self.APIExecutor.online_vol(new_vol_name, True)
  
          return self._get_model_info(new_vol_name)
  
      def manage_existing_get_size(self, volume, external_ref):
          """Return size of an existing volume"""
  
          LOG.debug('Volume name : %(name)s  External ref : %(ref)s',
                    {'name': volume['name'], 'ref': external_ref})
  
          target_vol_name = self._get_existing_volume_ref_name(external_ref)
  
          # get vol info
          vol_info = self.APIExecutor.get_vol_info(target_vol_name)
  
          LOG.debug('Volume size : %(size)s  Volume-name : %(name)s',
                    {'size': vol_info['size'], 'name': vol_info['name']})
  
          return int(vol_info['size'] / units.Ki)
  
      def unmanage(self, volume):
          """Removes the specified volume from Cinder management."""
  
          vol_name = volume['name']
          LOG.debug("Entering unmanage_volume volume =%s", vol_name)
  
          # check agent type
          vol_info = self.APIExecutor.get_vol_info(vol_name)
          if vol_info['agent_type'] != AGENT_TYPE_OPENSTACK and (
             vol_info['agent_type'] != AGENT_TYPE_OPENSTACK_GST):
              msg = (_('Only volumes managed by OpenStack can be unmanaged.'))
              raise exception.InvalidVolume(reason=msg)
  
          data = {'data': {'agent_type': AGENT_TYPE_NONE}}
          # update the agent-type to None
          self.APIExecutor.edit_vol(vol_name, data)
  
          # offline the volume
          self.APIExecutor.online_vol(vol_name, False)
  
      def do_setup(self, context):
          """Setup the Nimble Cinder volume driver."""
          self._check_config()
          # Setup API Executor
          try:
              self.APIExecutor = NimbleRestAPIExecutor(
                  username=self.configuration.san_login,
                  password=self.configuration.san_password,
                  ip=self.configuration.san_ip,
                  verify=self.verify)
              if self._storage_protocol == "iSCSI":
                  group_info = self.APIExecutor.get_group_info()
                  self._enable_group_scoped_target(group_info)
          except Exception:
              LOG.error('Failed to create REST client. '
                        'Check san_ip, username, password'
                        ' and make sure the array version is compatible')
              raise
          self._update_existing_vols_agent_type(context)
  
      def _update_existing_vols_agent_type(self, context):
          backend_name = self.configuration.safe_get('volume_backend_name')
          all_vols = volume.VolumeList.get_all(
              context, None, None, None, None, {'status': 'available'})
          for vol in all_vols:
              if backend_name in vol.host:
                  try:
                      vol_info = self.APIExecutor.get_vol_info(vol.name)
                      # update agent_type only if no ACL's are present
                      if 'access_control_records' in vol_info and (
                         vol_info['access_control_records'] is None):
                          if self._group_target_enabled:
                              LOG.debug("Updating %(vol)s to have agent_type :"
                                        "%(agent)s",
                                        {'vol': vol.name,
                                         'agent': AGENT_TYPE_OPENSTACK_GST})
                              # check if this is an upgrade case from
                              # openstack to openstackv2
                              if vol_info['agent_type'] == AGENT_TYPE_NONE:
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_OPENSTACK_GST}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                              elif vol_info['agent_type'] == (
                                      AGENT_TYPE_OPENSTACK):
                                  # 1. update the agent type to None
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_NONE}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                                  # 2. update the agent type to openstack_gst
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_OPENSTACK_GST}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                          else:
                              LOG.debug("Updating %(vol)s to have agent_type :"
                                        "%(agent)s",
                                        {'vol': vol.name,
                                         'agent': AGENT_TYPE_OPENSTACK_GST})
                              if vol_info['agent_type'] == AGENT_TYPE_NONE:
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_OPENSTACK}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                              elif vol_info['agent_type'] == (
                                      AGENT_TYPE_OPENSTACK_GST):
                                  # 1. update the agent type to None
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_NONE}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                                  # 2. update the agent type to openstack
                                  data = {'data': {'agent_type':
                                                   AGENT_TYPE_OPENSTACK}}
                                  self.APIExecutor.edit_vol(vol.name, data)
                  except NimbleAPIException:
                      # just log the error but don't fail driver initialization
                      LOG.warning('Error updating agent-type for '
                                  'volume %s.', vol.name)
  
      def _get_model_info(self, volume_name):
          """Get model info for the volume."""
          return (
              {'provider_location': self._get_provider_location(volume_name),
               'provider_auth': None})
  
      @abc.abstractmethod
      def _get_provider_location(self, volume_name):
          """Volume info for iSCSI and FC"""
  
          pass
  
      def _create_igroup_for_initiator(self, initiator_name, wwpns):
          """Creates igroup for an initiator and returns the igroup name."""
          igrp_name = 'openstack-' + self._generate_random_string(12)
          LOG.info('Creating initiator group %(grp)s '
                   'with initiator %(iname)s',
                   {'grp': igrp_name, 'iname': initiator_name})
          if self._storage_protocol == "iSCSI":
              self.APIExecutor.create_initiator_group(igrp_name)
              self.APIExecutor.add_initiator_to_igroup(igrp_name, initiator_name)
          elif self._storage_protocol == "FC":
              self.APIExecutor.create_initiator_group_fc(igrp_name)
              for wwpn in wwpns:
                  self.APIExecutor.add_initiator_to_igroup_fc(igrp_name, wwpn)
          return igrp_name
  
      def _get_igroupname_for_initiator_fc(self, initiator_wwpns):
          initiator_groups = self.APIExecutor.get_initiator_grp_list()
          for initiator_group in initiator_groups:
              if 'fc_initiators' in initiator_group and initiator_group[
                 'fc_initiators'] is not None:
                  wwpns_list = []
                  for initiator in initiator_group['fc_initiators']:
                      wwpn = str(initiator['wwpn']).replace(":", "")
                      wwpns_list.append(wwpn)
                  LOG.debug("initiator_wwpns=%(initiator)s "
                            "wwpns_list_from_array=%(wwpns)s",
                            {'initiator': initiator_wwpns,
                             'wwpns': wwpns_list})
                  if set(initiator_wwpns) == set(wwpns_list):
                      LOG.info('igroup %(grp)s found for '
                               'initiator %(wwpns_list)s',
                               {'grp': initiator_group['name'],
                                'wwpns_list': wwpns_list})
                      return initiator_group['name']
          LOG.info('No igroup found for initiators %s', initiator_wwpns)
          return ''
  
      def _get_igroupname_for_initiator(self, initiator_name):
          initiator_groups = self.APIExecutor.get_initiator_grp_list()
          for initiator_group in initiator_groups:
              if initiator_group['iscsi_initiators'] is not None:
                  if (len(initiator_group['iscsi_initiators']) == 1 and
                      initiator_group['iscsi_initiators'][0]['iqn'] ==
                          initiator_name):
                      LOG.info('igroup %(grp)s found for '
                               'initiator %(iname)s',
                               {'grp': initiator_group['name'],
                                'iname': initiator_name})
                      return initiator_group['name']
          LOG.info('No igroup found for initiator %s', initiator_name)
          return ''
  
      def get_lun_number(self, volume, initiator_group_name):
          vol_info = self.APIExecutor.get_vol_info(volume['name'])
          for acl in vol_info['access_control_records']:
              if (initiator_group_name == acl['initiator_group_name']):
                  LOG.info("access_control_record =%(acl)s",
                           {'acl': acl})
                  lun = acl['lun']
                  LOG.info("LUN : %(lun)s", {"lun": lun})
                  return lun
          raise NimbleAPIException(_("Lun number not found for volume %(vol)s "
                                     "with initiator_group: %(igroup)s") %
                                   {'vol': volume['name'],
                                    'igroup': initiator_group_name})
  
      def _is_multiattach(self, volume):
          if volume.multiattach:
              attachment_list = volume.volume_attachment
              try:
                  attachment_list = attachment_list.objects
              except AttributeError:
                  pass
  
              if attachment_list is not None and len(attachment_list) > 1:
                  LOG.info("Volume %(volume)s is attached to multiple "
                           "instances on host %(host_name)s, "
                           "skip terminate volume connection",
                           {'volume': volume.name,
                            'host_name': volume.host.split('@')[0]})
                  return True
          return False
  
      def revert_to_snapshot(self, context, volume, snapshot):
          vol_info = self.APIExecutor.get_vol_info(volume['name'])
          snap_id = vol_info['last_snap']['snap_id']
          volume_id = vol_info['id']
          LOG.debug("Reverting volume %(vol)s with snapshot id %(snap_id)s",
                    {'vol': volume['name'], 'snap_id': snap_id})
          data = {'data': {"base_snap_id": snap_id, "id": volume_id}}
          try:
              self.APIExecutor.online_vol(volume['name'], False)
              self.APIExecutor.volume_restore(volume['name'], data)
              LOG.info("Volume %(vol)s is successfully restored with "
                       "snap_id %(snap_id)s",
                       {'vol': volume['name'], 'snap_id': snap_id})
              self.APIExecutor.online_vol(volume['name'], True)
          except NimbleAPIException as ex:
              raise NimbleAPIException(_("Unable to restore %(vol)s to "
                                         "%(snap_id)s: %(err)s") %
                                       {'vol': volume['name'],
                                        'snap_id': snap_id,
                                        'err': ex.message})
          return self._get_model_info(volume['name'])
  
  
  @interface.volumedriver
  class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver):
  
      """OpenStack driver to enable Nimble ISCSI Controller."""
  
      def __init__(self, *args, **kwargs):
          super(NimbleISCSIDriver, self).__init__(*args, **kwargs)
          self._storage_protocol = "iSCSI"
          self._group_target_name = None
  
      def _set_gst_for_group(self):
          group_info = self.APIExecutor.get_group_info()
          if 'group_target_enabled' in group_info and (
                  group_info['group_target_enabled']) is True and (
                      'group_target_name' in group_info) and (
                      group_info['group_target_name'] is not None):
              self._group_target_name = group_info['group_target_name']
  
      def _get_gst_for_group(self):
          return self._group_target_name
  
      def initialize_connection(self, volume, connector):
          """Driver entry point to attach a volume to an instance."""
          LOG.info('Entering initialize_connection volume=%(vol)s'
                   ' connector=%(conn)s location=%(loc)s',
                   {'vol': volume,
                    'conn': connector,
                    'loc': volume['provider_location']})
          initiator_name = connector['initiator']
          initiator_group_name = self._get_igroupname_for_initiator(
              initiator_name)
          if not initiator_group_name:
              initiator_group_name = self._create_igroup_for_initiator(
                  initiator_name, None)
          LOG.info('Initiator group name is %(grp)s for initiator '
                   '%(iname)s',
                   {'grp': initiator_group_name, 'iname': initiator_name})
          self.APIExecutor.add_acl(volume, initiator_group_name)
          properties = {"driver_volume_type": "iscsi",
                        "data": {"target_discovered": False, "discard": True}}
          properties['data']['volume_id'] = volume['id']  # used by xen currently
          (iscsi_portal, iqn) = volume['provider_location'].split()
          if self._get_gst_for_group() is not None:
              lun_num = self.get_lun_number(volume, initiator_group_name)
              netconfig = self.APIExecutor.get_netconfig('active')
              target_portals = self._get_data_ips(netconfig)
              LOG.info("target portals %(portals)s", {'portals': target_portals})
              target_luns = [int(lun_num)] * len(target_portals)
              target_iqns = [iqn] * len(target_portals)
              LOG.debug("target iqns %(iqns)s target luns %(luns)s",
                        {'iqns': target_iqns, 'luns': target_luns})
              if target_luns and target_iqns and target_portals:
                  properties["data"]["target_luns"] = target_luns
                  properties["data"]["target_iqns"] = target_iqns
                  properties["data"]["target_portals"] = target_portals
          else:
              # handling volume scoped target
              lun_num = LUN_ID
              properties['data']['target_portal'] = iscsi_portal
              properties['data']['target_iqn'] = iqn
              properties['data']['target_lun'] = int(lun_num)
  
          return properties
  
      def terminate_connection(self, volume, connector, **kwargs):
          """Driver entry point to unattach a volume from an instance."""
          LOG.info('Entering terminate_connection volume=%(vol)s'
                   ' connector=%(conn)s location=%(loc)s.',
                   {'vol': volume['name'],
                    'conn': connector,
                    'loc': volume['provider_location']})
  
          if connector is None:
              LOG.warning("Removing ALL host connections for volume %s",
                          volume)
              self.APIExecutor.remove_all_acls(volume)
              return
          if self._is_multiattach(volume):
              return
  
          initiator_name = connector['initiator']
          initiator_group_name = self._get_igroupname_for_initiator(
              initiator_name)
          if not initiator_group_name:
              raise NimbleDriverException(_('No initiator group found for '
                                            'initiator %s') % initiator_name)
          self.APIExecutor.remove_acl(volume, initiator_group_name)
          eventlet.sleep(DEFAULT_SLEEP)
  
      def _get_provider_location(self, volume_name):
          """Get volume iqn for initiator access."""
          vol_info = self.APIExecutor.get_vol_info(volume_name)
          netconfig = self.APIExecutor.get_netconfig('active')
          self._set_gst_for_group()
          if self._get_gst_for_group() is not None:
              iqn = self._get_gst_for_group()
          else:
              iqn = vol_info['target_name']
          target_ipaddr = self._get_discovery_ip(netconfig)
          iscsi_portal = target_ipaddr + ':3260'
          provider_location = '%s %s' % (iscsi_portal, iqn)
          LOG.info('vol_name=%(name)s provider_location=%(loc)s',
                   {'name': volume_name, 'loc': provider_location})
          return provider_location
  
      def _get_data_ips(self, netconfig):
          """Get data ips."""
          subnet_label = self.configuration.nimble_subnet_label
          LOG.debug('subnet_label used %(netlabel)s, netconfig %(netconf)s',
                    {'netlabel': subnet_label, 'netconf': netconfig})
          ret_data_ips = []
          for subnet in netconfig['array_list'][0]['nic_list']:
              LOG.info('Exploring array subnet label %s', subnet[
                  'subnet_label'])
              if subnet['data_ip']:
                  if subnet_label == '*':
                      # if all subnets are mentioned then return all portals
                      # else just return specific subnet
                      LOG.info('Data ip %(data_ip)s is used '
                               'on data subnet %(net_label)s',
                               {'data_ip': subnet['data_ip'],
                                'net_label': subnet['subnet_label']})
                      ret_data_ips.append(str(subnet['data_ip']) + ':3260')
                  elif subnet_label == subnet['subnet_label']:
                      LOG.info('Data ip %(data_ip)s is used'
                               ' on subnet %(net_label)s',
                               {'data_ip': subnet['data_ip'],
                                'net_label': subnet['subnet_label']})
                      data_ips_single_subnet = []
                      data_ips_single_subnet.append(str(subnet['data_ip']) +
                                                    ':3260')
                      return data_ips_single_subnet
          if ret_data_ips:
              LOG.info('Data ips %s', ret_data_ips)
              return ret_data_ips
          else:
              raise NimbleDriverException(_('No suitable data ip found'))
  
      def _get_discovery_ip(self, netconfig):
          """Get discovery ip."""
          subnet_label = self.configuration.nimble_subnet_label
          LOG.debug('subnet_label used %(netlabel)s, netconfig %(netconf)s',
                    {'netlabel': subnet_label, 'netconf': netconfig})
          ret_discovery_ip = ''
          for subnet in netconfig['subnet_list']:
              LOG.info('Exploring array subnet label %s', subnet['label'])
              if subnet_label == '*':
                  # Use the first data subnet, save mgmt+data for later
                  if subnet['type'] == SM_SUBNET_DATA:
                      LOG.info('Discovery ip %(disc_ip)s is used '
                               'on data subnet %(net_label)s',
                               {'disc_ip': subnet['discovery_ip'],
                                'net_label': subnet['label']})
                      return subnet['discovery_ip']
                  elif (subnet['type'] == SM_SUBNET_MGMT_PLUS_DATA):
                      LOG.info('Discovery ip %(disc_ip)s is found'
                               ' on mgmt+data subnet %(net_label)s',
                               {'disc_ip': subnet['discovery_ip'],
                                'net_label': subnet['label']})
                      ret_discovery_ip = subnet['discovery_ip']
              # If subnet is specified and found, use the subnet
              elif subnet_label == subnet['label']:
                  LOG.info('Discovery ip %(disc_ip)s is used'
                           ' on subnet %(net_label)s',
                           {'disc_ip': subnet['discovery_ip'],
                            'net_label': subnet['label']})
                  return subnet['discovery_ip']
          if ret_discovery_ip:
              LOG.info('Discovery ip %s is used on mgmt+data subnet',
                       ret_discovery_ip)
              return ret_discovery_ip
          else:
              raise NimbleDriverException(_('No suitable discovery ip found'))
  
  
  @interface.volumedriver
  class NimbleFCDriver(NimbleBaseVolumeDriver, driver.FibreChannelDriver):
      """OpenStack driver to enable Nimble FC Driver Controller."""
  
      def __init__(self, *args, **kwargs):
          super(NimbleFCDriver, self).__init__(*args, **kwargs)
          self._storage_protocol = "FC"
          self._lookup_service = fczm_utils.create_lookup_service()
  
      def _get_provider_location(self, volume_name):
          """Get array info wwn details."""
          netconfig = self.APIExecutor.get_netconfig('active')
          array_name = netconfig['group_leader_array']
          provider_location = '%s' % (array_name)
          LOG.info('vol_name=%(name)s provider_location=%(loc)s',
                   {'name': volume_name, 'loc': provider_location})
          return provider_location
  
      def _build_initiator_target_map(self, target_wwns, connector):
          """Build the target_wwns and the initiator target map."""
          LOG.debug("_build_initiator_target_map for %(wwns)s",
                    {'wwns': target_wwns})
          init_targ_map = {}
  
          if self._lookup_service:
              # use FC san lookup to determine which wwpns to use
              # for the new VLUN.
              dev_map = self._lookup_service.get_device_mapping_from_network(
                  connector['wwpns'],
                  target_wwns)
              map_fabric = dev_map
              LOG.info("dev_map =%(fabric)s", {'fabric': map_fabric})
  
              for fabric_name in dev_map:
                  fabric = dev_map[fabric_name]
                  for initiator in fabric['initiator_port_wwn_list']:
                      if initiator not in init_targ_map:
                          init_targ_map[initiator] = []
                      init_targ_map[initiator] += fabric['target_port_wwn_list']
                      init_targ_map[initiator] = list(set(
                          init_targ_map[initiator]))
          else:
              init_targ_map = dict.fromkeys(connector["wwpns"], target_wwns)
  
          return init_targ_map
  
      def initialize_connection(self, volume, connector):
          """Driver entry point to attach a volume to an instance."""
          LOG.info('Entering initialize_connection volume=%(vol)s'
                   ' connector=%(conn)s location=%(loc)s',
                   {'vol': volume,
                    'conn': connector,
                    'loc': volume['provider_location']})
          wwpns = []
          initiator_name = connector['initiator']
          for wwpn in connector['wwpns']:
              wwpns.append(wwpn)
          initiator_group_name = self._get_igroupname_for_initiator_fc(wwpns)
  
          if not initiator_group_name:
              initiator_group_name = self._create_igroup_for_initiator(
                  initiator_name, wwpns)
  
          LOG.info('Initiator group name is %(grp)s for initiator '
                   '%(iname)s',
                   {'grp': initiator_group_name, 'iname': initiator_name})
          self.APIExecutor.add_acl(volume, initiator_group_name)
          lun = self.get_lun_number(volume, initiator_group_name)
          init_targ_map = {}
          (array_name) = volume['provider_location'].split()
  
          target_wwns = self.get_wwpns_from_array(array_name)
  
          init_targ_map = self._build_initiator_target_map(target_wwns,
                                                           connector)
  
          data = {'driver_volume_type': 'fibre_channel',
                  'data': {'target_lun': lun,
                           'target_discovered': True,
                           'discard': True,
                           'target_wwn': target_wwns,
                           'initiator_target_map': init_targ_map}}
  
          LOG.info("Return FC data for zone addition: %(data)s.",
                   {'data': data})
          fczm_utils.add_fc_zone(data)
          return data
  
      def terminate_connection(self, volume, connector, **kwargs):
          """Driver entry point to unattach a volume from an instance."""
          LOG.info('Entering terminate_connection volume=%(vol)s'
                   ' connector=%(conn)s location=%(loc)s.',
                   {'vol': volume,
                    'conn': connector,
                    'loc': volume['provider_location']})
          wwpns = []
          if connector is None:
              LOG.warning("Removing ALL host connections for volume %s",
                          volume)
              self.APIExecutor.remove_all_acls(volume)
              return
          if self._is_multiattach(volume):
              return
  
          initiator_name = connector['initiator']
          for wwpn in connector['wwpns']:
              wwpns.append(wwpn)
          (array_name) = volume['provider_location'].split()
          target_wwns = self.get_wwpns_from_array(array_name)
          init_targ_map = self._build_initiator_target_map(target_wwns,
                                                           connector)
          initiator_group_name = self._get_igroupname_for_initiator_fc(wwpns)
          if not initiator_group_name:
              raise NimbleDriverException(
                  _('No initiator group found for initiator %s') %
                  initiator_name)
          LOG.debug("initiator_target_map %s", init_targ_map)
          self.APIExecutor.remove_acl(volume, initiator_group_name)
          eventlet.sleep(DEFAULT_SLEEP)
          # FIXME to check for other volumes attached to the host and then
          # return the data. Bug https://bugs.launchpad.net/cinder/+bug/1617472
  
          data = {'driver_volume_type': 'fibre_channel',
                  'data': {'target_wwn': target_wwns}}
  
          # FIXME: need to optionally add the initiator_target_map here when
          # there are no more volumes exported to the initiator / target pair
          # otherwise the zone will never get removed.
          fczm_utils.remove_fc_zone(data)
          return data
  
      def get_wwpns_from_array(self, array_name):
          """Retrieve the wwpns from the array"""
          LOG.debug("get_wwpns_from_array %s", array_name)
          target_wwpns = []
          interface_info = self.APIExecutor.get_fc_interface_list(array_name)
          LOG.info("interface_info %(interface_info)s",
                   {"interface_info": interface_info})
          for wwpn_list in interface_info:
              wwpn = wwpn_list['wwpn']
              wwpn = wwpn.replace(":", "")
              target_wwpns.append(wwpn)
  
          return target_wwpns
  
      def _convert_string_to_colon_separated_wwnn(self, wwnn):
          return ':'.join(a + b for a, b in zip(wwnn[::2], wwnn[1::2]))
  
  
  def _connection_checker(func):
      """Decorator to re-establish and re-run the api if session has expired."""
      @functools.wraps(func)
      def inner_connection_checker(self, *args, **kwargs):
          for attempts in range(2):
              try:
                  return func(self, *args, **kwargs)
              except Exception as e:
                  if attempts < 1 and (re.search("Failed to execute",
                                       six.text_type(e))):
                      LOG.info('Session might have expired.'
                               ' Trying to relogin')
                      self.login()
                      continue
                  else:
                      LOG.error('Re-throwing Exception %s', e)
                      raise
      return inner_connection_checker
  
  
  class NimbleRestAPIExecutor(object):
  
      """Makes Nimble REST API calls."""
  
      def __init__(self, api_version=NimbleDefaultVersion, *args, **kwargs):
          self.token_id = None
          self.ip = kwargs['ip']
          self.username = kwargs['username']
          self.password = kwargs['password']
          self.verify = kwargs['verify']
          self.api_version = api_version
          self.uri = "https://%(ip)s:5392/v%(version)s/" % {
              'ip': self.ip,
              'version': self.api_version}
          self.login()
  
      def login(self):
          data = {'data': {"username": self.username,
                           "password": self.password,
                           "app_name": "NimbleCinderDriver"}}
          r = requests.post(self.uri + "tokens",
                            data=json.dumps(data),
                            verify=self.verify)
  
          if r.status_code != 201 and r.status_code != 200:
              msg = _("Failed to login for user %s"), self.username
              raise NimbleAPIException(msg)
          self.token_id = r.json()['data']['session_token']
          self.headers = {'X-Auth-Token': self.token_id}
  
      def get_group_id(self):
          api = 'groups'
          r = self.get(api)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve Group Object for : "
                                         "%s") % self.ip)
          return r.json()['data'][0]['id']
  
      def get_group_info(self):
          group_id = self.get_group_id()
          api = 'groups/' + six.text_type(group_id)
          r = self.get(api)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve Group info for: %s")
                                       % group_id)
          return r.json()['data']
  
      def get_folder_id(self, folder_name):
          api = 'folders'
          filter = {"name": folder_name}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "Folder: %s") % folder_name)
          return r.json()['data'][0]['id']
  
      def get_folder_info(self, folder_name):
          folder_id = self.get_folder_id(folder_name)
          api = "folders/" + six.text_type(folder_id)
          r = self.get(api)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve Folder info for: "
                                       "%s") % folder_id)
          return r.json()['data']
  
      def get_performance_policy_id(self, perf_policy_name):
          api = 'performance_policies/'
          filter = {'name': perf_policy_name}
          LOG.debug("Performance policy Name %s", perf_policy_name)
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("No performance policy found for: "
                                       "%(perf)s") % {'perf': perf_policy_name})
          LOG.debug("Performance policy ID :%(perf)s",
                    {'perf': r.json()['data'][0]['id']})
          return r.json()['data'][0]['id']
  
      def get_netconfig(self, role):
          api = "network_configs/detail"
          filter = {'role': role}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("No %s network config exists") % role)
          return r.json()['data'][0]
  
      def _get_volumetype_extraspecs(self, volume):
          specs = {}
  
          type_id = volume['volume_type_id']
          if type_id is not None:
              specs = volume_types.get_volume_type_extra_specs(type_id)
          return specs
  
      def _get_extra_spec_values(self, extra_specs):
          """Nimble specific extra specs."""
          perf_policy_name = extra_specs.get(EXTRA_SPEC_PERF_POLICY,
                                             DEFAULT_PERF_POLICY_SETTING)
          encryption = extra_specs.get(EXTRA_SPEC_ENCRYPTION,
                                       DEFAULT_ENCRYPTION_SETTING)
          iops_limit = extra_specs.get(EXTRA_SPEC_IOPS_LIMIT,
                                       DEFAULT_IOPS_LIMIT_SETTING)
          folder_name = extra_specs.get(EXTRA_SPEC_FOLDER,
                                        DEFAULT_FOLDER_SETTING)
          dedupe = extra_specs.get(EXTRA_SPEC_DEDUPE,
                                   DEFAULT_DEDUPE_SETTING)
          extra_specs_map = {}
          extra_specs_map[EXTRA_SPEC_PERF_POLICY] = perf_policy_name
          extra_specs_map[EXTRA_SPEC_ENCRYPTION] = encryption
          extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] = iops_limit
          extra_specs_map[EXTRA_SPEC_DEDUPE] = dedupe
          extra_specs_map[EXTRA_SPEC_FOLDER] = folder_name
          return extra_specs_map
  
      def get_valid_nimble_extraspecs(self, extra_specs_map, vol_info):
  
          extra_specs_map_updated = self._get_extra_spec_values(extra_specs_map)
          data = {"data": {}}
          perf_policy_name = extra_specs_map_updated[EXTRA_SPEC_PERF_POLICY]
          perf_policy_id = self.get_performance_policy_id(perf_policy_name)
          data['perfpolicy_id'] = perf_policy_id
  
          encrypt = extra_specs_map_updated[EXTRA_SPEC_ENCRYPTION]
          cipher = DEFAULT_CIPHER
          if encrypt.lower() == 'yes':
              cipher = AES_256_XTS_CIPHER
          data['cipher'] = cipher
          if extra_specs_map.get('multiattach') == "<is> True":
              data['multi_initiator'] = True
          else:
              data['multi_initiator'] = False
          folder_name = extra_specs_map_updated[EXTRA_SPEC_FOLDER]
          folder_id = None
          pool_id = vol_info['pool_id']
          pool_name = vol_info['pool_name']
          if folder_name is not None:
              # validate if folder exists in pool_name
              pool_info = self.get_pool_info(pool_id)
              if 'folder_list' in pool_info and (pool_info['folder_list'] is
                                                 not None):
                  for folder_list in pool_info['folder_list']:
                      LOG.debug("folder_list : %s", folder_list)
                      if folder_list['fqn'] == "/" + folder_name:
                          LOG.debug("Folder %(folder)s present in pool "
                                    "%(pool)s",
                                    {'folder': folder_name,
                                     'pool': pool_name})
                          folder_id = self.get_folder_id(folder_name)
                          if folder_id is not None:
                              data['data']["folder_id"] = folder_id
                  if folder_id is None:
                      raise NimbleAPIException(_("Folder '%(folder)s' not "
                                                 "present in pool  '%("
                                                 "pool)s'") %
                                               {'folder': folder_name,
                                                'pool': pool_name})
              else:
                  raise NimbleAPIException(_(
                      "Folder '%(folder)s' not present in pool '%(pool)s'")
                      % {'folder': folder_name,
                         'pool': pool_name})
          iops_limit = extra_specs_map_updated[EXTRA_SPEC_IOPS_LIMIT]
          if iops_limit is not None:
              if not iops_limit.isdigit() or (
                 int(iops_limit) < MIN_IOPS) or (int(iops_limit) > MAX_IOPS):
                  raise NimbleAPIException(_("%(err)s [%(min)s, %(max)s]")
                                           % {'err': IOPS_ERR_MSG,
                                              'min': MIN_IOPS,
                                              'max': MAX_IOPS})
  
              data['data']['limit_iops'] = iops_limit
  
          dedupe = extra_specs_map_updated[EXTRA_SPEC_DEDUPE]
          if dedupe.lower() == 'true':
              data['data']['dedupe_enabled'] = True
  
          return data
  
      def create_vol(self, volume, pool_name, reserve, protocol, is_gst_enabled):
          response = self._execute_create_vol(volume, pool_name, reserve,
                                              protocol, is_gst_enabled)
          LOG.info('Successfully created volume %(name)s',
                   {'name': response['name']})
          return response['name']
  
      def _is_ascii(self, value):
          try:
              return all(ord(c) < 128 for c in value)
          except TypeError:
              return False
  
      def _execute_create_vol(self, volume, pool_name, reserve, protocol,
                              is_gst_enabled):
          """Create volume
  
          :return: r['data']
          """
  
          # Set volume size, display name and description
          volume_size = volume['size'] * units.Ki
          reserve_size = 100 if reserve else 0
          # Set volume description
          display_name = getattr(volume, 'display_name', '')
          display_description = getattr(volume, 'display_description', '')
          if self._is_ascii(display_name) and self._is_ascii(
                  display_description):
              display_list = [getattr(volume, 'display_name', ''),
                              getattr(volume, 'display_description', '')]
              description = ':'.join(filter(None, display_list))
          elif self._is_ascii(display_name):
              description = display_name
          elif self._is_ascii(display_description):
              description = display_description
          else:
              description = ""
  
          # Limit description size to 254 characters
          description = description[:254]
          pool_id = self.get_pool_id(pool_name)
  
          specs = self._get_volumetype_extraspecs(volume)
          extra_specs_map = self._get_extra_spec_values(specs)
          perf_policy_name = extra_specs_map[EXTRA_SPEC_PERF_POLICY]
          perf_policy_id = self.get_performance_policy_id(perf_policy_name)
          encrypt = extra_specs_map[EXTRA_SPEC_ENCRYPTION]
          multi_initiator = volume.get('multiattach', False)
          folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
          iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
          dedupe = extra_specs_map[EXTRA_SPEC_DEDUPE]
  
          cipher = DEFAULT_CIPHER
          if encrypt.lower() == 'yes':
              cipher = AES_256_XTS_CIPHER
          if is_gst_enabled is True:
              agent_type = AGENT_TYPE_OPENSTACK_GST
          else:
              agent_type = AGENT_TYPE_OPENSTACK
  
          LOG.debug('Creating a new volume=%(vol)s size=%(size)s'
                    ' reserve=%(reserve)s in pool=%(pool)s'
                    ' description=%(description)s with Extra Specs'
                    ' perfpol-name=%(perfpol-name)s'
                    ' encryption=%(encryption)s cipher=%(cipher)s'
                    ' agent-type=%(agent-type)s'
                    ' multi-initiator=%(multi-initiator)s',
                    {'vol': volume['name'],
                     'size': volume_size,
                     'reserve': reserve_size,
                     'pool': pool_name,
                     'description': description,
                     'perfpol-name': perf_policy_name,
                     'encryption': encrypt,
                     'cipher': cipher,
                     'agent-type': agent_type,
                     'multi-initiator': multi_initiator})
          data = {"data":
                  {'name': volume['name'],
                   'description': description,
                   'size': volume_size,
                   'reserve': reserve_size,
                   'warn_level': int(WARN_LEVEL),
                   'limit': 100,
                   'snap_limit': DEFAULT_SNAP_QUOTA,
                   'online': True,
                   'pool_id': pool_id,
                   'agent_type': agent_type,
                   'perfpolicy_id': perf_policy_id,
                   'encryption_cipher': cipher}}
  
          if protocol == "iSCSI":
              data['data']['multi_initiator'] = multi_initiator
  
          if dedupe.lower() == 'true':
              data['data']['dedupe_enabled'] = True
  
          folder_id = None
          if folder_name is not None:
              # validate if folder exists in pool_name
              pool_info = self.get_pool_info(pool_id)
              if 'folder_list' in pool_info and (pool_info['folder_list'] is
                                                 not None):
                  for folder_list in pool_info['folder_list']:
                      LOG.debug("folder_list : %s", folder_list)
                      if folder_list['fqn'] == "/" + folder_name:
                          LOG.debug("Folder %(folder)s present in pool "
                                    "%(pool)s",
                                    {'folder': folder_name,
                                     'pool': pool_name})
                          folder_id = self.get_folder_id(folder_name)
                          if folder_id is not None:
                              data['data']["folder_id"] = folder_id
                  if folder_id is None:
                      raise NimbleAPIException(_("Folder '%(folder)s' not "
                                                 "present in pool '%(pool)s'") %
                                               {'folder': folder_name,
                                                'pool': pool_name})
              else:
                  raise NimbleAPIException(_("Folder '%(folder)s' not present in"
                                             " pool '%(pool)s'") %
                                           {'folder': folder_name,
                                            'pool': pool_name})
  
          if iops_limit is not None:
              if not iops_limit.isdigit() or (
                 int(iops_limit) < MIN_IOPS) or (int(iops_limit) > MAX_IOPS):
                  raise NimbleAPIException(_("%(err)s [%(min)s, %(max)s]") %
                                           {'err': IOPS_ERR_MSG,
                                            'min': MIN_IOPS,
                                            'max': MAX_IOPS})
  
              data['data']['limit_iops'] = iops_limit
  
          LOG.debug("Volume metadata :%s", volume.metadata)
          for key, value in volume.metadata.items():
              LOG.debug("Key %(key)s Value %(value)s",
                        {'key': key, 'value': value})
              if key == EXTRA_SPEC_IOPS_LIMIT and value.isdigit():
                  if type(value) == int or int(value) < MIN_IOPS or (
                     int(value) > MAX_IOPS):
                      raise NimbleAPIException(_("%(err)s [%(min)s, %(max)s]") %
                                               {'err': IOPS_ERR_MSG,
                                                'min': MIN_IOPS,
                                                'max': MAX_IOPS})
                  LOG.debug("IOPS Limit %s", value)
                  data['data']['limit_iops'] = value
          LOG.debug("Data : %s", data)
  
          api = 'volumes'
          r = self.post(api, data)
          return r['data']
  
      def create_initiator_group(self, initiator_grp_name):
          api = "initiator_groups"
          data = {"data": {"name": initiator_grp_name,
                           "access_protocol": "iscsi",
                           }}
          r = self.post(api, data)
          return r['data']
  
      def create_initiator_group_fc(self, initiator_grp_name):
          api = "initiator_groups"
  
          data = {}
          data["data"] = {}
          data["data"]["name"] = initiator_grp_name
          data["data"]["access_protocol"] = "fc"
          r = self.post(api, data)
          return r['data']
  
      def get_initiator_grp_id(self, initiator_grp_name):
          api = "initiator_groups"
          filter = {'name': initiator_grp_name}
          r = self.get_query(api, filter)
          return r.json()['data'][0]['id']
  
      def add_initiator_to_igroup(self, initiator_grp_name, initiator_name):
          initiator_group_id = self.get_initiator_grp_id(initiator_grp_name)
          api = "initiators"
          data = {"data": {
              "access_protocol": "iscsi",
              "initiator_group_id": initiator_group_id,
              "label": initiator_name,
              "iqn": initiator_name
          }}
          r = self.post(api, data)
          return r['data']
  
      def add_initiator_to_igroup_fc(self, initiator_grp_name, wwpn):
          initiator_group_id = self.get_initiator_grp_id(initiator_grp_name)
          api = "initiators"
          data = {"data": {
              "access_protocol": "fc",
              "initiator_group_id": initiator_group_id,
              "wwpn": self._format_to_wwpn(wwpn)
          }}
          r = self.post(api, data)
          return r['data']
  
      def get_pool_id(self, pool_name):
          api = "pools/"
          filter = {'name': pool_name}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "pool : %(pool)s") %
                                       {'pool': pool_name})
          return r.json()['data'][0]['id']
  
      def get_pool_info(self, pool_id):
          api = 'pools/' + six.text_type(pool_id)
          r = self.get(api)
          return r.json()['data']
  
      def get_initiator_grp_list(self):
          api = "initiator_groups/detail"
          r = self.get(api)
          if 'data' not in r.json():
              raise NimbleAPIException(_("Unable to retrieve initiator group "
                                         "list"))
          LOG.info('Successfully retrieved InitiatorGrpList')
          return r.json()['data']
  
      def get_initiator_grp_id_by_name(self, initiator_group_name):
          api = 'initiator_groups'
          filter = {"name": initiator_group_name}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "initiator group : %s") %
                                       initiator_group_name)
          return r.json()['data'][0]['id']
  
      def get_volume_id_by_name(self, name):
          api = "volumes"
          filter = {"name": name}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "volume: %s") % name)
          return r.json()['data'][0]['id']
  
      def get_volume_name(self, volume_id):
          api = "volumes/" + six.text_type(volume_id)
          r = self.get(api)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "volume: %s") % volume_id)
          return r.json()['data']['name']
  
      def add_acl(self, volume, initiator_group_name):
          initiator_group_id = self.get_initiator_grp_id_by_name(
              initiator_group_name)
          volume_id = self.get_volume_id_by_name(volume['name'])
          data = {'data': {"apply_to": 'both',
                           "initiator_group_id": initiator_group_id,
                           "vol_id": volume_id
                           }}
          api = 'access_control_records'
          try:
              self.post(api, data)
          except NimbleAPIException as ex:
              LOG.debug("add_acl_exception: %s", ex)
              if SM_OBJ_EXIST_MSG in six.text_type(ex):
                  LOG.warning('Volume %(vol)s : %(state)s',
                              {'vol': volume['name'],
                               'state': SM_OBJ_EXIST_MSG})
              else:
                  msg = (_("Add access control failed with error:  %s") %
                         six.text_type(ex))
                  raise NimbleAPIException(msg)
  
      def get_acl_record(self, volume_id, initiator_group_id):
          filter = {"vol_id": volume_id,
                    "initiator_group_id": initiator_group_id}
          api = "access_control_records"
          r = self.get_query(api, filter)
          LOG.info("ACL record is %(result)s", {'result': r.json()})
          if not r.json()['data']:
              LOG.warning('ACL is not available for this volume %(vol_id)s', {
                          'vol_id': volume_id})
              return
          return r.json()['data'][0]
  
      def get_volume_acl_records(self, volume_id):
          api = "volumes/" + six.text_type(volume_id)
          r = self.get(api)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve information for "
                                         "volume: %s") % volume_id)
          return r.json()['data']['access_control_records']
  
      def remove_all_acls(self, volume):
          LOG.info("removing all access control list from volume=%(vol)s",
                   {"vol": volume['name']})
          volume_id = self.get_volume_id_by_name(volume['name'])
          acl_records = self.get_volume_acl_records(volume_id)
          if acl_records is not None:
              for acl_record in acl_records:
                  LOG.info("removing acl=%(acl)s with igroup=%(igroup)s",
                           {"acl": acl_record['id'],
                            "igroup": acl_record['initiator_group_name']})
                  self.remove_acl(volume, acl_record['initiator_group_name'])
  
      def remove_acl(self, volume, initiator_group_name):
          LOG.info("removing ACL from volume=%(vol)s "
                   "and %(igroup)s",
                   {"vol": volume['name'],
                    "igroup": initiator_group_name})
          initiator_group_id = self.get_initiator_grp_id_by_name(
              initiator_group_name)
          volume_id = self.get_volume_id_by_name(volume['name'])
  
          try:
              acl_record = self.get_acl_record(volume_id, initiator_group_id)
              LOG.debug("ACL Record %(acl)s", {"acl": acl_record})
              if acl_record is not None:
                  acl_id = acl_record['id']
                  api = 'access_control_records/%s' % acl_id
                  self.delete(api)
          except NimbleAPIException as ex:
              LOG.debug("remove_acl_exception: %s", ex)
              if SM_OBJ_ENOENT_MSG in six.text_type(ex):
                  LOG.warning('Volume %(vol)s : %(state)s',
                              {'vol': volume['name'],
                               'state': SM_OBJ_ENOENT_MSG})
              else:
                  msg = (_("Remove access control failed with error:  %s") %
                         six.text_type(ex))
                  raise NimbleAPIException(msg)
  
      def get_snap_info_by_id(self, snap_id, vol_id):
          filter = {"id": snap_id, "vol_id": vol_id}
          api = 'snapshots'
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Unable to retrieve snapshot info for "
                                         "snap_id: %(snap)s volume id: %(vol)s")
                                       % {'snap': snap_id,
                                          'vol': vol_id})
          LOG.debug("SnapInfo :%s", r.json()['data'][0])
          return r.json()['data'][0]
  
      def get_snap_info(self, snap_name, vol_name):
          filter = {"name": snap_name, "vol_name": vol_name}
          api = 'snapshots'
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Snapshot: %(snap)s of Volume: %(vol)s "
                                         "doesn't exist") %
                                       {'snap': snap_name,
                                        'vol': vol_name})
          return r.json()['data'][0]
  
      def get_snap_info_detail(self, snap_id):
          api = 'snapshots/detail'
          filter = {'id': snap_id}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("Snapshot: %s doesn't exist") % snap_id)
          return r.json()['data'][0]
  
      @utils.retry(NimbleAPIException, 2, 3)
      def online_vol(self, volume_name, online_flag):
          volume_id = self.get_volume_id_by_name(volume_name)
          LOG.debug("volume_id %s", six.text_type(volume_id))
          eventlet.sleep(DEFAULT_SLEEP)
          api = "volumes/" + six.text_type(volume_id)
          data = {'data': {"online": online_flag, 'force': True}}
          try:
              LOG.debug("data :%s", data)
              self.put(api, data)
              LOG.debug("Volume %(vol)s is in requested online state :%(flag)s",
                        {'vol': volume_name,
                         'flag': online_flag})
          except Exception as ex:
              msg = (_("Error  %s") % ex)
              LOG.debug("online_vol_exception: %s", msg)
              if msg.__contains__("Object is %s" % SM_STATE_MSG):
                  LOG.warning('Volume %(vol)s : %(state)s',
                              {'vol': volume_name,
                               'state': SM_STATE_MSG})
              # TODO(rkumar): Check if we need to ignore the connected
              # initiator
              elif msg.__contains__("Initiators are connected to"):
                  raise NimbleAPIException(msg)
              else:
                  raise exception.InvalidVolume(reason=msg)
  
      def online_snap(self, volume_name, online_flag, snap_name):
          snap_info = self.get_snap_info(snap_name, volume_name)
          api = "snapshots/" + six.text_type(snap_info['id'])
          data = {'data': {"online": online_flag}}
          try:
              self.put(api, data)
              LOG.debug("Snapshot %(snap)s is in requested online state "
                        ":%(flag)s",
                        {'snap': snap_name, 'flag': online_flag})
          except Exception as ex:
              LOG.debug("online_snap_exception: %s", ex)
              if six.text_type(ex).__contains__("Object %s" % SM_STATE_MSG):
                  LOG.warning('Snapshot %(snap)s :%(state)s',
                              {'snap': snap_name,
                               'state': SM_STATE_MSG})
              else:
                  raise
  
      @utils.retry(NimbleAPIException, 2, 3)
      def get_vol_info(self, volume_name):
          volume_id = self.get_volume_id_by_name(volume_name)
          api = 'volumes/' + six.text_type(volume_id)
          r = self.get(api)
          if not r.json()['data']:
              raise exception.VolumeNotFound(_("Volume: %s not found") %
                                             volume_name)
          return r.json()['data']
  
      def delete_vol(self, volume_name):
          volume_id = self.get_volume_id_by_name(volume_name)
          api = "volumes/" + six.text_type(volume_id)
          self.delete(api)
  
      def snap_vol(self, snapshot):
          api = "snapshots"
          volume_name = snapshot['volume_name']
          vol_id = self.get_volume_id_by_name(volume_name)
          snap_name = snapshot['name']
          # Set snapshot description
          display_list = [
              getattr(snapshot, 'display_name', snapshot['display_name']),
              getattr(snapshot, 'display_description', '')]
          snap_description = ':'.join(filter(None, display_list))
          # Limit to 254 characters
          snap_description = snap_description[:254]
          data = {"data": {"name": snap_name,
                           "description": snap_description,
                           "vol_id": vol_id
                           }
                  }
          r = self.post(api, data)
          return r['data']
  
      def clone_vol(self, volume, snapshot, reserve, is_gst_enabled,
                    protocol, pool_name):
          api = "volumes"
          volume_name = snapshot['volume_name']
          snap_name = snapshot['name']
          snap_info = self.get_snap_info(snap_name, volume_name)
          clone_name = volume['name']
          snap_size = snapshot['volume_size']
          reserve_size = 100 if reserve else 0
  
          specs = self._get_volumetype_extraspecs(volume)
          extra_specs_map = self._get_extra_spec_values(specs)
          perf_policy_name = extra_specs_map.get(EXTRA_SPEC_PERF_POLICY)
          perf_policy_id = self.get_performance_policy_id(perf_policy_name)
          encrypt = extra_specs_map.get(EXTRA_SPEC_ENCRYPTION)
          multi_initiator = volume.get('multiattach', False)
          iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
          folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
          pool_id = self.get_pool_id(pool_name)
          # default value of cipher for encryption
          cipher = DEFAULT_CIPHER
          if encrypt.lower() == 'yes':
              cipher = AES_256_XTS_CIPHER
          if is_gst_enabled is True:
              agent_type = AGENT_TYPE_OPENSTACK_GST
          else:
              agent_type = AGENT_TYPE_OPENSTACK
  
          LOG.info('Cloning volume from snapshot volume=%(vol)s '
                   'snapshot=%(snap)s clone=%(clone)s snap_size=%(size)s '
                   'reserve=%(reserve)s' 'agent-type=%(agent-type)s '
                   'perfpol-name=%(perfpol-name)s '
                   'encryption=%(encryption)s cipher=%(cipher)s '
                   'multi-initiator=%(multi-initiator)s',
                   {'vol': volume_name,
                    'snap': snap_name,
                    'clone': clone_name,
                    'size': snap_size,
                    'reserve': reserve_size,
                    'agent-type': agent_type,
                    'perfpol-name': perf_policy_name,
                    'encryption': encrypt,
                    'cipher': cipher,
                    'multi-initiator': multi_initiator})
  
          data = {"data": {"name": clone_name,
                           "clone": 'true',
                           "base_snap_id": snap_info['id'],
                           'snap_limit': DEFAULT_SNAP_QUOTA,
                           'warn_level': int(WARN_LEVEL),
                           'limit': 100,
                           "online": 'true',
                           "reserve": reserve_size,
                           "agent_type": agent_type,
                           "perfpolicy_id": perf_policy_id,
                           "encryption_cipher": cipher
                           }
                  }
          if protocol == "iSCSI":
              data['data']['multi_initiator'] = multi_initiator
  
          folder_id = None
          if folder_name is not None:
              # validate if folder exists in pool_name
              pool_info = self.get_pool_info(pool_id)
              if 'folder_list' in pool_info and (pool_info['folder_list'] is
                                                 not None):
                  for folder_list in pool_info['folder_list']:
                      LOG.debug("folder_list : %s", folder_list)
                      if folder_list['fqn'] == "/" + folder_name:
                          LOG.debug("Folder %(folder)s present in pool "
                                    "%(pool)s",
                                    {'folder': folder_name,
                                     'pool': pool_name})
                          folder_id = self.get_folder_id(folder_name)
                          if folder_id is not None:
                              data['data']["folder_id"] = folder_id
                  if folder_id is None:
                      raise NimbleAPIException(_("Folder '%(folder)s' not "
                                                 "present in pool '%(pool)s'") %
                                               {'folder': folder_name,
                                                'pool': pool_name})
              else:
                  raise NimbleAPIException(_("Folder '%(folder)s' not present in"
                                             " pool '%(pool)s'") %
                                           {'folder': folder_name,
                                            'pool': pool_name})
  
          if iops_limit is not None:
              if not iops_limit.isdigit() or (
                 int(iops_limit) < MIN_IOPS) or (int(iops_limit) > MAX_IOPS):
                  raise NimbleAPIException(_("%(err)s [%(min)s, %(max)s]") %
                                           {'err': IOPS_ERR_MSG,
                                            'min': MIN_IOPS,
                                            'max': MAX_IOPS})
  
              data['data']['limit_iops'] = iops_limit
          if iops_limit is not None:
              if not iops_limit.isdigit() or (
                 int(iops_limit) < MIN_IOPS) or (int(iops_limit) > MAX_IOPS):
                  raise NimbleAPIException(_("Please set valid IOPS limit"
                                           " in the range [%(min)s, %(max)s]") %
                                           {'min': MIN_IOPS,
                                            'max': MAX_IOPS})
              data['data']['limit_iops'] = iops_limit
  
          LOG.debug("Volume metadata :%s", volume.metadata)
          for key, value in volume.metadata.items():
              LOG.debug("Key %(key)s Value %(value)s",
                        {'key': key, 'value': value})
              if key == EXTRA_SPEC_IOPS_LIMIT and value.isdigit():
                  if type(value) == int or int(value) < MIN_IOPS or (
                     int(value) > MAX_IOPS):
                      raise NimbleAPIException(_("Please enter valid IOPS "
                                                 "limit in the range ["
                                                 "%(min)s, %(max)s]") %
                                               {'min': MIN_IOPS,
                                                'max': MAX_IOPS})
                  LOG.debug("IOPS Limit %s", value)
                  data['data']['limit_iops'] = value
  
          r = self.post(api, data)
          return r['data']
  
      def edit_vol(self, volume_name, data):
          vol_id = self.get_volume_id_by_name(volume_name)
          api = "volumes/" + six.text_type(vol_id)
          self.put(api, data)
  
      def delete_snap(self, volume_name, snap_name):
          snap_info = self.get_snap_info(snap_name, volume_name)
          api = "snapshots/" + six.text_type(snap_info['id'])
          try:
              self.delete(api)
          except NimbleAPIException as ex:
              LOG.debug("delete snapshot exception: %s", ex)
              if SM_OBJ_HAS_CLONE in six.text_type(ex):
                  # if snap has a clone log the error and continue ahead
                  LOG.warning('Snapshot %(snap)s : %(state)s',
                              {'snap': snap_name,
                               'state': SM_OBJ_HAS_CLONE})
              else:
                  raise
  
      def volume_restore(self, volume_name, data):
          volume_id = self.get_volume_id_by_name(volume_name)
          api = 'volumes/%s/actions/restore' % volume_id
          self.post(api, data)
  
      @_connection_checker
      def get(self, api):
          return self.get_query(api, None)
  
      @_connection_checker
      def get_query(self, api, query):
          url = self.uri + api
          return requests.get(url, headers=self.headers,
                              params=query, verify=self.verify)
  
      @_connection_checker
      def put(self, api, payload):
          url = self.uri + api
          r = requests.put(url, data=json.dumps(payload),
                           headers=self.headers, verify=self.verify)
          if r.status_code != 201 and r.status_code != 200:
              base = "Failed to execute api %(api)s : Error Code :%(code)s" % {
                  'api': api,
                  'code': r.status_code}
              LOG.debug("Base error : %(base)s", {'base': base})
              try:
                  msg = _("%(base)s Message: %(msg)s") % {
                      'base': base,
                      'msg': r.json()['messages'][1]['text']}
              except IndexError:
                  msg = _("%(base)s Message: %(msg)s") % {
                      'base': base,
                      'msg': six.text_type(r.json())}
              raise NimbleAPIException(msg)
          return r.json()
  
      @_connection_checker
      def post(self, api, payload):
          url = self.uri + api
          r = requests.post(url, data=json.dumps(payload),
                            headers=self.headers, verify=self.verify)
          if r.status_code != 201 and r.status_code != 200:
              msg = _("Failed to execute api %(api)s : %(msg)s : %(code)s") % {
                  'api': api,
                  'msg': r.json()['messages'][1]['text'],
                  'code': r.status_code}
              raise NimbleAPIException(msg)
          return r.json()
  
      @_connection_checker
      def delete(self, api):
          url = self.uri + api
          r = requests.delete(url, headers=self.headers, verify=self.verify)
          if r.status_code != 201 and r.status_code != 200:
              base = "Failed to execute api %(api)s: Error Code: %(code)s" % {
                  'api': api,
                  'code': r.status_code}
              LOG.debug("Base error : %(base)s", {'base': base})
              try:
                  msg = _("%(base)s Message: %(msg)s") % {
                      'base': base,
                      'msg': r.json()['messages'][1]['text']}
              except IndexError:
                  msg = _("%(base)s Message: %(msg)s") % {
                      'base': base,
                      'msg': six.text_type(r.json())}
              raise NimbleAPIException(msg)
          return r.json()
  
      def _format_to_wwpn(self, string_wwpn):
          return ':'.join(a + b for a, b in zip(* [iter(string_wwpn)] * 2))
  
      def get_fc_interface_list(self, array_name):
          """getFibreChannelInterfaceList API to get FC interfaces on array."""
          api = 'fibre_channel_interfaces/detail'
          filter = {'array_name_or_serial': array_name}
          r = self.get_query(api, filter)
          if not r.json()['data']:
              raise NimbleAPIException(_("No fc interfaces for array %s") %
                                       array_name)
          return r.json()['data']
  
      def enable_group_scoped_target(self):
          group_id = self.get_group_id()
          api = "groups/" + six.text_type(group_id)
          data = {'data': {'group_target_enabled': True}}
          self.put(api, data)
  