Write initial grub config on startup

This change removes the documentation to copy master_grub_cfg.txt to
/tftpboot/grub/grub.cfg and instead writes it on conductor startup.
This grub config is a simple redirect config requested by grub network
boot. "master" has been renamed to "initial" as a more accurate label
of its function.

New configuration option [pxe]initial_grub_template allows the deployer
to specify a different initial grub template.

Change-Id: I71191dd399a6c49607f91d69b5b1673799a38624
This commit is contained in:
Steve Baker 2021-11-30 11:25:43 +13:00
parent 45e8adc1df
commit 3f76724dfb
11 changed files with 96 additions and 55 deletions

View File

@ -2745,20 +2745,6 @@ function configure_tftpd {
echo "re ^(^/) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file echo "re ^(^/) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file
echo "re ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file echo "re ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file
# Write a grub.cfg redirect for the ubuntu grub. The fedora grub
# will fetch the generated grub.cfg-01-<mac> directly
grub_dir=$IRONIC_TFTPBOOT_DIR/grub
mkdir -p $grub_dir
cat << EOF > $grub_dir/grub.cfg
set default=master
set timeout=1
set hidden_timeout_quiet=false
menuentry "master" {
configfile $IRONIC_TFTPBOOT_DIR/\$net_default_mac.conf
}
EOF
chmod 644 $grub_dir/grub.cfg
else else
echo "r ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >$IRONIC_TFTPBOOT_DIR/map-file echo "r ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >$IRONIC_TFTPBOOT_DIR/map-file
echo "r ^(/tftpboot/) $IRONIC_TFTPBOOT_DIR/\2" >>$IRONIC_TFTPBOOT_DIR/map-file echo "r ^(/tftpboot/) $IRONIC_TFTPBOOT_DIR/\2" >>$IRONIC_TFTPBOOT_DIR/map-file

View File

@ -157,38 +157,6 @@ the PXE UEFI environment.
sudo cp /usr/lib64/efi/shim.efi /tftpboot/bootx64.efi sudo cp /usr/lib64/efi/shim.efi /tftpboot/bootx64.efi
sudo cp /usr/lib/grub2/x86_64-efi/grub.efi /tftpboot/grubx64.efi sudo cp /usr/lib/grub2/x86_64-efi/grub.efi /tftpboot/grubx64.efi
#. Create master grub.cfg:
Ubuntu: Create grub.cfg under ``/tftpboot/grub`` directory::
GRUB_DIR=/tftpboot/grub
Fedora: Create grub.cfg under ``/tftpboot/EFI/fedora`` directory::
GRUB_DIR=/tftpboot/EFI/fedora
RHEL8/CentOS8: Create grub.cfg under ``/tftpboot/EFI/centos`` directory::
GRUB_DIR=/tftpboot/EFI/centos
SUSE: Create grub.cfg under ``/tftpboot/boot/grub`` directory::
GRUB_DIR=/tftpboot/boot/grub
Create directory ``GRUB_DIR``::
sudo mkdir -p $GRUB_DIR
This file is used to redirect grub to baremetal node specific config file.
It redirects it to specific grub config file based on DHCP IP assigned to
baremetal node.
.. literalinclude:: ../../../ironic/drivers/modules/master_grub_cfg.txt
Change the permission of grub.cfg::
sudo chmod 644 $GRUB_DIR/grub.cfg
#. Update the bare metal node with ``boot_mode:uefi`` capability in #. Update the bare metal node with ``boot_mode:uefi`` capability in
node's properties field. See :ref:`boot_mode_support` for details. node's properties field. See :ref:`boot_mode_support` for details.

View File

@ -24,6 +24,7 @@ import jinja2
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import fileutils
from ironic.common import dhcp_factory from ironic.common import dhcp_factory
from ironic.common import exception from ironic.common import exception
@ -1286,3 +1287,25 @@ def place_loaders_for_boot(base_path):
'the requested destination. %s' % e) 'the requested destination. %s' % e)
LOG.error(msg) LOG.error(msg)
raise exception.IncorrectConfiguration(error=msg) raise exception.IncorrectConfiguration(error=msg)
def place_common_config():
"""Place template generated config which is not node specific.
Currently places the initial grub config for grub network boot.
"""
if not CONF.pxe.initial_grub_template:
return
grub_dir_path = os.path.join(_get_root_dir(False), 'grub')
if not os.path.isdir(grub_dir_path):
fileutils.ensure_tree(grub_dir_path)
if CONF.pxe.dir_permission:
os.chmod(grub_dir_path, CONF.pxe.dir_permission)
initial_grub = utils.render_template(
CONF.pxe.initial_grub_template,
{'tftp_root': _get_root_dir(False)})
initial_grub_path = os.path.join(grub_dir_path, 'grub.cfg')
utils.write_to_file(initial_grub_path, initial_grub)

View File

