Skip to content

🌱 Refactor action EnsureProvisioned#2091

Draft
Dhairya-Arora01 wants to merge 2 commits into
mainfrom
2073
Draft

🌱 Refactor action EnsureProvisioned#2091
Dhairya-Arora01 wants to merge 2 commits into
mainfrom
2073

Conversation

@Dhairya-Arora01

@Dhairya-Arora01 Dhairya-Arora01 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:
Refactor actionEnsureProvisioned to reliably detect failed rescue mode reboot.

actionEnsureProvisioned is refactored into 2 steps:
Step-1: Check if the host is still in rescue mode SSH on port 22 and check hostname.

If hostname is rescue:

  • Check if the host was just rebooted (within 15s period)
    • If yes, reque with delay.
  • Ensure rescue is not set for the next reboot.
    • If rescue is set for next boot, unset it via robot API.
  • Based on the current ErrorType, decide what to do:
    • Empty: This means no prior reboot triggered. Use trigger software reboot (if the host supports it) otherwise hardware reboot.
    • ErrorTypeSoftwareRebootTriggered and not timed out yet (within softwareResetTimeout of 10 min): The software reboot is still in progress, so reque with delay.
    • ErrorTypeSoftwareRebootTriggered and timed out: Software reboot failed to bring the host out of rescue. Escalate to hardware reboot. Trigger hardware reboot via Robot API, set ErrorTypeHardwareRebootTriggered and move to Step-2.

If SSH error or hostname is not rescue, then also move to step-2

Step-2:
If SSHAfterInstallImage is disabled, mark the action as completed, clear the ErrorType and move to the next phase Provisioned. Otherwise, SSH via configured OS port and check hostname.

  • If hostname matches the desired hostname: mark the action as completed, clear the errorType and move to the next phase.
  • If the hostname doesn't match:
    • If ErrorTypeHardwareRebootTriggered and not yet timed out (within hardwareResetTimeout of 10 min): requeue with delay as the hardware reboot is still in progress.
    • Otherwise set a permanent error.

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #2073

Special notes for your reviewer:

Please confirm that if this PR changes any image versions, then that's the sole change this PR makes.

TODOs:

TODO: Instead of setting permanent error in step-2 when hardware reboot timed out set a fatal error so that it can retry.

  • squash commits
  • include documentation
  • add unit tests

Refactor `actionEnsureProvisioned` to reliably detect failed rescue mode
reboot.

actionEnsureProvisioned is refactored into 2 steps:
**Step-1**: Check if the host is still in rescue mode
SSH on port 22 and check `hostname`.

If hostname is `rescue`:
- Check if the host was just rebooted (within 15s period)
  - If yes, reque with delay.
- Ensure rescue is not set for the next reboot.
  - If rescue is set for next boot, unset it via robot API.
- Based on the current `ErrorType`, decide what to do:
  - *Empty*: This means no prior reboot triggered. Use
  trigger software reboot (if the host supports it) otherwise hardware reboot.
  - *`ErrorTypeSoftwareRebootTriggered` and not timed out yet
  (within `softwareResetTimeout` of 10 min)*: The software reboot is still in
  progress, so reque with delay.
  - *`ErrorTypeSoftwareRebootTriggered` and timed out*: Software reboot failed
  to bring the host out of rescue. Escalate to hardware reboot. Trigger
  hardware reboot via Robot API, set `ErrorTypeHardwareRebootTriggered`
  and move to Step-2.

If SSH error or hostname is not rescue, then also move to step-2

**Step-2**:
If `SSHAfterInstallImage` is disabled, mark the action as completed,
clear the `ErrorType` and move to the next phase `Provisioned`.
Otherwise, SSH via configured OS port and check hostname.
- If SSH error (connection reset or timed out): reque with delay.
- If hostname matches the desired hostname: mark the action as completed, clear
  the errorType and move to the next phase.
- If the hostname doesn't match:
  - If `ErrorTypeHardwareRebootTriggered` and not yet timed out
  (within `hardwareResetTimeout` of 10 min): requeue with delay
  as the hardware reboot is still in progress.
  - Otherwise set a permanent error.

Signed-off-by: Dhairya Arora <dhairya.arora@syself.com>
@github-actions github-actions Bot added area/code Changes made in the code directory size/L Denotes a PR that changes 200-800 lines, ignoring generated files. labels Jun 9, 2026
func (s *Service) actionEnsureProvisioned(ctx context.Context) (ar actionResult) {
markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateEnsureProvisioned)

// If hardware reboot was triggered, then move to Step-2 i.e. ensuring host is booted into the desired OS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain why you do this check...

IMO the explanation is: in case the hardware reboot is triggered already, we have reached the last phase of escalation already.

func (s *Service) actionEnsureProvisioned(ctx context.Context) (ar actionResult) {
markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateEnsureProvisioned)

// If hardware reboot was triggered, then move to Step-2 i.e. ensuring host is booted into the desired OS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain the two steps a bit better here in this function? It's not really undrestandable

// Check hostname with sshClient.
out := sshClient.GetHostName(ctx)
if out.Err != nil || out.StdErr != "" {
// Rescue system is not reachable, we can move to step-2 i.e. verifying if host is booted into provisioned OS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can write this a bit differently: "We failed to reach the server via ssh and assume that it rebooting, or has rebooted already. Therefore, we can go to step 2 to verify that it is provisions successfully.

if hostname != rescue {
// Host is not in rescue mode, we can move to step-2 i.e. verifying if host is booted into provisioned OS.
//
// NOTE: We are not analyzing the SSH errors because in this step we only need to detect if the host is in rescue mode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't we analyze SSH errors before? I don't understand this.. Do you mean that we don't check for specific errors and differentiate them? In this case, you should probably add this to the if statement above

return actionContinue{delay: 5 * time.Second}

case infrav1.ErrorTypeSoftwareRebootTriggered:
// If the software reboot timed out, then trigger a hardware reboot, otherwise reque.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

// If the software reboot timed out, then trigger a hardware reboot, otherwise reque.
if !hasTimedOut(s.scope.HetznerBareMetalHost.Spec.Status.RebootTriggeredAt, softwareResetTimeout) {
markProvisionPendingWithInfo(s.scope.HetznerBareMetalHost, infrav1.StateEnsureProvisioned, "waiting for software reboot to complete")
return actionContinue{delay: 5 * time.Second}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we requeue every 5 seconds? Seems very quickly. How do we do it in other places? We should keep that aligned probably.


if hostname != desiredHostName {
// if the host was just rebooted, give it some time.
if s.hasJustRebooted() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is something you don't re-use above - but you have the same case, don't you?

msg := "reboot handling failed"
if err != nil {
msg = err.Error()
if sshclient.IsConnectionRefusedError(out.Err) || os.IsTimeout(out.Err) || sshclient.IsTimeoutError(out.Err) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this makes a lot of sense. If it is another error why we cannot reach the server, then we would say "host name doesn't match" even though we didn't even manage to ssh onto the server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/code Changes made in the code directory size/L Denotes a PR that changes 200-800 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor ensureProvisioned to reliably detect failed rescue mode reboot

2 participants