diff --git a/ironic_python_agent/tests/unit/test_utils.py b/ironic_python_agent/tests/unit/test_utils.py index f5972b5b4..3dd7a5cb7 100644 --- a/ironic_python_agent/tests/unit/test_utils.py +++ b/ironic_python_agent/tests/unit/test_utils.py @@ -1028,21 +1028,35 @@ class TestCopyConfigFromVmedia(testtools.TestCase): mock_copy.assert_not_called() def test_no_files(self, mock_execute, mock_copy, mock_find_device): + mock_execute.side_effect = [ + processutils.ProcessExecutionError, + ('', ''), + ('', ''), + ] mock_find_device.return_value = '/dev/something' utils.copy_config_from_vmedia() mock_execute.assert_has_calls([ + mock.call('findmnt', '-n', '-oTARGET', '/dev/something'), mock.call('mount', '/dev/something', mock.ANY), mock.call('umount', mock.ANY), ]) mock_copy.assert_not_called() + def test_mounted_no_files(self, mock_execute, mock_copy, mock_find_device): + mock_execute.return_value = '/some/path', '' + mock_find_device.return_value = '/dev/something' + utils.copy_config_from_vmedia() + mock_execute.assert_called_once_with( + 'findmnt', '-n', '-oTARGET', '/dev/something') + mock_copy.assert_not_called() + @mock.patch.object(os, 'makedirs', autospec=True) def test_copy(self, mock_makedirs, mock_execute, mock_copy, mock_find_device): mock_find_device.return_value = '/dev/something' path = None - def _fake_exec(command, arg1, arg2=None): + def _fake_exec(command, arg1, arg2=None, *args): nonlocal path if command == 'mount': path = arg2 @@ -1059,6 +1073,8 @@ class TestCopyConfigFromVmedia(testtools.TestCase): with open(os.path.join(path, 'etc', 'ironic-python-agent.d', 'ironic.conf'), 'wt') as fp: fp.write('I am a config') + elif command == 'findmnt': + raise processutils.ProcessExecutionError("") else: self.assertEqual('umount', command) @@ -1080,3 +1096,39 @@ class TestCopyConfigFromVmedia(testtools.TestCase): mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'), ], any_order=True) self.assertFalse(os.path.exists(path)) + + @mock.patch.object(os, 'makedirs', autospec=True) + def test_copy_mounted(self, mock_makedirs, mock_execute, mock_copy, + mock_find_device): + mock_find_device.return_value = '/dev/something' + path = tempfile.mkdtemp() + self.addCleanup(lambda: shutil.rmtree(path)) + + # NOTE(dtantsur): makedirs is mocked + os.mkdir(os.path.join(path, 'etc')) + os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent')) + os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent.d')) + with open(os.path.join(path, 'not copied'), 'wt') as fp: + fp.write('not copied') + with open(os.path.join(path, 'etc', 'ironic-python-agent', + 'ironic.crt'), 'wt') as fp: + fp.write('I am a cert') + with open(os.path.join(path, 'etc', 'ironic-python-agent.d', + 'ironic.conf'), 'wt') as fp: + fp.write('I am a config') + + mock_execute.return_value = path, '' + mock_find_device.return_value = '/dev/something' + + utils.copy_config_from_vmedia() + + mock_makedirs.assert_has_calls([ + mock.call('/etc/ironic-python-agent', exist_ok=True), + mock.call('/etc/ironic-python-agent.d', exist_ok=True), + ], any_order=True) + mock_execute.assert_called_once_with( + 'findmnt', '-n', '-oTARGET', '/dev/something') + mock_copy.assert_has_calls([ + mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'), + mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'), + ], any_order=True) diff --git a/ironic_python_agent/utils.py b/ironic_python_agent/utils.py index b2310d00e..a897210ec 100644 --- a/ironic_python_agent/utils.py +++ b/ironic_python_agent/utils.py @@ -212,6 +212,39 @@ def _early_log(msg, *args): print('ironic-python-agent:', msg % args, file=sys.stderr) +def _copy_config_from(path): + for ext in ('', '.d'): + src = os.path.join(path, 'etc', 'ironic-python-agent%s' % ext) + if not os.path.isdir(src): + _early_log('%s not found', src) + continue + + dest = '/etc/ironic-python-agent%s' % ext + _early_log('Copying configuration from %s to %s', src, dest) + try: + os.makedirs(dest, exist_ok=True) + + # TODO(dtantsur): use shutil.copytree(.., dirs_exist_ok=True) + # when the minimum supported Python is 3.8. + for name in os.listdir(src): + src_file = os.path.join(src, name) + dst_file = os.path.join(dest, name) + shutil.copy(src_file, dst_file) + except Exception as exc: + msg = ("Unable to copy vmedia configuration %s to %s: %s" + % (src, dest, exc)) + raise errors.VirtualMediaBootError(msg) + + +def _find_mount_point(device): + try: + path, _e = execute('findmnt', '-n', '-oTARGET', device) + except processutils.ProcessExecutionError: + return + else: + return path.strip() + + def copy_config_from_vmedia(): """Copies any configuration from a virtual media device. @@ -223,29 +256,12 @@ def copy_config_from_vmedia(): _early_log('No virtual media device detected') return - with _mounted(vmedia_device_file) as vmedia_mount_point: - for ext in ('', '.d'): - src = os.path.join(vmedia_mount_point, 'etc', - 'ironic-python-agent%s' % ext) - if not os.path.isdir(src): - _early_log('%s not found', src) - continue - - dest = '/etc/ironic-python-agent%s' % ext - _early_log('Copying configuration from %s to %s', src, dest) - try: - os.makedirs(dest, exist_ok=True) - - # TODO(dtantsur): use shutil.copytree(.., dirs_exist_ok=True) - # when the minimum supported Python is 3.8. - for name in os.listdir(src): - src_file = os.path.join(src, name) - dst_file = os.path.join(dest, name) - shutil.copy(src_file, dst_file) - except Exception as exc: - msg = ("Unable to copy vmedia configuration %s to %s: %s" - % (src, dest, exc)) - raise errors.VirtualMediaBootError(msg) + mounted = _find_mount_point(vmedia_device_file) + if mounted: + _copy_config_from(mounted) + else: + with _mounted(vmedia_device_file) as vmedia_mount_point: + _copy_config_from(vmedia_mount_point) def _get_cached_params():