From 024f60ca95f97f605743ad790bc44da0a9a32b61 Mon Sep 17 00:00:00 2001 From: Shay Halsband Date: Wed, 14 Jan 2015 09:00:19 +0200 Subject: [PATCH] XtreamIO version 4.0 support * 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 | 5 + cinder/tests/test_emc_xtremio.py | 60 +++- cinder/volume/drivers/emc/xtremio.py | 422 +++++++++++++++++++-------- 3 files changed, 354 insertions(+), 133 deletions(-) diff --git a/cinder/exception.py b/cinder/exception.py index d886663fb..c5b3fd81b 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -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") diff --git a/cinder/tests/test_emc_xtremio.py b/cinder/tests/test_emc_xtremio.py index ad6b608d4..3ffc30662 100644 --- a/cinder/tests/test_emc_xtremio.py +++ b/cinder/tests/test_emc_xtremio.py @@ -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) diff --git a/cinder/volume/drivers/emc/xtremio.py b/cinder/volume/drivers/emc/xtremio.py index d6bdf5d76..cedb856f4 100644 --- a/cinder/volume/drivers/emc/xtremio.py +++ b/cinder/volume/drivers/emc/xtremio.py @@ -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: -- 2.45.2