cinder/cinder/volume/targets/spdknvmf.py

201 lines
6.7 KiB
Python

# 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 json
import string
from oslo_config import cfg
from oslo_log import log as logging
import requests
from cinder import exception
from cinder.i18n import _
from cinder.volume import configuration
from cinder.volume.targets import nvmeof
from cinder.volume import volume_utils
spdk_opts = [
cfg.StrOpt('spdk_rpc_ip',
help='The NVMe target remote configuration IP address.'),
cfg.PortOpt('spdk_rpc_port',
default=8000,
help='The NVMe target remote configuration port.'),
cfg.StrOpt('spdk_rpc_username',
help='The NVMe target remote configuration username.'),
cfg.StrOpt('spdk_rpc_password',
help='The NVMe target remote configuration password.',
secret=True),
cfg.StrOpt('spdk_rpc_protocol',
choices=['http', 'https'],
default='http',
help='Protocol to be used with SPDK RPC proxy'),
cfg.IntOpt('spdk_max_queue_depth',
default=64,
min=1, max=128,
help='Queue depth for rdma transport.'),
]
CONF = cfg.CONF
CONF.register_opts(spdk_opts, group=configuration.SHARED_CONF_GROUP)
LOG = logging.getLogger(__name__)
class SpdkNvmf(nvmeof.NVMeOF):
def __init__(self, *args, **kwargs):
super(SpdkNvmf, self).__init__(*args, **kwargs)
self.configuration.append_config_values(spdk_opts)
self.url = ('%(protocol)s://%(ip)s:%(port)s/' %
{'protocol': self.configuration.spdk_rpc_protocol,
'ip': self.configuration.spdk_rpc_ip,
'port': self.configuration.spdk_rpc_port})
# SPDK NVMe-oF Target application requires one time creation
# of RDMA transport type each time it is started. It will
# fail on second attempt which is expected behavior.
try:
params = {
'trtype': 'rdma',
'max_queue_depth':
self.configuration.spdk_max_queue_depth
}
self._rpc_call('nvmf_create_transport', params)
except Exception:
pass
def _rpc_call(self, method, params=None):
payload = {}
payload['jsonrpc'] = '2.0'
payload['id'] = 1
payload['method'] = method
if params is not None:
payload['params'] = params
req = requests.post(self.url,
data=json.dumps(payload),
auth=(self.configuration.spdk_rpc_username,
self.configuration.spdk_rpc_password),
verify=self.configuration.driver_ssl_cert_verify,
timeout=30)
if not req.ok:
raise exception.VolumeBackendAPIException(
data=_('SPDK target responded with error: %s') % req.text)
return req.json()['result']
def _get_spdk_volume_name(self, name):
output = self._rpc_call('bdev_get_bdevs')
for bdev in output:
for alias in bdev['aliases']:
if name in alias:
return bdev['name']
def _get_nqn_with_volume_name(self, name):
output = self._rpc_call('nvmf_get_subsystems')
spdk_name = self._get_spdk_volume_name(name)
if spdk_name is not None:
for subsystem in output[1:]:
for namespace in subsystem['namespaces']:
if spdk_name in namespace['bdev_name']:
return subsystem['nqn']
def _get_first_free_node(self):
cnode_num = []
output = self._rpc_call('nvmf_get_subsystems')
# Get node numbers for nqn string like this: nqn.2016-06.io.spdk:cnode1
for subsystem in output[1:]:
cnode_num.append(int(subsystem['nqn'].split("cnode")[1]))
test_set = set(range(1, len(cnode_num) + 2))
return list(test_set.difference(cnode_num))[0]
def create_nvmeof_target(self,
volume_id,
subsystem_name,
target_ip,
target_port,
transport_type,
nvmet_port_id,
ns_id,
volume_path):
LOG.debug('SPDK create target')
nqn = self._get_nqn_with_volume_name(volume_id)
if nqn is None:
node = self._get_first_free_node()
nqn = '%s:cnode%s' % (subsystem_name, node)
choice = string.ascii_uppercase + string.digits
serial = ''.join(
volume_utils.generate_password(length=12, symbolgroups=choice))
params = {
'nqn': nqn,
'allow_any_host': True,
'serial_number': serial,
}
self._rpc_call('nvmf_create_subsystem', params)
listen_address = {
'trtype': transport_type,
'traddr': target_ip,
'trsvcid': str(target_port),
}
params = {
'nqn': nqn,
'listen_address': listen_address,
}
self._rpc_call('nvmf_subsystem_add_listener', params)
ns = {
'bdev_name': self._get_spdk_volume_name(volume_id),
'nsid': ns_id,
}
params = {
'nqn': nqn,
'namespace': ns,
}
self._rpc_call('nvmf_subsystem_add_ns', params)
location = self.get_nvmeof_location(
nqn,
target_ip,
target_port,
transport_type,
ns_id)
return {'location': location, 'auth': '', 'provider_id': nqn}
def delete_nvmeof_target(self, target_name):
LOG.debug('SPDK delete target: %s', target_name)
nqn = self._get_nqn_with_volume_name(target_name.name)
if nqn is not None:
try:
params = {'nqn': nqn}
self._rpc_call('nvmf_delete_subsystem', params)
LOG.debug('SPDK subsystem %s deleted', nqn)
except Exception as e:
LOG.debug('SPDK ERROR: subsystem not deleted: %s', e)