Skip to content

fix(dell): merge BIOS writes into the pending job instead of failing - #467

Open
mcanevet wants to merge 2 commits into
bmc-toolbox:mainfrom
mcanevet:fix/bios-settings-conflict-recovery
Open

mcanevet wants to merge 2 commits into
bmc-toolbox:mainfrom
mcanevet:fix/bios-settings-conflict-recovery

Conversation

@mcanevet

@mcanevet mcanevet commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

iDRAC keeps one pending BIOS config job. Any write to Bios/Settings, or to a resource that mirrors a BIOS attribute (such as the SecureBoot resource), seals it, and every further write is rejected with SYS011 until the job is deleted or has run. Two BIOS-affecting calls in the same boot cycle therefore fail on the second one, although both would apply together at the next reset.

Change

The Dell provider's SetBiosConfiguration reads the pending attributes before writing:

  • nothing pending: a plain write, as before;
  • something pending: delete the live job (only if it has not started), merge the request over the staged attributes and write once;
  • everything requested already has the requested value (staged, or applied where nothing is staged) and a live job carries it: nothing is done, so repeating a call is idempotent.

The merge carries along attributes an earlier caller staged, because deleting the job discards what it staged. The deletion is logged, and if the merged write fails afterwards the staged attributes are put back. An unexpected SYS011 is returned to the caller. It is not safe against another client writing BIOS settings on the same BMC at the same time.

redfishwrapper gains ApplyBiosAttributes (typed values; SetBiosConfiguration now delegates to it) and Jobs() (one $expand request, falling back to per-job reads).

Testing

Unit tests, plus runs against a PowerEdge R6715 (iDRAC 1.20.80.51) and an R760xd2 (7.30.10.50): two writes in one boot cycle end up as one job, repeats leave the job alone, and the iDRAC is clean afterwards. Read-only checks on 29 other Dell hosts (iDRAC 5.00 to 7.30, and 1.20) confirmed the settings redirect, the JobService, the ConfigBIOS: job naming and Dell's job resource.

mcanevet added a commit to mcanevet/bmclib that referenced this pull request Sep 10, 2026
…lready matches

SetSecureBootKeyManagement skipped the PATCH whenever the BIOS attribute's
currently-applied value already matched what was requested. Confirmed
live: this is the same class of bug UpdateBiosAttributesExact fixed in
gofish - currently-applied state can spuriously match while a different
value is genuinely pending from an earlier, unrelated call in the same
boot cycle (e.g. a stale pending change left staged by an interrupted
Workflow). Skipping the PATCH left that stale pending value in place,
silently reverting an explicit request to change it on the next reboot -
observed live as a certificate import rejected because SecureBootPolicy
committed as Standard despite requesting Custom moments earlier.

Route through setBiosConfiguration (added in bmc-toolbox#467) instead of
c.redfishwrapper.SetBiosConfiguration directly, so a genuine conflict with
a different pending job is recovered from rather than failing outright,
matching every other Dell BIOS-backed feature.

Depends on bmc-toolbox#467.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch from c036feb to d29e6b6 Compare September 10, 2026 15:04
mcanevet added a commit to mcanevet/bmclib that referenced this pull request Sep 10, 2026
…atches

SetSecureBootKeyManagement read the attribute's currently applied value and
returned early when it already matched what was requested. Confirmed live,
that's unsafe: currently-applied state can match while a different value is
genuinely pending from an earlier call in the same boot cycle (e.g. a stale
pending PATCH left staged by an interrupted, unrelated caller). Skipping the
write left that stale pending value in place, silently reverting an explicit
request on the next reboot - observed as a certificate import rejected
because SecureBootPolicy committed as Standard despite requesting Custom
moments earlier. It's the same class of bug UpdateBiosAttributesExact was
written to fix in gofish, one layer up.

The attribute is still read first, but only to reject platforms that don't
expose it at all. The write itself is now unconditional; recovery from a
genuine pending-job conflict comes for free from the recovering client (bmc-toolbox#467),
so a redundant write costs a round trip rather than a hard failure.

Depends on bmc-toolbox#467.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch 2 times, most recently from f927ee3 to 522710f Compare September 10, 2026 15:45
@mcanevet mcanevet closed this Sep 11, 2026
@mcanevet mcanevet reopened this Sep 11, 2026
mcanevet added a commit to mcanevet/tinkerbell that referenced this pull request Sep 11, 2026
…ct recovery)

Reopened bmc-toolbox/bmclib#467 and rebased it under tinkerbell#471/tinkerbell#472 in
tink-integration: the earlier conclusion that tinkerbell#471 alone eliminated the
IDRAC.2.14.SYS011 conflict class was based on bmc-debug tests using bare
Bios/Settings PATCHes, which don't reflect what bmclib actually sends.
bmclib always asserts @Redfish.SettingsApplyTime, which is itself what
creates the one BIOS Configuration Job iDRAC allows at a time - so any
two separate Dell BIOS-attribute setter calls in one boot cycle still
conflict without tinkerbell#467's recoveringRedfishClient, including within a
single NetworkBootConfig action (SetNetworkBootEnabled then
SetHTTPBootURI) - verified live, through the real bmclib/gofish code
path, not just raw PATCHes.

Signed-off-by: Mickael Canevet <mickael.canevet@proton.ch>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch 4 times, most recently from a3365f7 to 2922ede Compare September 17, 2026 14:04
@mcanevet
mcanevet marked this pull request as ready for review September 17, 2026 14:07
mcanevet added a commit to mcanevet/tinkerbell that referenced this pull request Sep 23, 2026
…ct recovery)

