1.0.4 - support for FC zones
1.0.5 - add support for XtremIO 4.0
1.0.6 - add support for iSCSI multipath, CA validation, consistency groups,
- R/O snapshots
+ R/O snapshots, CHAP discovery authentication
"""
import json
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import objects
+from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.san import san
from cinder.zonemanager import utils as fczm_utils
XTREMIO_OPTS = [
cfg.StrOpt('xtremio_cluster_name',
default='',
- help='XMS cluster id in multi-cluster environment')]
+ help='XMS cluster id in multi-cluster environment'),
+ cfg.IntOpt('xtremio_array_busy_retry_count',
+ default=5,
+ help='Number of retries in case array is busy'),
+ cfg.IntOpt('xtremio_array_busy_retry_interval',
+ default=5,
+ help='Interval between retries in case array is busy')]
CONF.register_opts(XTREMIO_OPTS)
VOL_NOT_UNIQUE_ERR = 'vol_obj_name_not_unique'
VOL_OBJ_NOT_FOUND_ERR = 'vol_obj_not_found'
ALREADY_MAPPED_ERR = 'already_mapped'
+SYSTEM_BUSY = 'system_is_busy'
class XtremIOClient(object):
elif ver == 'v2':
return 'https://%s/api/json/v2/types' % self.configuration.san_ip
+ @utils.retry(exception.XtremIOArrayBusy,
+ CONF.xtremio_array_busy_retry_count,
+ CONF.xtremio_array_busy_retry_interval, 1)
def req(self, object_type='volumes', request_typ='GET', data=None,
name=None, idx=None, ver='v1'):
if not data:
raise exception.VolumeNotFound(volume_id=key)
elif ALREADY_MAPPED_ERR in err_msg:
raise exception.XtremIOAlreadyMappedError()
+ elif err_msg == SYSTEM_BUSY:
+ raise exception.XtremIOArrayBusy()
msg = _('Bad response from XMS, %s') % response.text
LOG.error(msg)
raise exception.VolumeBackendAPIException(message=msg)
def get_extra_capabilities(self):
return {}
+ def get_initiator(self, port_address):
+ raise NotImplementedError()
+
class XtremIOClient3(XtremIOClient):
def __init__(self, configuration, cluster_id):
return lm
except exception.NotFound:
raise (exception.VolumeDriverException
- (_("can't find lunmap, ig:%(ig)s vol:%(vol)s") %
+ (_("can't find lun-map, ig:%(ig)s vol:%(vol)s") %
{'ig': ig_name, 'vol': vol_name}))
def num_of_mapped_volumes(self, initiator):
self.req('snapshots', 'POST', data)
+ def get_initiator(self, port_address):
+ try:
+ return self.req('initiators', 'GET', name=port_address)['content']
+ except exception.NotFound:
+ pass
+
class XtremIOClient4(XtremIOClient):
def __init__(self, configuration, cluster_id):
add_data = {'vol-id': vol_id, 'cg-id': cg_id}
self.req('consistency-group-volumes', 'POST', add_data, ver='v2')
+ def get_initiator(self, port_address):
+ inits = self.req('initiators',
+ data={'filter': 'port-address:eq:' + port_address,
+ 'full': 1})['initiators']
+ if len(inits) == 1:
+ return inits[0]
+ else:
+ pass
+
class XtremIOVolumeDriver(san.SanDriver):
"""Executes commands relating to Volumes."""
}
self.client.req('volumes', 'POST', data)
- if volume.get('consistencygroup_id'):
+ if volume.get('consistencygroup_id') and self.client is XtremIOClient4:
self.client.add_vol_to_cg(volume['id'],
volume['consistencygroup_id'])
"""Creates a volume from a snapshot."""
self.client.create_snapshot(snapshot.id, volume['id'])
- if snapshot.get('consistencygroup_id'):
+ if (snapshot.get('consistencygroup_id') and
+ self.client is XtremIOClient4):
self.client.add_vol_to_cg(volume['id'],
snapshot['consistencygroup_id'])
"""Creates a clone of the specified volume."""
self.client.create_snapshot(src_vref['id'], volume['id'])
- if volume.get('consistencygroup_id'):
+ if volume.get('consistencygroup_id') and self.client is XtremIOClient4:
self.client.add_vol_to_cg(volume['id'],
volume['consistencygroup_id'])
"""Disallow connection from connector"""
try:
ig = self.client.req('initiator-groups',
- name=self._get_ig(connector))['content']
+ name=self._get_ig_name(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']),
six.text_type(tg['index']))
- LOG.debug('removing lun map %s', lm_name)
+ 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"))
(string.ascii_uppercase + string.digits)
for _ in range(12))
- def create_lun_map(self, volume, ig):
+ def create_lun_map(self, volume, ig, lun_num=None):
try:
- res = self.client.req('lun-maps', 'POST',
- {'ig-id': ig['ig-id'][2],
- 'vol-id': volume['id']})
+ data = {'ig-id': ig, 'vol-id': volume['id']}
+ if lun_num:
+ data['lun'] = lun_num
+ res = self.client.req('lun-maps', 'POST', data)
+
lunmap = self._obj_from_result(res)
- LOG.info(_LI('created lunmap\n%s'), lunmap)
+ LOG.info(_LI('Created lun-map:\n%s'), lunmap)
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'])
+ LOG.info(_LI('Volume already mapped, retrieving %(ig)s, %(vol)d'),
+ {'ig': ig, 'vol': volume['id']})
+ lunmap = self.client.find_lunmap(ig, volume['id'])
return lunmap
- def _get_ig(self, connector):
+ def _get_ig_name(self, connector):
raise NotImplementedError()
def create_consistencygroup(self, context, group):
return model_update, snapshots
+ def _get_ig(self, name):
+ try:
+ return self.client.req('initiator-groups', 'GET',
+ name=name)['content']
+ except exception.NotFound:
+ pass
+
+ def _create_ig(self, name):
+ # create an initiator group to hold the initiator
+ data = {'ig-name': name}
+ self.client.req('initiator-groups', 'POST', data)
+ try:
+ return self.client.req('initiator-groups', name=name)['content']
+ except exception.NotFound:
+ raise (exception.VolumeBackendAPIException
+ (data=_("Failed to create IG, %s") % name))
+
class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
"""Executes commands relating to ISCSI volumes.
super(XtremIOISCSIDriver, self).__init__(*args, **kwargs)
self.protocol = 'iSCSI'
+ def _add_auth(self, data, login_chap, discovery_chap):
+ login_passwd, discovery_passwd = None, None
+ if login_chap:
+ data['initiator-authentication-user-name'] = 'chap_user'
+ login_passwd = self._get_password()
+ data['initiator-authentication-password'] = login_passwd
+ if discovery_chap:
+ data['chap-discovery-initiator-user-name'] = 'chap_user'
+ discovery_passwd = self._get_password()
+ data['chap-discovery-initiator-password'] = discovery_passwd
+ return login_passwd, discovery_passwd
+
+ def _create_initiator(self, connector, login_chap, discovery_chap):
+ initiator = self._get_initiator_name(connector)
+ # create an initiator
+ data = {'initiator-name': initiator,
+ 'ig-id': initiator,
+ 'port-address': initiator}
+ l, d = self._add_auth(data, login_chap, discovery_chap)
+ self.client.req('initiators', 'POST', data)
+ return l, d
+
def initialize_connection(self, volume, connector):
try:
sys = self.client.get_cluster()
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')
+ login_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.client.req('initiator-groups', 'GET',
- name=self._get_ig(connector))['content']
- except exception.NotFound:
- # create an initiator group to hold the initiator
- data = {'ig-name': self._get_ig(connector)}
- self.client.req('initiator-groups', 'POST', data)
- try:
- 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.client.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(_LI('initiator has no password while using chap,'
- 'removing it'))
- self.client.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.client.req('initiators', 'POST', data)
+ initiator_name = self._get_initiator_name(connector)
+ initiator = self.client.get_initiator(initiator_name)
+ if initiator:
+ login_passwd = initiator['chap-authentication-initiator-password']
+ discovery_passwd = initiator['chap-discovery-initiator-password']
+ ig = self._get_ig(initiator['ig-id'][1])
+ else:
+ ig = self._get_ig(self._get_ig_name(connector))
+ if not ig:
+ ig = self._create_ig(self._get_ig_name(connector))
+ (login_passwd,
+ discovery_passwd) = self._create_initiator(connector,
+ login_chap,
+ discovery_chap)
+ # if CHAP was enabled after the the initiator was created
+ if login_chap and not login_passwd:
+ LOG.info(_LI('initiator has no password while using chap,'
+ 'adding it'))
+ data = {}
+ (login_passwd,
+ d_passwd) = self._add_auth(data, login_chap, discovery_chap and
+ not discovery_passwd)
+ discovery_passwd = (discovery_passwd if discovery_passwd
+ else d_passwd)
+ self.client.req('initiators', 'PUT', data, idx=initiator['index'])
+
# lun mappping
- lunmap = self.create_lun_map(volume, ig)
+ lunmap = self.create_lun_map(volume, ig['ig-id'][2])
properties = self._get_iscsi_properties(lunmap)
- if use_chap:
+ if login_chap:
properties['auth_method'] = 'CHAP'
properties['auth_username'] = 'chap_user'
- properties['auth_password'] = chap_passwd
-
+ properties['auth_password'] = login_passwd
+ if discovery_chap:
+ properties['discovery_auth_method'] = 'CHAP'
+ properties['discovery_auth_username'] = 'chap_user'
+ properties['discovery_auth_password'] = discovery_passwd
LOG.debug('init conn params:\n%s', properties)
return {
'driver_volume_type': 'iscsi',
'target_luns': [lunmap['lun']] * len(portals)}
return properties
- def _get_initiator(self, connector):
+ def _get_initiator_name(self, connector):
return connector['initiator']
- def _get_ig(self, connector):
+ def _get_ig_name(self, connector):
return connector['initiator']
@fczm_utils.AddFCZone
def initialize_connection(self, volume, connector):
- initiators = self._get_initiator(connector)
- ig_name = self._get_ig(connector)
+ wwpns = self._get_initiator_name(connector)
+ ig_name = self._get_ig_name(connector)
i_t_map = {}
+ found = []
+ new = []
+ for wwpn in wwpns:
+ init = self.client.get_initiator(wwpn)
+ if init:
+ found.append(init)
+ else:
+ new.append(wwpn)
+ i_t_map[wwpn.replace(':', '')] = self.get_targets()
# get or create initiator group
- try:
- # check if the IG already exists
- 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.client.req('initiator-groups', 'POST', data)
- try:
- 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.client.req('initiators', name=initiator)['content']
- except exception.NotFound:
- # create an initiator
- data = {'initiator-name': initiator,
- 'ig-id': ig['name'],
- 'port-address': initiator}
+ if new:
+ ig = self._get_ig(ig_name)
+ if not ig:
+ ig = self._create_ig(ig_name)
+ for wwpn in new:
+ data = {'initiator-name': wwpn, 'ig-id': ig_name,
+ 'port-address': wwpn}
self.client.req('initiators', 'POST', data)
- i_t_map[initiator] = self.get_targets()
+ igs = list(set([i['ig-id'][1] for i in found] + [ig_name]))
- lunmap = self.create_lun_map(volume, ig)
+ lun_num = None
+ for ig in igs:
+ lunmap = self.create_lun_map(volume, ig, lun_num)
+ lun_num = lunmap['lun']
return {'driver_volume_type': 'fibre_channel',
'data': {
- 'target_discovered': True,
- 'target_lun': lunmap['lun'],
+ 'target_discovered': False,
+ 'target_lun': lun_num,
'target_wwn': self.get_targets(),
'access_mode': 'rw',
'initiator_target_map': i_t_map}}
def terminate_connection(self, volume, connector, **kwargs):
(super(XtremIOFibreChannelDriver, self)
.terminate_connection(volume, connector, **kwargs))
- num_vols = self.client.num_of_mapped_volumes(self._get_ig(connector))
+ num_vols = (self.client
+ .num_of_mapped_volumes(self._get_ig_name(connector)))
if num_vols > 0:
data = {}
else:
i_t_map = {}
- for initiator in self._get_initiator(connector):
- i_t_map[initiator] = self.get_targets()
+ for initiator in self._get_initiator_name(connector):
+ i_t_map[initiator.replace(':', '')] = 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_initiator_name(self, connector):
+ return [wwpn if ':' in wwpn else
+ ':'.join(wwpn[i:i + 2] for i in range(0, len(wwpn), 2))
+ for wwpn in connector['wwpns']]
- def _get_ig(self, connector):
+ def _get_ig_name(self, connector):
return connector['host']