'lun-maps': 'mapping-id'}
xms_data = {'xms': {1: {'version': '4.0.0'}},
- 'clusters': {1: {'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73",
+ 'clusters': {'cluster1':
+ {'name': 'cluster1',
+ '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},
+ 1: {'name': 'cluster1',
+ 'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73",
'ud-ssd-space': '8146708710',
'ud-ssd-space-in-use': '708710',
'vol-size': '29884416',
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()]}
+ if data and data.get('full') == 1:
+ return {object_type: res.values()}
+ 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
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
+ clusters = xms_data['clusters']
+ del xms_data['clusters']
+ self.assertRaises(exception.VolumeDriverException,
+ self.driver.check_for_setup_error)
+ xms_data['clusters'] = clusters
self.driver.check_for_setup_error()
def test_create_extend_delete_volume(self, req):
self.driver.create_volume, self.data.test_volume)
self.driver.delete_volume(self.data.test_volume)
+ def test_no_portals_configured(self, req):
+ req.side_effect = xms_request
+ clean_xms_data()
+ portals = xms_data['iscsi-portals'].copy()
+ xms_data['iscsi-portals'].clear()
+ lunmap = {'lun': 4}
+ self.assertRaises(exception.VolumeDriverException,
+ self.driver._get_iscsi_properties, lunmap)
+ xms_data['iscsi-portals'] = portals
+
def test_initialize_terminate_connection(self, req):
req.side_effect = xms_request
clean_xms_data()
1.0.3 - update logging level, add translation
1.0.4 - support for FC zones
1.0.5 - add support for XtremIO 4.0
+1.0.6 - add support for iSCSI and CA validation
"""
-import base64
import json
import math
import random
+import requests
import string
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import units
import six
-from six.moves import urllib
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
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)
-
- 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)
- query = urllib.parse.urlencode(data, doseq=True)
- url = '%(url)s?%(query)s' % {'query': query, 'url': url}
- request = urllib.request.Request(url)
- else:
- if url_data:
- url = ('%(url)s?%(query)s' %
- {'query': urllib.parse.urlencode(url_data, doseq=True),
- 'url': url})
-
- self.update_data(data, self.cluster_id)
- LOG.debug('data: %s', data)
- request = urllib.request.Request(url, json.dumps(data))
- LOG.debug('%(type)s %(url)s', {'type': request_typ, 'url': url})
+ self.verify = (self.configuration.
+ safe_get('driver_ssl_cert_verify')
+ or False)
- def get_request_type():
- return request_typ
- request.get_method = get_request_type
- request.add_header("Authorization", "Basic %s" % (self.base64_auth, ))
- return request
-
- def _send_request(self, object_type, key, request):
- try:
- response = urllib.request.urlopen(request)
- except (urllib.error.HTTPError, ) as exc:
- if exc.code == 400 and hasattr(exc, 'read'):
- error = json.load(exc)
- 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 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)
- if response.code >= 300:
- LOG.error(_LE('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 get_base_url(self, ver):
+ if ver == 'v1':
+ return 'https://%s/api/json/types' % self.configuration.san_ip
+ elif ver == 'v2':
+ return 'https://%s/api/json/v2/types' % self.configuration.san_ip
def req(self, object_type='volumes', request_typ='GET', data=None,
- name=None, idx=None):
+ name=None, idx=None, ver='v1'):
if not data:
data = {}
if name and idx:
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
- url = '%s/%s' % (self.base_url, object_type)
- url_data = {}
+ url = '%s/%s' % (self.get_base_url(ver), object_type)
+ params = {}
key = None
if name:
- url_data['name'] = name
+ params['name'] = name
key = name
elif idx:
url = '%s/%d' % (url, idx)
key = str(idx)
- 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:
- try:
- return json.loads(str_result)
- except Exception:
- LOG.exception(_LE('querying %(typ)s, %(req)s failed to '
- 'parse result, return value = %(res)s'),
- {'typ': object_type,
- 'req': request_typ,
- 'res': str_result})
+ if request_typ in ('GET', 'DELETE'):
+ params.update(data)
+ self.update_url(params, self.cluster_id)
+ if request_typ != 'GET':
+ self.update_data(data, self.cluster_id)
+ LOG.debug('data: %s', data)
+ LOG.debug('%(type)s %(url)s', {'type': request_typ, 'url': url})
+ try:
+ response = requests.request(request_typ, url, params=params,
+ data=json.dumps(data),
+ verify=self.verify,
+ auth=(self.configuration.san_login,
+ self.configuration.san_password))
+ except requests.exceptions.RequestException as exc:
+ msg = (_('Exception: %s') % six.text_type(exc))
+ raise exception.VolumeDriverException(message=msg)
+
+ if 200 <= response.status_code < 300:
+ if request_typ in ('GET', 'POST'):
+ return response.json()
+ else:
+ return ''
+
+ self.handle_errors(response, key, object_type)
+
+ def handle_errors(self, response, key, object_type):
+ if response.status_code == 400:
+ error = response.json()
+ err_msg = error.get('message')
+ if err_msg.endswith(OBJ_NOT_FOUND_ERR):
+ LOG.warning(_LW("object %(key)s of "
+ "type %(typ)s not found, %(err_msg)s"),
+ {'key': key, 'typ': object_type,
+ 'err_msg': err_msg, })
+ raise exception.NotFound()
+ elif err_msg == VOL_NOT_UNIQUE_ERR:
+ LOG.error(_LE("can't create 2 volumes with the same name, %s"),
+ err_msg)
+ 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 %(key)s, %(msg)s"),
+ {'key': key, 'msg': err_msg, })
+ raise exception.VolumeNotFound(volume_id=key)
+ elif ALREADY_MAPPED_ERR in err_msg:
+ raise exception.XtremIOAlreadyMappedError()
+ msg = _('Bad response from XMS, %s') % response.text
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(message=msg)
def update_url(self, data, cluster_id):
return
class XtremIOClient3(XtremIOClient):
+ def __init__(self, configuration, cluster_id):
+ super(XtremIOClient3, self).__init__(configuration, cluster_id)
+ self._portals = []
+
def find_lunmap(self, ig_name, vol_name):
try:
for lm_link in self.req('lun-maps')['lun-maps']:
cnt += 1
return cnt
- def get_iscsi_portal(self):
+ def get_iscsi_portals(self):
+ if self._portals:
+ return self._portals
+
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))
+ for portal_name in iscsi_portals:
+ try:
+ self._portals.append(self.req('iscsi-portals',
+ name=portal_name)['content'])
+ except exception.NotFound:
+ raise (exception.VolumeBackendAPIException
+ (data=_("iscsi portal, %s, not found") % portal_name))
- return portal
+ return self._portals
class XtremIOClient4(XtremIOClient):
+ def __init__(self, configuration, cluster_id):
+ super(XtremIOClient4, self).__init__(configuration, cluster_id)
+ self._cluster_name = None
+
def find_lunmap(self, ig_name, vol_name):
try:
return (self.req('lun-maps',
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_iscsi_portals(self):
+ return self.req('iscsi-portals',
+ data={'full': 1})['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']
+ if not self.cluster_id:
+ self.cluster_id = self.req('clusters')['clusters'][0]['name']
+
+ return self.req('clusters', name=self.cluster_id)['content']
class XtremIOVolumeDriver(san.SanDriver):
"""Executes commands relating to Volumes."""
- VERSION = '1.0.5'
+ VERSION = '1.0.6'
driver_name = 'XtremIO'
MIN_XMS_VERSION = [3, 0, 0]
def check_for_setup_error(self):
try:
- 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']
+ name = self.client.req('clusters')['clusters'][0]['name']
+ cluster = self.client.req('clusters', name=name)['content']
+ version_text = cluster['sys-sw-version']
except exception.NotFound:
msg = _("XtremIO not initialized correctly, no clusters found")
raise (exception.VolumeBackendAPIException
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(ig['index']),
six.text_type(tg['index']))
LOG.debug('removing lun map %s', lm_name)
self.client.req('lun-maps', 'DELETE', name=lm_name)
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
+ # create an initiator group to hold the initiator
data = {'ig-name': self._get_ig(connector)}
self.client.req('initiator-groups', 'POST', data)
try:
meaning use CHAP with the specified credentials.
:access_mode: the volume access mode allow client used
('rw' or 'ro' currently supported)
+ multiple connection return
+ :target_iqns, :target_portals, :target_luns, which contain lists of
+ multiple values. The main portal information is also returned in
+ :target_iqn, :target_portal, :target_lun for backward compatibility.
"""
- portal = self.client.get_iscsi_portal()
- ip = portal['ip-addr'].split('/')[0]
+ portals = self.client.get_iscsi_portals()
+ if not portals:
+ msg = _("XtremIO not configured correctly, no iscsi portals found")
+ LOG.error(msg)
+ raise exception.VolumeDriverException(message=msg)
+ portal = RANDOM.choice(portals)
+ portal_addr = ('%(ip)s:%(port)d' %
+ {'ip': portal['ip-addr'].split('/')[0],
+ 'port': portal['ip-port']})
+
+ tg_portals = ['%(ip)s:%(port)d' % {'ip': p['ip-addr'].split('/')[0],
+ 'port': p['ip-port']}
+ for p in portals]
properties = {'target_discovered': False,
'target_iqn': portal['port-address'],
'target_lun': lunmap['lun'],
- 'target_portal': '%s:%d' % (ip, portal['ip-port']),
- 'access_mode': 'rw'}
+ 'target_portal': portal_addr,
+ 'access_mode': 'rw',
+ 'target_iqns': [p['port-address'] for p in portals],
+ 'target_portals': tg_portals,
+ 'target_luns': [lunmap['lun']] * len(portals)}
return properties
def _get_initiator(self, connector):