]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
XtreamIO version 4.0 support
authorShay Halsband <shay.halsband@emc.com>
Wed, 14 Jan 2015 07:00:19 +0000 (09:00 +0200)
committerShay Halsband <shay.halsband@emc.com>
Thu, 26 Feb 2015 10:42:13 +0000 (12:42 +0200)
* use new XMS Rest features to improve performance
* add support for manage/unmanage commands

Change-Id: Ib75ff304d5deff6ee44a0b74ba7264a060947537
Implements: blueprint emc-xtremio-driver-kilo-update

cinder/exception.py
cinder/tests/test_emc_xtremio.py
cinder/volume/drivers/emc/xtremio.py

index d886663fba820253abaa2fd8eefceddcf3a23f72..c5b3fd81b0bed7d5a835e27160e49368482d71d6 100644 (file)
@@ -948,3 +948,8 @@ class WebDAVClientError(CinderException):
         message = _("The WebDAV request failed. Reason: %(msg)s, "
                     "Return code/reason: %(code)s, Source Volume: %(src)s, "
                     "Destination Volume: %(dst)s, Method: %(method)s.")
+
+
+# XtremIO Drivers
+class XtremIOAlreadyMappedError(CinderException):
+    message = _("Volume to Initiator Group mapping already exists")
index ad6b608d4d16786a0f448c46abc741185dea80b1..3ffc30662f3ed1f6b381e7103bb42515f3759b86 100644 (file)
@@ -31,7 +31,11 @@ typ2id = {'volumes': 'vol-id',
           'initiator-groups': 'ig-id',
           'lun-maps': 'mapping-id'}
 
