Support multi arch deployment
Ironic is flexible for x86/x86_64 servers by supporting BIOS and UEFI. But to deploy servers of other architectures, such as aarch64 or ppc64, configuration(PXE boot file and config template) must be modified, which means one Ironic conductor can only deploy baremetal machines of one architecture. This patch adds multi arch deployment support. For example, to deploy x86_64 and aarch64 servers by one Ironic conductor. Closes-Bug: #1582964 Change-Id: I628320aeb44b232a262d0843bc726a68d297e1f8
This commit is contained in:
parent
85750a1913
commit
f8ff1b26be
@ -2723,6 +2723,11 @@
|
||||
# configuration for UEFI boot loader. (string value)
|
||||
#uefi_pxe_config_template = $pybasedir/drivers/modules/pxe_grub_config.template
|
||||
|
||||
# On ironic-conductor node, template file for PXE
|
||||
# configuration per node architecture. For example:
|
||||
# aarch64:/opt/share/grubaa64_pxe_config.template (dict value)
|
||||
#pxe_config_template_by_arch =
|
||||
|
||||
# IP address of ironic-conductor node's TFTP server. (string
|
||||
# value)
|
||||
#tftp_server = $my_ip
|
||||
@ -2742,6 +2747,10 @@
|
||||
# Bootfile DHCP parameter for UEFI boot mode. (string value)
|
||||
#uefi_pxe_bootfile_name = bootx64.efi
|
||||
|
||||
# Bootfile DHCP parameter per node architecture. For example:
|
||||
# aarch64:grubaa64.efi (dict value)
|
||||
#pxe_bootfile_name_by_arch =
|
||||
|
||||
# Enable iPXE boot. (boolean value)
|
||||
#ipxe_enabled = false
|
||||
|
||||
|
@ -306,6 +306,55 @@ on the Bare Metal service node(s) where ``ironic-conductor`` is running.
|
||||
sudo service ironic-conductor restart
|
||||
|
||||
|
||||
PXE Multi-Arch setup
|
||||
--------------------
|
||||
|
||||
It is possible to deploy servers of different architecture by one conductor.
|
||||
|
||||
To support this feature, architecture specific boot and template files must
|
||||
be configured correctly in the options listed below:
|
||||
|
||||
* ``pxe_bootfile_name_by_arch``
|
||||
* ``pxe_config_template_by_arch``
|
||||
|
||||
These two options are dictionary values. Node's ``cpu_arch`` property is used
|
||||
as the key to find according boot file and template. If according ``cpu_arch``
|
||||
is not found in the dictionary, ``pxe_bootfile_name``, ``pxe_config_template``,
|
||||
``uefi_pxe_bootfile_name`` and ``uefi_pxe_config_template`` are referenced as
|
||||
usual.
|
||||
|
||||
In below example, x86 and x86_64 nodes will be deployed by bootf1 or bootf2
|
||||
based on ``boot_mode`` capability('bios' or 'uefi') as there's no 'x86' or
|
||||
'x86_64' keys available in ``pxe_bootfile_name_by_arch``. While aarch64 nodes
|
||||
will be deployed by bootf3, and ppc64 nodes by bootf4::
|
||||
|
||||
pxe_bootfile_name = bootf1
|
||||
uefi_pxe_bootfile_name = bootf2
|
||||
pxe_bootfile_name_by_arch = aarch64:bootf3,ppc64:bootf4
|
||||
|
||||
Following example assumes you are provisioning x86_64 and aarch64 servers, both
|
||||
in UEFI boot mode.
|
||||
|
||||
Update bootfile and template file configuration parameters in the Bare Metal
|
||||
Service's configuration file (/etc/ironic/ironic.conf)::
|
||||
|
||||
[pxe]
|
||||
|
||||
# Bootfile DHCP parameter for UEFI boot mode. (string value)
|
||||
uefi_pxe_bootfile_name=bootx64.efi
|
||||
|
||||
# Template file for PXE configuration for UEFI boot loader.
|
||||
# (string value)
|
||||
uefi_pxe_config_template=$pybasedir/drivers/modules/pxe_grub_config.template
|
||||
|
||||
# Bootfile DHCP parameter per node architecture. (dictionary value)
|
||||
pxe_bootfile_name_by_arch=aarch64:grubaa64.efi
|
||||
|
||||
# Template file for PXE configuration per node architecture.
|
||||
# (dictionary value)
|
||||
pxe_config_template_by_arch=aarch64:pxe_grubaa64_config.template
|
||||
|
||||
|
||||
Networking service configuration
|
||||
--------------------------------
|
||||
|
||||
|
@ -206,13 +206,13 @@ def create_pxe_config(task, pxe_options, template=None):
|
||||
:param pxe_options: A dictionary with the PXE configuration
|
||||
parameters.
|
||||
:param template: The PXE configuration template. If no template is
|
||||
given the CONF.pxe.pxe_config_template will be used.
|
||||
given the node specific template will be used.
|
||||
|
||||
"""
|
||||
LOG.debug("Building PXE config for node %s", task.node.uuid)
|
||||
|
||||
if template is None:
|
||||
template = CONF.pxe.pxe_config_template
|
||||
template = deploy_utils.get_pxe_config_template(task.node)
|
||||
|
||||
_ensure_config_dirs_exist(task.node.uuid)
|
||||
|
||||
@ -294,10 +294,7 @@ def dhcp_options_for_instance(task):
|
||||
"""
|
||||
dhcp_opts = []
|
||||
|
||||
if deploy_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
|
||||
boot_file = CONF.pxe.uefi_pxe_bootfile_name
|
||||
else:
|
||||
boot_file = CONF.pxe.pxe_bootfile_name
|
||||
boot_file = deploy_utils.get_pxe_boot_file(task.node)
|
||||
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
|
||||
|
@ -58,6 +58,12 @@ opts = [
|
||||
'drivers/modules/pxe_grub_config.template'),
|
||||
help=_('On ironic-conductor node, template file for PXE '
|
||||
'configuration for UEFI boot loader.')),
|
||||
cfg.DictOpt('pxe_config_template_by_arch',
|
||||
default={},
|
||||
help=_('On ironic-conductor node, template file for PXE '
|
||||
'configuration per node architecture. '
|
||||
'For example: '
|
||||
'aarch64:/opt/share/grubaa64_pxe_config.template')),
|
||||
cfg.StrOpt('tftp_server',
|
||||
default='$my_ip',
|
||||
help=_("IP address of ironic-conductor node's TFTP server.")),
|
||||
@ -71,14 +77,16 @@ opts = [
|
||||
help=_('On ironic-conductor node, directory where master TFTP '
|
||||
'images are stored on disk. '
|
||||
'Setting to <None> disables image caching.')),
|
||||
# NOTE(dekehn): Additional boot files options may be created in the event
|
||||
# other architectures require different boot files.
|
||||
cfg.StrOpt('pxe_bootfile_name',
|
||||
default='pxelinux.0',
|
||||
help=_('Bootfile DHCP parameter.')),
|
||||
cfg.StrOpt('uefi_pxe_bootfile_name',
|
||||
default='bootx64.efi',
|
||||
help=_('Bootfile DHCP parameter for UEFI boot mode.')),
|
||||
cfg.DictOpt('pxe_bootfile_name_by_arch',
|
||||
default={},
|
||||
help=_('Bootfile DHCP parameter per node architecture. '
|
||||
'For example: aarch64:grubaa64.efi')),
|
||||
cfg.BoolOpt('ipxe_enabled',
|
||||
default=False,
|
||||
help=_('Enable iPXE boot.')),
|
||||
|
@ -819,6 +819,48 @@ def get_boot_mode_for_deploy(node):
|
||||
return boot_mode.lower() if boot_mode else boot_mode
|
||||
|
||||
|
||||
def get_pxe_boot_file(node):
|
||||
"""Return the PXE boot file name requested for deploy.
|
||||
|
||||
This method returns PXE boot file name to be used for deploy.
|
||||
Architecture specific boot file is searched first. BIOS/UEFI
|
||||
boot file is used if no valid architecture specific file found.
|
||||
|
||||
:param node: A single Node.
|
||||
:returns: The PXE boot file name.
|
||||
"""
|
||||
cpu_arch = node.properties.get('cpu_arch')
|
||||
boot_file = CONF.pxe.pxe_bootfile_name_by_arch.get(cpu_arch)
|
||||
if boot_file is None:
|
||||
if get_boot_mode_for_deploy(node) == 'uefi':
|
||||
boot_file = CONF.pxe.uefi_pxe_bootfile_name
|
||||
else:
|
||||
boot_file = CONF.pxe.pxe_bootfile_name
|
||||
|
||||
return boot_file
|
||||
|
||||
|
||||
def get_pxe_config_template(node):
|
||||
"""Return the PXE config template file name requested for deploy.
|
||||
|
||||
This method returns PXE config template file to be used for deploy.
|
||||
Architecture specific template file is searched first. BIOS/UEFI
|
||||
template file is used if no valid architecture specific file found.
|
||||
|
||||
:param node: A single Node.
|
||||
:returns: The PXE config template file name.
|
||||
"""
|
||||
cpu_arch = node.properties.get('cpu_arch')
|
||||
config_template = CONF.pxe.pxe_config_template_by_arch.get(cpu_arch)
|
||||
if config_template is None:
|
||||
if get_boot_mode_for_deploy(node) == 'uefi':
|
||||
config_template = CONF.pxe.uefi_pxe_config_template
|
||||
else:
|
||||
config_template = CONF.pxe.pxe_config_template
|
||||
|
||||
return config_template
|
||||
|
||||
|
||||
def validate_capabilities(node):
|
||||
"""Validates that specified supported capabilities have valid value
|
||||
|
||||
|
@ -399,10 +399,7 @@ class PXEBoot(base.BootInterface):
|
||||
pxe_options = _build_pxe_config_options(task, pxe_info)
|
||||
pxe_options.update(ramdisk_params)
|
||||
|
||||
if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi':
|
||||
pxe_config_template = CONF.pxe.uefi_pxe_config_template
|
||||
else:
|
||||
pxe_config_template = CONF.pxe.pxe_config_template
|
||||
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
||||
|
||||
pxe_utils.create_pxe_config(task, pxe_options,
|
||||
pxe_config_template)
|
||||
|
@ -1247,6 +1247,95 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
|
||||
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
||||
|
||||
|
||||
class GetPxeBootConfigTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(GetPxeBootConfigTestCase, self).setUp()
|
||||
self.node = obj_utils.get_test_node(self.context, driver='fake')
|
||||
self.config(pxe_bootfile_name='bios-bootfile', group='pxe')
|
||||
self.config(uefi_pxe_bootfile_name='uefi-bootfile', group='pxe')
|
||||
self.config(pxe_config_template='bios-template', group='pxe')
|
||||
self.config(uefi_pxe_config_template='uefi-template', group='pxe')
|
||||
self.bootfile_by_arch = {'aarch64': 'aarch64-bootfile',
|
||||
'ppc64': 'ppc64-bootfile'}
|
||||
self.template_by_arch = {'aarch64': 'aarch64-template',
|
||||
'ppc64': 'ppc64-template'}
|
||||
|
||||
def test_get_pxe_boot_file_bios_without_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_bootfile_name_by_arch={}, group='pxe')
|
||||
result = utils.get_pxe_boot_file(self.node)
|
||||
self.assertEqual('bios-bootfile', result)
|
||||
|
||||
def test_get_pxe_config_template_bios_without_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_config_template_by_arch={}, group='pxe')
|
||||
result = utils.get_pxe_config_template(self.node)
|
||||
self.assertEqual('bios-template', result)
|
||||
|
||||
def test_get_pxe_boot_file_uefi_without_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86_64', 'capabilities': 'boot_mode:uefi'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_bootfile_name_by_arch={}, group='pxe')
|
||||
result = utils.get_pxe_boot_file(self.node)
|
||||
self.assertEqual('uefi-bootfile', result)
|
||||
|
||||
def test_get_pxe_config_template_uefi_without_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86_64', 'capabilities': 'boot_mode:uefi'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_config_template_by_arch={}, group='pxe')
|
||||
result = utils.get_pxe_config_template(self.node)
|
||||
self.assertEqual('uefi-template', result)
|
||||
|
||||
def test_get_pxe_boot_file_cpu_not_in_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_boot_file(self.node)
|
||||
self.assertEqual('bios-bootfile', result)
|
||||
|
||||
def test_get_pxe_config_template_cpu_not_in_by_arch(self):
|
||||
properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_config_template_by_arch=self.template_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_config_template(self.node)
|
||||
self.assertEqual('bios-template', result)
|
||||
|
||||
def test_get_pxe_boot_file_cpu_in_by_arch(self):
|
||||
properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_boot_file(self.node)
|
||||
self.assertEqual('aarch64-bootfile', result)
|
||||
|
||||
def test_get_pxe_config_template_cpu_in_by_arch(self):
|
||||
properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
|
||||
self.node.properties = properties
|
||||
self.config(pxe_config_template_by_arch=self.template_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_config_template(self.node)
|
||||
self.assertEqual('aarch64-template', result)
|
||||
|
||||
def test_get_pxe_boot_file_emtpy_property(self):
|
||||
self.node.properties = {}
|
||||
self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_boot_file(self.node)
|
||||
self.assertEqual('bios-bootfile', result)
|
||||
|
||||
def test_get_pxe_config_template_emtpy_property(self):
|
||||
self.node.properties = {}
|
||||
self.config(pxe_config_template_by_arch=self.template_by_arch,
|
||||
group='pxe')
|
||||
result = utils.get_pxe_config_template(self.node)
|
||||
self.assertEqual('bios-template', result)
|
||||
|
||||
|
||||
@mock.patch('time.sleep', lambda sec: None)
|
||||
class OtherFunctionTestCase(db_base.DbTestCase):
|
||||
|
||||
|
13
releasenotes/notes/multi-arch-deploy-bcf840107fc94bef.yaml
Normal file
13
releasenotes/notes/multi-arch-deploy-bcf840107fc94bef.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- Support multi architecture deployment. E.g., to
|
||||
deploy x86_64, aarch64 servers by one ironic conductor.
|
||||
Two new config options, ``pxe_config_template_by_arch``
|
||||
and ``pxe_bootfile_name_by_arch``, are introduced to
|
||||
support multi architecture deployment. They are
|
||||
dictionary values to hold pxe config templates and
|
||||
boot files for multiple architectures, with cpu_arch
|
||||
property per node as the key. If cpu_arch is not found
|
||||
in dictionary, options ``pxe_config_template``,
|
||||
``pxe_bootfile_name``, ``uefi_pxe_config_template``,
|
||||
``uefi_pxe_bootfile_name`` will be used as usual.
|
Loading…
x
Reference in New Issue
Block a user