]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
XtremIO cinder iSCSI & FC volume drivers for Juno
authorShay Halsband <shay.halsband@emc.com>
Sun, 6 Jul 2014 13:01:06 +0000 (16:01 +0300)
committerShay Halsband <shay.halsband@emc.com>
Wed, 27 Aug 2014 12:53:37 +0000 (15:53 +0300)
XtremIO Direct Driver has been contributed to Juno release.
support all minimum required features by Juno for both iSCSI and FibreChannel
Certificate Test Results
    https://bugs.launchpad.net/cinder/+bug/1336844

Implements: blueprint xtremio-cinder-volume-driver

Change-Id: I93ade3064532ec60662e92c56e5fefd4035a0d5b

cinder/tests/test_emc_xtremio.py [new file with mode: 0644]
cinder/volume/drivers/emc/xtremio.py [new file with mode: 0644]

diff --git a/cinder/tests/test_emc_xtremio.py b/cinder/tests/test_emc_xtremio.py
new file mode 100644 (file)
index 0000000..0e34923
--- /dev/null
@@ -0,0 +1,307 @@
+# Copyright (c) 2012 - 2014 EMC Corporation, 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.
+
+
+import mock
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume.drivers.emc.xtremio import XtremIOFibreChannelDriver
+from cinder.volume.drivers.emc.xtremio import XtremIOISCSIDriver
+
+
+LOG = logging.getLogger(__name__)
+
+typ2id = {'volumes': 'vol-id',
+          'snapshots': 'vol-id',
+          'initiators': 'initiator-id',
+          'initiator-groups': 'ig-id',
+          'lun-maps': 'mapping-id'}
+
+xms_data = {'clusters': {1: {'sys-sw-version': "2.4.0-devel_ba23ee5381eeab73",
+                             'chap-authentication-mode': 'disabled',
+                             'chap-discovery-mode': 'disabled',
+                             "index": 1}},
+            'target-groups': {'Default': {"index": 1, }},
+            'iscsi-portals': {'10.205.68.5/16':
+                              {"port-address":
+                               "iqn.2008-05.com.xtremio:001e67939c34",
+                               "ip-port": 3260,
+                               "ip-addr": "10.205.68.5/16",
+                               "name": "10.205.68.5/16",
+                               "index": 1}},
+            'targets': {'X1-SC2-fc1': {'index': 1, "name": "X1-SC2-fc1",
+                                       "port-address":
+                                       "21:00:00:24:ff:57:b2:36",
+                                       'port-state': 'up'},
+                        'X1-SC2-fc2': {'index': 2, "name": "X1-SC2-fc2",
+                                       "port-address":
+                                       "21:00:00:24:ff:57:b2:55",
+                                       'port-state': 'up'}
+                        },
+            'volumes': {},
+            'initiator-groups': {},
+            'initiators': {},
+            'lun-maps': {},
+            }
+
+
+def clean_xms_data():
+    xms_data['volumes'] = {}
+    xms_data['initiator-groups'] = {}
+    xms_data['initiators'] = {}
+    xms_data['lun-maps'] = {}
+
+
+def fix_data(data, object_type):
+    d = {}
+    for key, value in data.items():
+        if 'name' in key:
+            key = 'name'
+        d[key] = value
+
+    if object_type == 'lun-maps':
+        d['lun'] = 1
+
+    d[typ2id[object_type]] = ["a91e8c81c2d14ae4865187ce4f866f8a",
+                              d.get('name'),
+                              len(xms_data[object_type]) + 1]
+    d['index'] = len(xms_data[object_type]) + 1
+    return d
+
+
+def get_xms_obj_key(data):
+    for key in data.keys():
+        if 'name' in key:
+            return key
+
+
+def xms_request(object_type='volumes', request_typ='GET', data=None,
+                name=None, idx=None):
+    if object_type == 'snapshots':
+        object_type = 'volumes'
+
+    obj_key = name if name else idx
+    if request_typ == 'GET':
+        res = xms_data[object_type]
+        if name or idx:
+            if obj_key not in res:
+                raise exception.NotFound()
+            return {"content": res[obj_key]}
+        else:
+            return {object_type: [{"href": "/%s/%d" % (object_type,
+                                                       obj['index']),
+                                   "name": obj.get('name')}
+                                  for obj in res.values()]}
+    elif request_typ == 'POST':
+        data = fix_data(data, object_type)
+        data['index'] = len(xms_data[object_type]) + 1
+        xms_data[object_type][data['index']] = data
+        # find the name key
+        name_key = get_xms_obj_key(data)
+        if object_type == 'lun-maps':
+            data['ig-name'] = data['ig-id']
+        if name_key:
+            if data[name_key] in xms_data[object_type]:
+                raise (exception
+                       .VolumeBackendAPIException
+                       ('Volume by this name already exists'))
+            xms_data[object_type][data[name_key]] = data
+
+        return {"links": [{"href": "/%s/%d" %
+                          (object_type, data[typ2id[object_type]][2])}]}
+    elif request_typ == 'DELETE':
+        if obj_key in xms_data[object_type]:
+            data = xms_data[object_type][obj_key]
+            del xms_data[object_type][data['index']]
+            del xms_data[object_type][data[typ2id[object_type]][1]]
+        else:
+            LOG.error('trying to delete a missing object %s', str(obj_key))
+            raise exception.NotFound()
+    elif request_typ == 'PUT':
+        if obj_key in xms_data[object_type]:
+            obj = xms_data[object_type][obj_key]
+            obj.update(data)
+        else:
+            LOG.error('trying to update a missing object %s', str(obj_key))
+            raise exception.NotFound()
+
+
+def xms_bad_request(object_type='volumes', request_typ='GET', data=None,
+                    name=None, idx=None):
+    if request_typ == 'GET':
+        raise exception.NotFound()
+    elif request_typ == 'POST':
+        raise exception.VolumeBackendAPIException('failed to create ig')
+
+
+class D(dict):
+    def update(self, *args, **kwargs):
+        self.__dict__.update(*args, **kwargs)
+        return dict.update(self, *args, **kwargs)
+
+
+class CommonData():
+    connector = {'ip': '10.0.0.2',
+                 'initiator': 'iqn.1993-08.org.debian:01:222',
+                 'wwpns': ["123456789012345", "123456789054321"],
+                 'wwnns': ["223456789012345", "223456789054321"],
+                 'host': 'fakehost'}
+
+    test_volume = {'name': 'vol1',
+                   'size': 1,
+                   'volume_name': 'vol1',
+                   'id': '192eb39b-6c2f-420c-bae3-3cfd117f0001',
+                   'provider_auth': None,
+                   'project_id': 'project',
+                   'display_name': 'vol1',
+                   'display_description': 'test volume',
+                   'volume_type_id': None}
+    test_snapshot = D()
+    test_snapshot.update({'name': 'snapshot1',
+                          'size': 1,
+                          'id': '192eb39b-6c2f-420c-bae3-3cfd117f0002',
+                          'volume_name': 'vol-vol1',
+                          'volume_id': '192eb39b-6c2f-420c-bae3-3cfd117f0001',
+                          'project_id': 'project'})
+    test_snapshot.__dict__.update(test_snapshot)
+    test_volume2 = {'name': 'vol2',
+                    'size': 1,
+                    'volume_name': 'vol2',
+                    'id': '192eb39b-6c2f-420c-bae3-3cfd117f0004',
+                    'provider_auth': None,
+                    'project_id': 'project',
+                    'display_name': 'vol2',
+                    'display_description': 'test volume 2',
+                    'volume_type_id': None}
+    test_clone = {'name': 'clone1',
+                  'size': 1,
+                  'volume_name': 'vol3',
+                  'id': '192eb39b-6c2f-420c-bae3-3cfd117f0003',
+                  'provider_auth': None,
+                  'project_id': 'project',
+                  'display_name': 'clone1',
+                  'display_description': 'volume created from snapshot',
+                  'volume_type_id': None}
+
+
+@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOISCSIDriver.req')
+class EMCXIODriverISCSITestCase(test.TestCase):
+    def setUp(self):
+        super(EMCXIODriverISCSITestCase, self).setUp()
+
+        configuration = mock.Mock()
+        configuration.san_login = ''
+        configuration.san_password = ''
+        configuration.san_ip = ''
+        self.driver = XtremIOISCSIDriver(configuration=configuration)
+
+        self.data = CommonData()
+
+    def test_create_extend_delete_volume(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        self.driver.extend_volume(self.data.test_volume, 5)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_create_delete_snapshot(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        self.driver.create_snapshot(self.data.test_snapshot)
+        self.driver.delete_snapshot(self.data.test_snapshot)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_volume_from_snapshot(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        xms_data['volumes'] = {}
+        self.driver.create_volume(self.data.test_volume)
+        self.driver.create_snapshot(self.data.test_snapshot)
+        self.driver.create_volume_from_snapshot(self.data.test_volume2,
+                                                self.data.test_snapshot)
+        self.driver.delete_volume(self.data.test_volume2)
+        self.driver.delete_volume(self.data.test_snapshot)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_clone_volume(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        self.driver.create_cloned_volume(self.data.test_clone,
+                                         self.data.test_volume)
+        self.driver.delete_volume(self.data.test_clone)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_duplicate_volume(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume, self.data.test_volume)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_initialize_terminate_connection(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        map_data = self.driver.initialize_connection(self.data.test_volume,
+                                                     self.data.connector)
+        self.assertEqual(map_data['data']['target_lun'], 1)
+        self.driver.terminate_connection(self.data.test_volume,
+                                         self.data.connector)
+
+    def test_initialize_connection_bad_ig(self, req):
+        req.side_effect = xms_bad_request
+        clean_xms_data()
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.initialize_connection,
+                          self.data.test_volume,
+                          self.data.connector)
+        self.driver.delete_volume(self.data.test_volume)
+
+    def test_get_stats(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        stats = self.driver.get_volume_stats(True)
+        self.assertEqual(stats['volume_backend_name'],
+                         self.driver.backend_name)
+
+
+@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOFibreChannelDriver.req')
+class EMCXIODriverFibreChannelTestCase(test.TestCase):
+    def setUp(self):
+        super(EMCXIODriverFibreChannelTestCase, self).setUp()
+
+        configuration = mock.Mock()
+        configuration.san_login = ''
+        configuration.san_password = ''
+        configuration.san_ip = ''
+        self.driver = XtremIOFibreChannelDriver(configuration=configuration)
+
+        self.data = CommonData()
+
+    def test_initialize_terminate_connection(self, req):
+        req.side_effect = xms_request
+        clean_xms_data()
+        self.driver.create_volume(self.data.test_volume)
+        map_data = self.driver.initialize_connection(self.data.test_volume,
+                                                     self.data.connector)
+        self.assertEqual(map_data['data']['target_lun'], 1)
+        self.driver.terminate_connection(self.data.test_volume,
+                                         self.data.connector)
+        self.driver.delete_volume(self.data.test_volume)
diff --git a/cinder/volume/drivers/emc/xtremio.py b/cinder/volume/drivers/emc/xtremio.py
new file mode 100644 (file)
index 0000000..438472a
--- /dev/null
@@ -0,0 +1,509 @@
+# Copyright (c) 2012 - 2014 EMC Corporation.
+# 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.
+"""
+Driver for EMC XtremIO Storage.
+supported XtremIO version 2.4 and up
+
+1.0.0 - initial release
+1.0.1 - enable volume extend
+1.0.2 - added FC support, improved error handling
+1.0.3 - update logging level, add translation
+1.0.4 - support for FC zones
+"""
+
+import base64
+import json
+import random
+import string
+import urllib
+import urllib2
+
+import six
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.openstack.common import log as logging
+from cinder.volume import driver
+from cinder.volume.drivers.san import san
+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))
+                            .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)
+            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
+        request.add_header("Authorization", "Basic %s" % (self.base64_auth, ))
+        return request
+
+    def _send_request(self, object_type, key, request):
+        try:
+            response = urllib2.urlopen(request)
+        except (urllib2.HTTPError, ) as exc:
+            if exc.code == 400 and hasattr(exc, 'read'):
+                error = json.load(exc)
+                if error['message'].endswith('obj_not_found'):
+                    LOG.warning(_("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':
+                    LOG.error(_("can't create 2 volumes with the same name"))
+                    msg = (_('Volume by this name already exists'))
+                    raise exception.VolumeBackendAPIException(data=msg)
+            LOG.error(_('Bad response from XMS, %s'), exc.read())
+            msg = (_('Exception: %s') % six.text_type(exc))
+            raise exception.VolumeDriverException(message=msg)
+        if response.code >= 300:
+            LOG.error(_('bad API response, %s'), response.msg)
+            msg = (_('bad response from XMS got http code %(code)d, %(msg)s') %
+                   {'code': response.code, 'msg': response.msg})
+            raise exception.VolumeBackendAPIException(data=msg)
+        return response
+
+    def req(self, object_type='volumes', request_typ='GET', data=None,
+            name=None, idx=None):
+        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)
+        key = None
+        if name:
+            url = '%s?%s' % (url, urllib.urlencode({'name': name}))
+            key = name
+        elif idx:
+            url = '%s/%d' % (url, idx)
+            key = str(idx)
+        request = self._create_request(request_typ, data, url, urllib)
+        response = self._send_request(object_type, key, request)
+        str_result = response.read()
+        if str_result:
+            try:
+                return json.loads(str_result)
+            except Exception:
+                LOG.exception(_('querying %(typ)s, %(req)s failed to '
+                                'parse result, return value = %(res)s'),
+                              {'typ': object_type,
+                               'req': request_typ,
+                               'res': str_result})
+
+    def _obj_from_result(self, res):
+        typ, idx = res['links'][0]['href'].split('/')[-2:]
+        return self.req(typ, idx=int(idx))['content']
+
+    def check_for_setup_error(self):
+        try:
+            sys = self.req('clusters', idx=1)['content']
+        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('.')]
+        if ver < self.MIN_XMS_VERSION:
+            msg = _('Invalid XtremIO version %s,'
+                    ' version 2.4 or up is required') % sys['sys-sw-version']
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+        else:
+            LOG.info(_('XtremIO SW version %s'), sys['sys-sw-version'])
+
+    def create_volume(self, volume):
+        "Creates a volume"
+        data = {'vol-name': volume['id'],
+                'vol-size': str(volume['size']) + 'g'
+                }
+
+        self.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)
+
+    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)
+
+    def delete_volume(self, volume):
+        """Deletes a volume."""
+        try:
+            self.req('volumes', 'DELETE', name=volume['id'])
+        except exception.NotFound:
+            LOG.info(_("volume %s doesn't exist"), volume['id'])
+
+    def create_snapshot(self, snapshot):
+        """Creates a snapshot."""
+        data = {'snap-vol-name': snapshot.id,
+                'ancestor-vol-id': snapshot.volume_id}
+
+        self.req('snapshots', 'POST', data)
+
+    def delete_snapshot(self, snapshot):
+        """Deletes a snapshot."""
+        try:
+            self.req('volumes', 'DELETE', name=snapshot.id)
+        except exception.NotFound:
+            LOG.info(_("snapshot %s doesn't exist"), snapshot.id)
+
+    def _update_volume_stats(self):
+        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,
+                       'QoS_support': False}
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume stats.
+        If 'refresh' is True, run update the stats first.
+        """
+        if refresh:
+            self._update_volume_stats()
+        return self._stats
+
+    def extend_volume(self, volume, new_size):
+        """Extend an existing volume's size."""
+        data = {'vol-size': str(new_size) + 'g'}
+        try:
+            self.req('volumes', 'PUT', data, name=volume['id'])
+        except exception.NotFound:
+            msg = _("can't find the volume to extend")
+            raise (exception.VolumeDriverException(message=msg))
+
+    def check_for_export(self, context, volume_id):
+        """Make sure volume is exported."""
+        pass
+
+    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(_('removing lun map %s'), lm_name)
+            self.req('lun-maps', 'DELETE', name=lm_name)
+        except exception.NotFound:
+            LOG.warning(_("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
+                       (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']})
+            lunmap = self._obj_from_result(res)
+            LOG.info(_('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(_('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(_("Can't find volume to map %s"), volume['id'])
+                    raise exception.VolumeNotFound(volume_id=volume['id'])
+                else:
+                    raise
+            else:
+                raise
+        return lunmap
+
+    def _get_ig(self, connector):
+        raise NotImplementedError()
+
+
+class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
+    """Executes commands relating to ISCSI volumes.
+
+    We make use of model provider properties as follows:
+
+    ``provider_location``
+      if present, contains the iSCSI target information in the same
+      format as an ietadm discovery
+      i.e. '<ip>:<port>,<portal> <target IQN>'
+
+    ``provider_auth``
+      if present, contains a space-separated triple:
+      '<auth method> <auth username> <auth password>'.
+      `CHAP` is the only auth_method in use at the moment.
+    """
+    driver_name = 'XtremIO_ISCSI'
+
+    def __init__(self, *args, **kwargs):
+        super(XtremIOISCSIDriver, self).__init__(*args, **kwargs)
+        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']
+        except exception.NotFound:
+            msg = _("XtremIO not initialized correctly, no clusters found")
+            raise exception.VolumeBackendAPIException(data=msg)
+        use_chap = (sys.get('chap-authentication-mode', 'disabled') !=
+                    'disabled')
+        discovery_chap = (sys.get('chap-discovery-mode', 'disabled') !=
+                          'disabled')
+        initiator = self._get_initiator(connector)
+        try:
+            # check if the IG already exists
+            ig = self.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)
+            try:
+                ig = self.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']
+            if use_chap:
+                chap_passwd = init['chap-authentication-initiator-'
+                                   'password']
+                # delete the initiator to create a new one with password
+                if not chap_passwd:
+                    LOG.info(_('initiator has no password while using chap,'
+                             'removing it'))
+                    self.req('initiators', 'DELETE', name=initiator)
+                    # check if the initiator already exists
+                    raise exception.NotFound()
+        except exception.NotFound:
+            # create an initiator
+            data = {'initiator-name': initiator,
+                    'ig-id': initiator,
+                    'port-address': initiator}
+            if use_chap:
+                data['initiator-authentication-user-name'] = 'chap_user'
+                chap_passwd = self._get_password()
+                data['initiator-authentication-password'] = chap_passwd
+            if discovery_chap:
+                data['initiator-discovery-user-name'] = 'chap_user'
+                data['initiator-discovery-'
+                     'password'] = self._get_password()
+            self.req('initiators', 'POST', data)
+        # lun mappping
+        lunmap = self.create_lun_map(volume, ig)
+
+        properties = self._get_iscsi_properties(lunmap)
+
+        if use_chap:
+            properties['auth_method'] = 'CHAP'
+            properties['auth_username'] = 'chap_user'
+            properties['auth_password'] = chap_passwd
+
+        LOG.debug('init conn params:\n%s', properties)
+        return {
+            'driver_volume_type': 'iscsi',
+            'data': properties
+        }
+
+    def _get_iscsi_properties(self, lunmap):
+        """Gets iscsi configuration
+        :target_discovered:    boolean indicating whether discovery was used
+        :target_iqn:    the IQN of the iSCSI target
+        :target_portal:    the portal of the iSCSI target
+        :target_lun:    the lun of the iSCSI target
+        :volume_id:    the id of the volume (currently used by xen)
+        :auth_method:, :auth_username:, :auth_password:
+            the authentication details. Right now, either auth_method is not
+            present meaning no authentication, or auth_method == `CHAP`
+            meaning use CHAP with the specified credentials.
+        :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))
+        ip = portal['ip-addr'].split('/')[0]
+        properties = {'target_discovered': False,
+                      'target_iqn': portal['port-address'],
+                      'target_lun': lunmap['lun'],
+                      'target_portal': '%s:%d' % (ip, portal['ip-port']),
+                      'access_mode': 'rw'}
+        return properties
+
+    def _get_initiator(self, connector):
+        return connector['initiator']
+
+    def _get_ig(self, connector):
+        return connector['initiator']
+
+
+class XtremIOFibreChannelDriver(XtremIOVolumeDriver,
+                                driver.FibreChannelDriver):
+
+    def __init__(self, *args, **kwargs):
+        super(XtremIOFibreChannelDriver, self).__init__(*args, **kwargs)
+        self.protocol = 'FC'
+
+    def get_targets(self):
+        if not hasattr(self, '_targets'):
+            try:
+                target_list = self.req('targets')["targets"]
+                targets = [self.req('targets', name=target['name'])['content']
+                           for target in target_list
+                           if '-fc' in target['name']]
+                self._targets = [target['port-address'].replace(':', '')
+                                 for target in targets
+                                 if target['port-state'] == 'up']
+            except exception.NotFound:
+                raise (exception.VolumeBackendAPIException
+                       (data=_("Failed to get targets")))
+        return self._targets
+
+    @fczm_utils.AddFCZone
+    def initialize_connection(self, volume, connector):
+        initiators = self._get_initiator(connector)
+        ig_name = self._get_ig(connector)
+        i_t_map = {}
+        # get or create initiator group
+        try:
+            # check if the IG already exists
+            ig = self.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)
+            try:
+                ig = self.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']
+            except exception.NotFound:
+                # create an initiator
+                data = {'initiator-name': initiator,
+                        'ig-id': ig['name'],
+                        'port-address': initiator}
+                self.req('initiators', 'POST', data)
+            i_t_map[initiator] = self.get_targets()
+
+        lunmap = self.create_lun_map(volume, ig)
+        return {'driver_volume_type': 'fibre_channel',
+                'data': {
+                    'target_discovered': True,
+                    'target_lun': lunmap['lun'],
+                    'target_wwn': self.get_targets(),
+                    'access_mode': 'rw',
+                    'initiator_target_map': i_t_map}}
+
+    @fczm_utils.RemoveFCZone
+    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))
+        if num_vols > 0:
+            data = {}
+        else:
+            i_t_map = {}
+            for initiator in self._get_initiator(connector):
+                i_t_map[initiator] = self.get_targets()
+            data = {'target_wwn': self.get_targets(),
+                    'initiator_target_map': i_t_map}
+
+        return {'driver_volume_type': 'fibre_channel',
+                'data': data}
+
+    def _get_initiator(self, connector):
+        return connector['wwpns']
+
+    def _get_ig(self, connector):
+        return connector['host']