From c197a2d8b24e2fa4c5e7901e448da1b0c93fcd26 Mon Sep 17 00:00:00 2001 From: Iury Gregory Melo Ferreira Date: Fri, 5 Aug 2022 02:44:03 -0300 Subject: [PATCH] Override external_http_url at node level This patch adds support to specify the URL to be used to publish the node image when using virtual media. The option is available under `driver_info['external_http_url']`, if set this value has priority over the values in the configuration file ([deploy]/external_http_url and [deploy]/http_url) Story: 2010221 Task: 45970 Change-Id: If6a117a756b7d2a04251792f88c2ee412a040b28 --- ironic/drivers/modules/image_utils.py | 13 ++- ironic/drivers/modules/redfish/boot.py | 6 +- .../unit/drivers/modules/test_image_utils.py | 89 ++++++++++++++++++- ...al_http_url-per-node-f5423b00b373e528.yaml | 8 ++ 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py index bb0dfa166e..304c199bf8 100644 --- a/ironic/drivers/modules/image_utils.py +++ b/ironic/drivers/modules/image_utils.py @@ -169,7 +169,7 @@ class ImageHandler(object): return urlparse.urlunparse(parsed_url) - def publish_image(self, image_file, object_name): + def publish_image(self, image_file, object_name, node_http_url=None): """Make image file downloadable. Depending on ironic settings, pushes given file into Swift or copies @@ -178,6 +178,9 @@ class ImageHandler(object): :param image_file: path to file to publish :param object_name: name of the published file + :param node_http_url: a url to be used to publish the image. If set, + the values from external_http_url and http_url + from CONF.deploy won't be used. :return: a URL to download published file """ @@ -220,7 +223,8 @@ class ImageHandler(object): shutil.copyfile(image_file, published_file) os.chmod(published_file, self._file_permission) - http_url = CONF.deploy.external_http_url or CONF.deploy.http_url + http_url = (node_http_url or CONF.deploy.external_http_url + or CONF.deploy.http_url) image_url = os.path.join(http_url, self._image_subdir, object_name) return image_url @@ -302,8 +306,9 @@ def prepare_floppy_image(task, params=None): images.create_vfat_image(vfat_image_tmpfile, parameters=params) img_handler = ImageHandler(task.node.driver) - - image_url = img_handler.publish_image(vfat_image_tmpfile, object_name) + node_http_url = task.node.driver_info.get("external_http_url") + image_url = img_handler.publish_image(vfat_image_tmpfile, object_name, + node_http_url) LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, " "exposed as temporary URL " diff --git a/ironic/drivers/modules/redfish/boot.py b/ironic/drivers/modules/redfish/boot.py index 164425eeee..a321c08ec5 100644 --- a/ironic/drivers/modules/redfish/boot.py +++ b/ironic/drivers/modules/redfish/boot.py @@ -53,7 +53,11 @@ OPTIONAL_PROPERTIES = { "used by ironic when building UEFI-bootable ISO " "out of kernel and ramdisk. Required for UEFI " "when deploy_iso is not provided."), - + 'external_http_url': _("External URL that is used when the image could " + "be served outside of the provisioning network. " + "If set it will have priority over the following " + "configs: CONF.deploy.external_http_url and " + "CONF.deploy.http_url. Defaults to None.") } RESCUE_PROPERTIES = { diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py index 6d79629d9a..753452f5d1 100644 --- a/ironic/tests/unit/drivers/modules/test_image_utils.py +++ b/ironic/tests/unit/drivers/modules/test_image_utils.py @@ -147,6 +147,32 @@ class RedfishImageHandlerTestCase(db_base.DbTestCase): 'file.iso', '/httpboot/redfish/boot.iso') mock_chmod.assert_called_once_with('file.iso', 0o644) + @mock.patch.object(os, 'chmod', autospec=True) + @mock.patch.object(image_utils, 'shutil', autospec=True) + @mock.patch.object(os, 'link', autospec=True) + @mock.patch.object(os, 'mkdir', autospec=True) + def test_publish_image_external_ip_node_override( + self, mock_mkdir, mock_link, mock_shutil, mock_chmod): + self.config(use_swift=False, group='redfish') + self.config(http_url='http://localhost', + external_http_url='http://non-local.host', + group='deploy') + img_handler_obj = image_utils.ImageHandler(self.node.driver) + self.node.driver_info["external_http_url"] = "http://node.override.url" + + override_url = self.node.driver_info.get("external_http_url") + + url = img_handler_obj.publish_image('file.iso', 'boot.iso', + override_url) + + self.assertEqual( + 'http://node.override.url/redfish/boot.iso', url) + + mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755) + mock_link.assert_called_once_with( + 'file.iso', '/httpboot/redfish/boot.iso') + mock_chmod.assert_called_once_with('file.iso', 0o644) + @mock.patch.object(os, 'chmod', autospec=True) @mock.patch.object(image_utils, 'shutil', autospec=True) @mock.patch.object(os, 'link', autospec=True) @@ -271,8 +297,8 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): object_name = 'image-%s' % task.node.uuid - mock_publish_image.assert_called_once_with(mock.ANY, - mock.ANY, object_name) + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) mock_create_vfat_image.assert_called_once_with( mock.ANY, parameters=None) @@ -295,8 +321,63 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): object_name = 'image-%s' % task.node.uuid - mock_publish_image.assert_called_once_with(mock.ANY, - mock.ANY, object_name) + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) + + mock_create_vfat_image.assert_called_once_with( + mock.ANY, parameters={"ipa-api-url": "http://callback"}) + + self.assertEqual(expected_url, url) + + @mock.patch.object(image_utils.ImageHandler, 'publish_image', + autospec=True) + @mock.patch.object(images, 'create_vfat_image', autospec=True) + def test_prepare_floppy_image_publish_with_config_external_http_url( + self, mock_create_vfat_image, mock_publish_image): + self.config(external_callback_url='http://callback/', + external_http_url='http://config.external.url', + group='deploy') + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_url = 'https://config.external.url/c.f?e=f' + + mock_publish_image.return_value = expected_url + + url = image_utils.prepare_floppy_image(task) + + object_name = 'image-%s' % task.node.uuid + + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) + + mock_create_vfat_image.assert_called_once_with( + mock.ANY, parameters={"ipa-api-url": "http://callback"}) + + self.assertEqual(expected_url, url) + + @mock.patch.object(image_utils.ImageHandler, 'publish_image', + autospec=True) + @mock.patch.object(images, 'create_vfat_image', autospec=True) + def test_prepare_floppy_image_publish_with_node_external_http_url( + self, mock_create_vfat_image, mock_publish_image): + self.config(external_callback_url='http://callback/', + external_http_url='http://config.external.url', + group='deploy') + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.driver_info["external_http_url"] = \ + "https://node.external" + override_url = task.node.driver_info.get("external_http_url") + expected_url = '"https://node.external/c.f?e=f' + + mock_publish_image.return_value = expected_url + + url = image_utils.prepare_floppy_image(task) + + object_name = 'image-%s' % task.node.uuid + + mock_publish_image.assert_called_once_with( + mock.ANY, mock.ANY, object_name, override_url) mock_create_vfat_image.assert_called_once_with( mock.ANY, parameters={"ipa-api-url": "http://callback"}) diff --git a/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml b/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml new file mode 100644 index 0000000000..93e661ba7c --- /dev/null +++ b/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Nodes using virtual media can now specify their own external URL. + This setting can be leveraged via the ``driver_info\external_http_url`` + node setting. + When used, this setting overrides the ``[deploy]http_url`` and + ``[deploy]external_http_url`` settings in the configuration file.