Adding abstract class NVMeOF
A new driver is added, and allows Targets to implement create_nvmeof_target, delete_nvmeof_target methods. NVMeOF is an abstract class to be implemented by NVMeOF helpers. Implements: blueprint nvme-target-cli Change-Id: I344da50112af8e559408b5b7c8532c659a97c7c2
This commit is contained in:
parent
4158fb44bc
commit
10c801c1d5
@ -1349,3 +1349,8 @@ class ServiceUserTokenNoAuth(CinderException):
|
|||||||
message = _("The [service_user] send_service_user_token option was "
|
message = _("The [service_user] send_service_user_token option was "
|
||||||
"requested, but no service auth could be loaded. Please check "
|
"requested, but no service auth could be loaded. Please check "
|
||||||
"the [service_user] configuration section.")
|
"the [service_user] configuration section.")
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedNVMETProtocol(Invalid):
|
||||||
|
message = _("An invalid 'target_protocol' "
|
||||||
|
"value was provided: %(protocol)s")
|
||||||
|
@ -254,6 +254,7 @@ def list_opts():
|
|||||||
[cinder_volume_api.az_cache_time_opt],
|
[cinder_volume_api.az_cache_time_opt],
|
||||||
cinder_volume_driver.volume_opts,
|
cinder_volume_driver.volume_opts,
|
||||||
cinder_volume_driver.iser_opts,
|
cinder_volume_driver.iser_opts,
|
||||||
|
cinder_volume_driver.nvmet_opts,
|
||||||
cinder_volume_drivers_datacore_driver.datacore_opts,
|
cinder_volume_drivers_datacore_driver.datacore_opts,
|
||||||
cinder_volume_drivers_datacore_iscsi.datacore_iscsi_opts,
|
cinder_volume_drivers_datacore_iscsi.datacore_iscsi_opts,
|
||||||
cinder_volume_drivers_inspur_instorage_instoragecommon.
|
cinder_volume_drivers_inspur_instorage_instoragecommon.
|
||||||
@ -283,6 +284,7 @@ def list_opts():
|
|||||||
itertools.chain(
|
itertools.chain(
|
||||||
cinder_volume_driver.volume_opts,
|
cinder_volume_driver.volume_opts,
|
||||||
cinder_volume_driver.iser_opts,
|
cinder_volume_driver.iser_opts,
|
||||||
|
cinder_volume_driver.nvmet_opts,
|
||||||
cinder_volume_drivers_coprhd_common.volume_opts,
|
cinder_volume_drivers_coprhd_common.volume_opts,
|
||||||
cinder_volume_drivers_coprhd_scaleio.scaleio_opts,
|
cinder_volume_drivers_coprhd_scaleio.scaleio_opts,
|
||||||
cinder_volume_drivers_datera_dateraiscsi.d_opts,
|
cinder_volume_drivers_datera_dateraiscsi.d_opts,
|
||||||
|
139
cinder/tests/unit/targets/test_nvmeof_driver.py
Normal file
139
cinder/tests/unit/targets/test_nvmeof_driver.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# 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 mock
|
||||||
|
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from cinder import context
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.tests.unit.targets import targets_fixture as tf
|
||||||
|
from cinder import utils
|
||||||
|
from cinder.volume.targets import nvmeof
|
||||||
|
|
||||||
|
|
||||||
|
class FakeNVMeOFDriver(nvmeof.NVMeOF):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FakeNVMeOFDriver, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def create_nvmeof_target(
|
||||||
|
self, target_name, target_ip, target_port,
|
||||||
|
transport_type, ns_id, volume_path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_nvmeof_target(self, target_name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNVMeOFDriver, self).setUp()
|
||||||
|
|
||||||
|
self.configuration.target_protocol = 'nvmet_rdma'
|
||||||
|
self.target = FakeNVMeOFDriver(root_helper=utils.get_root_helper(),
|
||||||
|
configuration=self.configuration)
|
||||||
|
|
||||||
|
self.target_ip = self.configuration.target_ip_address
|
||||||
|
self.target_port = self.configuration.target_port
|
||||||
|
self.nvmet_subsystem_name = self.configuration.target_prefix
|
||||||
|
self.nvmet_ns_id = self.configuration.nvmet_ns_id
|
||||||
|
self.nvmet_port_id = self.configuration.nvmet_port_id
|
||||||
|
self.nvme_transport_type = 'rdma'
|
||||||
|
|
||||||
|
self.fake_volume_id = 'c446b9a2-c968-4260-b95f-a18a7b41c004'
|
||||||
|
self.testvol_path = (
|
||||||
|
'/dev/stack-volumes-lvmdriver-1/volume-%s' % self.fake_volume_id)
|
||||||
|
self.fake_project_id = 'ed2c1fd4-5555-1111-aa15-123b93f75cba'
|
||||||
|
self.testvol = (
|
||||||
|
{'project_id': self.fake_project_id,
|
||||||
|
'name': 'testvol',
|
||||||
|
'size': 1,
|
||||||
|
'id': self.fake_volume_id,
|
||||||
|
'volume_type_id': None,
|
||||||
|
'provider_location':
|
||||||
|
self.target.get_nvmeof_location(
|
||||||
|
"ngn.%s-%s" % (
|
||||||
|
self.nvmet_subsystem_name,
|
||||||
|
self.fake_volume_id),
|
||||||
|
self.target_ip,
|
||||||
|
self.target_port,
|
||||||
|
self.nvme_transport_type,
|
||||||
|
self.nvmet_ns_id
|
||||||
|
),
|
||||||
|
'provider_auth': None,
|
||||||
|
'provider_geometry': None,
|
||||||
|
'created_at': timeutils.utcnow(),
|
||||||
|
'host': 'fake_host@lvm#lvm'})
|
||||||
|
|
||||||
|
def test_initialize_connection(self):
|
||||||
|
mock_connector = {'initiator': 'fake_init'}
|
||||||
|
mock_testvol = self.testvol
|
||||||
|
expected_return = {
|
||||||
|
'driver_volume_type': 'nvmeof',
|
||||||
|
'data': self.target._get_connection_properties(mock_testvol)
|
||||||
|
}
|
||||||
|
self.assertEqual(expected_return,
|
||||||
|
self.target.initialize_connection(mock_testvol,
|
||||||
|
mock_connector))
|
||||||
|
|
||||||
|
@mock.patch.object(FakeNVMeOFDriver, 'create_nvmeof_target')
|
||||||
|
def test_create_export(self, mock_create_nvme_target):
|
||||||
|
ctxt = context.get_admin_context()
|
||||||
|
self.target.create_export(ctxt, self.testvol, self.testvol_path)
|
||||||
|
mock_create_nvme_target.assert_called_once_with(
|
||||||
|
self.fake_volume_id,
|
||||||
|
self.configuration.target_prefix,
|
||||||
|
self.target_ip,
|
||||||
|
self.target_port,
|
||||||
|
self.nvme_transport_type,
|
||||||
|
self.nvmet_port_id,
|
||||||
|
self.nvmet_ns_id,
|
||||||
|
self.testvol_path
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch.object(FakeNVMeOFDriver, 'delete_nvmeof_target')
|
||||||
|
def test_remove_export(self, mock_delete_nvmeof_target):
|
||||||
|
ctxt = context.get_admin_context()
|
||||||
|
self.target.remove_export(ctxt, self.testvol)
|
||||||
|
mock_delete_nvmeof_target.assert_called_once_with(
|
||||||
|
self.testvol
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_connection_properties(self):
|
||||||
|
expected_return = {
|
||||||
|
'target_portal': self.target_ip,
|
||||||
|
'target_port': str(self.target_port),
|
||||||
|
'nqn': "ngn.%s-%s" % (
|
||||||
|
self.nvmet_subsystem_name, self.fake_volume_id),
|
||||||
|
'transport_type': self.nvme_transport_type,
|
||||||
|
'ns_id': str(self.nvmet_ns_id)
|
||||||
|
}
|
||||||
|
self.assertEqual(expected_return,
|
||||||
|
self.target._get_connection_properties(self.testvol))
|
||||||
|
|
||||||
|
def test_validate_connector(self):
|
||||||
|
mock_connector = {'initiator': 'fake_init'}
|
||||||
|
self.assertTrue(self.target.validate_connector(mock_connector))
|
||||||
|
|
||||||
|
def test_validate_connector_not_found(self):
|
||||||
|
mock_connector = {'fake_init'}
|
||||||
|
self.assertRaises(exception.InvalidConnectorException,
|
||||||
|
self.target.validate_connector,
|
||||||
|
mock_connector)
|
||||||
|
|
||||||
|
def test_invalid_target_protocol(self):
|
||||||
|
self.configuration.target_protocol = 'iser'
|
||||||
|
self.assertRaises(exception.UnsupportedNVMETProtocol,
|
||||||
|
FakeNVMeOFDriver,
|
||||||
|
root_helper=utils.get_root_helper(),
|
||||||
|
configuration=self.configuration)
|
@ -146,12 +146,13 @@ volume_opts = [
|
|||||||
cfg.StrOpt('target_protocol',
|
cfg.StrOpt('target_protocol',
|
||||||
deprecated_name='iscsi_protocol',
|
deprecated_name='iscsi_protocol',
|
||||||
default='iscsi',
|
default='iscsi',
|
||||||
choices=['iscsi', 'iser'],
|
choices=['iscsi', 'iser', 'nvmet_rdma'],
|
||||||
help='Determines the iSCSI protocol for new iSCSI volumes, '
|
help='Determines the target protocol for new volumes, '
|
||||||
'created with tgtadm or lioadm target helpers. In '
|
'created with tgtadm, lioadm and nvmet target helpers. '
|
||||||
'order to enable RDMA, this parameter should be set '
|
'In order to enable RDMA, this parameter should be set '
|
||||||
'with the value "iser". The supported iSCSI protocol '
|
'with the value "iser". The supported iSCSI protocol '
|
||||||
'values are "iscsi" and "iser".'),
|
'values are "iscsi" and "iser", in case of nvmet target '
|
||||||
|
'set to "nvmet_rdma".'),
|
||||||
cfg.StrOpt('driver_client_cert_key',
|
cfg.StrOpt('driver_client_cert_key',
|
||||||
help='The path to the client certificate key for verification, '
|
help='The path to the client certificate key for verification, '
|
||||||
'if the driver supports it.'),
|
'if the driver supports it.'),
|
||||||
@ -304,12 +305,24 @@ iser_opts = [
|
|||||||
help='The name of the iSER target user-land tool to use'),
|
help='The name of the iSER target user-land tool to use'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
nvmet_opts = [
|
||||||
|
cfg.PortOpt('nvmet_port_id',
|
||||||
|
default=1,
|
||||||
|
help='The port that the NVMe target is listening on.'),
|
||||||
|
cfg.IntOpt('nvmet_ns_id',
|
||||||
|
default=10,
|
||||||
|
help='The namespace id associated with the subsystem '
|
||||||
|
'that will be created with the path for the LVM volume.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP)
|
CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(iser_opts, group=configuration.SHARED_CONF_GROUP)
|
CONF.register_opts(iser_opts, group=configuration.SHARED_CONF_GROUP)
|
||||||
|
CONF.register_opts(nvmet_opts, group=configuration.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(volume_opts)
|
CONF.register_opts(volume_opts)
|
||||||
CONF.register_opts(iser_opts)
|
CONF.register_opts(iser_opts)
|
||||||
|
CONF.register_opts(nvmet_opts)
|
||||||
CONF.import_opt('backup_use_same_host', 'cinder.backup.api')
|
CONF.import_opt('backup_use_same_host', 'cinder.backup.api')
|
||||||
|
|
||||||
|
|
||||||
@ -367,6 +380,7 @@ class BaseVD(object):
|
|||||||
if self.configuration:
|
if self.configuration:
|
||||||
self.configuration.append_config_values(volume_opts)
|
self.configuration.append_config_values(volume_opts)
|
||||||
self.configuration.append_config_values(iser_opts)
|
self.configuration.append_config_values(iser_opts)
|
||||||
|
self.configuration.append_config_values(nvmet_opts)
|
||||||
utils.setup_tracing(self.configuration.safe_get('trace_flags'))
|
utils.setup_tracing(self.configuration.safe_get('trace_flags'))
|
||||||
|
|
||||||
# NOTE(geguileo): Don't allow to start if we are enabling
|
# NOTE(geguileo): Don't allow to start if we are enabling
|
||||||
|
157
cinder/volume/targets/nvmeof.py
Normal file
157
cinder/volume/targets/nvmeof.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# 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 abc
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.volume.targets import driver
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NVMeOF(driver.Target):
|
||||||
|
|
||||||
|
"""Target object for block storage devices with RDMA transport."""
|
||||||
|
|
||||||
|
protocol = 'nvmeof'
|
||||||
|
target_protocol_map = {
|
||||||
|
'nvmet_rdma': 'rdma',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Reads NVMeOF configurations."""
|
||||||
|
|
||||||
|
super(NVMeOF, self).__init__(*args, **kwargs)
|
||||||
|
self.target_ip = self.configuration.target_ip_address
|
||||||
|
self.target_port = self.configuration.target_port
|
||||||
|
self.nvmet_port_id = self.configuration.nvmet_port_id
|
||||||
|
self.nvmet_ns_id = self.configuration.nvmet_ns_id
|
||||||
|
self.nvmet_subsystem_name = self.configuration.target_prefix
|
||||||
|
target_protocol = self.configuration.target_protocol
|
||||||
|
if target_protocol in self.target_protocol_map:
|
||||||
|
self.nvme_transport_type = self.target_protocol_map[
|
||||||
|
target_protocol]
|
||||||
|
else:
|
||||||
|
raise exception.UnsupportedNVMETProtocol(
|
||||||
|
protocol=target_protocol
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize_connection(self, volume, connector):
|
||||||
|
"""Returns the connection info.
|
||||||
|
|
||||||
|
In NVMeOF driver, :driver_volume_type: is set to 'nvmeof',
|
||||||
|
:data: is the driver data that has the value of
|
||||||
|
_get_connection_properties
|
||||||
|
Example return value::
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
'driver_volume_type': 'nvmeof'
|
||||||
|
'data': {
|
||||||
|
'target_portal': '1.1.1.1',
|
||||||
|
'target_port': 4420,
|
||||||
|
'nqn': 'nqn.volume-0001',
|
||||||
|
'transport_type': 'rdma',
|
||||||
|
'ns_id': 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'driver_volume_type': self.protocol,
|
||||||
|
'data': self._get_connection_properties(volume)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_connection_properties(self, volume):
|
||||||
|
"""Gets NVMeOF connection configuration.
|
||||||
|
|
||||||
|
:return: dictionary of the following keys:
|
||||||
|
:target_portal: NVMe target IP address
|
||||||
|
:target_port: NVMe target port
|
||||||
|
:nqn: NQN of the NVMe target
|
||||||
|
:transport_type: Network fabric being used for an
|
||||||
|
NVMe-over-Fabrics network
|
||||||
|
:ns_id: namespace id associated with the subsystem
|
||||||
|
"""
|
||||||
|
|
||||||
|
location = volume['provider_location']
|
||||||
|
target_connection, nvme_transport_type, nqn, nvmet_ns_id = (
|
||||||
|
location.split(' '))
|
||||||
|
target_portal, target_port = target_connection.split(':')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'target_portal': target_portal,
|
||||||
|
'target_port': target_port,
|
||||||
|
'nqn': nqn,
|
||||||
|
'transport_type': nvme_transport_type,
|
||||||
|
'ns_id': nvmet_ns_id
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_nvmeof_location(self, nqn, target_ip, target_port,
|
||||||
|
nvme_transport_type, nvmet_ns_id):
|
||||||
|
"""Serializes driver data into single line string."""
|
||||||
|
|
||||||
|
return "%(ip)s:%(port)s %(transport)s %(nqn)s %(ns_id)s" % (
|
||||||
|
{'ip': target_ip,
|
||||||
|
'port': target_port,
|
||||||
|
'transport': nvme_transport_type,
|
||||||
|
'nqn': nqn,
|
||||||
|
'ns_id': nvmet_ns_id})
|
||||||
|
|
||||||
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_export(self, context, volume, volume_path):
|
||||||
|
"""Creates export data for a logical volume."""
|
||||||
|
|
||||||
|
return self.create_nvmeof_target(
|
||||||
|
volume['id'],
|
||||||
|
self.configuration.target_prefix,
|
||||||
|
self.target_ip,
|
||||||
|
self.target_port,
|
||||||
|
self.nvme_transport_type,
|
||||||
|
self.nvmet_port_id,
|
||||||
|
self.nvmet_ns_id,
|
||||||
|
volume_path)
|
||||||
|
|
||||||
|
def ensure_export(self, context, volume, volume_path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def remove_export(self, context, volume):
|
||||||
|
return self.delete_nvmeof_target(volume)
|
||||||
|
|
||||||
|
def validate_connector(self, connector):
|
||||||
|
if 'initiator' not in connector:
|
||||||
|
LOG.error('The volume driver requires the NVMe initiator '
|
||||||
|
'name in the connector.')
|
||||||
|
raise exception.InvalidConnectorException(
|
||||||
|
missing='initiator')
|
||||||
|
return True
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_nvmeof_target(self,
|
||||||
|
volume_id,
|
||||||
|
subsystem_name,
|
||||||
|
target_ip,
|
||||||
|
target_port,
|
||||||
|
transport_type,
|
||||||
|
nvmet_port_id,
|
||||||
|
ns_id,
|
||||||
|
volume_path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_nvmeof_target(self, target_name):
|
||||||
|
pass
|
Loading…
x
Reference in New Issue
Block a user