@ -204,6 +204,11 @@ opts = [
'for bootloaders. Use example: ' 'for bootloaders. Use example: '
'ipxe.efi:/usr/share/ipxe/ipxe-snponly-x86_64.efi,' 'ipxe.efi:/usr/share/ipxe/ipxe-snponly-x86_64.efi,'
'undionly.kpxe:/usr/share/ipxe/undionly.kpxe')), 'undionly.kpxe:/usr/share/ipxe/undionly.kpxe')),
cfg.StrOpt('initial_grub_template',
default=os.path.join(
'$pybasedir', 'drivers/modules/initial_grub_cfg.template'),
help=_('On ironic-conductor node, the path to the initial grub'
'configuration template for grub network boot.')),
] ]

View File

@ -0,0 +1,7 @@
set default=initial
set timeout=5
set hidden_timeout_quiet=false
menuentry "initial" {
configfile {{ tftp_root }}/$net_default_mac.conf
}

View File

@ -1,7 +0,0 @@
set default=master
set timeout=5
set hidden_timeout_quiet=false
menuentry "master" {
configfile /tftpboot/$net_default_mac.conf
}

View File

@ -39,6 +39,7 @@ class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
capabilities = ['ramdisk_boot', 'pxe_boot'] capabilities = ['ramdisk_boot', 'pxe_boot']
def __init__(self): def __init__(self):
pxe_utils.place_common_config()
pxe_utils.place_loaders_for_boot(CONF.deploy.http_root) pxe_utils.place_loaders_for_boot(CONF.deploy.http_root)
pxe_utils.place_loaders_for_boot(CONF.pxe.tftp_root) pxe_utils.place_loaders_for_boot(CONF.pxe.tftp_root)

View File

@ -152,6 +152,7 @@ class TestCase(oslo_test_base.BaseTestCase):
group='neutron') group='neutron')
self.config(enabled_hardware_types=['fake-hardware', self.config(enabled_hardware_types=['fake-hardware',
'manual-management']) 'manual-management'])
self.config(initial_grub_template=None, group='pxe')
for iface in drivers_base.ALL_INTERFACES: for iface in drivers_base.ALL_INTERFACES:
default = None default = None

View File

@ -1035,6 +1035,53 @@ class TestPXEUtils(db_base.DbTestCase):
next(actual)) next(actual))
self.assertEqual('/tftpboot-path/' + address + '.conf', next(actual)) self.assertEqual('/tftpboot-path/' + address + '.conf', next(actual))
@mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(os.path, 'isdir', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
def test_place_common_config(self, mock_chmod, mock_isdir,
mock_makedirs):
self.config(initial_grub_template=os.path.join(
'$pybasedir',
'drivers/modules/initial_grub_cfg.template'),
group='pxe')
mock_isdir.return_value = False
self.config(group='pxe', dir_permission=0o777)
def write_to_file(path, contents):
self.assertEqual('/tftpboot/grub/grub.cfg', path)
self.assertIn(
'configfile /tftpboot/$net_default_mac.conf',
contents
)
with mock.patch('ironic.common.utils.write_to_file',
wraps=write_to_file):
pxe_utils.place_common_config()
mock_isdir.assert_called_once_with('/tftpboot/grub')
mock_makedirs.assert_called_once_with('/tftpboot/grub', 511)
mock_chmod.assert_called_once_with('/tftpboot/grub', 0o777)
@mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(os.path, 'isdir', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
def test_place_common_config_existing_dirs(self, mock_chmod, mock_isdir,
mock_makedirs):
self.config(initial_grub_template=os.path.join(
'$pybasedir',
'drivers/modules/initial_grub_cfg.template'),
group='pxe')
mock_isdir.return_value = True
with mock.patch('ironic.common.utils.write_to_file',
autospec=True) as mock_write:
pxe_utils.place_common_config()
mock_write.assert_called_once()
mock_isdir.assert_called_once_with('/tftpboot/grub')
mock_makedirs.assert_not_called()
mock_chmod.assert_not_called()
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None) @mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None) @mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)

View File

@ -24,6 +24,7 @@ from oslo_utils import strutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from ironic.common import exception from ironic.common import exception
from ironic.common import pxe_utils
from ironic.common import states from ironic.common import states
from ironic.conductor import manager from ironic.conductor import manager
from ironic import objects from ironic import objects
@ -143,8 +144,10 @@ class ServiceSetUpMixin(object):
self.service.init_host() self.service.init_host()
else: else:
with mock.patch.object(periodics, 'PeriodicWorker', autospec=True): with mock.patch.object(periodics, 'PeriodicWorker', autospec=True):
self.service.prepare_host() with mock.patch.object(pxe_utils, 'place_common_config',
self.service.init_host() autospec=True):
self.service.prepare_host()
self.service.init_host()
self.addCleanup(self._stop_service) self.addCleanup(self._stop_service)

View File

@ -0,0 +1,7 @@
---
features:
- |
Manually copying the initial grub config for grub network boot is no longer
necessary, as this file is now written to the TFTP root directory on
conductor startup. A custom template can be used to generate this file with
config option ``[pxe]initial_grub_template``.