
When using the LVM cinder driver the cacheable capability is not being reported by the backend to the scheduler when the transport protocol is NVMe-oF (nvmet target driver), but it is properly reported if it's the LIO target driver. This also happens with other drivers that should be reporting that they are cacheable. This happens because even if the volume manager correctly uses the "storage_protocol" reported by the drivers on their stats to add the "cacheable" capability for iSCSI, FC, and NVMe-oF protocols, it isn't taking into account all the variants these have: - FC, fc, fibre_channel - iSCSI, iscsi - NVMe-oF, nvmeof, NVMeOF Same thing happens for the shared_targets of the volumes, which are not missing an iSCSI variant. This patch creates constants for the different storge protocols to try to avoid these variants (as agreed on the PTG) and also makes the cacheable and shared_targets check against all the existing variants. This change facilitates identifying NVMe-oF drivers (for bug 1961102) for the shared_targets part. Closes-Bug: #1969366 Related-Bug: #1961102 Change-Id: I1333b0471974e94eb2b3b79ea70a06e0afe28cd9
399 lines
16 KiB
Python
399 lines
16 KiB
Python
# Copyright (c) 2014 ProphetStor, 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 errno
|
|
|
|
from oslo_log import log as logging
|
|
|
|
from cinder.common import constants
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder import interface
|
|
from cinder.volume import driver
|
|
from cinder.volume.drivers.prophetstor import dplcommon
|
|
from cinder.zonemanager import utils as fczm_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
@interface.volumedriver
|
|
class DPLFCDriver(dplcommon.DPLCOMMONDriver,
|
|
driver.FibreChannelDriver):
|
|
def __init__(self, *args, **kwargs):
|
|
super(DPLFCDriver, self).__init__(*args, **kwargs)
|
|
|
|
def _get_fc_channel(self):
|
|
"""Get FibreChannel info.
|
|
|
|
:returns: fcInfos[uuid]
|
|
fcInfo[uuid]['display_name']
|
|
fcInfo[uuid]['display_description']
|
|
fcInfo[uuid]['hardware_address']
|
|
fcInfo[uuid]['type']
|
|
fcInfo[uuid]['speed']
|
|
fcInfo[uuid]['state']
|
|
"""
|
|
output = None
|
|
fcInfos = {}
|
|
try:
|
|
retCode, output = self.dpl.get_server_info()
|
|
if retCode == 0 and output:
|
|
fcUuids = output.get('metadata',
|
|
{}).get('storage_adapter', {}).keys()
|
|
for fcUuid in fcUuids:
|
|
fcInfo = output.get('metadata',
|
|
{}).get('storage_adapter',
|
|
{}).get(fcUuid)
|
|
if fcInfo['type'] == 'fc':
|
|
fcInfos[fcUuid] = fcInfo
|
|
except Exception as e:
|
|
LOG.error("Failed to get fiber channel info from storage "
|
|
"due to %(stat)s", {'stat': e})
|
|
return fcInfos
|
|
|
|
def _get_targets(self):
|
|
"""Get targets.
|
|
|
|
:returns: targetInfos[uuid] = targetInfo
|
|
targetInfo['targetUuid']
|
|
targetInfo['targetName']
|
|
targetInfo['targetAddr']
|
|
"""
|
|
output = None
|
|
targetInfos = {}
|
|
try:
|
|
retCode, output = self.dpl.get_target_list('target')
|
|
if retCode == 0 and output:
|
|
for targetInfo in output.get('children', []):
|
|
targetI = {}
|
|
targetI['targetUuid'] = targetInfo[0]
|
|
targetI['targetName'] = targetInfo[1]
|
|
targetI['targetAddr'] = targetInfo[2]
|
|
targetInfos[str(targetInfo[0])] = targetI
|
|
except Exception as e:
|
|
targetInfos = {}
|
|
LOG.error("Failed to get fiber channel target from "
|
|
"storage server due to %(stat)s",
|
|
{'stat': e})
|
|
return targetInfos
|
|
|
|
def _get_targetwpns(self, volumeid, initiatorWwpns):
|
|
lstargetWwpns = []
|
|
try:
|
|
ret, output = self.dpl.get_vdev(volumeid)
|
|
if ret == 0 and output:
|
|
exports = output.get('exports', {})
|
|
fc_infos = exports.get('Network/FC', {})
|
|
for fc_info in fc_infos:
|
|
for p in fc_info.get('permissions', []):
|
|
if p.get(initiatorWwpns, None):
|
|
targetWwpns = fc_info.get('target_identifier', '')
|
|
lstargetWwpns.append(targetWwpns)
|
|
except Exception as e:
|
|
LOG.error("Failed to get target wwpns from storage due "
|
|
"to %(stat)s", {'stat': e})
|
|
lstargetWwpns = []
|
|
return lstargetWwpns
|
|
|
|
def _convertHex2String(self, wwpns):
|
|
szwwpns = ''
|
|
if len(str(wwpns)) == 16:
|
|
szwwpns = '%2s:%2s:%2s:%2s:%2s:%2s:%2s:%2s' % (
|
|
str(wwpns)[0:2],
|
|
str(wwpns)[2:4],
|
|
str(wwpns)[4:6],
|
|
str(wwpns)[6:8],
|
|
str(wwpns)[8:10],
|
|
str(wwpns)[10:12],
|
|
str(wwpns)[12:14],
|
|
str(wwpns)[14:16])
|
|
return szwwpns
|
|
|
|
def _export_fc(self, volumeid, targetwwpns, initiatorwwpns, volumename):
|
|
ret = 0
|
|
output = ''
|
|
LOG.debug('Export fc: %(volume)s, %(wwpns)s, %(iqn)s, %(volumename)s',
|
|
{'volume': volumeid, 'wwpns': targetwwpns,
|
|
'iqn': initiatorwwpns, 'volumename': volumename})
|
|
try:
|
|
ret, output = self.dpl.assign_vdev_fc(
|
|
self._conver_uuid2hex(volumeid), targetwwpns,
|
|
initiatorwwpns, volumename)
|
|
except Exception:
|
|
LOG.error('Volume %(volumeid)s failed to send assign command, '
|
|
'ret: %(status)s output: %(output)s',
|
|
{'volumeid': volumeid, 'status': ret, 'output': output})
|
|
ret = errno.EFAULT
|
|
|
|
if ret == errno.EAGAIN:
|
|
ret, event_uuid = self._get_event_uuid(output)
|
|
if len(event_uuid):
|
|
ret = 0
|
|
status = self._wait_event(
|
|
self.dpl.get_vdev_status,
|
|
self._conver_uuid2hex(volumeid), event_uuid)
|
|
if status['state'] == 'error':
|
|
ret = errno.EFAULT
|
|
msg = _('Flexvisor failed to assign volume %(id)s: '
|
|
'%(status)s.') % {'id': volumeid,
|
|
'status': status}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
ret = errno.EFAULT
|
|
msg = _('Flexvisor failed to assign volume %(id)s due to '
|
|
'unable to query status by event '
|
|
'id.') % {'id': volumeid}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
elif ret != 0:
|
|
msg = _('Flexvisor assign volume failed:%(id)s:'
|
|
'%(status)s.') % {'id': volumeid, 'status': ret}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
return ret
|
|
|
|
def _delete_export_fc(self, volumeid, targetwwpns, initiatorwwpns):
|
|
ret = 0
|
|
output = ''
|
|
ret, output = self.dpl.unassign_vdev_fc(
|
|
self._conver_uuid2hex(volumeid),
|
|
targetwwpns, initiatorwwpns)
|
|
if ret == errno.EAGAIN:
|
|
ret, event_uuid = self._get_event_uuid(output)
|
|
if ret == 0 and len(event_uuid):
|
|
status = self._wait_event(
|
|
self.dpl.get_vdev_status, volumeid, event_uuid)
|
|
if status['state'] == 'error':
|
|
ret = errno.EFAULT
|
|
msg = _('Flexvisor failed to unassign volume %(id)s:'
|
|
' %(status)s.') % {'id': volumeid,
|
|
'status': status}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
msg = _('Flexvisor failed to unassign volume (get event) '
|
|
'%(id)s.') % {'id': volumeid}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
elif ret != 0:
|
|
msg = _('Flexvisor unassign volume failed:%(id)s:'
|
|
'%(status)s.') % {'id': volumeid, 'status': ret}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
LOG.info('Flexvisor succeeded to unassign volume %(id)s.',
|
|
{'id': volumeid})
|
|
|
|
return ret
|
|
|
|
def _build_initiator_target_map(self, connector, tgtwwns):
|
|
"""Build the target_wwns and the initiator target map."""
|
|
init_targ_map = {}
|
|
initiator_wwns = connector['wwpns']
|
|
for initiator in initiator_wwns:
|
|
init_targ_map[initiator] = tgtwwns
|
|
|
|
return init_targ_map
|
|
|
|
def initialize_connection(self, volume, connector):
|
|
"""Allow connection to connector and return connection info."""
|
|
"""
|
|
connector = {'ip': CONF.my_ip,
|
|
'host': CONF.host,
|
|
'initiator': self._initiator,
|
|
'wwnns': self._fc_wwnns,
|
|
'wwpns': self._fc_wwpns}
|
|
|
|
"""
|
|
dc_fc = {}
|
|
dc_target = {}
|
|
lsTargetWwpn = []
|
|
output = None
|
|
properties = {}
|
|
preferTargets = {}
|
|
ret = 0
|
|
targetIdentifier = []
|
|
szwwpns = []
|
|
LOG.info('initialize_connection volume: %(volume)s, connector:'
|
|
' %(connector)s',
|
|
{"volume": volume, "connector": connector})
|
|
# Get Storage Fiber channel controller
|
|
dc_fc = self._get_fc_channel()
|
|
|
|
# Get existed FC target list to decide target wwpn
|
|
dc_target = self._get_targets()
|
|
if len(dc_target) == 0:
|
|
msg = _('Backend storage did not configure fiber channel '
|
|
'target.')
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
for keyFc in dc_fc:
|
|
for targetuuid in dc_target:
|
|
if dc_fc[keyFc]['hardware_address'] == \
|
|
dc_target[targetuuid]['targetAddr']:
|
|
preferTargets[targetuuid] = dc_target[targetuuid]
|
|
break
|
|
# Confirm client wwpn is existed in sns table
|
|
# Covert wwwpns to 'xx:xx:xx:xx:xx:xx:xx:xx' format
|
|
for dwwpn in connector['wwpns']:
|
|
szwwpn = self._convertHex2String(dwwpn)
|
|
if len(szwwpn) == 0:
|
|
msg = _('Invalid wwpns format %(wwpns)s') % \
|
|
{'wwpns': connector['wwpns']}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
szwwpns.append(szwwpn)
|
|
|
|
if len(szwwpns):
|
|
for targetUuid in preferTargets:
|
|
targetWwpn = ''
|
|
targetWwpn = preferTargets.get(targetUuid,
|
|
{}).get('targetAddr', '')
|
|
lsTargetWwpn.append(targetWwpn)
|
|
# Use wwpns to assign volume.
|
|
LOG.info('Prefer use target wwpn %(wwpn)s',
|
|
{'wwpn': lsTargetWwpn})
|
|
# Start to create export in all FC target node.
|
|
assignedTarget = []
|
|
for pTarget in lsTargetWwpn:
|
|
try:
|
|
ret = self._export_fc(volume['id'], str(pTarget), szwwpns,
|
|
volume['name'])
|
|
if ret:
|
|
break
|
|
else:
|
|
assignedTarget.append(pTarget)
|
|
except Exception as e:
|
|
LOG.error('Failed to export fiber channel target '
|
|
'due to %s', e)
|
|
ret = errno.EFAULT
|
|
break
|
|
if ret == 0:
|
|
ret, output = self.dpl.get_vdev(self._conver_uuid2hex(
|
|
volume['id']))
|
|
nLun = -1
|
|
if ret == 0:
|
|
try:
|
|
for p in output['exports']['Network/FC']:
|
|
# check initiator wwpn existed in target initiator list
|
|
for initI in p.get('permissions', []):
|
|
for szwpn in szwwpns:
|
|
if initI.get(szwpn, None):
|
|
nLun = initI[szwpn]
|
|
break
|
|
if nLun != -1:
|
|
break
|
|
|
|
if nLun != -1:
|
|
targetIdentifier.append(
|
|
str(p['target_identifier']).replace(':', ''))
|
|
|
|
except Exception:
|
|
msg = _('Invalid connection initialization response of '
|
|
'volume %(name)s: '
|
|
'%(output)s') % {'name': volume['name'],
|
|
'output': output}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
if nLun != -1:
|
|
init_targ_map = self._build_initiator_target_map(connector,
|
|
targetIdentifier)
|
|
properties['target_discovered'] = True
|
|
properties['target_wwn'] = targetIdentifier
|
|
properties['target_lun'] = int(nLun)
|
|
properties['volume_id'] = volume['id']
|
|
properties['initiator_target_map'] = init_targ_map
|
|
LOG.info('%(volume)s assign type fibre_channel, properties '
|
|
'%(properties)s',
|
|
{'volume': volume['id'], 'properties': properties})
|
|
else:
|
|
msg = _('Invalid connection initialization response of '
|
|
'volume %(name)s') % {'name': volume['name']}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
LOG.info('Connect initialization info: '
|
|
'{driver_volume_type: fibre_channel, '
|
|
'data: %(properties)s', {'properties': properties})
|
|
conn_info = {'driver_volume_type': 'fibre_channel',
|
|
'data': properties}
|
|
fczm_utils.add_fc_zone(conn_info)
|
|
return conn_info
|
|
|
|
def terminate_connection(self, volume, connector, **kwargs):
|
|
"""Disallow connection from connector."""
|
|
"""
|
|
connector = {'ip': CONF.my_ip,
|
|
'host': CONF.host,
|
|
'initiator': self._initiator,
|
|
'wwnns': self._fc_wwnns,
|
|
'wwpns': self._fc_wwpns}
|
|
"""
|
|
lstargetWwpns = []
|
|
lsTargets = []
|
|
szwwpns = []
|
|
ret = 0
|
|
info = {'driver_volume_type': 'fibre_channel', 'data': {}}
|
|
LOG.info('terminate_connection volume: %(volume)s, '
|
|
'connector: %(con)s',
|
|
{'volume': volume, 'con': connector})
|
|
# Query targetwwpns.
|
|
# Get all target list of volume.
|
|
for dwwpn in connector['wwpns']:
|
|
szwwpn = self._convertHex2String(dwwpn)
|
|
if len(szwwpn) == 0:
|
|
msg = _('Invalid wwpns format %(wwpns)s') % \
|
|
{'wwpns': connector['wwpns']}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
szwwpns.append(szwwpn)
|
|
|
|
if len(szwwpns) == 0:
|
|
ret = errno.EFAULT
|
|
msg = _('Invalid wwpns format %(wwpns)s') % \
|
|
{'wwpns': connector['wwpns']}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
for szwwpn in szwwpns:
|
|
lstargetWwpns = self._get_targetwpns(
|
|
self._conver_uuid2hex(volume['id']), szwwpn)
|
|
lsTargets = list(set(lsTargets + lstargetWwpns))
|
|
|
|
# Remove all export target
|
|
try:
|
|
for ptarget in lsTargets:
|
|
ret = self._delete_export_fc(volume['id'], ptarget, szwwpns)
|
|
if ret:
|
|
break
|
|
except Exception:
|
|
ret = errno.EFAULT
|
|
finally:
|
|
if ret:
|
|
msg = _('Faield to unassign %(volume)s') % (volume['id'])
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Failed to delete export with fibre channel
|
|
if ret:
|
|
init_targ_map = self._build_initiator_target_map(connector,
|
|
lsTargets)
|
|
info['data'] = {'target_wwn': lsTargets,
|
|
'initiator_target_map': init_targ_map}
|
|
fczm_utils.remove_fc_zone(info)
|
|
|
|
return info
|
|
|
|
def get_volume_stats(self, refresh=False):
|
|
if refresh:
|
|
data = super(DPLFCDriver, self).get_volume_stats(refresh)
|
|
if data:
|
|
data['storage_protocol'] = constants.FC
|
|
backend_name = \
|
|
self.configuration.safe_get('volume_backend_name')
|
|
data['volume_backend_name'] = (backend_name or 'DPLFCDriver')
|
|
self._stats = data
|
|
return self._stats
|