diff --git a/doc/source/admin/ramdisk-boot.rst b/doc/source/admin/ramdisk-boot.rst index 7ee15721a0..29708b7810 100644 --- a/doc/source/admin/ramdisk-boot.rst +++ b/doc/source/admin/ramdisk-boot.rst @@ -92,14 +92,6 @@ For example, --instance-info boot_iso=http://path/to/boot.iso baremetal node deploy -By default the Bare Metal service will cache the ISO locally and serve from its -HTTP server. If you want to avoid that, set the following: - -.. code-block:: shell - - baremetal node set \ - --instance-info ramdisk_image_download_source=http - .. warning:: This feature, when utilized with the ``ipxe`` ``boot_interface``, will only allow a kernel and ramdisk to be booted from the @@ -113,6 +105,18 @@ HTTP server. If you want to avoid that, set the following: This is a limitation of iPXE and the overall boot process of the operating system where memory allocated by iPXE is released. +By default the Bare Metal service will cache the ISO locally and serve from its +HTTP server. If you want to avoid that, set the following: + +.. code-block:: shell + + baremetal node set \ + --instance-info ramdisk_image_download_source=http + +ISO images are also cached across deployments, similarly to how it is done for +normal instance images. The URL together with the last modified response header +are used to determine if an image needs updating. + Limitations ----------- diff --git a/ironic/conf/deploy.py b/ironic/conf/deploy.py index 7163db9e2c..5729be74fc 100644 --- a/ironic/conf/deploy.py +++ b/ironic/conf/deploy.py @@ -212,6 +212,21 @@ opts = [ 'url directly, or if ironic should cache the image on ' 'the conductor and serve it from ironic\'s own http ' 'server.')), + cfg.StrOpt('iso_master_path', + default='/var/lib/ironic/master_iso_images', + help=_('On the ironic-conductor node, directory where master ' + 'ISO images are stored on disk. ' + 'Setting to the empty string disables image caching.')), + cfg.IntOpt('iso_cache_size', + default=20480, + help=_('Maximum size (in MiB) of cache for master ISO images, ' + 'including those in use.')), + # 10080 here is 1 week - 60*24*7. It is entirely arbitrary in the absence + # of a facility to disable the ttl entirely. + cfg.IntOpt('iso_cache_ttl', + default=10080, + help=_('Maximum TTL (in minutes) for old master ISO images in ' + 'cache.')), ] diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py index 5d4f7fe5fd..e42a645aee 100644 --- a/ironic/drivers/modules/image_utils.py +++ b/ironic/drivers/modules/image_utils.py @@ -35,6 +35,7 @@ from ironic.common import utils from ironic.conf import CONF from ironic.drivers.modules import boot_mode_utils from ironic.drivers.modules import deploy_utils +from ironic.drivers.modules import image_cache from ironic.drivers import utils as driver_utils LOG = log.getLogger(__name__) @@ -226,6 +227,19 @@ class ImageHandler(object): return image_url +@image_cache.cleanup(priority=75) +class ISOImageCache(image_cache.ImageCache): + + def __init__(self): + master_path = CONF.deploy.iso_master_path or None + super(self.__class__, self).__init__( + master_path, + # MiB -> B + cache_size=CONF.deploy.iso_cache_size * 1024 * 1024, + # min -> sec + cache_ttl=CONF.deploy.iso_cache_ttl * 60) + + def _get_name(node, prefix='', suffix=''): """Get an object name for a given node. @@ -467,10 +481,9 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href, boot_mode = boot_mode_utils.get_boot_mode(task.node) - with tempfile.NamedTemporaryFile( - dir=CONF.tempdir, suffix='.iso') as boot_fileobj: + with tempfile.TemporaryDirectory(dir=CONF.tempdir) as boot_file_dir: - boot_iso_tmp_file = boot_fileobj.name + boot_iso_tmp_file = os.path.join(boot_file_dir, 'boot.iso') if base_iso: # NOTE(dtantsur): this should be "params or inject_files", but # params are always populated in the calling code. @@ -479,7 +492,9 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href, '%(node)s, custom configuration will not be available', {'boot_mode': boot_mode, 'node': task.node.uuid, 'iso': base_iso}) - images.fetch_into(task.context, base_iso, boot_iso_tmp_file) + + ISOImageCache().fetch_image(base_iso, boot_iso_tmp_file, + ctx=task.context, force_raw=False) else: if is_ramdisk_boot: kernel_params = "root=/dev/ram0 text " diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py index 549a97d91c..ff061277a9 100644 --- a/ironic/tests/unit/drivers/modules/test_image_utils.py +++ b/ironic/tests/unit/drivers/modules/test_image_utils.py @@ -671,10 +671,10 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): @mock.patch.object(image_utils.ImageHandler, 'publish_image', autospec=True) - @mock.patch.object(images, 'fetch_into', autospec=True) + @mock.patch.object(image_utils, 'ISOImageCache', autospec=True) @mock.patch.object(images, 'create_boot_iso', autospec=True) def test__prepare_iso_image_bootable_iso_file(self, mock_create_boot_iso, - mock_fetch, + mock_cache, mock_publish_image): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: @@ -684,8 +684,8 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): task, 'http://kernel/img', 'http://ramdisk/img', bootloader_href=None, root_uuid=task.node.uuid, base_iso=base_image_url) - mock_fetch.assert_called_once_with(task.context, - base_image_url, mock.ANY) + mock_cache.return_value.fetch_image.assert_called_once_with( + base_image_url, mock.ANY, ctx=task.context, force_raw=False) mock_create_boot_iso.assert_not_called() @mock.patch.object(images, 'get_temp_url_for_glance_image', diff --git a/releasenotes/notes/iso-cache-5330b63c9e3a02db.yaml b/releasenotes/notes/iso-cache-5330b63c9e3a02db.yaml new file mode 100644 index 0000000000..502e6de58f --- /dev/null +++ b/releasenotes/notes/iso-cache-5330b63c9e3a02db.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + ISO images provided via ``instance_info/boot_iso`` or + `instance_info/deploy_iso`` are now cached in a similar way to normal + instance images. Set ``[deploy]iso_master_path`` to an empty string + to disable.