Reopened bmc-toolbox/bmclib#467 and rebased it under tinkerbell#471/tinkerbell#472 in
tink-integration: the earlier conclusion that tinkerbell#471 alone eliminated the
IDRAC.2.14.SYS011 conflict class was based on bmc-debug tests using bare
Bios/Settings PATCHes, which don't reflect what bmclib actually sends.
bmclib always asserts @Redfish.SettingsApplyTime, which is itself what
creates the one BIOS Configuration Job iDRAC allows at a time - so any
two separate Dell BIOS-attribute setter calls in one boot cycle still
conflict without tinkerbell#467's recoveringRedfishClient, including within a
single NetworkBootConfig action (SetNetworkBootEnabled then
SetHTTPBootURI) - verified live, through the real bmclib/gofish code
path, not just raw PATCHes.

Signed-off-by: Mickael Canevet <mickael.canevet@proton.ch>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mcanevet added a commit to mcanevet/tinkerbell that referenced this pull request Sep 23, 2026
…ct recovery)

Reopened bmc-toolbox/bmclib#467 and rebased it under tinkerbell#471/tinkerbell#472 in
tink-integration: the earlier conclusion that tinkerbell#471 alone eliminated the
IDRAC.2.14.SYS011 conflict class was based on bmc-debug tests using bare
Bios/Settings PATCHes, which don't reflect what bmclib actually sends.
bmclib always asserts @Redfish.SettingsApplyTime, which is itself what
creates the one BIOS Configuration Job iDRAC allows at a time - so any
two separate Dell BIOS-attribute setter calls in one boot cycle still
conflict without tinkerbell#467's recoveringRedfishClient, including within a
single NetworkBootConfig action (SetNetworkBootEnabled then
SetHTTPBootURI) - verified live, through the real bmclib/gofish code
path, not just raw PATCHes.

Signed-off-by: Mickael Canevet <mickael.canevet@proton.ch>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch from ca8397d to 00a3a46 Compare September 23, 2026 14:11
@joelrebel

Copy link
Copy Markdown
Member

@mcanevet can you elaborate what problem is this PR trying to solve and why
It would help if you can keep the PR summary clear and concise, leave the details of how its being done to the code and comments.

@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch from 00a3a46 to 7e14bef Compare September 25, 2026 07:54
@mcanevet

Copy link
Copy Markdown
Contributor Author

@joelrebel I updated the PR description. Does it make more sense now? I'm not convinced on the solution, but the problem is real. Currently we can't change 2 BIOS parameters on Dell iDRAC without rebooting.

@joelrebel

Copy link
Copy Markdown
Member

bmclib's Dell setters (SetSecureBoot, SetSecureBootKeyManagement, SetNetworkBootEnabled, SetHTTPBootURI, SetHTTPBootTLSMode) each use the first shape independently, with no visibility into whether another one is coming later in the same boot cycle. So two of them in one boot cycle fail on the second with IDRAC..SYS011, even though both would apply together fine as one job — this isn't an edge case, Tinkerbell's provisioning workflow routinely calls several in the same boot cycle.

@mcanevet based on my understanding, what this PR is trying to achieve is to clear a pending operation that is in conflict and apply the provided settings, correct me if I'm misunderstanding

By forcing all of this into a single method, we prevent the caller from being able to make a decision and it often
hides a real problem in the orchestration/provisioning system where multiple calls are made with no lock like mechanism.

