diff --git a/lower-constraints.txt b/lower-constraints.txt index 2d464e0afe..99b46bfaae 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -66,7 +66,7 @@ netifaces==0.10.4 networkx==1.11 numpy==1.14.2 openstacksdk==0.12.0 -os-brick==2.4.0 +os-brick==2.5.0 os-client-config==1.29.0 os-service-types==1.2.0 os-traits==0.4.0 diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py index 8349455d06..af96840db9 100644 --- a/nova/conf/libvirt.py +++ b/nova/conf/libvirt.py @@ -1190,6 +1190,18 @@ v2.10."""), ] +libvirt_volume_nvmeof_opts = [ + cfg.IntOpt('num_nvme_discover_tries', + default=3, + help=""" +Number of times to rediscover NVMe target to find volume + +Nova provides support for block storage attaching to hosts via NVMe +(Non-Volatile Memory Express). This option allows the user to specify the +maximum number of retry attempts that can be made to discover the NVMe device. +"""), +] + ALL_OPTS = list(itertools.chain( libvirt_general_opts, libvirt_imagebackend_opts, @@ -1208,6 +1220,7 @@ ALL_OPTS = list(itertools.chain( libvirt_remotefs_opts, libvirt_volume_vzstorage_opts, libvirt_virtio_queue_sizes, + libvirt_volume_nvmeof_opts, )) diff --git a/nova/tests/unit/virt/libvirt/volume/test_nvme.py b/nova/tests/unit/virt/libvirt/volume/test_nvme.py new file mode 100644 index 0000000000..997c914927 --- /dev/null +++ b/nova/tests/unit/virt/libvirt/volume/test_nvme.py @@ -0,0 +1,66 @@ +# 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 nova.tests.unit.virt.libvirt.volume import test_volume +from nova.virt.libvirt.volume import nvme + +from os_brick.initiator import connector + + +class LibvirtNVMEVolumeDriverTestCase(test_volume.LibvirtVolumeBaseTestCase): + + @mock.patch('os.path.exists', return_value=True) + def test_libvirt_nvme_driver(self, exists): + libvirt_driver = nvme.LibvirtNVMEVolumeDriver(self.fake_host) + self.assertIsInstance(libvirt_driver.connector, + connector.NVMeConnector) + + def test_libvirt_nvme_driver_connect(self): + nvme_driver = nvme.LibvirtNVMEVolumeDriver(self.fake_host) + config = {'server_ip': '127.0.0.1', 'server_port': 9898} + disk_info = { + 'id': '1234567', + 'name': 'aNVMEVolume', + 'conf': config} + connection_info = {'data': disk_info} + with mock.patch.object(nvme_driver.connector, + 'connect_volume', + return_value={'path': '/dev/dms1234567'}): + nvme_driver.connect_volume(connection_info, None) + self.assertEqual('/dev/dms1234567', + connection_info['data']['device_path']) + + def test_libvirt_nvme_driver_disconnect(self): + nvme_con = nvme.LibvirtNVMEVolumeDriver(self.connr) + nvme_con.connector.disconnect_volume = mock.MagicMock() + disk_info = { + 'path': '/dev/dms1234567', 'name': 'aNVMEVolume', + 'type': 'raw', 'dev': 'vda1', 'bus': 'pci0', + 'device_path': '/dev/dms123456'} + connection_info = {'data': disk_info} + nvme_con.disconnect_volume(connection_info, None) + nvme_con.connector.disconnect_volume.assert_called_once_with( + disk_info, None) + + def test_libvirt_nvme_driver_get_config(self): + nvme_driver = nvme.LibvirtNVMEVolumeDriver(self.fake_host) + device_path = '/dev/fake-dev' + connection_info = {'data': {'device_path': device_path}} + + conf = nvme_driver.get_config(connection_info, self.disk_info) + tree = conf.format_dom() + + self.assertEqual('block', tree.get('type')) + self.assertEqual(device_path, tree.find('./source').get('dev')) + self.assertEqual('raw', tree.find('./driver').get('type')) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 4e3de84435..9d283db1b6 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -187,6 +187,7 @@ libvirt_volume_drivers = [ 'nova.virt.libvirt.volume.vrtshyperscale.' 'LibvirtHyperScaleVolumeDriver', 'storpool=nova.virt.libvirt.volume.storpool.LibvirtStorPoolVolumeDriver', + 'nvmeof=nova.virt.libvirt.volume.nvme.LibvirtNVMEVolumeDriver', ] diff --git a/nova/virt/libvirt/volume/nvme.py b/nova/virt/libvirt/volume/nvme.py new file mode 100644 index 0000000000..8d20ab10d5 --- /dev/null +++ b/nova/virt/libvirt/volume/nvme.py @@ -0,0 +1,63 @@ +# 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 nova.conf +from nova import utils +from nova.virt.libvirt.volume import volume as libvirt_volume + +from os_brick import initiator +from os_brick.initiator import connector + +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + +CONF = nova.conf.CONF + + +class LibvirtNVMEVolumeDriver(libvirt_volume.LibvirtVolumeDriver): + """Driver to attach NVMe volumes to libvirt.""" + + def __init__(self, connection): + super(LibvirtNVMEVolumeDriver, + self).__init__(connection) + + self.connector = connector.InitiatorConnector.factory( + initiator.NVME, utils.get_root_helper(), + device_scan_attempts=CONF.libvirt.num_nvme_discover_tries) + + def connect_volume(self, connection_info, instance): + + device_info = self.connector.connect_volume( + connection_info['data']) + LOG.debug( + "Connecting NVMe volume with device_info %s", + device_info) + + connection_info['data']['device_path'] = device_info['path'] + + def disconnect_volume(self, connection_info, instance): + """Detach the volume from the instance.""" + LOG.debug("Disconnecting NVMe disk") + self.connector.disconnect_volume( + connection_info['data'], None) + super(LibvirtNVMEVolumeDriver, + self).disconnect_volume(connection_info, instance) + + def extend_volume(self, connection_info, instance): + """Extend the volume.""" + LOG.debug("calling os-brick to extend NVMe Volume", instance=instance) + new_size = self.connector.extend_volume(connection_info['data']) + LOG.debug("Extend NVMe Volume %s; new_size=%s", + connection_info['data']['device_path'], + new_size, instance=instance) + return new_size diff --git a/releasenotes/notes/bp-nvme-over-fabric-nova-ae1ef46fb5a7fc02.yaml b/releasenotes/notes/bp-nvme-over-fabric-nova-ae1ef46fb5a7fc02.yaml new file mode 100644 index 0000000000..1ec6f33d0c --- /dev/null +++ b/releasenotes/notes/bp-nvme-over-fabric-nova-ae1ef46fb5a7fc02.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added support for ``nvmeof`` type volumes to the libvirt driver. diff --git a/requirements.txt b/requirements.txt index 1b7631771e..43b82f6cfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,7 +52,7 @@ rfc3986>=0.3.1 # Apache-2.0 oslo.middleware>=3.31.0 # Apache-2.0 psutil>=3.2.2 # BSD oslo.versionedobjects>=1.31.2 # Apache-2.0 -os-brick>=2.4.0 # Apache-2.0 +os-brick>=2.5.0 # Apache-2.0 os-traits>=0.4.0 # Apache-2.0 os-vif!=1.8.0,>=1.7.0 # Apache-2.0 os-win>=3.0.0 # Apache-2.0