diff options
Diffstat (limited to 'ironic_python_agent/tests/unit/test_hardware.py')
-rw-r--r-- | ironic_python_agent/tests/unit/test_hardware.py | 202 |
1 files changed, 196 insertions, 6 deletions
diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index a610eb2f..21349050 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -1710,10 +1710,12 @@ class TestGenericHardwareManager(base.IronicAgentTest): mocked_listdir.assert_has_calls(expected_calls) mocked_mpath.assert_called_once_with() + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch.object(hardware, 'ThreadPool', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) def test_erase_devices_no_parallel_by_default(self, mocked_dispatch, - mock_threadpool): + mock_threadpool, + mock_safety_check): # NOTE(TheJulia): This test was previously more elaborate and # had a high failure rate on py37 and py38. So instead, lets just @@ -1732,10 +1734,43 @@ class TestGenericHardwareManager(base.IronicAgentTest): calls = [mock.call(1)] self.hardware.erase_devices({}, []) mock_threadpool.assert_has_calls(calls) + mock_safety_check.assert_has_calls([ + mock.call({}, '/dev/sdj'), + mock.call({}, '/dev/hdaa') + ]) + + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) + @mock.patch.object(hardware, 'ThreadPool', autospec=True) + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + def test_erase_devices_no_parallel_by_default_protected_device( + self, mocked_dispatch, + mock_threadpool, + mock_safety_check): + mock_safety_check.side_effect = errors.ProtectedDeviceError( + device='foo', + what='bar') + + self.hardware.list_block_devices = mock.Mock() + + self.hardware.list_block_devices.return_value = [ + hardware.BlockDevice('/dev/sdj', 'big', 1073741824, True), + hardware.BlockDevice('/dev/hdaa', 'small', 65535, False), + ] + + calls = [mock.call(1)] + self.assertRaises(errors.ProtectedDeviceError, + self.hardware.erase_devices, {}, []) + mock_threadpool.assert_has_calls(calls) + mock_safety_check.assert_has_calls([ + mock.call({}, '/dev/sdj'), + ]) + mock_threadpool.apply_async.assert_not_called() + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch('multiprocessing.pool.ThreadPool.apply_async', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - def test_erase_devices_concurrency(self, mocked_dispatch, mocked_async): + def test_erase_devices_concurrency(self, mocked_dispatch, mocked_async, + mock_safety_check): internal_info = self.node['driver_internal_info'] internal_info['disk_erasure_concurrency'] = 10 mocked_dispatch.return_value = 'erased device' @@ -1760,9 +1795,15 @@ class TestGenericHardwareManager(base.IronicAgentTest): for dev in (blkdev1, blkdev2)] mocked_async.assert_has_calls(calls) self.assertEqual(expected, result) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sdj'), + mock.call(self.node, '/dev/hdaa'), + ]) + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch.object(hardware, 'ThreadPool', autospec=True) - def test_erase_devices_concurrency_pool_size(self, mocked_pool): + def test_erase_devices_concurrency_pool_size(self, mocked_pool, + mock_safety_check): self.hardware.list_block_devices = mock.Mock() self.hardware.list_block_devices.return_value = [ hardware.BlockDevice('/dev/sdj', 'big', 1073741824, True), @@ -1782,6 +1823,10 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.hardware.erase_devices(self.node, []) mocked_pool.assert_called_with(1) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sdj'), + mock.call(self.node, '/dev/hdaa'), + ]) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) def test_erase_devices_without_disk(self, mocked_dispatch): @@ -2441,6 +2486,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('/sys/fs/pstore/' + arg) for arg in pstore_entries ]) + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch.object(il_utils, 'execute', autospec=True) @mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, @@ -2449,7 +2495,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): '_list_erasable_devices', autospec=True) def test_erase_devices_express( self, mock_list_erasable_devices, mock_nvme_erase, - mock_destroy_disk_metadata, mock_execute): + mock_destroy_disk_metadata, mock_execute, mock_safety_check): block_devices = [ hardware.BlockDevice('/dev/sda', 'sata', 65535, False), hardware.BlockDevice('/dev/md0', 'raid-device', 32767, False), @@ -2466,7 +2512,44 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('/dev/md0', self.node['uuid'])], mock_destroy_disk_metadata.call_args_list) mock_list_erasable_devices.assert_called_with(self.hardware) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sda'), + mock.call(self.node, '/dev/md0'), + mock.call(self.node, '/dev/nvme0n1'), + mock.call(self.node, '/dev/nvme1n1') + ]) + + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + @mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) + @mock.patch.object(hardware.GenericHardwareManager, + '_nvme_erase', autospec=True) + @mock.patch.object(hardware.GenericHardwareManager, + '_list_erasable_devices', autospec=True) + def test_erase_devices_express_stops_on_safety_failure( + self, mock_list_erasable_devices, mock_nvme_erase, + mock_destroy_disk_metadata, mock_execute, mock_safety_check): + mock_safety_check.side_effect = errors.ProtectedDeviceError( + device='foo', + what='bar') + block_devices = [ + hardware.BlockDevice('/dev/sda', 'sata', 65535, False), + hardware.BlockDevice('/dev/md0', 'raid-device', 32767, False), + hardware.BlockDevice('/dev/nvme0n1', 'nvme', 32767, False), + hardware.BlockDevice('/dev/nvme1n1', 'nvme', 32767, False) + ] + mock_list_erasable_devices.return_value = list(block_devices) + + self.assertRaises(errors.ProtectedDeviceError, + self.hardware.erase_devices_express, self.node, []) + mock_nvme_erase.assert_not_called() + mock_destroy_disk_metadata.assert_not_called() + mock_list_erasable_devices.assert_called_with(self.hardware) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sda') + ]) + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch.object(il_utils, 'execute', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, '_is_virtual_media_device', autospec=True) @@ -2475,7 +2558,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): @mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) def test_erase_devices_metadata( self, mock_metadata, mock_list_devs, mock__is_vmedia, - mock_execute): + mock_execute, mock_safety_check): block_devices = [ hardware.BlockDevice('/dev/sr0', 'vmedia', 12345, True), hardware.BlockDevice('/dev/sdb2', 'raid-member', 32767, False), @@ -2509,7 +2592,62 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call(self.hardware, block_devices[2]), mock.call(self.hardware, block_devices[5])], mock__is_vmedia.call_args_list) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sda1'), + mock.call(self.node, '/dev/sda'), + # This is kind of redundant code/pattern behavior wise + # but you never know what someone has done... + mock.call(self.node, '/dev/md0') + ]) + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + @mock.patch.object(hardware.GenericHardwareManager, + '_is_virtual_media_device', autospec=True) + @mock.patch.object(hardware.GenericHardwareManager, + 'list_block_devices', autospec=True) + @mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) + def test_erase_devices_metadata_safety_check( + self, mock_metadata, mock_list_devs, mock__is_vmedia, + mock_execute, mock_safety_check): + block_devices = [ + hardware.BlockDevice('/dev/sr0', 'vmedia', 12345, True), + hardware.BlockDevice('/dev/sdb2', 'raid-member', 32767, False), + hardware.BlockDevice('/dev/sda', 'small', 65535, False), + hardware.BlockDevice('/dev/sda1', '', 32767, False), + hardware.BlockDevice('/dev/sda2', 'raid-member', 32767, False), + hardware.BlockDevice('/dev/md0', 'raid-device', 32767, False) + ] + # NOTE(coreywright): Don't return the list, but a copy of it, because + # we depend on its elements' order when referencing it later during + # verification, but the method under test sorts the list changing it. + mock_list_devs.return_value = list(block_devices) + mock__is_vmedia.side_effect = lambda _, dev: dev.name == '/dev/sr0' + mock_execute.side_effect = [ + ('sdb2 linux_raid_member host:1 f9978968', ''), + ('sda2 linux_raid_member host:1 f9978969', ''), + ('sda1', ''), ('sda', ''), ('md0', '')] + mock_safety_check.side_effect = [ + None, + errors.ProtectedDeviceError( + device='foo', + what='bar') + ] + + self.assertRaises(errors.ProtectedDeviceError, + self.hardware.erase_devices_metadata, + self.node, []) + + self.assertEqual([mock.call('/dev/sda1', self.node['uuid'])], + mock_metadata.call_args_list) + mock_list_devs.assert_called_with(self.hardware, + include_partitions=True) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sda1'), + mock.call(self.node, '/dev/sda'), + ]) + + @mock.patch.object(hardware, 'safety_check_block_device', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, '_is_linux_raid_member', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, @@ -2519,7 +2657,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): @mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) def test_erase_devices_metadata_error( self, mock_metadata, mock_list_devs, mock__is_vmedia, - mock__is_raid_member): + mock__is_raid_member, mock_safety_check): block_devices = [ hardware.BlockDevice('/dev/sda', 'small', 65535, False), hardware.BlockDevice('/dev/sdb', 'big', 10737418240, True), @@ -2553,6 +2691,10 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([mock.call(self.hardware, block_devices[1]), mock.call(self.hardware, block_devices[0])], mock__is_vmedia.call_args_list) + mock_safety_check.assert_has_calls([ + mock.call(self.node, '/dev/sdb'), + mock.call(self.node, '/dev/sda') + ]) @mock.patch.object(il_utils, 'execute', autospec=True) def test__is_linux_raid_member(self, mocked_execute): @@ -4991,3 +5133,51 @@ class TestAPIClientSaveAndUse(base.IronicAgentTest): calls = [mock.call('list_hardware_info'), mock.call('wait_for_disks')] mock_dispatch.assert_has_calls(calls) + + +@mock.patch.object(il_utils, 'execute', autospec=True) +class TestProtectedDiskSafetyChecks(base.IronicAgentTest): + + def test_special_filesystem_guard_not_enabled(self, mock_execute): + CONF.set_override('guard_special_filesystems', False) + hardware.safety_check_block_device({}, '/dev/foo') + mock_execute.assert_not_called() + + def test_special_filesystem_guard_node_indicates_skip(self, mock_execute): + node = { + 'driver_internal_info': { + 'wipe_special_filesystems': False + } + } + mock_execute.return_value = ('', '') + hardware.safety_check_block_device(node, '/dev/foo') + mock_execute.assert_not_called() + + def test_special_filesystem_guard_enabled_no_results(self, mock_execute): + mock_execute.return_value = ('', '') + hardware.safety_check_block_device({}, '/dev/foo') + + def test_special_filesystem_guard_raises(self, mock_execute): + GFS2 = 'FSTYPE="gfs2"\n' + GPFS1 = 'UUID="37AFFC90-EF7D-4E96-91C3-2D7AE055B174"\n' + GPFS2 = 'PTUUID="37AFFC90-EF7D-4E96-91C3-2D7AE055B174"\n' + GPFS3 = 'PARTTYPE="37AFFC90-EF7D-4E96-91C3-2D7AE055B174"\n' + GPFS4 = 'PARTUUID="37AFFC90-EF7D-4E96-91C3-2D7AE055B174"\n' + VMFS1 = 'UUID="AA31E02A-400F-11DB-9590-000C2911D1B8"\n' + VMFS2 = 'UUID="AA31E02A-400F-11DB-9590-000C2911D1B8"\n' + VMFS3 = 'UUID="AA31E02A-400F-11DB-9590-000C2911D1B8"\n' + VMFS4 = 'UUID="AA31E02A-400F-11DB-9590-000C2911D1B8"\n' + VMFS5 = 'UUID="0xfb"\n' + VMFS6 = 'PTUUID="0xfb"\n' + VMFS7 = 'PARTTYPE="0xfb"\n' + VMFS8 = 'PARTUUID="0xfb"\n' + + expected_failures = [GFS2, GPFS1, GPFS2, GPFS3, GPFS4, VMFS1, VMFS2, + VMFS3, VMFS4, VMFS5, VMFS6, VMFS7, VMFS8] + for failure in expected_failures: + mock_execute.reset_mock() + mock_execute.return_value = (failure, '') + self.assertRaises(errors.ProtectedDeviceError, + hardware.safety_check_block_device, + {}, '/dev/foo') + self.assertEqual(1, mock_execute.call_count) |