diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index 45634a9f6a..b6279e5435 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -15,6 +15,7 @@ PXE Boot Interface """ +import filecmp import os import shutil @@ -413,13 +414,14 @@ class PXEBoot(base.BootInterface): """ node = task.node - # TODO(deva): optimize this if rerun on existing files if CONF.pxe.ipxe_enabled: # Copy the iPXE boot script to HTTP root directory bootfile_path = os.path.join( CONF.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) - shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) + if (not os.path.isfile(bootfile_path) or + not filecmp.cmp(CONF.pxe.ipxe_boot_script, bootfile_path)): + shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py index 9e02d7b4d2..3f314c4860 100644 --- a/ironic/tests/unit/drivers/modules/test_pxe.py +++ b/ironic/tests/unit/drivers/modules/test_pxe.py @@ -15,6 +15,7 @@ """Test class for PXE driver.""" +import filecmp import os import shutil import tempfile @@ -698,12 +699,17 @@ class PXEBootTestCase(db_base.DbTestCase): self.node.save() self._test_prepare_ramdisk(uefi=True) + @mock.patch.object(os.path, 'isfile', autospec=True) + @mock.patch.object(filecmp, 'cmp', autospec=True) @mock.patch.object(shutil, 'copyfile', autospec=True) - def test_prepare_ramdisk_ipxe(self, copyfile_mock): + def test_prepare_ramdisk_ipxe_with_copy_file_different( + self, copyfile_mock, cmp_mock, isfile_mock): self.node.provision_state = states.DEPLOYING self.node.save() self.config(group='pxe', ipxe_enabled=True) self.config(group='deploy', http_url='http://myserver') + isfile_mock.return_value = True + cmp_mock.return_value = False self._test_prepare_ramdisk() copyfile_mock.assert_called_once_with( CONF.pxe.ipxe_boot_script, @@ -711,6 +717,38 @@ class PXEBootTestCase(db_base.DbTestCase): CONF.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script))) + @mock.patch.object(os.path, 'isfile', autospec=True) + @mock.patch.object(filecmp, 'cmp', autospec=True) + @mock.patch.object(shutil, 'copyfile', autospec=True) + def test_prepare_ramdisk_ipxe_with_copy_no_file( + self, copyfile_mock, cmp_mock, isfile_mock): + self.node.provision_state = states.DEPLOYING + self.node.save() + self.config(group='pxe', ipxe_enabled=True) + self.config(group='deploy', http_url='http://myserver') + isfile_mock.return_value = False + self._test_prepare_ramdisk() + self.assertFalse(cmp_mock.called) + copyfile_mock.assert_called_once_with( + CONF.pxe.ipxe_boot_script, + os.path.join( + CONF.deploy.http_root, + os.path.basename(CONF.pxe.ipxe_boot_script))) + + @mock.patch.object(os.path, 'isfile', autospec=True) + @mock.patch.object(filecmp, 'cmp', autospec=True) + @mock.patch.object(shutil, 'copyfile', autospec=True) + def test_prepare_ramdisk_ipxe_without_copy( + self, copyfile_mock, cmp_mock, isfile_mock): + self.node.provision_state = states.DEPLOYING + self.node.save() + self.config(group='pxe', ipxe_enabled=True) + self.config(group='deploy', http_url='http://myserver') + isfile_mock.return_value = True + cmp_mock.return_value = True + self._test_prepare_ramdisk() + self.assertFalse(copyfile_mock.called) + def test_prepare_ramdisk_cleaning(self): self.node.provision_state = states.CLEANING self.node.save()