Files
cinder/cinder/volume/drivers/hedvig/hedvig_cinder.py
Gorka Eguileor 68311a0794 Fix cacheable capability
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
2022-04-20 18:47:46 +02:00

615 lines
25 KiB
Python

# Copyright (c) 2018 Hedvig, 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.
"""
Volume driver for Hedvig Block Storage.
"""
import socket
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import units
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.hedvig import config
from cinder.volume.drivers.hedvig import rest_client
from cinder.volume.drivers.san import san
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
@interface.volumedriver
class HedvigISCSIDriver(driver.ISCSIDriver, san.SanDriver):
"""OpenStack Cinder driver to enable Hedvig storage.
.. code-block:: none
Version history:
1.0 - Initial driver
"""
DEFAULT_VOL_BLOCK_SIZE = 4 * units.Ki
DEFAULT_CREATEDBY = "OpenStack"
DEFAULT_EXPORT_BLK_SIZE = 4096
DEFAULT_CAPACITY = units.Gi
DEFAULT_ISCSI_PORT = 3260
DEFAULT_TARGET_NAME = "iqn.2012-05.com.hedvig:storage."
VERSION = "1.0.0"
CI_WIKI_NAME = "Hedvig_CI"
def __init__(self, *args, **kwargs):
super(HedvigISCSIDriver, self).__init__(*args, **kwargs)
self.group_stats = {}
self.hrs = None
@staticmethod
def get_driver_options():
return []
def check_for_setup_error(self):
self.hrs.connect()
LOG.info("Initialization complete")
def do_setup(self, context):
# Ensure that the data required by hedvig are provided
required_config = ['san_login', 'san_password', 'san_ip',
'san_clustername']
for attr in required_config:
if not getattr(self.configuration, attr, None):
msg = _('Hedvig param %s is not set.') % attr
LOG.error(msg)
raise exception.VolumeDriverException(msg)
self.san_ip = self.configuration.san_ip
self.san_login = self.configuration.san_login
self.san_password = self.configuration.san_password
self.san_clustername = self.configuration.san_clustername
LOG.info('Initializing hedvig cinder driver with '
'server: %s', self.san_ip)
self.hrs = rest_client.RestClient(self.san_ip,
self.san_login,
self.san_password,
self.san_clustername)
def get_volume_stats(self, refresh=False):
# we need to get stats for server.
if refresh is True:
total_capacity, free_capacity = self.update_volume_stats()
stats = dict()
stats["volume_backend_name"] = "hedvig"
stats["vendor_name"] = "Hedvig Inc"
stats["driver_version"] = self.VERSION
stats["storage_protocol"] = constants.ISCSI
stats["total_capacity_gb"] = total_capacity
stats["free_capacity_gb"] = free_capacity
stats["QoS_support"] = True
self.group_stats = stats
return self.group_stats
def create_volume(self, volume):
"""Driver entry point for creating a new volume."""
try:
qos_specs = None
name, description, size = self.get_hedvig_volume_details(volume)
vol_type_id = volume.volume_type_id
if vol_type_id is not None:
qos = volume_types.get_volume_type_qos_specs(vol_type_id)
qos_specs = qos['qos_specs']
self.hedvig_create_virtualdisk(name, description, size, qos_specs)
except exception.VolumeDriverException:
msg = _('Failed to create volume %s. Rest API failed'
) % volume.name
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to create volume: %s') % volume.name
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def delete_volume(self, volume):
"""Driver entry point for deleting volume."""
LOG.debug("Deleting volume: %s", volume.name)
name = volume.name
try:
self.hedvig_delete_virtualdisk(name)
except exception.VolumeDriverException:
msg = _('Failed to delete volume %s. Rest API failed'
) % volume.name
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to delete volume: %s') % volume.name
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the volume."""
try:
LOG.debug('Create cloned volume called '
'volume_id = %(volume)s and src_vol_id = %(src_vol_id)s',
{'volume': volume.id, 'src_vol_id': src_vref.id})
name, desc, size = self.get_hedvig_volume_details(volume)
self.hrs.clone_vdisk(srcVolName=src_vref.name, dstVolName=name,
size=size)
except exception.VolumeDriverException:
msg = _('Failed to create cloned volume. Rest API failed')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to create cloned volume')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def initialize_connection(self, volume, connector):
"""Driver entry point to attach a volume to an instance.
Assign any created volume to a compute node/controllerVM so
that it can be attached to a instance.
This driver returns a driver_volume_type of 'iscsi'.
The format of the driver data is defined as follows -- similar
to _get_iscsi_properties.
"""
LOG.debug('Initializing connection. volume: %s, '
'connector: %s', volume, connector)
try:
computeHost = self.get_compute_host(connector)
volName = volume.name
tgtHost = self.hedvig_lookup_tgt(computeHost)
if tgtHost is None:
LOG.warning("No target registered for compute host %s",
computeHost)
tgtHost = self.hedvig_lookup_tgt()
lunnum = self.hedvig_get_lun(tgtHost, volName)
if lunnum == -1:
LOG.error('Failed to get lun for volume: %s, '
'hedvig controller: %s', volume, tgtHost)
raise exception.VolumeDriverException()
# Add access to the mgmt interface addr and iqn of compute host
LOG.debug("Calling add access %(host)s : %(vol)s : %(iqn)s ",
{'host': tgtHost, 'vol': volName,
'iqn': connector['initiator']})
self.hedvig_add_access(tgtHost, volName, connector['initiator'])
# Add access to both storage and mgmt interface addrs for
# iscsi discovery to succeed
LOG.debug("Calling hedvig_get_iqn %s", socket.getfqdn())
controller_host_iqn = self.hedvig_get_iqn(socket.getfqdn())
LOG.debug("Calling add access with %s : %s : %s ", tgtHost,
volName, controller_host_iqn)
self.hedvig_add_access(tgtHost, volName, controller_host_iqn)
targetName = ("%s%s-%s" % (self.DEFAULT_TARGET_NAME, tgtHost,
lunnum))
portal = ("%s:%s" % (socket.gethostbyname(tgtHost),
self.DEFAULT_ISCSI_PORT))
iscsi_properties = ({'target_discovered': True,
'target_iqn': targetName,
'target_portal': portal,
'target_lun': lunnum})
LOG.debug("iscsi_properties: %s", iscsi_properties)
return {'driver_volume_type': 'iscsi', 'data': iscsi_properties}
except exception.VolumeDriverException:
msg = _('Volume assignment to connect failed. volume: %s '
'Rest API failed') % volume
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Volume assignment to connect failed. volume: %s') % volume
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to detach volume from instance."""
LOG.debug("Terminating connection. volume: %s, connector: %s",
volume, connector)
try:
volName = volume.name
if connector is None:
LOG.debug("Removing ALL host connections for volume %s",
volume)
targetList = self.hrs.list_targets(computeHost=None)
for target in targetList:
self.hedvig_delete_lun(target, volName)
return
computeHost = self.get_compute_host(connector)
tgtHost = self.hedvig_lookup_tgt(computeHost)
if tgtHost is None:
LOG.debug("No target registered for compute host %s",
computeHost)
tgtHost = self.hedvig_lookup_tgt()
if tgtHost is None:
msg = _('Failed to get hedvig controller')
LOG.error(msg)
raise exception.VolumeDriverException(msg)
self.hedvig_delete_lun(tgtHost, volName)
except exception.VolumeDriverException:
msg = _('Failed to terminate connection. volume: %s '
'Rest API failed') % volume
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to terminate connection. volume: %s') % volume
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def create_snapshot(self, snapshot):
"""Driver entry point for creating a snapshot."""
try:
volName = snapshot.volume_name
snapshotName = snapshot.name
project = snapshot.project_id
snapshotId = snapshot.id
LOG.info("Creating snapshot. volName: %s, snapshotName: %s, "
"project: %s, snapshotId: %s", volName,
snapshotName, project, snapshotId)
self.hedvig_create_snapshot(volName, snapshotId)
except exception.VolumeDriverException:
msg = (_('Failed to create snapshot. snapshotName: %s '
'Rest API failed') % snapshotName)
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = (_('Failed to create snapshot. snapshotName: %s')
% snapshotName)
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def delete_snapshot(self, snapshot):
"""Driver entry point for deleting a snapshot."""
try:
volName = snapshot.volume_name
snapshotName = snapshot.display_name
project = snapshot.project_id
snapshotId = snapshot.id
LOG.info("Deleting snapshot. volName: %s, snapshotName: %s, "
"project: %s", volName, snapshotName, project)
self.hrs.delete_snapshot(snapshotName, volName, snapshotId)
except exception.VolumeDriverException:
msg = _('Failed to delete snapshot: %s, '
'Rest API failed') % snapshotName
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to delete snapshot: %s') % snapshotName
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def create_volume_from_snapshot(self, volume, snapshot):
"""Driver entry point for creating a new volume from a snapshot.
This is the same as cloning.
"""
name, description, size = self.get_hedvig_volume_details(volume)
snapshotName = snapshot.display_name
snapshotId = snapshot.id
srcVolName = snapshot.volume_name
try:
LOG.info('Creating volume from snapshot. Name: %(volname)s,'
' SrcVolName: %(src)s, Snap_id: %(sid)s',
{'volname': name, 'src': srcVolName, 'sid': snapshotId})
self.hedvig_clone_snapshot(name, snapshotId, srcVolName, size)
except exception.VolumeDriverException:
msg = _('Failed to create volume from snapshot %s'
' Rest API failed') % snapshotName
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to create volume from snapshot %s') % snapshotName
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def extend_volume(self, volume, newSize):
"""Resizes virtual disk.
newSize should be greater than current size.
"""
try:
name, description, size = self.get_hedvig_volume_details(volume)
LOG.info('Resizing virtual disk. name: %s, '
'newSize: %s', name, newSize)
if (size / units.Gi) >= newSize:
err = _("Shrinking of volumes are not allowed")
LOG.error(err)
raise exception.VolumeDriverException(err)
self.hrs.resize_vdisk(
name,
newSize)
except exception.VolumeDriverException:
msg = _('Failed to extend volume. Rest API failed')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to extend volume')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def check_for_export(self, context, volume_id):
"""Not relevant to Hedvig"""
pass
def get_export(self, volume):
"""Get the iSCSI export details for a volume."""
pass
def ensure_export(self, context, volume):
"""Driver entry point to get the export info for an existing volume.
Irrelevant for Hedvig. Export is created during attachment to instance.
"""
pass
def create_export(self, context, volume, properties):
"""Driver entry point to get the export info for a new volume.
Irrelevant for Hedvig. Export is created during attachment to instance.
"""
pass
def remove_export(self, context, volume):
"""Driver entry point to remove an export for a volume.
Irrelevant for Hedvig. Export should be deleted on detachment.
"""
pass
def detach_volume(self, context, volume, attachment):
pass
def hedvig_create_snapshot(self, vDiskName, snapshotId=None):
"""Hedvig call to create snapshot of vdisk."""
LOG.debug("Creating snapshot..%s , %s.", vDiskName, snapshotId)
try:
snapshotName = self.hrs.create_snapshot(vDiskName, snapshotId)
LOG.debug("Received snapshotName %s from rest call",
snapshotName)
return snapshotName
except exception.VolumeDriverException:
msg = _('Failed to create snapshot for vdisk %s '
'Rest API failed') % vDiskName
LOG.exception(msg)
raise exception.VolumeDriverException()
except Exception:
msg = _('Failed to create snapshot for vdisk %s') % vDiskName
LOG.exception(msg)
raise exception.VolumeDriverException()
def update_volume_stats(self):
LOG.debug('Update volume stats called')
try:
total_capacity, free_capacity = self.hrs.update_volume_stats()
except exception.VolumeDriverException:
msg = _('Unable to fetch volume stats. Rest API failed')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Unable to fetch volume stats')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
return (total_capacity, free_capacity)
def get_hedvig_volume_details(self, volume):
volName = volume.name
project = volume.project_id
displayName = volume.display_name
displayDescription = volume.display_description
description = ("%s\n%s\n%s" % (project, displayName,
displayDescription))
size = volume.size * units.Gi
return volName, description, size
def get_compute_host(self, connector):
connectorHost = socket.getfqdn(connector['host'])
localHost = socket.gethostname()
computeHost = localHost
if connectorHost != localHost:
computeHost = connectorHost
return computeHost
def hedvig_lookup_tgt(self, host=None):
"""Get the tgt instance associated with the compute host"""
LOG.debug("Looking up hedvig controller for compute host: %s",
host)
try:
targetList = self.hrs.list_targets(computeHost=host)
tgt = None
if len(targetList) > 0:
tgt = targetList[0]
LOG.debug("Found hedvig controller: %s, for host: %s", tgt, host)
return tgt
except exception.VolumeDriverException:
msg = _('Failed to get hedvig controller for compute %s '
'Rest API failed') % host
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
except Exception:
msg = _('Failed to get hedvig controller for compute %s ') % host
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_delete_lun(self, tgtHost, vDiskName):
try:
LOG.debug("Deleting lun. hedvig controller: %s, vDiskName: %s,",
tgtHost, vDiskName)
self.hrs.unmap_lun(tgtHost, vDiskName)
except Exception:
msg = _('Failed to delete lun')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_get_lun(self, tgtHost, vDiskName):
"""Looks up lun based on tgthost and vDiskName.
If lun does not exist then call add_lun and return the lun number.
If lun exists, just return the lun number.
"""
LOG.debug("Getting lun. hedvig controller: %s, vDiskName: %s",
tgtHost, vDiskName)
try:
lunNo = self.hrs.get_lun(tgtHost, vDiskName)
if lunNo > -1:
return lunNo
# If the lun is not found, add lun for the vdisk
LOG.debug("Calling add lun on target : %s vdisk %s", tgtHost,
vDiskName)
self.hrs.add_lun(tgtHost, vDiskName, False)
lunNo = self.hrs.get_lun(tgtHost, vDiskName)
return lunNo
except Exception:
msg = _('Failed to get lun for vdisk: %s') % vDiskName
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_get_iqn(self, hostname):
"""Looks up the iqn for the given host."""
try:
iqn = self.hrs.get_iqn(hostname)
LOG.debug("Got IQN: %s, for hostname: %s", iqn, hostname)
return iqn
except Exception:
msg = _('Failed to get iqn for hostname: %s') % hostname
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_add_access(self, tgtHost, volName, initiator):
"""Adds access to LUN for initiator's ip/iqn."""
try:
LOG.info("Adding access. hedvig controller: %s, vol name %s, "
"initiator: %s", tgtHost, volName, initiator)
self.hrs.add_access(tgtHost, volName, "iqn", initiator)
except Exception:
msg = _('Failed to add access. hedvig controller: %s') % tgtHost
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_create_virtualdisk(self, name, description, size, qos_specs):
try:
LOG.info('Creating virtual disk. name: %s, description: %s,'
'size: %s', name, description, size)
vDiskInfo = {
'name': name,
'blockSize': HedvigISCSIDriver.DEFAULT_VOL_BLOCK_SIZE,
'size': size,
'createdBy':
HedvigISCSIDriver.DEFAULT_CREATEDBY,
'description': description,
'residence': config.Config.DiskResidence[1],
'replicationFactor': 3,
'replicationPolicy': 'Agnostic',
'clusteredFileSystem': False,
'exportedBlockSize': HedvigISCSIDriver.DEFAULT_EXPORT_BLK_SIZE,
'cacheEnabled': config.Config.defaultCinderCacheEnable,
'diskType': 'BLOCK',
'immutable': False,
'deduplication': config.Config.defaultCinderDedupEnable,
'compressed': config.Config.defaultCinderCompressEnable,
'cloudEnabled': False,
'cloudProvider': 0,
'isClone': False,
'consistency': 'STRONG',
'scsi3pr': False
}
if qos_specs:
kvs = qos_specs['specs']
for key, value in kvs.items():
if "dedup_enable" == key:
val = self.parse_and_get_boolean_entry(
value)
if val:
vDiskInfo['deduplication'] = val
elif "compressed_enable" == key:
val = self.parse_and_get_boolean_entry(
value)
if val:
vDiskInfo['compressed'] = True
elif "cache_enable" == key:
val = self.parse_and_get_boolean_entry(
value)
if val:
vDiskInfo['cacheEnabled'] = val
elif "encryption" == key:
val = self.parse_and_get_boolean_entry(
value)
if val:
vDiskInfo['encryption'] = val
elif "replication_factor" == key:
val = int(value)
if val > 0:
vDiskInfo['replicationFactor'] = val
elif "replication_policy" == key:
val = value.strip(" \n\t").lower()
if val:
vDiskInfo['replicationPolicy'] = val
elif "disk_residence" == key:
val = value.strip(" \n\t").lower()
if val:
vDiskInfo['residence'] = val
elif "replication_policy_info" == key:
val = value.split(',')
if len(val) != 0:
dcList = []
for dataCenter in val:
dcList.append(dataCenter)
vDiskInfo['dataCenters'] = dcList
if vDiskInfo['deduplication'] and (
vDiskInfo['compressed'] is False):
LOG.error('Cannot create dedup enabled disk without'
' compression enabled')
raise exception.VolumeDriverException()
self.hrs.create_vdisk(vDiskInfo)
except Exception:
msg = _('Failed to create volume')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_delete_virtualdisk(self, name):
LOG.info('Deleting virtual disk. name - %s', name)
try:
self.hrs.delete_vdisk(name)
except Exception:
msg = _('Failed to delete Vdisk')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def hedvig_clone_snapshot(self, dstVolName,
openstackSID, srcVolName, size):
LOG.info("Cloning a snapshot.dstVolName: %s,openstackSID:%s,"
"srcVolName: %s", dstVolName, openstackSID, srcVolName)
try:
self.hrs.clone_hedvig_snapshot(
dstVolName=dstVolName,
snapshotID=openstackSID,
srcVolName=srcVolName,
size=size)
except Exception:
msg = _('Failed to clone snapshot')
LOG.exception(msg)
raise exception.VolumeDriverException(msg)
def parse_and_get_boolean_entry(self, entry):
entry = entry.strip(" \t\n")
return strutils.bool_from_string(entry)