-xms_data = {'clusters': {1: {'sys-sw-version': "2.4.0-devel_ba23ee5381eeab73",
+xms_data = {'xms': {1: {'version': '4.0.0'}},
+            'clusters': {1: {'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73",
+                             'ud-ssd-space': '8146708710',
+                             'ud-ssd-space-in-use': '708710',
+                             'vol-size': '29884416',
                              'chap-authentication-mode': 'disabled',
                              'chap-discovery-mode': 'disabled',
                              "index": 1}},
@@ -96,7 +100,10 @@ def xms_request(object_type='volumes', request_typ='GET', data=None,
 
     obj_key = name if name else idx
     if request_typ == 'GET':
-        res = xms_data[object_type]
+        try:
+            res = xms_data[object_type]
+        except KeyError:
+            raise exception.VolumeDriverException
         if name or idx:
             if obj_key not in res:
                 raise exception.NotFound()
@@ -136,6 +143,9 @@ def xms_request(object_type='volumes', request_typ='GET', data=None,
         if obj_key in xms_data[object_type]:
             obj = xms_data[object_type][obj_key]
             obj.update(data)
+            key = get_xms_obj_key(data)
+            if key:
+                xms_data[object_type][data[key]] = obj
         else:
             LOG.error('Trying to update a missing object %s',
                       six.text_type(obj_key))
@@ -198,9 +208,12 @@ class CommonData():
                   'display_name': 'clone1',
                   'display_description': 'volume created from snapshot',
                   'volume_type_id': None}
+    unmanaged1 = {'id': 'unmanaged1',
+                  'name': 'unmanaged1',
+                  'size': 3}
 
 
-@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOISCSIDriver.req')
+@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req')
 class EMCXIODriverISCSITestCase(test.TestCase):
     def setUp(self):
         super(EMCXIODriverISCSITestCase, self).setUp()
@@ -209,10 +222,25 @@ class EMCXIODriverISCSITestCase(test.TestCase):
         configuration.san_login = ''
         configuration.san_password = ''
         configuration.san_ip = ''
+        configuration.xtremio_cluster_name = ''
+        configuration.xtremio_provisioning_factor = 20.0
+
+        def safe_get(key):
+            getattr(configuration, key)
+
+        configuration.safe_get = safe_get
         self.driver = xtremio.XtremIOISCSIDriver(configuration=configuration)
 
         self.data = CommonData()
 
+    def test_check_for_setup_error(self, req):
+        req.side_effect = xms_request
+        xms = xms_data['xms']
+        del xms_data['xms']
+        self.driver.check_for_setup_error()
+        xms_data['xms'] = xms
+        self.driver.check_for_setup_error()
+
     def test_create_extend_delete_volume(self, req):
         req.side_effect = xms_request
         clean_xms_data()
@@ -283,8 +311,28 @@ class EMCXIODriverISCSITestCase(test.TestCase):
         self.assertEqual(stats['volume_backend_name'],
                          self.driver.backend_name)
 
-
-@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOFibreChannelDriver.req')
+    def test_manage_unmanage(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        xms_data['volumes'] = {'unmanaged1': {'vol-name': 'unmanaged1',
+                                              'index': 'unmanaged1',
+                                              'vol-size': '3'}}
+        ref_vol = {"source-name": "unmanaged1"}
+        invalid_ref = {"source-name": "invalid"}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing_get_size,
+                          self.data.test_volume, invalid_ref)
+        self.driver.manage_existing_get_size(self.data.test_volume, ref_vol)
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          self.data.test_volume, invalid_ref)
+        self.driver.manage_existing(self.data.test_volume, ref_vol)
+        self.assertRaises(exception.VolumeNotFound, self.driver.unmanage,
+                          self.data.test_volume2)
+        self.driver.unmanage(self.data.test_volume)
+
+
+@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req')
 class EMCXIODriverFibreChannelTestCase(test.TestCase):
     def setUp(self):
         super(EMCXIODriverFibreChannelTestCase, self).setUp()
@@ -293,6 +341,8 @@ class EMCXIODriverFibreChannelTestCase(test.TestCase):
         configuration.san_login = ''
         configuration.san_password = ''
         configuration.san_ip = ''
+        configuration.xtremio_cluster_name = ''
+        configuration.xtremio_provisioning_factor = 20.0
         self.driver = xtremio.XtremIOFibreChannelDriver(
             configuration=configuration)
 
index d6bdf5d76f4051c1d5ed5d00d1c07f3028c69b5d..cedb856f464abfc62a4d7812c23758940fb87b11 100644 (file)
@@ -21,15 +21,19 @@ supported XtremIO version 2.4 and up
 1.0.2 - added FC support, improved error handling
 1.0.3 - update logging level, add translation
 1.0.4 - support for FC zones
+1.0.5 - add support for XtremIO 4.0
 """
 
 import base64
 import json
+import math
 import random
 import string
 import urllib
 import urllib2
 
+from oslo_config import cfg
+from oslo_utils import units
 import six
 
 from cinder import exception
@@ -42,39 +46,56 @@ from cinder.zonemanager import utils as fczm_utils
 
 LOG = logging.getLogger(__name__)
 
-
-class XtremIOVolumeDriver(san.SanDriver):
-    """Executes commands relating to Volumes."""
-
-    VERSION = '1.0.4'
-    driver_name = 'XtremIO'
-    MIN_XMS_VERSION = [2, 4, 0]
-    random = random.Random()
-
-    def __init__(self, *args, **kwargs):
-        super(XtremIOVolumeDriver, self).__init__(*args, **kwargs)
-        self.base64_auth = (base64.encodestring('%s:%s' %
-                            (self.configuration.san_login,
-                             self.configuration.san_password))
+CONF = cfg.CONF
+DEFAULT_PROVISIONING_FACTOR = 20.0
+XTREMIO_OPTS = [
+    cfg.StrOpt('xtremio_cluster_name',
+               default='',
+               help='XMS cluster id in multi-cluster environment')]
+
+CONF.register_opts(XTREMIO_OPTS)
+
+RANDOM = random.Random()
+OBJ_NOT_FOUND_ERR = 'obj_not_found'
+VOL_NOT_UNIQUE_ERR = 'vol_obj_name_not_unique'
+VOL_OBJ_NOT_FOUND_ERR = 'vol_obj_not_found'
+ALREADY_MAPPED_ERR = 'already_mapped'
+
+
+class XtremIOClient(object):
+    def __init__(self, configuration, cluster_id):
+        self.configuration = configuration
+        self.cluster_id = cluster_id
+        self.base64_auth = (base64
+                            .encodestring('%s:%s' %
+                                          (self.configuration.san_login,
+                                           self.configuration.san_password))
                             .replace('\n', ''))
         self.base_url = ('https://%s/api/json/types' %
                          self.configuration.san_ip)
-        self.protocol = None
-        self.backend_name = (self.configuration.safe_get(
-                             'volume_backend_name') or
-                             self.driver_name)
 
-    def _create_request(self, request_typ, data, url, urllib):
-        if data and request_typ == 'GET':
-            url + '?' + urllib.urlencode(data)
+    def _create_request(self, request_typ, data, url, url_data):
+        if request_typ in ('GET', 'DELETE'):
+            data.update(url_data)
+            self.update_url(data, self.cluster_id)
+            url = '%(url)s?%(query)s' % {'query': urllib.urlencode(data,
+                                                                   doseq=True),
+                                         'url': url}
             request = urllib2.Request(url)
-        elif data:
-            LOG.debug('data: %s', json.dumps(data))
-            request = urllib2.Request(url, json.dumps(data))
         else:
-            request = urllib2.Request(url)
-        LOG.debug('quering url: %s', url)
-        request.get_method = lambda: request_typ
+            if url_data:
+                url = ('%(url)s?%(query)s' %
+                       {'query': urllib.urlencode(url_data, doseq=True),
+                        'url': url})
+
+            self.update_data(data, self.cluster_id)
+            LOG.debug('data: %s', data)
+            request = urllib2.Request(url, json.dumps(data))
+            LOG.debug('%(type)s %(url)s', {'type': request_typ, 'url': url})
+
+        def get_request_type():
+            return request_typ
+        request.get_method = get_request_type
         request.add_header("Authorization", "Basic %s" % (self.base64_auth, ))
         return request
 
@@ -84,15 +105,21 @@ class XtremIOVolumeDriver(san.SanDriver):
         except (urllib2.HTTPError, ) as exc:
             if exc.code == 400 and hasattr(exc, 'read'):
                 error = json.load(exc)
-                if error['message'].endswith('obj_not_found'):
+                err_msg = error['message']
+                if err_msg.endswith(OBJ_NOT_FOUND_ERR):
                     LOG.warning(_LW("object %(key)s of "
                                     "type %(typ)s not found"),
                                 {'key': key, 'typ': object_type})
                     raise exception.NotFound()
-                elif error['message'] == 'vol_obj_name_not_unique':
+                elif err_msg == VOL_NOT_UNIQUE_ERR:
                     LOG.error(_LE("can't create 2 volumes with the same name"))
                     msg = (_('Volume by this name already exists'))
                     raise exception.VolumeBackendAPIException(data=msg)
+                elif err_msg == VOL_OBJ_NOT_FOUND_ERR:
+                    LOG.error(_LE("Can't find volume to map %s"), key)
+                    raise exception.VolumeNotFound(volume_id=key)
+                elif ALREADY_MAPPED_ERR in err_msg:
+                    raise exception.XtremIOAlreadyMappedError()
             LOG.error(_LE('Bad response from XMS, %s'), exc.read())
             msg = (_('Exception: %s') % six.text_type(exc))
             raise exception.VolumeDriverException(message=msg)
@@ -105,20 +132,23 @@ class XtremIOVolumeDriver(san.SanDriver):
 
     def req(self, object_type='volumes', request_typ='GET', data=None,
             name=None, idx=None):
+        if not data:
+            data = {}
         if name and idx:
             msg = _("can't handle both name and index in req")
             LOG.error(msg)
             raise exception.VolumeDriverException(message=msg)
 
         url = '%s/%s' % (self.base_url, object_type)
+        url_data = {}
         key = None
         if name:
-            url = '%s?%s' % (url, urllib.urlencode({'name': name}))
+            url_data['name'] = name
             key = name
         elif idx:
             url = '%s/%d' % (url, idx)
             key = str(idx)
-        request = self._create_request(request_typ, data, url, urllib)
+        request = self._create_request(request_typ, data, url, url_data)
         response = self._send_request(object_type, key, request)
         str_result = response.read()
         if str_result:
@@ -131,25 +161,139 @@ class XtremIOVolumeDriver(san.SanDriver):
                                'req': request_typ,
                                'res': str_result})
 
+    def update_url(self, data, cluster_id):
+        return
+
+    def update_data(self, data, cluster_id):
+        return
+
+    def get_cluster(self):
+        return self.req('clusters', idx=1)['content']
+
+
+class XtremIOClient3(XtremIOClient):
+    def find_lunmap(self, ig_name, vol_name):
+        try:
+            for lm_link in self.req('lun-maps')['lun-maps']:
+                idx = lm_link['href'].split('/')[-1]
+                lm = self.req('lun-maps', idx=int(idx))['content']
+                if lm['ig-name'] == ig_name and lm['vol-name'] == vol_name:
+                    return lm
+        except exception.NotFound:
+            raise (exception.VolumeDriverException
+                   (_("can't find lunmap, ig:%(ig)s vol:%(vol)s") %
+                    {'ig': ig_name, 'vol': vol_name}))
+
+    def num_of_mapped_volumes(self, initiator):
+        cnt = 0
+        for lm_link in self.req('lun-maps')['lun-maps']:
+            idx = lm_link['href'].split('/')[-1]
+            lm = self.req('lun-maps', idx=int(idx))['content']
+            if lm['ig-name'] == initiator:
+                cnt += 1
+        return cnt
+
+    def get_iscsi_portal(self):
+        iscsi_portals = [t['name'] for t in self.req('iscsi-portals')
+                         ['iscsi-portals']]
+        # Get a random portal
+        portal_name = RANDOM.choice(iscsi_portals)
+        try:
+            portal = self.req('iscsi-portals',
+                              name=portal_name)['content']
+        except exception.NotFound:
+            raise (exception.VolumeBackendAPIException
+                   (data=_("iscsi portal, %s, not found") % portal_name))
+
+        return portal
+
+
+class XtremIOClient4(XtremIOClient):
+    def find_lunmap(self, ig_name, vol_name):
+        try:
+            return (self.req('lun-maps',
+                             data={'full': 1,
+                                   'filter': ['vol-name:eq:%s' % vol_name,
+                                              'ig-name:eq:%s' % ig_name]})
+                    ['lun-maps'][0])
+        except (KeyError, IndexError):
+            raise exception.VolumeNotFound(volume_id=vol_name)
+
+    def num_of_mapped_volumes(self, initiator):
+        return len(self.req('lun-maps',
+                            data={'filter': 'ig-name:eq:%s' % initiator})
+                   ['lun-maps'])
+
+    def update_url(self, data, cluster_id):
+        if cluster_id:
+            data['cluster-name'] = cluster_id
+
+    def update_data(self, data, cluster_id):
+        if cluster_id:
+            data['cluster-id'] = cluster_id
+
+    def get_iscsi_portal(self):
+        iscsi_portals = self.req('iscsi-portals',
+                                 data={'full': 1})['iscsi-portals']
+        return RANDOM.choice(iscsi_portals)
+
+    def get_cluster(self):
+        if self.cluster_id:
+            return self.req('clusters', name=self.cluster_id)['content']
+        else:
+            name = self.req('clusters')['clusters'][0]['name']
+            return self.req('clusters', name=name)['content']
+
+
+class XtremIOVolumeDriver(san.SanDriver):
+    """Executes commands relating to Volumes."""
+
+    VERSION = '1.0.5'
+    driver_name = 'XtremIO'
+    MIN_XMS_VERSION = [3, 0, 0]
+
+    def __init__(self, *args, **kwargs):
+        super(XtremIOVolumeDriver, self).__init__(*args, **kwargs)
+        self.configuration.append_config_values(XTREMIO_OPTS)
+        self.protocol = None
+        self.backend_name = (self.configuration.safe_get('volume_backend_name')
+                             or self.driver_name)
+        self.cluster_id = (self.configuration.safe_get('xtremio_cluster_name')
+                           or '')
+        self.provisioning_factor = (self.configuration.
+                                    safe_get('max_over_subscription_ratio')
+                                    or DEFAULT_PROVISIONING_FACTOR)
+        self._stats = {}
+        self.client = XtremIOClient3(self.configuration, self.cluster_id)
+
     def _obj_from_result(self, res):
         typ, idx = res['links'][0]['href'].split('/')[-2:]
-        return self.req(typ, idx=int(idx))['content']
+        return self.client.req(typ, idx=int(idx))['content']
 
     def check_for_setup_error(self):
         try:
-            sys = self.req('clusters', idx=1)['content']
+            try:
+                xms = self.client.req('xms', idx=1)['content']
+                version_text = xms['version']
+            except exception.VolumeDriverException:
+                cluster = self.client.req('clusters', idx=1)['content']
+                version_text = cluster['sys-sw-version']
         except exception.NotFound:
             msg = _("XtremIO not initialized correctly, no clusters found")
             raise (exception.VolumeBackendAPIException
                    (data=msg))
-        ver = [int(n) for n in sys['sys-sw-version'].split('-')[0].split('.')]
+        ver = [int(n) for n in version_text.split('-')[0].split('.')]
         if ver < self.MIN_XMS_VERSION:
-            msg = _('Invalid XtremIO version %s,'
-                    ' version 2.4 or up is required') % sys['sys-sw-version']
+            msg = (_('Invalid XtremIO version %(cur)s,'
+                     ' version %(min)s or up is required') %
+                   {'min': self.MIN_XMS_VERSION,
+                    'cur': ver})
             LOG.error(msg)
             raise exception.VolumeBackendAPIException(data=msg)
         else:
-            LOG.info(_LI('XtremIO SW version %s'), sys['sys-sw-version'])
+            LOG.info(_LI('XtremIO SW version %s'), version_text)
+        if ver[0] >= 4:
+            self.client = XtremIOClient4(self.configuration, self.cluster_id)
 
     def create_volume(self, volume):
         "Creates a volume"
@@ -157,26 +301,26 @@ class XtremIOVolumeDriver(san.SanDriver):
                 'vol-size': str(volume['size']) + 'g'
                 }
 
-        self.req('volumes', 'POST', data)
+        self.client.req('volumes', 'POST', data)
 
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a volume from a snapshot."""
         data = {'snap-vol-name': volume['id'],
                 'ancestor-vol-id': snapshot.id}
 
-        self.req('snapshots', 'POST', data)
+        self.client.req('snapshots', 'POST', data)
 
     def create_cloned_volume(self, volume, src_vref):
         """Creates a clone of the specified volume."""
         data = {'snap-vol-name': volume['id'],
                 'ancestor-vol-id': src_vref['id']}
 
-        self.req('snapshots', 'POST', data)
+        self.client.req('snapshots', 'POST', data)
 
     def delete_volume(self, volume):
         """Deletes a volume."""
         try:
-            self.req('volumes', 'DELETE', name=volume['id'])
+            self.client.req('volumes', 'DELETE', name=volume['id'])
         except exception.NotFound:
             LOG.info(_LI("volume %s doesn't exist"), volume['id'])
 
@@ -185,23 +329,34 @@ class XtremIOVolumeDriver(san.SanDriver):
         data = {'snap-vol-name': snapshot.id,
                 'ancestor-vol-id': snapshot.volume_id}
 
-        self.req('snapshots', 'POST', data)
+        self.client.req('snapshots', 'POST', data)
 
     def delete_snapshot(self, snapshot):
         """Deletes a snapshot."""
         try:
-            self.req('volumes', 'DELETE', name=snapshot.id)
+            self.client.req('volumes', 'DELETE', name=snapshot.id)
         except exception.NotFound:
             LOG.info(_LI("snapshot %s doesn't exist"), snapshot.id)
 
     def _update_volume_stats(self):
+        sys = self.client.get_cluster()
+        physical_space = int(sys["ud-ssd-space"]) / units.Mi
+        used_physical_space = int(sys["ud-ssd-space-in-use"]) / units.Mi
+        free_physical = physical_space - used_physical_space
+        actual_prov = int(sys["vol-size"]) / units.Mi
         self._stats = {'volume_backend_name': self.backend_name,
                        'vendor_name': 'EMC',
                        'driver_version': self.VERSION,
                        'storage_protocol': self.protocol,
-                       'total_capacity_gb': 'infinite',
-                       'free_capacity_gb': 'infinite',
-                       'reserved_percentage': 0,
+                       'total_capacity_gb': physical_space,
+                       'free_capacity_gb': (free_physical *
+                                            self.provisioning_factor),
+                       'provisioned_capacity_gb': actual_prov,
+                       'max_over_subscription_ratio': self.provisioning_factor,
+                       'thin_provisioning_support': True,
+                       'thick_provisioning_support': False,
+                       'reserved_percentage':
+                       self.configuration.reserved_percentage,
                        'QoS_support': False}
 
     def get_volume_stats(self, refresh=False):
@@ -212,11 +367,59 @@ class XtremIOVolumeDriver(san.SanDriver):
             self._update_volume_stats()
         return self._stats
 
+    def manage_existing(self, volume, existing_ref):
+        """Manages an existing LV."""
+        lv_name = existing_ref['source-name']
+        # Attempt to locate the volume.
+        try:
+            vol_obj = self.client.req('volumes', name=lv_name)['content']
+        except exception.NotFound:
+            kwargs = {'existing_ref': lv_name,
+                      'reason': 'Specified logical volume does not exist.'}
+            raise exception.ManageExistingInvalidReference(**kwargs)
+
+        # Attempt to rename the LV to match the OpenStack internal name.
+        self.client.req('volumes', 'PUT', data={'vol-name': volume['id']},
+                        idx=vol_obj['index'])
+
+    def manage_existing_get_size(self, volume, existing_ref):
+        """Return size of an existing LV for manage_existing."""
+        # Check that the reference is valid
+        if 'source-name' not in existing_ref:
+            reason = _('Reference must contain source-name element.')
+            raise exception.ManageExistingInvalidReference(
+                existing_ref=existing_ref, reason=reason)
+        lv_name = existing_ref['source-name']
+        # Attempt to locate the volume.
+        try:
+            vol_obj = self.client.req('volumes', name=lv_name)['content']
+        except exception.NotFound:
+            kwargs = {'existing_ref': lv_name,
+                      'reason': 'Specified logical volume does not exist.'}
+            raise exception.ManageExistingInvalidReference(**kwargs)
+        # LV size is returned in gigabytes.  Attempt to parse size as a float
+        # and round up to the next integer.
+        lv_size = int(math.ceil(int(vol_obj['vol-size']) / units.Mi))
+
+        return lv_size
+
+    def unmanage(self, volume):
+        """Removes the specified volume from Cinder management."""
+        # trying to rename the volume to [cinder name]-unmanged
+        try:
+            self.client.req('volumes', 'PUT', name=volume['id'],
+                            data={'vol-name': volume['name'] + '-unmanged'})
+        except exception.NotFound:
+            LOG.info(_LI("Volume with the name %s wasn't found,"
+                         " can't unmanage"),
+                     volume['id'])
+            raise exception.VolumeNotFound(volume_id=volume['id'])
+
     def extend_volume(self, volume, new_size):
         """Extend an existing volume's size."""
-        data = {'vol-size': str(new_size) + 'g'}
+        data = {'vol-size': six.text_type(new_size) + 'g'}
         try:
-            self.req('volumes', 'PUT', data, name=volume['id'])
+            self.client.req('volumes', 'PUT', data, name=volume['id'])
         except exception.NotFound:
             msg = _("can't find the volume to extend")
             raise (exception.VolumeDriverException(message=msg))
@@ -228,66 +431,37 @@ class XtremIOVolumeDriver(san.SanDriver):
     def terminate_connection(self, volume, connector, **kwargs):
         """Disallow connection from connector"""
         try:
-            ig = self.req('initiator-groups',
-                          name=self._get_ig(connector))['content']
-            tg = self.req('target-groups', name='Default')['content']
-            vol = self.req('volumes', name=volume['id'])['content']
-
-            lm_name = '%s_%s_%s' % (str(vol['index']),
-                                    str(ig['index']) if ig else 'any',
-                                    str(tg['index']))
-            LOG.info(_LI('removing lun map %s'), lm_name)
-            self.req('lun-maps', 'DELETE', name=lm_name)
+            ig = self.client.req('initiator-groups',
+                                 name=self._get_ig(connector))['content']
+            tg = self.client.req('target-groups', name='Default')['content']
+            vol = self.client.req('volumes', name=volume['id'])['content']
+
+            lm_name = '%s_%s_%s' % (six.text_type(vol['index']),
+                                    six.text_type(ig['index'])
+                                    if ig else 'any',
+                                    six.text_type(tg['index']))
+            LOG.debug('removing lun map %s', lm_name)
+            self.client.req('lun-maps', 'DELETE', name=lm_name)
         except exception.NotFound:
             LOG.warning(_LW("terminate_connection: lun map not found"))
 
-    def _find_lunmap(self, ig_name, vol_name):
-        try:
-            for lm_link in self.req('lun-maps')['lun-maps']:
-                idx = lm_link['href'].split('/')[-1]
-                lm = self.req('lun-maps', idx=int(idx))['content']
-                if lm['ig-name'] == ig_name and lm['vol-name'] == vol_name:
-                    return lm
-        except exception.NotFound:
-            raise (exception.VolumeDriverException
-                   (_("can't find lunmap, ig:%(ig)s vol:%(vol)s") %
-                    {'ig': ig_name, 'vol': vol_name}))
-
-    def _num_of_mapped_volumes(self, initiator):
-        cnt = 0
-        for lm_link in self.req('lun-maps')['lun-maps']:
-            idx = lm_link['href'].split('/')[-1]
-            lm = self.req('lun-maps', idx=int(idx))['content']
-            if lm['ig-name'] == initiator:
-                cnt += 1
-        return cnt
-
     def _get_password(self):
-        return ''.join(self.random.choice
+        return ''.join(RANDOM.choice
                        (string.ascii_uppercase + string.digits)
                        for _ in range(12))
 
     def create_lun_map(self, volume, ig):
         try:
-            res = self.req('lun-maps', 'POST', {'ig-id': ig['ig-id'][2],
-                                                'vol-id': volume['id']})
+            res = self.client.req('lun-maps', 'POST',
+                                  {'ig-id': ig['ig-id'][2],
+                                   'vol-id': volume['id']})
             lunmap = self._obj_from_result(res)
             LOG.info(_LI('created lunmap\n%s'), lunmap)
-        except urllib2.HTTPError as exc:
-            if exc.code == 400:
-                error = json.load(exc)
-                if 'already_mapped' in error.message:
-                    LOG.info(_LI('volume already mapped,'
-                                 ' trying to retrieve it %(ig)s, %(vol)d'),
-                             {'ig': ig['ig-id'][1], 'vol': volume['id']})
-                    lunmap = self._find_lunmap(ig['ig-id'][1], volume['id'])
-                elif error.message == 'vol_obj_not_found':
-                    LOG.error(_LE("Can't find volume to map %s"), volume['id'])
-                    raise exception.VolumeNotFound(volume_id=volume['id'])
-                else:
-                    raise
-            else:
-                raise
+        except exception.XtremIOAlreadyMappedError:
+            LOG.info(_LI('volume already mapped,'
+                         ' trying to retrieve it %(ig)s, %(vol)d'),
+                     {'ig': ig['ig-id'][1], 'vol': volume['id']})
+            lunmap = self.client.find_lunmap(ig['ig-id'][1], volume['id'])
         return lunmap
 
     def _get_ig(self, connector):
@@ -316,10 +490,8 @@ class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
         self.protocol = 'iSCSI'
 
     def initialize_connection(self, volume, connector):
-        # FIXME(shay-halsband): query the cluster index instead of using
-        # the 1st one
         try:
-            sys = self.req('clusters', idx=1)['content']
+            sys = self.client.get_cluster()
         except exception.NotFound:
             msg = _("XtremIO not initialized correctly, no clusters found")
             raise exception.VolumeBackendAPIException(data=msg)
@@ -330,22 +502,22 @@ class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
         initiator = self._get_initiator(connector)
         try:
             # check if the IG already exists
-            ig = self.req('initiator-groups', 'GET',
-                          name=self._get_ig(connector))['content']
+            ig = self.client.req('initiator-groups', 'GET',
+                                 name=self._get_ig(connector))['content']
         except exception.NotFound:
             # create an initiator group to hold the the initiator
             data = {'ig-name': self._get_ig(connector)}
-            self.req('initiator-groups', 'POST', data)
+            self.client.req('initiator-groups', 'POST', data)
             try:
-                ig = self.req('initiator-groups',
-                              name=self._get_ig(connector))['content']
+                ig = self.client.req('initiator-groups',
+                                     name=self._get_ig(connector))['content']
             except exception.NotFound:
                 raise (exception.VolumeBackendAPIException
                        (data=_("Failed to create IG, %s") %
                         self._get_ig(connector)))
         try:
-            init = self.req('initiators', 'GET',
-                            name=initiator)['content']
+            init = self.client.req('initiators', 'GET',
+                                   name=initiator)['content']
             if use_chap:
                 chap_passwd = init['chap-authentication-initiator-'
                                    'password']
@@ -353,7 +525,7 @@ class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
                 if not chap_passwd:
                     LOG.info(_LI('initiator has no password while using chap,'
                                  'removing it'))
-                    self.req('initiators', 'DELETE', name=initiator)
+                    self.client.req('initiators', 'DELETE', name=initiator)
                     # check if the initiator already exists
                     raise exception.NotFound()
         except exception.NotFound:
@@ -369,7 +541,7 @@ class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
                 data['initiator-discovery-user-name'] = 'chap_user'
                 data['initiator-discovery-'
                      'password'] = self._get_password()
-            self.req('initiators', 'POST', data)
+            self.client.req('initiators', 'POST', data)
         # lun mappping
         lunmap = self.create_lun_map(volume, ig)
 
@@ -400,16 +572,7 @@ class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
         :access_mode:    the volume access mode allow client used
                          ('rw' or 'ro' currently supported)
         """
-        iscsi_portals = [t['name'] for t in self.req('iscsi-portals')
-                         ['iscsi-portals']]
-        # get a random portal
-        portal_name = self.random.choice(iscsi_portals)
-        try:
-            portal = self.req('iscsi-portals',
-                              name=portal_name)['content']
-        except exception.NotFound:
-            raise (exception.VolumeBackendAPIException
-                   (data=_("iscsi portal, %s, not found") % portal_name))
+        portal = self.client.get_iscsi_portal()
         ip = portal['ip-addr'].split('/')[0]
         properties = {'target_discovered': False,
                       'target_iqn': portal['port-address'],
@@ -431,12 +594,14 @@ class XtremIOFibreChannelDriver(XtremIOVolumeDriver,
     def __init__(self, *args, **kwargs):
         super(XtremIOFibreChannelDriver, self).__init__(*args, **kwargs)
         self.protocol = 'FC'
+        self._targets = None
 
     def get_targets(self):
-        if not hasattr(self, '_targets'):
+        if not self._targets:
             try:
-                target_list = self.req('targets')["targets"]
-                targets = [self.req('targets', name=target['name'])['content']
+                target_list = self.client.req('targets')["targets"]
+                targets = [self.client.req('targets',
+                                           name=target['name'])['content']
                            for target in target_list
                            if '-fc' in target['name']]
                 self._targets = [target['port-address'].replace(':', '')
@@ -455,26 +620,27 @@ class XtremIOFibreChannelDriver(XtremIOVolumeDriver,
         # get or create initiator group
         try:
             # check if the IG already exists
-            ig = self.req('initiator-groups', name=ig_name)['content']
+            ig = self.client.req('initiator-groups', name=ig_name)['content']
         except exception.NotFound:
             # create an initiator group to hold the the initiator
             data = {'ig-name': ig_name}
-            self.req('initiator-groups', 'POST', data)
+            self.client.req('initiator-groups', 'POST', data)
             try:
-                ig = self.req('initiator-groups', name=ig_name)['content']
+                ig = self.client.req('initiator-groups',
+                                     name=ig_name)['content']
             except exception.NotFound:
                 raise (exception.VolumeBackendAPIException
                        (data=_("Failed to create IG, %s") % ig_name))
         # get or create all initiators
         for initiator in initiators:
             try:
-                self.req('initiators', name=initiator)['content']
+                self.client.req('initiators', name=initiator)['content']
             except exception.NotFound:
                 # create an initiator
                 data = {'initiator-name': initiator,
                         'ig-id': ig['name'],
                         'port-address': initiator}
-                self.req('initiators', 'POST', data)
+                self.client.req('initiators', 'POST', data)
             i_t_map[initiator] = self.get_targets()
 
         lunmap = self.create_lun_map(volume, ig)
@@ -490,7 +656,7 @@ class XtremIOFibreChannelDriver(XtremIOVolumeDriver,
     def terminate_connection(self, volume, connector, **kwargs):
         (super(XtremIOFibreChannelDriver, self)
          .terminate_connection(volume, connector, **kwargs))
-        num_vols = self._num_of_mapped_volumes(self._get_ig(connector))
+        num_vols = self.client.num_of_mapped_volumes(self._get_ig(connector))
         if num_vols > 0:
             data = {}
         else: