summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavlo Shchelokovskyy <pshchelokovskyy@mirantis.com>2016-08-18 12:06:18 +0300
committerPavlo Shchelokovskyy <pshchelokovskyy@mirantis.com>2016-09-07 20:40:53 +0300
commit497be96d4a18e4f2893147ecff6f83d5d64a984e (patch)
treef78c551e52bf137acde1d8e9e2f60c35fba759a1
parent4ce008e8ffe7d56b23738e6699b591ee6734a828 (diff)
downloadironic-497be96d4a18e4f2893147ecff6f83d5d64a984e.tar.gz
Allow using TempURLs for deploy images
when iPXE is enabled, it is possible for the bootloader to download the deploy kernel and ramdisk directly from Swift TempURL instead of downloading them to conductor and serving from local HTTP server. This patch adds the required logic and a new config option `ipxe_use_swift` (default False), setting which to True enables using Swift TempURLs for deploy ramdisk and kernel. Note that local caching and serving for kernel and ramdisk of user image is still performed for partition images that require non-local boot, as moving those to use TempURLs will make it impossible for the user to reboot the instance when TempURLs time out or image is deleted from Glance/Swift. Change-Id: I106cc6148c329e784bfbb5019fdfeb0509a9de09 Closes-Bug: #1526404 Co-Authored-By: Andrey Shestakov <ashestakov@mirantis.com>
-rw-r--r--devstack/lib/ironic5
-rw-r--r--etc/ironic/ironic.conf.sample7
-rw-r--r--ironic/conf/pxe.py10
-rw-r--r--ironic/drivers/modules/pxe.py106
-rw-r--r--ironic/tests/unit/drivers/modules/test_pxe.py227
-rw-r--r--releasenotes/notes/ipxe-use-swift-5ccf490daab809cc.yaml12
6 files changed, 240 insertions, 127 deletions
diff --git a/devstack/lib/ironic b/devstack/lib/ironic
index 983d68e3e..efb189c05 100644
--- a/devstack/lib/ironic
+++ b/devstack/lib/ironic
@@ -208,6 +208,8 @@ IRONIC_HOSTPORT=${IRONIC_HOSTPORT:-$SERVICE_HOST:$IRONIC_SERVICE_PORT}
# Enable iPXE
IRONIC_IPXE_ENABLED=$(trueorfalse True IRONIC_IPXE_ENABLED)
+# Options below are only applied when IRONIC_IPXE_ENABLED is True
+IRONIC_IPXE_USE_SWIFT=$(trueorfalse False IRONIC_IPXE_USE_SWIFT)
IRONIC_HTTP_DIR=${IRONIC_HTTP_DIR:-$IRONIC_DATA_DIR/httpboot}
IRONIC_HTTP_SERVER=${IRONIC_HTTP_SERVER:-$IRONIC_TFTPSERVER_IP}
IRONIC_HTTP_PORT=${IRONIC_HTTP_PORT:-3928}
@@ -690,6 +692,9 @@ function configure_ironic_conductor {
iniset $IRONIC_CONF_FILE pxe pxe_bootfile_name $pxebin
iniset $IRONIC_CONF_FILE deploy http_root $IRONIC_HTTP_DIR
iniset $IRONIC_CONF_FILE deploy http_url "http://$IRONIC_HTTP_SERVER:$IRONIC_HTTP_PORT"
+ if [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then
+ iniset $IRONIC_CONF_FILE pxe ipxe_use_swift True
+ fi
fi
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index 844adbd76..181717f59 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -2621,6 +2621,13 @@
# Allowed values: 4, 6
#ip_version = 4
+# Download deploy images directly from swift using temporary
+# URLs. If set to false (default), images are downloaded to
+# the ironic-conductor node and served over its local HTTP
+# server. Applicable only when 'ipxe_enabled' option is set to
+# true. (boolean value)
+#ipxe_use_swift = false
+
[seamicro]
diff --git a/ironic/conf/pxe.py b/ironic/conf/pxe.py
index 7e16724ec..e17a919d7 100644
--- a/ironic/conf/pxe.py
+++ b/ironic/conf/pxe.py
@@ -96,6 +96,16 @@ opts = [
choices=['4', '6'],
help=_('The IP version that will be used for PXE booting. '
'Defaults to 4. EXPERIMENTAL')),
+ cfg.BoolOpt('ipxe_use_swift',
+ default=False,
+ help=_("Download deploy images directly from swift using "
+ "temporary URLs. "
+ "If set to false (default), images are downloaded "
+ "to the ironic-conductor node and served over its "
+ "local HTTP server. "
+ "Applicable only when 'ipxe_enabled' option is "
+ "set to true.")),
+
]
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index 0ddf747ab..383520c64 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -32,6 +32,7 @@ from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common.i18n import _LW
from ironic.common import image_service as service
+from ironic.common import images
from ironic.common import pxe_utils
from ironic.common import states
from ironic.conf import CONF
@@ -83,11 +84,15 @@ def _get_instance_image_info(node, ctx):
:param ctx: context
:returns: a dictionary whose keys are the names of the images (kernel,
ramdisk) and values are the absolute paths of them. If it's a whole
- disk image, it returns an empty dictionary.
+ disk image or node is configured for localboot,
+ it returns an empty dictionary.
"""
image_info = {}
- if node.driver_internal_info.get('is_whole_disk_image'):
- return image_info
+ # NOTE(pas-ha) do not report image kernel and ramdisk for
+ # local boot or whole disk images so that they are not cached
+ if (node.driver_internal_info.get('is_whole_disk_image') or
+ deploy_utils.get_boot_option(node) == 'local'):
+ return image_info
root_dir = pxe_utils.get_root_dir()
i_info = node.instance_info
@@ -127,6 +132,48 @@ def _get_deploy_image_info(node):
return pxe_utils.get_deploy_kr_info(node.uuid, d_info)
+def _get_pxe_kernel_ramdisk(pxe_info):
+ pxe_opts = {}
+ pxe_opts['deployment_aki_path'] = pxe_info['deploy_kernel'][1]
+ pxe_opts['deployment_ari_path'] = pxe_info['deploy_ramdisk'][1]
+ # It is possible that we don't have kernel/ramdisk or even
+ # image_source to determine if it's a whole disk image or not.
+ # For example, when transitioning to 'available' state for first
+ # time from 'manage' state.
+ if 'kernel' in pxe_info:
+ pxe_opts['aki_path'] = pxe_info['kernel'][1]
+ if 'ramdisk' in pxe_info:
+ pxe_opts['ari_path'] = pxe_info['ramdisk'][1]
+ return pxe_opts
+
+
+def _get_ipxe_kernel_ramdisk(task, pxe_info):
+ pxe_opts = {}
+ node = task.node
+
+ for label, option in (('deploy_kernel', 'deployment_aki_path'),
+ ('deploy_ramdisk', 'deployment_ari_path')):
+ image_href = pxe_info[label][0]
+ if (CONF.pxe.ipxe_use_swift and
+ service_utils.is_glance_image(image_href)):
+ pxe_opts[option] = images.get_temp_url_for_glance_image(
+ task.context, image_href)
+ else:
+ pxe_opts[option] = '/'.join([CONF.deploy.http_url, node.uuid,
+ label])
+ # NOTE(pas-ha) do not use Swift TempURLs for kernel and ramdisk
+ # of user image when boot_option is not local,
+ # as this will break instance reboot later when temp urls have timed out.
+ if 'kernel' in pxe_info:
+ pxe_opts['aki_path'] = '/'.join(
+ [CONF.deploy.http_url, node.uuid, 'kernel'])
+ if 'ramdisk' in pxe_info:
+ pxe_opts['ari_path'] = '/'.join(
+ [CONF.deploy.http_url, node.uuid, 'ramdisk'])
+
+ return pxe_opts
+
+
def _build_pxe_config_options(task, pxe_info):
"""Build the PXE config options for a node
@@ -141,47 +188,21 @@ def _build_pxe_config_options(task, pxe_info):
:returns: A dictionary of pxe options to be used in the pxe bootfile
template.
"""
- node = task.node
- is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
+ if CONF.pxe.ipxe_enabled:
+ pxe_options = _get_ipxe_kernel_ramdisk(task, pxe_info)
+ else:
+ pxe_options = _get_pxe_kernel_ramdisk(pxe_info)
# These are dummy values to satisfy elilo.
# image and initrd fields in elilo config cannot be blank.
- kernel = 'no_kernel'
- ramdisk = 'no_ramdisk'
+ pxe_options.setdefault('aki_path', 'no_kernel')
+ pxe_options.setdefault('ari_path', 'no_ramdisk')
- if CONF.pxe.ipxe_enabled:
- deploy_kernel = '/'.join([CONF.deploy.http_url, node.uuid,
- 'deploy_kernel'])
- deploy_ramdisk = '/'.join([CONF.deploy.http_url, node.uuid,
- 'deploy_ramdisk'])
- if not is_whole_disk_image:
- kernel = '/'.join([CONF.deploy.http_url, node.uuid,
- 'kernel'])
- ramdisk = '/'.join([CONF.deploy.http_url, node.uuid,
- 'ramdisk'])
- else:
- deploy_kernel = pxe_info['deploy_kernel'][1]
- deploy_ramdisk = pxe_info['deploy_ramdisk'][1]
- if not is_whole_disk_image:
- # It is possible that we don't have kernel/ramdisk or even
- # image_source to determine if it's a whole disk image or not.
- # For example, when transitioning to 'available' state for first
- # time from 'manage' state. Retain dummy values if we don't have
- # kernel/ramdisk.
- if 'kernel' in pxe_info:
- kernel = pxe_info['kernel'][1]
- if 'ramdisk' in pxe_info:
- ramdisk = pxe_info['ramdisk'][1]
-
- pxe_options = {
- 'deployment_aki_path': deploy_kernel,
- 'deployment_ari_path': deploy_ramdisk,
+ pxe_options.update({
'pxe_append_params': CONF.pxe.pxe_append_params,
'tftp_server': CONF.pxe.tftp_server,
- 'aki_path': kernel,
- 'ari_path': ramdisk,
'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000
- }
+ })
return pxe_options
@@ -326,7 +347,8 @@ class PXEBoot(base.BootInterface):
_parse_driver_info(node)
d_info = deploy_utils.get_image_instance_info(node)
- if node.driver_internal_info.get('is_whole_disk_image'):
+ if (node.driver_internal_info.get('is_whole_disk_image') or
+ deploy_utils.get_boot_option(node) == 'local'):
props = []
elif service_utils.is_glance_image(d_info['image_source']):
props = ['kernel_id', 'ramdisk_id']
@@ -388,9 +410,11 @@ class PXEBoot(base.BootInterface):
pxe_config_template)
deploy_utils.try_set_boot_device(task, boot_devices.PXE)
- # FIXME(lucasagomes): If it's local boot we should not cache
- # the image kernel and ramdisk (Or even require it).
- _cache_ramdisk_kernel(task.context, node, pxe_info)
+ if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift:
+ pxe_info.pop('deploy_kernel', None)
+ pxe_info.pop('deploy_ramdisk', None)
+ if pxe_info:
+ _cache_ramdisk_kernel(task.context, node, pxe_info)
@METRICS.timer('PXEBoot.clean_up_ramdisk')
def clean_up_ramdisk(self, task):
diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py
index 3f314c486..3e67a0341 100644
--- a/ironic/tests/unit/drivers/modules/test_pxe.py
+++ b/ironic/tests/unit/drivers/modules/test_pxe.py
@@ -19,6 +19,7 @@ import filecmp
import os
import shutil
import tempfile
+import uuid
from ironic_lib import utils as ironic_utils
import mock
@@ -145,6 +146,14 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self.node.save()
self._test__get_instance_image_info()
+ @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option',
+ return_value='local')
+ def test__get_instance_image_info_localboot(self, boot_opt_mock):
+ self.node.driver_internal_info['is_whole_disk_image'] = False
+ self.node.save()
+ image_info = pxe._get_instance_image_info(self.node, self.context)
+ self.assertEqual({}, image_info)
+
@mock.patch.object(base_image_service.BaseImageService, '_show',
autospec=True)
def test__get_instance_image_info_whole_disk_image(self, show_mock):
@@ -154,11 +163,14 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
image_info = pxe._get_instance_image_info(self.node, self.context)
self.assertEqual({}, image_info)
+ @mock.patch('ironic.common.image_service.GlanceImageService',
+ autospec=True)
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
- def _test_build_pxe_config_options(self, build_pxe_mock,
+ def _test_build_pxe_config_options(self, build_pxe_mock, glance_mock,
whle_dsk_img=False,
ipxe_enabled=False,
- ipxe_timeout=0):
+ ipxe_timeout=0,
+ ipxe_use_swift=False):
self.config(pxe_append_params='test_param', group='pxe')
# NOTE: right '/' should be removed from url string
self.config(api_url='http://192.168.122.184:6385', group='conductor')
@@ -175,11 +187,18 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
http_url = 'http://192.1.2.3:1234'
self.config(ipxe_enabled=True, group='pxe')
self.config(http_url=http_url, group='deploy')
-
- deploy_kernel = os.path.join(http_url, self.node.uuid,
- 'deploy_kernel')
- deploy_ramdisk = os.path.join(http_url, self.node.uuid,
- 'deploy_ramdisk')
+ if ipxe_use_swift:
+ self.config(ipxe_use_swift=True, group='pxe')
+ glance = mock.Mock()
+ glance_mock.return_value = glance
+ glance.swift_temp_url.side_effect = [
+ deploy_kernel, deploy_ramdisk] = [
+ 'swift_kernel', 'swift_ramdisk']
+ else:
+ deploy_kernel = os.path.join(http_url, self.node.uuid,
+ 'deploy_kernel')
+ deploy_ramdisk = os.path.join(http_url, self.node.uuid,
+ 'deploy_ramdisk')
kernel = os.path.join(http_url, self.node.uuid, 'kernel')
ramdisk = os.path.join(http_url, self.node.uuid, 'ramdisk')
root_dir = CONF.deploy.http_root
@@ -194,9 +213,44 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'ramdisk')
root_dir = CONF.pxe.tftp_root
- if whle_dsk_img:
- ramdisk = 'no_ramdisk'
- kernel = 'no_kernel'
+ if ipxe_use_swift:
+ image_info = {
+ 'deploy_kernel': (str(uuid.uuid4()),
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_kernel')),
+ 'deploy_ramdisk': (str(uuid.uuid4()),
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_ramdisk'))
+ }
+ else:
+ image_info = {
+ 'deploy_kernel': ('deploy_kernel',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_kernel')),
+ 'deploy_ramdisk': ('deploy_ramdisk',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_ramdisk'))
+ }
+
+ if (whle_dsk_img or
+ deploy_utils.get_boot_option(self.node) == 'local'):
+ ramdisk = 'no_ramdisk'
+ kernel = 'no_kernel'
+ else:
+ image_info.update({
+ 'kernel': ('kernel_id',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'kernel')),
+ 'ramdisk': ('ramdisk_id',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'ramdisk'))
+ })
ipxe_timeout_in_ms = ipxe_timeout * 1000
@@ -210,23 +264,6 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'ipxe_timeout': ipxe_timeout_in_ms,
}
- image_info = {'deploy_kernel': ('deploy_kernel',
- os.path.join(root_dir,
- self.node.uuid,
- 'deploy_kernel')),
- 'deploy_ramdisk': ('deploy_ramdisk',
- os.path.join(root_dir,
- self.node.uuid,
- 'deploy_ramdisk')),
- 'kernel': ('kernel_id',
- os.path.join(root_dir,
- self.node.uuid,
- 'kernel')),
- 'ramdisk': ('ramdisk_id',
- os.path.join(root_dir,
- self.node.uuid,
- 'ramdisk'))}
-
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
options = pxe._build_pxe_config_options(task, image_info)
@@ -236,76 +273,48 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self._test_build_pxe_config_options(whle_dsk_img=True,
ipxe_enabled=False)
+ def test__build_pxe_config_options_local_boot(self):
+ del self.node.driver_internal_info['is_whole_disk_image']
+ i_info = self.node.instance_info
+ i_info.update({'capabilities': {'boot_option': 'local'}})
+ self.node.instance_info = i_info
+ self.node.save()
+ self._test_build_pxe_config_options(whle_dsk_img=False,
+ ipxe_enabled=False)
+
def test__build_pxe_config_options_ipxe(self):
self._test_build_pxe_config_options(whle_dsk_img=True,
ipxe_enabled=True)
- def test__build_pxe_config_options_without_is_whole_disk_image(self):
+ def test__build_pxe_config_options_ipxe_local_boot(self):
del self.node.driver_internal_info['is_whole_disk_image']
+ i_info = self.node.instance_info
+ i_info.update({'capabilities': {'boot_option': 'local'}})
+ self.node.instance_info = i_info
self.node.save()
self._test_build_pxe_config_options(whle_dsk_img=False,
- ipxe_enabled=False)
+ ipxe_enabled=True)
- def test__build_pxe_config_options_ipxe_and_ipxe_timeout(self):
+ def test__build_pxe_config_options_ipxe_swift_wdi(self):
self._test_build_pxe_config_options(whle_dsk_img=True,
ipxe_enabled=True,
- ipxe_timeout=120)
-
- @mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
- def test__build_pxe_config_options_whole_disk_image(self,
- build_pxe_mock,
- ipxe_enabled=False):
- self.config(pxe_append_params='test_param', group='pxe')
- # NOTE: right '/' should be removed from url string
- self.config(api_url='http://192.168.122.184:6385', group='conductor')
+ ipxe_use_swift=True)
- tftp_server = CONF.pxe.tftp_server
-
- if ipxe_enabled:
- http_url = 'http://192.1.2.3:1234'
- self.config(ipxe_enabled=True, group='pxe')
- self.config(http_url=http_url, group='deploy')
-
- deploy_kernel = os.path.join(http_url, self.node.uuid,
- 'deploy_kernel')
- deploy_ramdisk = os.path.join(http_url, self.node.uuid,
- 'deploy_ramdisk')
- root_dir = CONF.deploy.http_root
- else:
- deploy_kernel = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
- 'deploy_kernel')
- deploy_ramdisk = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
- 'deploy_ramdisk')
- root_dir = CONF.pxe.tftp_root
-
- expected_options = {
- 'deployment_ari_path': deploy_ramdisk,
- 'pxe_append_params': 'test_param',
- 'deployment_aki_path': deploy_kernel,
- 'tftp_server': tftp_server,
- 'aki_path': 'no_kernel',
- 'ari_path': 'no_ramdisk',
- 'ipxe_timeout': 0,
- }
+ def test__build_pxe_config_options_ipxe_swift_partition(self):
+ self._test_build_pxe_config_options(whle_dsk_img=False,
+ ipxe_enabled=True,
+ ipxe_use_swift=True)
- image_info = {'deploy_kernel': ('deploy_kernel',
- os.path.join(root_dir,
- self.node.uuid,
- 'deploy_kernel')),
- 'deploy_ramdisk': ('deploy_ramdisk',
- os.path.join(root_dir,
- self.node.uuid,
- 'deploy_ramdisk')),
- }
- driver_internal_info = self.node.driver_internal_info
- driver_internal_info['is_whole_disk_image'] = True
- self.node.driver_internal_info = driver_internal_info
+ def test__build_pxe_config_options_without_is_whole_disk_image(self):
+ del self.node.driver_internal_info['is_whole_disk_image']
self.node.save()
+ self._test_build_pxe_config_options(whle_dsk_img=False,
+ ipxe_enabled=False)
- with task_manager.acquire(self.context, self.node.uuid,
- shared=True) as task:
- options = pxe._build_pxe_config_options(task, image_info)
- self.assertEqual(expected_options, options)
+ def test__build_pxe_config_options_ipxe_and_ipxe_timeout(self):
+ self._test_build_pxe_config_options(whle_dsk_img=True,
+ ipxe_enabled=True,
+ ipxe_timeout=120)
def test__build_pxe_config_options_no_kernel_no_ramdisk(self):
del self.node.driver_internal_info['is_whole_disk_image']
@@ -655,20 +664,38 @@ class PXEBootTestCase(db_base.DbTestCase):
mock_deploy_img_info,
mock_instance_img_info,
dhcp_factory_mock, uefi=False,
- cleaning=False):
+ cleaning=False,
+ ipxe_use_swift=False,
+ whole_disk_image=False):
mock_build_pxe.return_value = {}
mock_deploy_img_info.return_value = {'deploy_kernel': 'a'}
- mock_instance_img_info.return_value = {'kernel': 'b'}
+ if whole_disk_image:
+ mock_instance_img_info.return_value = {}
+ else:
+ mock_instance_img_info.return_value = {'kernel': 'b'}
mock_pxe_config.return_value = None
mock_cache_r_k.return_value = None
provider_mock = mock.MagicMock()
dhcp_factory_mock.return_value = provider_mock
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info['is_whole_disk_image'] = whole_disk_image
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
mock_deploy_img_info.assert_called_once_with(task.node)
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
- if cleaning is False:
+ if ipxe_use_swift:
+ if whole_disk_image:
+ self.assertFalse(mock_cache_r_k.called)
+ else:
+ mock_cache_r_k.assert_called_once_with(
+ self.context, task.node,
+ {'kernel': 'b'})
+ mock_instance_img_info.assert_called_once_with(task.node,
+ self.context)
+ elif cleaning is False:
mock_cache_r_k.assert_called_once_with(
self.context, task.node,
{'deploy_kernel': 'a', 'kernel': 'b'})
@@ -749,6 +776,34 @@ class PXEBootTestCase(db_base.DbTestCase):
self._test_prepare_ramdisk()
self.assertFalse(copyfile_mock.called)
+ @mock.patch.object(shutil, 'copyfile', autospec=True)
+ def test_prepare_ramdisk_ipxe_swift(self, copyfile_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.save()
+ self.config(group='pxe', ipxe_enabled=True)
+ self.config(group='pxe', ipxe_use_swift=True)
+ self.config(group='deploy', http_url='http://myserver')
+ self._test_prepare_ramdisk(ipxe_use_swift=True)
+ 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(shutil, 'copyfile', autospec=True)
+ def test_prepare_ramdisk_ipxe_swift_whole_disk_image(self, copyfile_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.save()
+ self.config(group='pxe', ipxe_enabled=True)
+ self.config(group='pxe', ipxe_use_swift=True)
+ self.config(group='deploy', http_url='http://myserver')
+ self._test_prepare_ramdisk(ipxe_use_swift=True, whole_disk_image=True)
+ 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)))
+
def test_prepare_ramdisk_cleaning(self):
self.node.provision_state = states.CLEANING
self.node.save()
diff --git a/releasenotes/notes/ipxe-use-swift-5ccf490daab809cc.yaml b/releasenotes/notes/ipxe-use-swift-5ccf490daab809cc.yaml
new file mode 100644
index 000000000..dc6fe708d
--- /dev/null
+++ b/releasenotes/notes/ipxe-use-swift-5ccf490daab809cc.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - By default, the ironic-conductor service caches the node's deploy
+ ramdisk and kernel images locally and serves them via a separate
+ HTTP server. A new ``[pxe]ipxe_use_swift`` configuration option
+ (disabled by default) allows images to be accessed directly
+ from object store via Swift temporary URLs.
+ This is only applicable if iPXE is enabled (via ``[pxe]ipxe_enabled``
+ configuration option) and image store is in Glance/Swift.
+ For user images that are partition images requiring non-local boot,
+ the default behavior with local caching and an HTTP server
+ will still apply for user image kernel and ramdisk.