# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2014 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 Memory Fibrechannel (FCP) Driver for Openstack Cinder

Uses Violin REST API via XG-Tools to manage a standard V6000 series
flash array to provide network block-storage services.

by Ryan Lucio
Senior Software Engineer
Violin Memory

Driver support (verified for G6.3.0):
-------------------------------------
Driver Setup:                   YES
Volume Create/Delete:           YES
Export Create/Remove:           YES
Volume Attach/Detach:           YES
Snapshot Create/Delete:         YES
Create Volume from Snapshot:    YES
Clone Volume:                   YES
Get Volume Stats:               YES
Copy Image to Volume:           YES*
Copy Volume to Image:           YES*

* functionality inherited from base class driver
"""

import random
import re
import string
import time

from oslo.config import cfg

from cinder import context
from cinder.db.sqlalchemy import models
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common import timeutils
from cinder import utils
from cinder.volume.driver import FibreChannelDriver
from cinder.volume import volume_types

LOG = logging.getLogger(__name__)

try:
    from . import version
    __version__ = version.__version__
except Exception:
    # version.py is autogenerated during packaging. If we are running
    # against original source it will not be present.
    __version__ = "unknown"

try:
    from . import vxg
    from .vxg.core.node import XGNode
    from .vxg.core.session import XGSession
except ImportError:
    LOG.exception(
        _("The Violin v6000 driver for Cinder requires the presence of "
          "the Violin 'XG-Tools', python libraries for facilitating "
          "communication between applications and the v6000 XML API. "
          "The libraries can be downloaded from the Violin Memory "
          "support website at http://www.violin-memory.com/support"))
    raise
else:
    LOG.info(_("Running with xg-tools version: %s"), vxg.__version__)

violin_opts = [
    cfg.StrOpt('gateway_vip',
               default='',
               help='IP address or hostname of the v6000 master VIP'),
    cfg.StrOpt('gateway_mga',
               default='',
               help='IP address or hostname of mg-a'),
    cfg.StrOpt('gateway_mgb',
               default='',
               help='IP address or hostname of mg-b'),
    cfg.StrOpt('gateway_user',
               default='admin',
               help='User name for connecting to the Memory Gateway'),
    cfg.StrOpt('gateway_password',
               default='',
               help='User name for connecting to the Memory Gateway',
               secret=True),
    cfg.BoolOpt('use_igroups',
                default=False,
                help='Use igroups to manage targets and initiators'),
    cfg.BoolOpt('use_thin_luns',
                default=False,
                help='Use thin luns instead of thick luns'), ]

CONF = cfg.CONF
CONF.register_opts(violin_opts)


class InvalidBackendConfig(exception.CinderException):
    message = _("Volume backend config is invalid: %(reason)s")


class RequestRetryTimeout(exception.CinderException):
    message = _("Backend service retry timeout hit: %(timeout)s sec")


class ViolinBackendErr(exception.CinderException):
    message = _("Backend reports: %(message)s")


class ViolinBackendErrExists(exception.CinderException):
    message = _("Backend reports: item already exists")


class ViolinBackendErrNotFound(exception.CinderException):
    message = _("Backend reports: item not found")


class ViolinFCDriver(FibreChannelDriver):
    """Executes commands relating to Violin Memory Arrays """

    def __init__(self, *args, **kwargs):
        super(ViolinFCDriver, self).__init__(*args, **kwargs)
        self.session_start_time = 0
        self.session_timeout = 900
        self.request_timeout = 300
        self.gateway_ids = {}
        self.vmem_vip = None
        self.vmem_mga = None
        self.vmem_mgb = None
        self.container = ""
        self.stats = {}
        self.gateway_fc_wwns = []
        self.config = kwargs.get('configuration', None)
        self.context = None
        self.lun_tracker = LunIdList(self.db)
        if self.config:
            self.config.append_config_values(violin_opts)

        LOG.info(_("Initialized driver %(name)s version: %(vers)s") %
                 {'name': self.__class__.__name__, 'vers': __version__})

    def do_setup(self, context):
        """Any initialization the driver does while starting """
        if not self.config.gateway_vip:
            raise exception.InvalidInput(
                reason=_('Gateway VIP is not set'))
        if not self.config.gateway_mga:
            raise exception.InvalidInput(
                reason=_('Gateway IP for mg-a is not set'))
        if not self.config.gateway_mgb:
            raise exception.InvalidInput(
                reason=_('Gateway IP for mg-b is not set'))

        self.vmem_vip = vxg.open(self.config.gateway_vip,
                                 self.config.gateway_user,
                                 self.config.gateway_password)
        self.vmem_mga = vxg.open(self.config.gateway_mga,
                                 self.config.gateway_user,
                                 self.config.gateway_password)
        self.vmem_mgb = vxg.open(self.config.gateway_mgb,
                                 self.config.gateway_user,
                                 self.config.gateway_password)
        self.context = context

        vip = self.vmem_vip.basic

        self.gateway_ids = self.vmem_vip.basic.get_node_values(
            '/vshare/state/global/*')

        ret_dict = vip.get_node_values("/vshare/state/local/container/*")
        if ret_dict:
            self.container = ret_dict.items()[0][1]
        ret_dict = vip.get_node_values("/wsm/inactivity_timeout")
        if ret_dict:
            self.session_timeout = ret_dict.items()[0][1]

        self.gateway_fc_wwns = self._get_active_fc_targets()

        ret_dict = vip.get_node_values(
            "/vshare/state/local/container/%s/lun/*"
            % self.container)
        if ret_dict:
            self.lun_tracker.update_from_volume_ids(ret_dict.values())

        ret_dict = vip.get_node_values(
            "/vshare/state/snapshot/container/%s/lun/*"
            % self.container)
        if ret_dict:
            for vol_id in ret_dict.values():
                snaps = vip.get_node_values(
                    "/vshare/state/snapshot/container/%s/lun/%s/snap/*"
                    % (self.container, vol_id))
                self.lun_tracker.update_from_snapshot_ids(snaps.values())

    def check_for_setup_error(self):
        """Returns an error if prerequisites aren't met"""
        vip = self.vmem_vip.basic
        if len(self.container) == 0:
            raise InvalidBackendConfig(reason=_('container is missing'))
        if len(self.gateway_fc_wwns) == 0:
            raise InvalidBackendConfig(reason=_('No FCP targets found'))

    def create_volume(self, volume):
        """Creates a volume """
        self._login()
        self._create_lun(volume)

    def delete_volume(self, volume):
        """Deletes a volume """
        self._login()
        self._delete_lun(volume)

    def create_volume_from_snapshot(self, volume, snapshot):
        """Creates a volume from an existing snapshot """
        snapshot['size'] = snapshot['volume']['size']
        self._login()
        self._create_lun(volume)
        self.copy_volume_data(self.context, snapshot, volume)

    def create_cloned_volume(self, volume, src_vref):
        """Creates a full clone of the specified volume."""
        self._login()
        self._create_lun(volume)
        self.copy_volume_data(self.context, src_vref, volume)

    def create_snapshot(self, snapshot):
        """Creates a snapshot from an existing volume """
        self._login()
        self._create_lun_snapshot(snapshot)

    def delete_snapshot(self, snapshot):
        """Deletes a snapshot """
        self._login()
        self._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):
        """Exports the volume """
        pass

    def remove_export(self, context, volume):
        """Removes an export for a logical volume """
        pass

    def initialize_connection(self, volume, connector):
        """Initializes the connection (target<-->initiator) """
        self._login()

        igroup = None

        if self.config.use_igroups:
            #
            # Most drivers don't use igroups, because there are a
            # number of issues with multipathing and iscsi/fcp where
            # lun devices either aren't cleaned up properly or are
            # stale (from previous scans).
            #
            # If the customer really wants igroups for whatever
            # reason, we create a new igroup for each host/hypervisor.
            # Every lun that is exported to the particular
            # hypervisor/host will be contained in this igroup.  This
            # should prevent other hosts from seeing luns they aren't
            # using when they perform scans.
            #
            igroup = self._get_igroup(volume, connector)
            self._add_igroup_member(connector, igroup)

        if isinstance(volume, models.Volume):
            lun_id = self._export_lun(volume, connector, igroup)
        else:
            lun_id = self._export_snapshot(volume, connector, igroup)

        self.vmem_vip.basic.save_config()

        properties = {}
        properties['target_discovered'] = True
        properties['target_wwn'] = self.gateway_fc_wwns
        properties['target_lun'] = lun_id
        properties['access_mode'] = 'rw'

        return {'driver_volume_type': 'fibre_channel', 'data': properties}

    def terminate_connection(self, volume, connector, force=False, **kwargs):
        """Terminates the connection (target<-->initiator) """
        self._login()

        if isinstance(volume, models.Volume):
            self._unexport_lun(volume)
        else:
            self._unexport_snapshot(volume)

        self.vmem_vip.basic.save_config()

    def get_volume_stats(self, refresh=False):
        """Get volume stats """
        if refresh or not self.stats:
            self._login()
            self._update_stats()
        return self.stats

    @utils.synchronized('vmem-lun')
    def _create_lun(self, volume):
        """
        Creates a new lun.

        The equivalent CLI command is "lun create container
        <container_name> name <lun_name> size <gb>"

        Arguments:
            volume -- volume object provided by the Manager
        """
        lun_type = '0'
        v = self.vmem_vip

        LOG.info(_("Creating lun %(name)s, %(size)s GB") % volume)

        if self.config.use_thin_luns:
            lun_type = '1'

        # using the defaults for fields: quantity, nozero,
        # readonly, startnum, blksize, naca, alua, preferredport
        #
        try:
            self._send_cmd(v.lun.create_lun,
                           'LUN create: success!',
                           self.container, volume['id'],
                           volume['size'], 1, '0', lun_type, 'w',
                           1, 512, False, False, None)

        except ViolinBackendErrExists:
            LOG.info(_("Lun %s already exists, continuing"), volume['id'])

        except Exception:
            LOG.warn(_("Lun create failed!"))
            raise

    @utils.synchronized('vmem-lun')
    def _delete_lun(self, volume):
        """
        Deletes a lun.

        The equivalent CLI command is "no lun create container
        <container_name> name <lun_name>"

        Arguments:
            volume -- volume object provided by the Manager
        """
        v = self.vmem_vip

        LOG.info(_("Deleting lun %s"), volume['id'])

        try:
            self._send_cmd(v.lun.bulk_delete_luns,
                           'LUN deletion started',
                           self.container, volume['id'])

        except ViolinBackendErrNotFound:
            LOG.info(_("Lun %s already deleted, continuing"), volume['id'])

        except ViolinBackendErrExists:
            LOG.warn(_("Lun %s has dependent snapshots, skipping"),
                     volume['id'])
            raise exception.VolumeIsBusy(volume_name=volume['id'])

        except Exception:
            LOG.exception(_("Lun delete failed!"))
            raise

        self.lun_tracker.free_lun_id_for_volume(volume)

    @utils.synchronized('vmem-snap')
    def _create_lun_snapshot(self, snapshot):
        """
        Creates a new snapshot for a lun

        The equivalent CLI command is "snapshot create container
        <container> lun <volume_name> name <snapshot_name>"

        Arguments:
            snapshot -- snapshot object provided by the Manager
        """
        v = self.vmem_vip

        LOG.info(_("Creating snapshot %s"), snapshot['id'])

        try:
            self._send_cmd(v.snapshot.create_lun_snapshot,
                           'Snapshot create: success!',
                           self.container, snapshot['volume_id'],
                           snapshot['id'])

        except ViolinBackendErrExists:
            LOG.info(_("Snapshot %s already exists, continuing"),
                     snapshot['id'])

        except Exception:
            LOG.exception(_("LUN snapshot create failed!"))
            raise

    @utils.synchronized('vmem-snap')
    def _delete_lun_snapshot(self, snapshot):
        """
        Deletes an existing snapshot for a lun

        The equivalent CLI command is "no snapshot create container
        <container> lun <volume_name> name <snapshot_name>"

        Arguments:
            snapshot -- snapshot object provided by the Manager
        """
        v = self.vmem_vip

        LOG.info(_("Deleting snapshot %s"), snapshot['id'])

        try:
            self._send_cmd(v.snapshot.delete_lun_snapshot,
                           'Snapshot delete: success!',
                           self.container, snapshot['volume_id'],
                           snapshot['id'])

        except ViolinBackendErrNotFound:
            LOG.info(_("Snapshot %s already deleted, continuing"),
                     snapshot['id'])

        except Exception:
            LOG.exception(_("LUN snapshot delete failed!"))
            raise

        self.lun_tracker.free_lun_id_for_snapshot(snapshot)

    @utils.synchronized('vmem-export')
    def _export_lun(self, volume, connector=None, igroup=None):
        """
        Generates the export configuration for the given volume

        The equivalent CLI command is "lun export container
        <container_name> name <lun_name>"

        Arguments:
            volume -- volume object provided by the Manager
            connector -- connector object provided by the Manager
            igroup -- name of igroup to use for exporting

        Returns:
            lun_id -- the LUN ID assigned by the backend
        """
        lun_id = ''
        export_to = ''
        v = self.vmem_vip

        lun_id = self.lun_tracker.get_lun_id_for_volume(volume)

        if igroup:
            export_to = igroup
        elif connector:
            export_to = self._convert_wwns_openstack_to_vmem(
                connector['wwpns'])
        else:
            raise exception.Error(_("No initiators found, cannot proceed"))

        LOG.info(_("Exporting lun %(vol_id)s on lun_id %(lun_id)s") %
                 {'vol_id': volume['id'], 'lun_id': lun_id})

        try:
            self._send_cmd_and_verify(v.lun.export_lun,
                                      self._wait_for_exportstate,
                                      '',
                                      [self.container, volume['id'],
                                       'all', export_to, lun_id],
                                      [volume['id'], True])

        except Exception:
            LOG.exception(_("LUN export failed!"))
            raise

        return lun_id

    @utils.synchronized('vmem-export')
    def _unexport_lun(self, volume):
        """
        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.vmem_vip

        LOG.info(_("Unexporting lun %s"), volume['id'])

        try:
            self._send_cmd_and_verify(v.lun.unexport_lun,
                                      self._wait_for_exportstate,
                                      '',
                                      [self.container, volume['id'],
                                       'all', 'all', 'auto'],
                                      [volume['id'], False])

        except ViolinBackendErrNotFound:
            LOG.info(_("Lun %s already unexported, continuing"),
                     volume['id'])

        except Exception:
            LOG.exception(_("LUN unexport failed!"))
            raise

    @utils.synchronized('vmem-export')
    def _export_snapshot(self, snapshot, connector=None, igroup=None):
        """
        Generates the export configuration for the given snapshot.

        The equivalent CLI command is "snapshot export container
        PROD08 lun <snapshot_name> name <volume_name>"

        Arguments:
            snapshot -- snapshot object provided by the Manager
            connector -- connector object provided by the Manager
            igroup -- name of igroup to use for exporting

        Returns:
            lun_id -- the LUN ID assigned by the backend
        """
        lun_id = ''
        export_to = ''
        v = self.vmem_vip

        lun_id = self.lun_tracker.get_lun_id_for_snapshot(snapshot)

        if igroup:
            export_to = igroup
        elif connector:
            export_to = self._convert_wwns_openstack_to_vmem(
                connector['wwpns'])
        else:
            raise exception.Error(_("No initiators found, cannot proceed"))

        LOG.info(_("Exporting snapshot %s"), snapshot['id'])

        try:
            self._send_cmd(v.snapshot.export_lun_snapshot, '',
                           self.container, snapshot['volume_id'],
                           snapshot['id'], export_to, 'all', lun_id)

        except Exception:
            LOG.exception(_("Snapshot export failed!"))
            raise

        else:
            self._wait_for_exportstate(snapshot['id'], True)

        return lun_id

    @utils.synchronized('vmem-export')
    def _unexport_snapshot(self, snapshot):
        """
        Removes the export configuration for the given snapshot.

        The equivalent CLI command is "no snapshot export container
        PROD08 lun <snapshot_name> name <volume_name>"

        Arguments:
            snapshot -- snapshot object provided by the Manager
        """
        v = self.vmem_vip

        LOG.info(_("Unexporting snapshot %s"), snapshot['id'])

        try:
            self._send_cmd(v.snapshot.unexport_lun_snapshot, '',
                           self.container, snapshot['volume_id'],
                           snapshot['id'], 'all', 'all', 'auto', False)

        except Exception:
            LOG.exception(_("Snapshot export failed!"))
            raise

        else:
            self._wait_for_exportstate(snapshot['id'], False)

    def _add_igroup_member(self, connector, igroup):
        """
        Add an initiator to the openstack igroup so it can see exports.

        The equivalent CLI command is "igroup addto name <igroup_name>
        initiators <initiator_name>"

        Arguments:
            connector -- connector object provided by the Manager
        """
        v = self.vmem_vip
        wwpns = self._convert_wwns_openstack_to_vmem(connector['wwpns'])

        LOG.info(_("Adding initiators %(wwpns)s to igroup %(igroup)s") %
                 {'wwpns': wwpns, 'igroup': igroup})

        resp = v.igroup.add_initiators(igroup, wwpns)

        if resp['code'] != 0:
            raise exception.Error(
                _('Failed to add igroup member: %(code)d, %(message)s') % resp)

    def _update_stats(self):
        """
        Gathers array stats from the backend and converts them to GB values.
        """
        data = {}
        total_gb = 'unknown'
        free_gb = 'unknown'
        v = self.vmem_vip

        bn1 = "/vshare/state/global/1/container/%s/total_bytes" \
            % self.container
        bn2 = "/vshare/state/global/1/container/%s/free_bytes" \
            % self.container
        resp = v.basic.get_node_values([bn1, bn2])

        if bn1 in resp:
            total_gb = resp[bn1] / 1024 / 1024 / 1024
        else:
            LOG.warn(_("Failed to receive update for total_gb stat!"))

        if bn2 in resp:
            free_gb = resp[bn2] / 1024 / 1024 / 1024
        else:
            LOG.warn(_("Failed to receive update for free_gb stat!"))

        backend_name = self.config.volume_backend_name
        data['volume_backend_name'] = backend_name or self.__class__.__name__
        data['vendor_name'] = 'Violin Memory, Inc.'
        data['driver_version'] = __version__
        data['storage_protocol'] = 'fibre_channel'
        data['reserved_percentage'] = 0
        data['QoS_support'] = False
        data['total_capacity_gb'] = total_gb
        data['free_capacity_gb'] = free_gb

        for i in data:
            LOG.debug(_("stat update: %(name)s=%(data)s") %
                      {'name': i, 'data': data[i]})

        self.stats = data

    def _convert_wwns_openstack_to_vmem(self, wwns):
        """
        Convert a list of Openstack WWNs to VMEM compatible WWN
        strings.

        Arguments:
            wwns -- list of Openstack-based WWN strings.

        Returns:
            output -- list of VMEM-based WWN strings.
        """
        # input format is '50014380186b3f65', output format is
        # 'wwn.50:01:43:80:18:6b:3f:65'
        #
        output = []
        for w in wwns:
            output.append('wwn.{0}'.format(
                ':'.join(w[x:x + 2] for x in xrange(0, len(w), 2))))
        return output

    def _convert_wwns_vmem_to_openstack(self, wwns):
        """
        Convert a list of VMEM WWNs to Openstack compatible WWN
        strings.

        Arguments:
            wwns -- list of VMEM-based WWN strings.

        Returns:
            output -- list of Openstack-based WWN strings.
        """
        # input format is 'wwn.50:01:43:80:18:6b:3f:65', output format
        # is '50014380186b3f65'
        #
        output = []
        for w in wwns:
            output.append(''.join(w[4:].split(':')))
        return output

###############################################################################
###############################################################################
###############################################################################

    def _login(self, force=False):
        """
        Get new api creds from the backend, only if needed.

        Arguments:
            force -- re-login on all sessions regardless of last login time

        Returns:
           True if sessions were refreshed, false otherwise.
        """
        now = time.time()
        if abs(now - self.session_start_time) >= self.session_timeout or \
                force == True:
            self.vmem_vip.basic.login()
            self.vmem_mga.basic.login()
            self.vmem_mgb.basic.login()
            self.session_start_time = now
            return True
        return False

    def _send_cmd(self, request_func, success_msg, *args):
        """
        Run an XG request function, and retry until the request
        returns a success message, a failure message, or the global
        request timeout is hit.

        This wrapper is meant to deal with backend requests that can
        fail for any variety of reasons, for instance, when the system
        is already busy handling other LUN requests.  It is also smart
        enough to give up if clustering is down (eg no HA available),
        there is no space left, or other "fatal" errors are returned
        (see _fatal_error_code() for a list of all known error
        conditions).

        Arguments:
            request_func    -- XG api method to call
            success_msg     -- Success message expected from the backend
            *args           -- argument array to be passed to the request_func

        Returns:
            The response dict from the last XG call.
        """
        resp = {}
        start = time.time()

        while True:
            if time.time() - start >= self.request_timeout:
                raise RequestRetryTimeout(timeout=self.request_timeout)

            resp = request_func(*args)

            if not resp['message']:
                # XG requests will return None for a message if no message
                # string is passed int the raw response
                resp['message'] = ''

            if not resp['code'] and success_msg in resp['message']:
                break

            self._fatal_error_code(resp)

        return resp

    def _send_cmd_and_verify(self, request_func, verify_func,
                             request_success_msg='', rargs=[], vargs=[]):
        """
        Run an XG request function, and verify success using an
        additional verify function.  If the verification fails, then
        retry the request/verify cycle until both functions are
        successful, the request function returns a failure message, or
        the global request timeout is hit.

        This wrapper is meant to deal with backend requests that can
        fail for any variety of reasons, for instance, when the system
        is already busy handling other LUN requests.  It is also smart
        enough to give up if clustering is down (eg no HA available),
        there is no space left, or other "fatal" errors are returned
        (see _fatal_error_code() for a list of all known error
        conditions).

        Arguments:
            request_func        -- XG api method to call
            verify_func         -- function to call to verify request was
                                   completed successfully (eg for export)
            request_success_msg -- Success message expected from the backend
                                   for the request_func
            *rargs              -- argument array to be passed to the
                                   request_func
            *vargs              -- argument array to be passed to the
                                   verify_func

        Returns:
            The response dict from the last XG call.
        """
        resp = {}
        start = time.time()
        request_needed = True
        verify_needed = True

        while request_needed or verify_needed:
            if time.time() - start >= self.request_timeout:
                raise RequestRetryTimeout(timeout=self.request_timeout)

            if request_needed:
                resp = request_func(*rargs)
                if not resp['message']:
                    # XG requests will return None for a message if no message
                    # string is passed int the raw response
                    resp['message'] = ''
                if not resp['code'] and request_success_msg in resp['message']:
                    # XG request func was completed
                    request_needed = False
                self._fatal_error_code(resp)

            elif verify_needed:
                success = verify_func(*vargs)
                if success:
                    # XG verify func was completed
                    verify_needed = False
                else:
                    # try sending the request again
                    request_needed = True

        return resp

    def _get_active_fc_targets(self):
        """
        Get a list of gateway WWNs that can be used as FCP targets.

        Arguments:
            mg_conn -- active XG connection to one of the gateways

        Returns:
            active_gw_fcp_wwns -- list of WWNs
        """
        v = self.vmem_vip.basic
        active_gw_fcp_wwns = []

        for i in self.gateway_ids:
            bn = "/vshare/state/global/%d/target/fc/**" % self.gateway_ids[i]
            resp = v.get_node_values(bn)

            for node in resp:
                if node.endswith('/wwn'):
                    active_gw_fcp_wwns.append(resp[node])

        return self._convert_wwns_vmem_to_openstack(active_gw_fcp_wwns)

    def _get_igroup(self, volume, connector):
        """
        Gets the igroup that should be used when configuring a volume

        Arguments:
            volume -- volume object used to determine the igroup name

        Returns:
            igroup_name -- name of igroup (for configuring targets &
                           initiators)
        """
        v = self.vmem_vip

        # Use the connector's primary hostname and use that as the
        # name of the igroup.  The name must follow syntax rules
        # required by the array: "must contain only alphanumeric
        # characters, dashes, and underscores.  The first character
        # must be alphanumeric".
        #
        igroup_name = re.sub(r'[\W]', '_', connector['host'])

        # verify that the igroup has been created on the backend, and
        # if it doesn't exist, create it!
        #
        bn = "/vshare/config/igroup/%s" % igroup_name
        resp = v.basic.get_node_values(bn)

        if not len(resp):
            v.igroup.create_igroup(igroup_name)

        return igroup_name

    def _get_volume_type_extra_spec(self, volume, spec_key):
        """
        Parse data stored in a volume_type's extra_specs table.

        Code adapted from examples in
        cinder/volume/drivers/solidfire.py and
        cinder/openstack/common/scheduler/filters/capabilities_filter.py.

        Arguments:
            volume   -- volume object containing volume_type to query
            spec_key -- the metadata key to search for

        Returns:
            spec_value -- string value associated with spec_key
        """
        spec_value = None
        ctxt = context.get_admin_context()
        typeid = volume['volume_type_id']
        if typeid:
            volume_type = volume_types.get_volume_type(ctxt, typeid)
            volume_specs = volume_type.get('extra_specs')
            for key, val in volume_specs.iteritems():

                # Havana release altered extra_specs to require a
                # prefix on all non-host-capability related extra
                # specs, so that prefix is stripped here before
                # checking the key.
                #
                if ':' in key:
                    scope = key.split(':')
                    key = scope[1]
                if key == spec_key:
                    spec_value = val
                    break

        return spec_value

    def _wait_for_exportstate(self, volume_name, state=False):
        """
        Polls backend to verify volume's export configuration.

        XG sets/queries following a request to create or delete a lun
        export may fail on the backend if vshared is still processing
        the export action (or times out).  We can check whether it is
        done by polling the export binding for a lun to ensure it is
        created or deleted.

        This function will try to verify the creation or removal of
        export state on both gateway nodes of the array every 5
        seconds for up to 30 seconds.

        Arguments:
            volume_name -- name of volume to be polled
            state       -- True to poll for existence, False for lack of

        Returns:
            True if the export state was correctly added or removed
            (depending on 'state' param)
        """
        vip = self.vmem_vip.basic
        status = [False, False]
        mg_conns = [self.vmem_mga.basic, self.vmem_mgb.basic]
        success = False

        bn = "/vshare/config/export/container/%s/lun/%s" \
            % (self.container, volume_name)

        for i in xrange(6):
            for node_id in xrange(2):
                if not status[node_id]:
                    resp = mg_conns[node_id].get_node_values(bn)
                    if state and len(resp.keys()):
                        status[node_id] = True
                    elif (not state) and (not len(resp.keys())):
                        status[node_id] = True

            if status[0] and status[1]:
                success = True
                break
            else:
                time.sleep(5)

        return success

    def _fatal_error_code(self, response):
        """
        Check the error code in a XG response for a fatal error,
        and returns an appropriate exception.  Error codes extracted
        from vdmd_mgmt.c.

        Arguments:
            response -- a response dict result from an XG request
        """
        # known non-fatal response codes
        #
        retry_codes = {1024: 'lun deletion in progress, try again later',
                       14032: 'lc_err_lock_busy'}

        if response['code'] == 14000:
            # lc_generic_error
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14002:
            # lc_err_assertion_failed
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14004:
            # lc_err_not_found
            raise ViolinBackendErrNotFound()
        elif response['code'] == 14005:
            # lc_err_exists
            raise ViolinBackendErrExists()
        elif response['code'] == 14008:
            # lc_err_unexpected_arg
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14014:
            # lc_err_io_error
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14016:
            # lc_err_io_closed
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14017:
            # lc_err_io_timeout
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14021:
            # lc_err_unexpected_case
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14025:
            # lc_err_no_fs_space
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14035:
            # lc_err_range
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14036:
            # lc_err_invalid_param
            raise ViolinBackendErr(message=response['message'])
        elif response['code'] == 14121:
            # lc_err_cancelled_err
            raise ViolinBackendErr(message=response['message'])


class LunIdList(object):
    """
    Tracks available lun_ids for use when exporting a new lun for the
    first time.  After instantiating a new LunIdList object, it should
    be updated (basically quiescing volumes/snapshot lun ID allocation
    between the array and the corresponding Openstack DB metadata).

    After that, the object can be queried to capture the next
    'available' lun ID for use with exporting a new volume or
    snapshot.  Only when the volume/snapshot is deleted entirely, the
    lun ID should be freed.

    Lun IDs are montonically increasing up to a max value of 16k,
    after which the selection will loop around to lun ID 1 and will
    continue to increment until an available ID is found.
    """
    def __init__(self, db, *args, **kwargs):
        self.max_lun_id = 16000
        self.lun_id_list = [0] * self.max_lun_id
        self.lun_id_list[0] = 1
        self.prev_index = 1
        self.free_index = 1
        self.context = context.get_admin_context()
        self.db = db

    def update_from_volume_ids(self, id_list=[]):
        """
        Walk a list of volumes collected that the array knows about and
        check for any saved lun_id metadata for each of those volumes to
        fully sync the list.  Note that the metadata keys are stored as
        strings.

        Arguments:
            id_list -- array containing names of volumes that exist on the
                       backend (volume 'names' are UUIDs if they were made
                       via the VMEM driver API)
        """
        for item in id_list:
            try:
                metadata = self.db.volume_metadata_get(self.context, item)
            except exception.VolumeNotFound:
                LOG.warn(_("No db state for lun %s, skipping lun_id update"),
                         item)
            else:
                if metadata and 'lun_id' in metadata:
                    index = int(metadata['lun_id'])
                    self.lun_id_list[index] = 1
                    LOG.debug("Set lun_id=%d for volume_id=%s" % (index, item))
                    self.update_free_index(index)

    def update_from_snapshot_ids(self, id_list=[]):
        """
        Walk a list of snapshots collected that the array knows about and
        check for any saved lun_id metadata for each of those snapshots to
        fully sync the list.  Note that the metadata keys are stored as
        strings.

        Arguments:
            id_list -- array containing names of snapshots that exist on the
                       backend (snapshot 'names' are UUIDs if they were made
                       via the VMEM driver API)
        """
        for item in id_list:
            try:
                metadata = self.db.snapshot_metadata_get(self.context, item)
            except exception.SnapshotNotFound:
                LOG.warn(_("No db state for snap %s, skipping lun_id update"),
                         item)
            else:
                if metadata and 'lun_id' in metadata:
                    index = int(metadata['lun_id'])
                    self.lun_id_list[index] = 1
                    LOG.debug("Set lun_id=%d for snapshot_id=%s" %
                              (index, item))
                    self.update_free_index(index)

    def get_lun_id_for_volume(self, volume):
        """
        Allocate a free a lun ID to a volume and create a lun_id tag
        in the volume's metadata.

        Arguments:
            volume -- the volume object to allocate a lun_id to
        """
        metadata = self.db.volume_metadata_get(self.context, volume['id'])
        if not metadata or 'lun_id' not in metadata:
            metadata = {}
            metadata['lun_id'] = self.get_next_lun_id_str()
            self.db.volume_metadata_update(self.context, volume['id'],
                                           metadata, False)
            LOG.debug("Assigned lun_id %s to volume %s" %
                      (metadata['lun_id'], volume['id']))
        return metadata['lun_id']

    def get_lun_id_for_snapshot(self, snapshot):
        """
        Allocate a free a lun ID to a snapshot and create a lun_id tag
        in the snapshot's metadata.

        Arguments:
            snapshot -- the snapshot object to allocate a lun_id to
        """
        metadata = self.db.snapshot_metadata_get(self.context, snapshot['id'])
        if not metadata or 'lun_id' not in metadata:
            metadata = {}
            metadata['lun_id'] = self.get_next_lun_id_str()
            self.db.snapshot_metadata_update(self.context, snapshot['id'],
                                             metadata, False)
            LOG.debug("Assigned lun_id %s to volume %s" %
                      (metadata['lun_id'], snapshot['id']))
        return metadata['lun_id']

    def free_lun_id_for_volume(self, volume):
        """
        Remove the lun_id tag saved in the volume's metadata and
        free the lun ID in the internal tracking array.

        Arguments:
            volume -- the volume object with a lun ID to be free'd
        """
        metadata = self.db.volume_metadata_get(self.context, volume['id'])
        if metadata and 'lun_id' in metadata:
            self.free_lun_id_str(metadata['lun_id'])

    def free_lun_id_for_snapshot(self, snapshot):
        """
        Remove the lun_id tag saved in the snapshot's metadata and
        free the lun ID in the internal tracking array.

        Arguments:
            snapshot -- the snapshot object with a lun ID to be free'd
        """
        metadata = self.db.snapshot_metadata_get(self.context, snapshot['id'])
        if metadata and 'lun_id' in metadata:
            self.free_lun_id_str(metadata['lun_id'])

    def get_next_lun_id_str(self):
        """
        Mark the next available lun_id as allocated and return
        it to the caller.

        Returns:
            next_id -- the lun ID that being allocated to the caller
        """
        next_id = self.free_index
        self.lun_id_list[next_id] = 1
        self.update_free_index()
        return str(next_id)

    def free_lun_id_str(self, value_str):
        """
        Mark a lun_id as now available, as if the lun was de-allocated.

        Arguments:
            value_str -- lun ID to free (in string format)
        """
        value = int(value_str)
        self.lun_id_list[value] = 0
        self.update_free_index()

    def update_free_index(self, index=None):
        """
        Update the free index, monotonically increasing, and
        looping back to 1 after the max lun ID value is hit.

        Arguments:
            index -- assume that all values below this number may be already
                     allocated, so start searching at that value if it is
                     higher than the free_index
        """
        i = 0
        count = 0
        max_size = len(self.lun_id_list)
        if index and index > self.free_index:
            i = index + 1
        else:
            i = self.free_index
        # avoid possibility of indexError
        if i >= max_size:
            i = 1
        while self.lun_id_list[i] == 1 and count < max_size:
            count += 1
            i += 1
            if i >= max_size:
                i = 1
        self.free_index = i
        if count == max_size:
            raise exception.Error("Cannot find free lun_id, giving up!")