bmclib methods should ideally perform a single operation (read, write) on the BMC and return quickly,
In this case - I would suggest splitting into two methods, see the firmware install methods in the Dell provider as an example*

  1. BIOSConfigurationStatus() which would return the current BIOS configuration status - any active/pending jobs
  2. ApplyBIOSConfiguration() with a force option, this method first checks if any jobs are currently non-terminal (pending/active) and returns an error to the caller, if the force option was specified it logs the pending operation and clears it before applying.

Also note, we could re-use some of how the dell Job and task state is being converted instead of re-implementing, see

type oem struct {

@mcanevet

mcanevet commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor Author

From what I observe, Dell iDRAC works like this:

  • A PATCH without @Redfish.SettingsApplyTime only stages values. Several of these accumulate fine, but staged values are gone after a reboot unless a job exists.
  • Creating a job (a PATCH with OnReset, or POST .../Jobs) locks Bios/Settings. Any further PATCH, with or without apply time, fails with SYS011.
  • Deleting the job wipes its staged values.

So two BIOS changes in the same boot cycle can only work by either cancelling the job and resubmitting the merged set (what this PR does), or by splitting stage and commit into separate calls. This also affects force in your proposal: clearing the job drops the earlier change unless it merges what was staged.

In Tinkerbell's case an explicit commit would just be one more action before the reboot, which is fine. What I'd like to avoid is making the caller aware of Dell's quirk. So if we go that way, I'd rather have a generic stage/commit contract (commit is a no-op on vendors that already apply at reset) than a Dell-specific opt-in.

The options I can see:

  1. Keep it transparent: read pending, cancel the scheduled job, resubmit the merged attributes. No caller changes, but it does cancel a job.
  2. Generic stage/commit: BIOS setters stage, and a new commit call (a Dell job, a no-op elsewhere) applies them before reboot. No cancelling, but callers must commit.

Open to other approaches if you see a better one.

@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch from 7e14bef to b4c93a2 Compare September 29, 2026 08:27
@joelrebel

joelrebel commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

@mcanevet thanks for elaborating that helps clear up the reasoning, the concerns I have is based on these two points, if you can look into resolving these then we likely have a path to have this merged

  1. The current SetBiosConfiguration cancels, merges and applies the merged BIOS attributes - regardless of if the new attributes are the same as the one staged (so we're not able to keep this operation idempotent), does the API provide a mechanism to determine which attributes were previously staged and have this method return if its the same as before?
  2. By merging previously staged attributes, we accept the trade off that its likely - attributes that were not intended by the caller to SetBiosConfiguration could be applied.

side note: The orchestrator (Rufio) in this case should ideally batch up the BIOS changes required and create a single request to set the configuration and follow up the related job on the BMC is finalized

@mcanevet

Copy link
Copy Markdown
Contributor Author

One alternative to the current "PATCH, then recover on SYS011": check for a pending job first and only write once nothing is blocking.

  1. GET Bios/Settings.
  2. If there are no pending attributes, PATCH as it does today.
  3. If there are some, find the pending config job, cancel it if it hasn't started, merge the attributes and PATCH once.

From what I observe, any pending BIOS job locks Bios/Settings regardless of which attribute you write, so the pending-attributes check should catch every case. That way the SYS011 rejection wouldn't happen in the normal flow.

The cost is one extra GET on every Dell BIOS write, which is about 0.5s in my setup. Today a write with no conflict is a single PATCH. Stacked writes in a Tinkerbell workflow would save the rejected PATCH each time.

Not sure which fits bmclib better. Happy to keep it as is, or rework it if you'd prefer this.

@joelrebel

Copy link
Copy Markdown
Member

One alternative to the current "PATCH, then recover on SYS011": check for a pending job first and only write once nothing is blocking.

1. GET `Bios/Settings`.
2. If there are no pending attributes, PATCH as it does today.
3. If there are some, find the pending config job, cancel it if it hasn't started, merge the attributes and PATCH once.

What you suggest here sounds like a fair approach, if you can include those changes, and also if determine if any of the existing dell job struct, task unpacking can be re-used

mcanevet and others added 2 commits October 2, 2026 12:10
ApplyBiosAttributes writes BIOS attributes keeping their native JSON
types (bool, number, string), for callers that resubmit attributes they
read back from the BMC, where stringifying a value could be rejected by
a strict attribute registry. SetBiosConfiguration now delegates to it,
so the apply-time handling exists once.

Jobs lists the jobs of the Redfish JobService, in a single request on a
BMC that supports $expand: a used iDRAC holds well over a hundred jobs,
and reading them one by one takes a request each.

Both are used by the Dell provider to merge a BIOS write into an
already pending job.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch>
iDRAC allows one pending BIOS config job at a time. Any write to
Bios/Settings, or to a resource that mirrors a BIOS attribute such as
the SecureBoot resource, seals that job, and every further write is
rejected with SYS011 until the job is deleted or has run. Two
BIOS-affecting calls in one boot cycle therefore fail on the second,
although both would apply together at the next reset.

SetBiosConfiguration now reads the pending attributes before writing:

- Nothing pending: a plain write, as before.
- Something pending: delete the live job (only if it has not started
  applying), merge the requested attributes over the staged ones and
  write once. The merge carries attributes an earlier caller staged,
  because iDRAC keeps a single job and deleting it discards what it
  staged. If the merged write fails, the attributes the deleted job
  held are staged again and the error says whether that worked. The
  deletion is logged.
- Every requested attribute already at the requested value, staged or,
  where nothing is staged for it, applied, with a live job carrying the
  staged ones: nothing is done, so repeating a call is idempotent.

A SYS011 the read could not foresee is returned to the caller, with a
note when no job could be found to delete.
The sequence is not safe against another client writing BIOS settings
on the same BMC at the same time.

A job is recognized by Oem.Dell.JobType or by its "ConfigBIOS:" name, as
not every iDRAC generation returns an Oem block in the JobService
collection. It is deleted only in a state in which it has not started,
and only if Dell's own job resource, the one the firmware code already
reads, shows no ActualRunningStartTime: that resource reports it on
every generation. A pending job shows "Scheduled" or "Starting"
depending on the generation; gofish's JobState set has neither. Unknown
states are refused rather than assumed safe.

Tested against a PowerEdge R760xd2 (iDRAC 7.30.10.50) and a PowerEdge
R6715 (iDRAC 1.20.80.51).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch>
@mcanevet
mcanevet force-pushed the fix/bios-settings-conflict-recovery branch from b4c93a2 to e34b654 Compare October 2, 2026 11:04
@mcanevet mcanevet changed the title fix(dell): recover instead of failing when a BIOS settings PATCH conflicts fix(dell): merge BIOS writes into the pending job instead of failing Oct 2, 2026
@mcanevet

mcanevet commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@joelrebel I reworked this along the lines you agreed to, and rewrote the description to be shorter.

  • Look first. SetBiosConfiguration now reads the pending attributes before writing (two extra GETs) and only deletes and merges when something is pending. The retry on SYS011 is gone, so a SYS011 the read could not foresee reaches the caller. I went with this rather than the separate status and force methods, as agreed above.
  • Idempotence. If every requested attribute already has the requested value (staged, or applied where nothing is staged for it) and a live job carries the staged ones, nothing is touched. I checked on hardware that repeating a call keeps the same job.
  • Unintended attributes. This remains a trade-off I can't remove: iDRAC keeps one job and deleting it discards what it staged, so the merge carries those attributes along. The deletion is logged with the staged and requested attribute names, and if the merged write fails afterwards, the attributes of the deleted job are staged again and the error says whether that worked.
  • Reuse. The "has it started" check now reads Dell's own job resource through the existing Dell struct and the endpoint that job() in firmware.go already reads (moved onto the wrapper, Conn.job delegates to it). That turned out to be needed: on iDRAC 7.30 the JobService job has no Oem block, so ActualRunningStartTime is only visible there. I did not reuse ConvertTaskState/TaskStateActive: Scheduled maps to PowerCycleHost, which TaskStateActive rejects, and paused jobs end up in Failed, which I need to treat as possibly started.
  • Cost. Reading jobs one by one took up to a minute on hosts with about 120 jobs, so Jobs() asks for the collection expanded (a single request on every iDRAC I tried, with a fallback to per-job reads).

I ran two writes in one boot cycle against a PowerEdge R6715 (iDRAC 1.20.80.51) and an R760xd2 (7.30.10.50): they end up as one job, repeats leave it alone, and the iDRAC is clean afterwards. I also did read-only checks on 29 other Dell hosts (iDRAC 5.00 to 7.30, and 1.20) for the settings redirect, JobService, the ConfigBIOS: naming and Dell's job resource. The in-flight job states (Scheduled, Starting, …) are only confirmed on the two generations where I created a job; an unknown state is refused rather than deleted.

mcanevet added a commit to mcanevet/tinkerbell that referenced this pull request Oct 2, 2026
tink-integration is rebuilt from upstream main plus bmc-toolbox/bmclib#467
and tinkerbell#471. It now merges BIOS writes into a pending Dell BIOS job instead
of failing the second write of a boot cycle with SYS011, which the
customboot BMC actions (NetworkBootConfig, SecureBoot, ...) hit when
several run before one reboot.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants