# 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. from unittest import mock import ddt from oslo_concurrency import processutils as putils from os_brick import exception from os_brick.initiator.connectors import nvmeof from os_brick.initiator import linuxscsi from os_brick.tests.initiator import test_connector FAKE_NVME_LIST_OUTPUT = """ Node SN Model \ Namespace Usage Format FW Rev\n ---------------- -------------------- ---------------------------------------\ - --------- -------------------------- ---------------- --------\n /dev/nvme0n1 67ff9467da6e5567 Linux \ 10 1.07 GB / 1.07 GB 512 B + 0 B 4.8.0-58\n /dev/nvme11n12 fecc8e73584753d7 Linux \ 1 3.22 GB / 3.22 GB 512 B + 0 B 4.8.0-56\n """ FAKE_NVME_LIST_SUBSYS = """ { "Subsystems" : [ { "Name" : "nvme-subsys0", "NQN" : "nqn.fake:cnode1" }, { "Paths" : [ { "Name" : "nvme0", "Transport" : "rdma", "Address" : "traddr=10.0.2.15 trsvcid=4420" } ] }, { "Name" : "nvme-subsys1", "NQN" : "nqn.2016-06.io.spdk:cnode1" }, { "Paths" : [ { "Name" : "nvme1", "Transport" : "rdma", "Address" : "traddr=10.0.2.15 trsvcid=4420" } ] } ] } """ NVME_DATA1 = {'nvme_transport_type': 'rdma', 'conn_nqn': 'nqn.2016-06.io.spdk:cnode1', 'target_portal': '10.0.2.15', 'port': '4420'} NVME_DATA2 = {'nvme_transport_type': 'rdma', 'conn_nqn': 'nqn.2016-06.io.spdk:cnode2', 'target_portal': '10.0.2.15', 'port': '4420'} @ddt.ddt class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase): """Test cases for NVMe initiator class.""" def setUp(self): super(NVMeOFConnectorTestCase, self).setUp() self.connector = nvmeof.NVMeOFConnector(None, execute=self.fake_execute, use_multipath=False) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) def test_get_sysuuid_without_newline(self, mock_execute): mock_execute.return_value = ( "9126E942-396D-11E7-B0B7-A81E84C186D1\n", "") uuid = self.connector._get_system_uuid() expected_uuid = "9126E942-396D-11E7-B0B7-A81E84C186D1" self.assertEqual(expected_uuid, uuid) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) def test_get_connector_properties_without_sysuuid( self, mock_execute): mock_execute.side_effect = putils.ProcessExecutionError props = self.connector.get_connector_properties('sudo') expected_props = {} self.assertEqual(expected_props, props) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid', autospec=True) def test_get_connector_properties_with_sysuuid( self, mock_sysuuid): mock_sysuuid.return_value = "9126E942-396D-11E7-B0B7-A81E84C186D1" props = self.connector.get_connector_properties('sudo') expected_props = { "system uuid": "9126E942-396D-11E7-B0B7-A81E84C186D1"} self.assertEqual(expected_props, props) def _nvmeof_list_cmd(self, *args, **kwargs): return FAKE_NVME_LIST_OUTPUT, None def test__get_nvme_devices(self): expected = ['/dev/nvme0n1', '/dev/nvme11n12'] self.connector._execute = self._nvmeof_list_cmd actual = self.connector._get_nvme_devices() self.assertEqual(expected, actual) @ddt.unpack @ddt.data({'expected': True, 'nvme': NVME_DATA1, 'list_subsys': FAKE_NVME_LIST_SUBSYS, 'nvme_list': ['/dev/nvme0n1', '/dev/nvme1n1']}, {'expected': False, 'nvme': NVME_DATA2, 'list_subsys': FAKE_NVME_LIST_SUBSYS, 'nvme_list': ['/dev/nvme1n1']}, {'expected': False, 'nvme': NVME_DATA1, 'list_subsys': '{}', 'nvme_list': ['dev/nvme1n1']}) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_subsys', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test__wait_for_blk(self, mock_sleep, mock_nvme_subsys, mock_nvme_dev, expected, nvme, list_subsys, nvme_list): mock_nvme_subsys.return_value = (list_subsys, "") mock_nvme_dev.return_value = nvme_list actual = self.connector._wait_for_blk(**nvme) self.assertEqual(expected, actual) @ddt.unpack @ddt.data({'expected': False, 'nvme': NVME_DATA1, 'list_subsys': FAKE_NVME_LIST_SUBSYS, 'nvme_list': ['/dev/nvme0n1']}) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_subsys', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test__wait_for_blk_raise(self, mock_sleep, mock_nvme_subsys, mock_nvme_dev, expected, nvme, list_subsys, nvme_list): mock_nvme_subsys.return_value = (list_subsys, "") mock_nvme_dev.return_value = nvme_list self.assertRaises(exception.NotFound, self.connector._wait_for_blk, **nvme) @ddt.unpack @ddt.data({'expected': True, 'nvme': NVME_DATA1, 'list_subsys': FAKE_NVME_LIST_SUBSYS, 'nvme_list': ['dev/nvme0n1', '/dev/nvme1n1']}) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_subsys', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test__wait_for_blk_retry_success(self, mock_sleep, mock_nvme_subsys, mock_nvme_dev, expected, nvme, list_subsys, nvme_list): mock_nvme_subsys.return_value = (list_subsys, "") mock_nvme_dev.side_effect = [[], nvme_list] actual = self.connector._wait_for_blk(**nvme) self.assertEqual(expected, actual) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_get_nvme_devices_raise(self, mock_sleep, mock_execute): mock_execute.side_effect = putils.ProcessExecutionError self.assertRaises(exception.CommandExecutionFailed, self.connector._get_nvme_devices) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume(self, mock_sleep, mock_execute, mock_devices, mock_blk): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_devices.side_effect = [ ['/dev/nvme0n1'], ['/dev/nvme0n2']] mock_blk.return_value = True device_info = self.connector.connect_volume( connection_properties) self.assertEqual('/dev/nvme0n2', device_info['path']) self.assertEqual('block', device_info['type']) self.assertEqual(2, mock_devices.call_count) mock_execute.assert_called_once_with( self.connector, 'nvme', 'connect', '-t', connection_properties['transport_type'], '-n', 'nqn.volume_123', '-a', connection_properties['target_portal'], '-s', connection_properties['target_port'], root_helper=None, run_as_root=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume_hostnqn( self, mock_sleep, mock_execute, mock_devices, mock_blk): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma', 'host_nqn': 'nqn.host_456'} mock_devices.side_effect = [ ['/dev/nvme0n1'], ['/dev/nvme0n2']] mock_blk.return_value = True device_info = self.connector.connect_volume( connection_properties) self.assertEqual('/dev/nvme0n2', device_info['path']) self.assertEqual('block', device_info['type']) self.assertEqual(2, mock_devices.call_count) mock_execute.assert_called_once_with( self.connector, 'nvme', 'connect', '-t', connection_properties['transport_type'], '-n', connection_properties['nqn'], '-a', connection_properties['target_portal'], '-s', connection_properties['target_port'], '-q', connection_properties['host_nqn'], root_helper=None, run_as_root=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume_raise(self, mock_sleep, mock_execute): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_execute.side_effect = putils.ProcessExecutionError self.assertRaises(exception.CommandExecutionFailed, self.connector.connect_volume, connection_properties) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_subsys', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume_wait_for_blk_raise(self, mock_sleep, mock_blk, mock_subsys, mock_devices, mock_execute): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_blk.side_effect = exception.NotFound self.assertRaises(exception.NotFound, self.connector.connect_volume, connection_properties) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume_max_retry( self, mock_sleep, mock_execute, mock_devices, mock_blk): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_devices.return_value = '/dev/nvme0n1' mock_blk.return_value = True self.assertRaises(exception.VolumePathsNotFound, self.connector.connect_volume, connection_properties) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_volume_nvmelist_retry_success( self, mock_sleep, mock_execute, mock_devices, mock_blk): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_devices.side_effect = [ ['/dev/nvme0n1'], ['/dev/nvme0n1'], ['/dev/nvme0n1', '/dev/nvme0n2']] mock_blk.return_value = True device_info = self.connector.connect_volume( connection_properties) self.assertEqual('/dev/nvme0n2', device_info['path']) self.assertEqual('block', device_info['type']) @mock.patch.object(nvmeof.NVMeOFConnector, '_wait_for_blk', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_connect_nvme_retry_success( self, mock_sleep, mock_execute, mock_devices, mock_blk): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} mock_devices.side_effect = [ ['/dev/nvme0n1'], ['/dev/nvme0n1', '/dev/nvme0n2']] mock_blk.return_value = True device_info = self.connector.connect_volume( connection_properties) mock_execute.side_effect = [ putils.ProcessExecutionError, putils.ProcessExecutionError, None] self.assertEqual('/dev/nvme0n2', device_info['path']) self.assertEqual('block', device_info['type']) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_disconnect_volume_nova( self, mock_sleep, mock_execute, mock_devices): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '/dev/nvme0n1', 'transport_type': 'rdma'} mock_devices.return_value = '/dev/nvme0n1' self.connector.disconnect_volume(connection_properties, None) mock_execute.assert_called_once_with( self.connector, 'nvme', 'disconnect', '-n', 'nqn.volume_123', root_helper=None, run_as_root=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_disconnect_volume_cinder( self, mock_sleep, mock_execute, mock_devices): connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'transport_type': 'rdma'} device_info = {'path': '/dev/nvme0n1'} mock_devices.return_value = '/dev/nvme0n1' self.connector.disconnect_volume(connection_properties, device_info, ignore_errors=True) mock_execute.assert_called_once_with( self.connector, 'nvme', 'disconnect', '-n', 'nqn.volume_123', root_helper=None, run_as_root=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_devices', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True) @mock.patch('os_brick.utils._time_sleep') def test_disconnect_volume_raise( self, mock_sleep, mock_execute, mock_devices): mock_execute.side_effect = putils.ProcessExecutionError mock_devices.return_value = '/dev/nvme0n1' connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '/dev/nvme0n1', 'transport_type': 'rdma'} self.assertRaises(putils.ProcessExecutionError, self.connector.disconnect_volume, connection_properties, None) @mock.patch.object(nvmeof.NVMeOFConnector, 'get_volume_paths', autospec=True) def test_extend_volume_no_path(self, mock_volume_paths): mock_volume_paths.return_value = [] connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} self.assertRaises(exception.VolumePathsNotFound, self.connector.extend_volume, connection_properties) @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path', autospec=True) @mock.patch.object(linuxscsi.LinuxSCSI, 'extend_volume', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, 'get_volume_paths', autospec=True) def test_extend_volume(self, mock_volume_paths, mock_scsi_extend, mock_scsi_find_mpath): fake_new_size = 1024 mock_volume_paths.return_value = ['/dev/vdx'] mock_scsi_extend.return_value = fake_new_size connection_properties = {'target_portal': 'portal', 'target_port': 1, 'nqn': 'nqn.volume_123', 'device_path': '', 'transport_type': 'rdma'} new_size = self.connector.extend_volume(connection_properties) self.assertEqual(fake_new_size, new_size) self.assertFalse(mock_scsi_find_mpath.called)