Skip to content

Merge stable/2024.1 into stackhpc/2024.1 #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 30, 2025
6 changes: 6 additions & 0 deletions ironic_python_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,12 @@ def process_lookup_data(self, content):
if config.get('metrics_statsd'):
for opt, val in config.items():
setattr(cfg.CONF.metrics_statsd, opt, val)
if config.get('disable_deep_image_inspection') is not None:
cfg.CONF.set_override('disable_deep_image_inspection',
config['disable_deep_image_inspection'])
if config.get('permitted_image_formats') is not None:
cfg.CONF.set_override('permitted_image_formats',
config['permitted_image_formats'])
md5_allowed = config.get('agent_md5_checksum_enable')
if md5_allowed is not None:
cfg.CONF.set_override('md5_enabled', md5_allowed)
Expand Down
76 changes: 74 additions & 2 deletions ironic_python_agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,82 @@
help='If the agent should rebuild the configuration drive '
'using a local filesystem, instead of letting Ironic '
'determine if this action is necessary.'),
cfg.BoolOpt('disable_deep_image_inspection',
default=False,
help='This disables the additional deep image inspection '
'the agent does before converting and writing an image. '
'Generally, this should remain enabled for maximum '
'security, but this option allows disabling it if there '
'is a compatability concern.'),
cfg.ListOpt('permitted_image_formats',
default='raw,qcow2',
help='The supported list of image formats which are '
'permitted for deployment with Ironic Python Agent. If '
'an image format outside of this list is detected, the '
'image validation logic will fail the deployment '
'process. This check is skipped if deep image '
'inspection is disabled.'),
]

CONF.register_cli_opts(cli_opts)
disk_utils_opts = [
cfg.IntOpt('efi_system_partition_size',
default=550,
help='Size of EFI system partition in MiB when configuring '
'UEFI systems for local boot. A common minimum is ~200 '
'megabytes, however OS driven firmware updates and '
'unikernel usage generally requires more space on the '
'efi partition.'),
cfg.IntOpt('bios_boot_partition_size',
default=1,
help='Size of BIOS Boot partition in MiB when configuring '
'GPT partitioned systems for local boot in BIOS.'),
cfg.StrOpt('dd_block_size',
default='1M',
help='Block size to use when writing to the nodes disk.'),
cfg.IntOpt('partition_detection_attempts',
default=3,
min=1,
help='Maximum attempts to detect a newly created partition.'),
cfg.IntOpt('partprobe_attempts',
default=10,
help='Maximum number of attempts to try to read the '
'partition.'),
cfg.IntOpt('image_convert_memory_limit',
default=2048,
help='Memory limit for "qemu-img convert" in MiB. Implemented '
'via the address space resource limit.'),
cfg.IntOpt('image_convert_attempts',
default=3,
help='Number of attempts to convert an image.'),
]

disk_part_opts = [
cfg.IntOpt('check_device_interval',
default=1,
help='After Ironic has completed creating the partition table, '
'it continues to check for activity on the attached iSCSI '
'device status at this interval prior to copying the image'
' to the node, in seconds'),
cfg.IntOpt('check_device_max_retries',
default=20,
help='The maximum number of times to check that the device is '
'not accessed by another process. If the device is still '
'busy after that, the disk partitioning will be treated as'
' having failed.')
]


def list_opts():
return [('DEFAULT', cli_opts)]
return [('DEFAULT', cli_opts),
('disk_utils', disk_utils_opts),
('disk_partitioner', disk_part_opts)]


def populate_config():
"""Populate configuration. In a method so tests can easily utilize it."""
CONF.register_cli_opts(cli_opts)
CONF.register_opts(disk_utils_opts, group='disk_utils')
CONF.register_opts(disk_part_opts, group='disk_partitioner')


def override(params):
Expand All @@ -403,3 +472,6 @@ def override(params):
LOG.warning('Unable to override configuration option %(key)s '
'with %(value)r: %(exc)s',
{'key': key, 'value': value, 'exc': exc})


populate_config()
124 changes: 124 additions & 0 deletions ironic_python_agent/disk_partitioner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2014 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""
Code for creating partitions on a disk.

Imported from ironic-lib's disk_utils as of the following commit:
https://opendev.org/openstack/ironic-lib/commit/42fa5d63861ba0f04b9a4f67212173d7013a1332
"""

import logging

from ironic_lib.common.i18n import _
from ironic_lib import exception
from ironic_lib import utils
from oslo_config import cfg

CONF = cfg.CONF

LOG = logging.getLogger(__name__)


class DiskPartitioner(object):

def __init__(self, device, disk_label='msdos', alignment='optimal'):
"""A convenient wrapper around the parted tool.

:param device: The device path.
:param disk_label: The type of the partition table. Valid types are:
"bsd", "dvh", "gpt", "loop", "mac", "msdos",
"pc98", or "sun".
:param alignment: Set alignment for newly created partitions.
Valid types are: none, cylinder, minimal and
optimal.

"""
self._device = device
self._disk_label = disk_label
self._alignment = alignment
self._partitions = []

def _exec(self, *args):
# NOTE(lucasagomes): utils.execute() is already a wrapper on top
# of processutils.execute() which raises specific
# exceptions. It also logs any failure so we don't
# need to log it again here.
utils.execute('parted', '-a', self._alignment, '-s', self._device,
'--', 'unit', 'MiB', *args, use_standard_locale=True)

def add_partition(self, size, part_type='primary', fs_type='',
boot_flag=None, extra_flags=None):
"""Add a partition.

:param size: The size of the partition in MiB.
:param part_type: The type of the partition. Valid values are:
primary, logical, or extended.
:param fs_type: The filesystem type. Valid types are: ext2, fat32,
fat16, HFS, linux-swap, NTFS, reiserfs, ufs.
If blank (''), it will create a Linux native
partition (83).
:param boot_flag: Boot flag that needs to be configured on the
partition. Ignored if None. It can take values
'bios_grub', 'boot'.
:param extra_flags: List of flags to set on the partition. Ignored
if None.
:returns: The partition number.

"""
self._partitions.append({'size': size,
'type': part_type,
'fs_type': fs_type,
'boot_flag': boot_flag,
'extra_flags': extra_flags})
return len(self._partitions)

def get_partitions(self):
"""Get the partitioning layout.

:returns: An iterator with the partition number and the
partition layout.

"""
return enumerate(self._partitions, 1)

def commit(self):
"""Write to the disk."""
LOG.debug("Committing partitions to disk.")
cmd_args = ['mklabel', self._disk_label]
# NOTE(lucasagomes): Lead in with 1MiB to allow room for the
# partition table itself.
start = 1
for num, part in self.get_partitions():
end = start + part['size']
cmd_args.extend(['mkpart', part['type'], part['fs_type'],
str(start), str(end)])
if part['boot_flag']:
cmd_args.extend(['set', str(num), part['boot_flag'], 'on'])
if part['extra_flags']:
for flag in part['extra_flags']:
cmd_args.extend(['set', str(num), flag, 'on'])
start = end

self._exec(*cmd_args)

try:
from ironic_python_agent import disk_utils # circular dependency
disk_utils.wait_for_disk_to_become_available(self._device)
except exception.IronicException as e:
raise exception.InstanceDeployFailure(
_('Disk partitioning failed on device %(device)s. '
'Error: %(error)s')
% {'device': self._device, 'error': e})
Loading