[sonic-installer] Add dedicated SONiC-BMC U-Boot bootloader#4602
Conversation
SONiC-BMC runs U-Boot with a fixed two-slot ASPEED environment that the shared UbootBootloader mishandles. Add BmcUbootBootloader: Aboot-style boot contract (set-default wins, boot_once one-shot), exact slot match, per-slot FIPS, and platforms_asic verification. Detect via is_bmc() and guard UbootBootloader.detect() to exclude BMC. Signed-off-by: William Tsai <willtsai@nvidia.com>
…te set-fips echo Make BmcUbootBootloader the first entry in BOOTLOADERS so a BMC is never preempted by an accidental/stale /host/grub/grub.cfg. This is safe because BmcUbootBootloader.detect() is is_bmc(), which is False on every non-BMC platform, so probing it first cannot mis-select on non-BMC systems. Drop the duplicate 'Done' echo in set_fips(); the CLI already prints the user-facing success message. Add a regression test asserting BMC wins when both BMC and GRUB detectors return true. Signed-off-by: William Tsai <willtsai@nvidia.com>
…m; add tests verify_image_platform() returned True whenever the tar extraction of installer/platforms_asic failed, so a malformed/non-tar payload or one missing the manifest silently passed platform validation. Key the result on grep (p3) instead: compatible only when the running platform is explicitly listed. This fails closed on malformed/missing manifests while still accepting an early grep match where tar is then SIGPIPE'd (which would wrongly fail if keyed on tar's return code). Add unit tests for verify_image_platform (match / mismatch / fail-closed / SIGPIPE-match / non-file), fw_printenv read-failure handling, get_installed_images empty-slot skipping, and the only-populated-slot remove guard. Signed-off-by: William Tsai <willtsai@nvidia.com>
…mment Signed-off-by: William Tsai <willtsai@nvidia.com>
…-boot Signed-off-by: William Tsai <willtsai@nvidia.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
…fy flake8 E501 The single-line 'return ... or ... or ...' in get_next_image() was 128 characters, exceeding the 120-char limit enforced by the pre-commit flake8 check (Pretest Static Analysis). Wrap it across lines; behavior is unchanged. Signed-off-by: William Tsai <willtsai@nvidia.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
yxieca
left a comment
There was a problem hiding this comment.
Reviewed the dedicated SONiC-BMC U-Boot bootloader. This is a clean, security-conscious plugin: transparent on every existing platform (no CLI/output change), the two-slot lifecycle is correct, verify_image_platform faithfully mirrors the GRUB pipeline and fails closed, and the 12 tests are genuinely thorough — including negative cases (only-slot abort, graceful fw_printenv failure, fail-closed platform check) and a test that locks in the detect() mutual-exclusion contract.
I paid particular attention to the one change with cross-platform blast radius — registering BmcUbootBootloader first in BOOTLOADERS plus the generic UbootBootloader.detect() guard. Verified it's safe: is_bmc() short-circuits False on non-BMC platforms (with a hasattr guard for older sonic_py_common), the two detect() paths are mutually exclusive, and it's regression-locked by test_detect_uses_bmc_and_generic_uboot_excludes_bmc. Existing switches see zero behavior change.
A couple of non-blocking items:
-
install_imagerelies on an implicit cross-repo contract. It assumes the image's embedded installer rotates the new image into slot 1 (boot_next = run sonic_image_1) and then just clearsboot_once; it never verifies which slot actually received the image. That matches buildimage'ssonic-program-uboot-env.shtoday, so it's correct — but it's an undocumented coupling that would silently mis-select if the installer's slot rotation ever changed. Suggest either an inline comment naming the buildimage contract, or re-reading the resulting slot after the install instead of assuming slot 1. -
Backport scope. This is new-platform enablement (ASPEED AST2700 BMC), so master-only seems right. If the BMC platform is being shipped on a release branch, this would need to go with the buildimage +
is_switch_bmcpieces on that branch too. Worth a one-line confirmation.
Neither blocks. Nice work — the test coverage and the fail-closed platform check in particular.
AI agent on behalf of Ying.
|
IMHO, the fixes for set_fips, remove_image, verify_image_platform, and get_next_image (checking and returning the image set in boot_once) are generic to bring into UbootBootLoader itself. In fact, we had the changes for get_next _image checking boot_once locally but for some reason, this fix was missed in the upstream pr we raised. Regarding set_default behavior to clear boot_once, IIUC, even grub.py is implemented as it's been done for uboot (default and boot_once remain orthogonal). Only Aboot seem to have taken a different approach. I am a little confused with the base Uboot class matching Grub's and the specialized Aspeed class matching Aboot's. In addition, assuming most of the existing deployments use Grub, I would prefer BMC to follow that model as customers workflow may not expect the BMC to behave differently than their grub based host (x86) or uboot based host (marvell) Adding @pavannaregundi @antony-rheneus for their inputs too. |
UbootBootLoaderPR #4489 fixed One-shot mechanismBMC should behave like the operator's other hosts, and GRUB is the right reference. def set_default_image(self, image): # sonic-installer set-default
run_command(['grub-set-default', '--boot-directory=/host', str(idx)])
def set_next_image(self, image): # sonic-installer set-next-boot
run_command(['grub-reboot', '--boot-directory=/host', str(idx)])
1. GRUB source — $grub_editenv ${grubdir}/grubenv unset prev_saved_entry
$grub_editenv ${grubdir}/grubenv unset next_entry # <-- clears any pending grub-reboot (set-next-boot)
$grub_editenv ${grubdir}/grubenv set saved_entry="$entry"
$grub_editenv ${grubdir}/grubenv set next_entry="$entry" # saved_entry left as-isSo the relationship is not orthogonal — it's deliberately asymmetric: 2. This is long-standing GRUB behavior, not a recent or distro-specific quirk. The 3. Verified on real hardware — a GRUB x86 switch, driving the actual
This matches the source exactly. What that means for this PR
So the genuinely orthogonal one is the current generic Reference links
|
|
@william8545 Thank you for the details. I missed checking the GRUB source code and only went with grub.py. I don't have any more comments. Thanks for fixing this. |
|
Cherry-pick PR to msft-202608: Azure/sonic-utilities.msft#386 |
…t#4602) Address issue - sonic-net#4548 What I did Added a dedicated BmcUbootBootloader so sonic-installer manages SONiC-BMC boot state correctly. SONiC-BMC platforms (ASPEED AST2700) run U-Boot with a two-slot environment, which the generic UbootBootloader does not model — on a BMC it previously fell through to that generic handler and operated on the wrong environment variables. Specifically: New sonic_installer/bootloader/bmc_uboot.py (BmcUbootBootloader, NAME = 'bmc-uboot'), selected via utilities_common.chassis.is_bmc(). Registered first in BOOTLOADERS so a stray /host/grub/grub.cfg cannot preempt it, and guarded UbootBootloader.detect() to exclude BMC. Full two-slot lifecycle: list, set-default, set-next-boot, install, remove, cleanup, verify-next-image, set-fips/get-fips, binary-version, plus a BMC-specific verify_image_platform (fails closed). Unit tests in tests/installer_bootloader_bmc_uboot_test.py (12 tests). No change to sonic_installer/main.py — the work is confined to the bootloader plugin and its test. How I did it BmcUbootBootloader subclasses OnieInstallerBootloader and models the ASPEED AST2700 two-slot U-Boot environment: Concept Slot 1 Slot 2 version var sonic_version_1 sonic_version_2 boot command run sonic_image_1 run sonic_image_2 kernel args linuxargs linuxargs_old Empty slots are written/read as the marker None (also accepts NONE/empty, case-insensitively). Boot selection (Aboot-style contract): set-default writes the persistent boot_next and clears the one-shot boot_once; set-next-boot writes only boot_once. get_next_image() resolves boot_once first (it wins, matching U-Boot bootcmd), then boot_next, and surfaces an empty/unrecognized selector as a raw string so a broken boot state stays visible rather than being masked. Install: install_image() runs the image's own installer (bash <image>, which performs the slot rotation and sets boot_next) and then clears any stale boot_once that would otherwise shadow the new default. Remove: remove_image() repoints boot_once/boot_next to the surviving slot before clearing the removed slot's version + auxiliary vars and deleting its rootfs; it refuses to remove the only populated slot. FIPS: set-fips/get-fips rewrite the sonic_fips= token in the target slot's linuxargs/linuxargs_old only (per-slot isolation). Platform check: verify_image_platform() mirrors the GRUB approach — extracts installer/platforms_asic from the image and matches the running device_info.get_platform() with grep -Fxq (whole-line). It fails closed: compatible only when grep matches, so a malformed/manifest-less payload is rejected rather than silently accepted. How to verify it On a SONiC-BMC (ASPEED AST2700) board, the dedicated bootloader is auto-selected and the full command surface was exercised end-to-end, cross-checking the U-Boot environment (fw_printenv) and, across real reboots, /proc/cmdline: sonic-installer list / verify-next-image reflect the two-slot state. set-default <img> sets boot_next and clears boot_once; set-next-boot <img> sets a one-shot boot_once that U-Boot consumes on the next reboot and then reverts to boot_next. install <bmc.bin> rotates the new image into a slot and boots it after reboot. remove / cleanup clear the slot's vars, repoint the boot selectors, and delete the rootfs. set-fips/get-fips toggle sonic_fips= in the correct per-slot args. A wrong-platform (switch) image is rejected by verify_image_platform; --skip-platform-check overrides it. Previous command output (if the output of a command-line utility has changed) New command output (if the output of a command-line utility has changed) Signed-off-by: William Tsai <willtsai@nvidia.com> Signed-off-by: Yogapriya Mohankumar <ymohanku@cisco.com>
Address issue - #4548
What I did
Added a dedicated
BmcUbootBootloadersosonic-installermanages SONiC-BMC boot state correctly. SONiC-BMC platforms (ASPEED AST2700) run U-Boot with a two-slot environment, which the genericUbootBootloaderdoes not model — on a BMC it previously fell through to that generic handler and operated on the wrong environment variables.Specifically:
sonic_installer/bootloader/bmc_uboot.py(BmcUbootBootloader,NAME = 'bmc-uboot'), selected viautilities_common.chassis.is_bmc().BOOTLOADERSso a stray/host/grub/grub.cfgcannot preempt it, and guardedUbootBootloader.detect()to exclude BMC.list,set-default,set-next-boot,install,remove,cleanup,verify-next-image,set-fips/get-fips,binary-version, plus a BMC-specificverify_image_platform(fails closed).tests/installer_bootloader_bmc_uboot_test.py(12 tests).No change to
sonic_installer/main.py— the work is confined to the bootloader plugin and its test.How I did it
BmcUbootBootloadersubclassesOnieInstallerBootloaderand models the ASPEED AST2700 two-slot U-Boot environment:sonic_version_1sonic_version_2run sonic_image_1run sonic_image_2linuxargslinuxargs_oldEmpty slots are written/read as the marker
None(also acceptsNONE/empty, case-insensitively).set-defaultwrites the persistentboot_nextand clears the one-shotboot_once;set-next-bootwrites onlyboot_once.get_next_image()resolvesboot_oncefirst (it wins, matching U-Bootbootcmd), thenboot_next, and surfaces an empty/unrecognized selector as a raw string so a broken boot state stays visible rather than being masked.install_image()runs the image's own installer (bash <image>, which performs the slot rotation and setsboot_next) and then clears any staleboot_oncethat would otherwise shadow the new default.remove_image()repointsboot_once/boot_nextto the surviving slot before clearing the removed slot's version + auxiliary vars and deleting its rootfs; it refuses to remove the only populated slot.set-fips/get-fipsrewrite thesonic_fips=token in the target slot'slinuxargs/linuxargs_oldonly (per-slot isolation).verify_image_platform()mirrors the GRUB approach — extractsinstaller/platforms_asicfrom the image and matches the runningdevice_info.get_platform()withgrep -Fxq(whole-line). It fails closed: compatible only when grep matches, so a malformed/manifest-less payload is rejected rather than silently accepted.How to verify it
On a SONiC-BMC (ASPEED AST2700) board, the dedicated bootloader is auto-selected and the full command surface was exercised end-to-end, cross-checking the U-Boot environment (
fw_printenv) and, across real reboots,/proc/cmdline:sonic-installer list/verify-next-imagereflect the two-slot state.set-default <img>setsboot_nextand clearsboot_once;set-next-boot <img>sets a one-shotboot_oncethat U-Boot consumes on the next reboot and then reverts toboot_next.install <bmc.bin>rotates the new image into a slot and boots it after reboot.remove/cleanupclear the slot's vars, repoint the boot selectors, and delete the rootfs.set-fips/get-fipstogglesonic_fips=in the correct per-slot args.verify_image_platform;--skip-platform-checkoverrides it.Previous command output (if the output of a command-line utility has changed)
New command output (if the output of a command-line utility has changed)