Skip to content

OSAC-3402: Add E2E test for bare metal instance restart - #277

Closed
mennyaboush wants to merge 10 commits into
osac-project:mainfrom
mennyaboush:feat/OSAC-3402-e2e-bmi-restart
Closed

mennyaboush wants to merge 10 commits into
osac-project:mainfrom
mennyaboush:feat/OSAC-3402-e2e-bmi-restart

Conversation

@mennyaboush

@mennyaboush mennyaboush commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add dedicated E2E test for BMI restart (power cycle) in tests/bmaas/test_baremetal_instance_restart.py
  • Add update_baremetal_instance_restart_trigger() helper to GRPCClient for field-masked spec.restart_trigger updates

Test Coverage

The test exercises the full restart_trigger flow:

  1. Provision a BMI and wait for Running state
  2. Increment spec.restart_trigger via BareMetalInstances/Update with field mask
  3. Wait for RESTART_IN_PROGRESS condition to appear
  4. Verify status.restart_trigger echoes the value set in spec
  5. Verify host comes back online (BMH status.poweredOn=true, BMI back to Running)
  6. Verify RESTART_IN_PROGRESS condition clears and RESTART_FAILED is absent
  7. Delete BMI, verify cleanup from both K8s and gRPC

Jira

OSAC-3402

Test plan

  • ruff check passes (verified locally)
  • ruff format --check passes (verified locally)
  • make test-bmaas passes in CI (e2e-bmaas-full-install workflow)
  • No regressions in existing test_baremetal_instance_lifecycle

Assisted-by: Claude Code noreply@anthropic.com

Summary by CodeRabbit

  • Tests
    • Added end-to-end coverage for restarting bare-metal instances.
    • Verifies restart requests, trigger updates, power cycling, and return to a running state.
    • Confirms restart conditions clear after successful completion.
    • Validates instance deletion and complete resource cleanup, including failure handling.
    • Improved lifecycle diagnostics and progress reporting for provisioning, assignment, power state, and readiness checks.

Add a dedicated test for BMI restart (power cycle) that verifies
the full restart_trigger flow: increment spec.restart_trigger via
Update RPC, observe RESTART_IN_PROGRESS condition, wait for
status.restart_trigger echo, confirm host comes back online, and
clean up.

Also adds update_baremetal_instance_restart_trigger() helper to
GRPCClient for field-masked restart_trigger updates.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
@openshift-ci-robot

openshift-ci-robot commented Jul 30, 2026 •

Copy link
Copy Markdown

@mennyaboush: This pull request references OSAC-3402 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Add dedicated E2E test for BMI restart (power cycle) in tests/bmaas/test_baremetal_instance_restart.py
  • Add update_baremetal_instance_restart_trigger() helper to GRPCClient for field-masked spec.restart_trigger updates

Test Coverage

The test exercises the full restart_trigger flow:

  1. Provision a BMI and wait for Running state
  2. Increment spec.restart_trigger via BareMetalInstances/Update with field mask
  3. Wait for RESTART_IN_PROGRESS condition to appear
  4. Verify status.restart_trigger echoes the value set in spec
  5. Verify host comes back online (BMH status.poweredOn=true, BMI back to Running)
  6. Verify RESTART_IN_PROGRESS condition clears and RESTART_FAILED is absent
  7. Delete BMI, verify cleanup from both K8s and gRPC

Jira

OSAC-3402

Test plan

  • ruff check passes (verified locally)
  • ruff format --check passes (verified locally)
  • make test-bmaas passes in CI (e2e-bmaas-full-install workflow)
  • No regressions in existing test_baremetal_instance_lifecycle

Assisted-by: Claude Code noreply@anthropic.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: mennyaboush
Once this PR has been reviewed and has the lgtm label, please assign jhernand for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a gRPC method for updating a bare metal instance restart trigger. Adds lifecycle diagnostics and an end-to-end test for restart, recovery, deletion, and failure cleanup.

Changes

Bare Metal Instance Restart

Layer / File(s) Summary
Restart trigger update API
tests/core/grpc_client.py
Adds a BareMetalInstances/Update wrapper that updates only spec.restart_trigger.
Lifecycle diagnostics
tests/bmaas/test_baremetal_instance_lifecycle.py
Adds BMH inventory, lifecycle progress, gRPC state, and CR condition logging.
Restart lifecycle end-to-end test
tests/bmaas/test_baremetal_instance_restart.py
Creates a BMI, triggers a restart, polls restart conditions and trigger propagation, verifies BMH power-on and BMI recovery, and cleans up on success or failure.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RestartTest as pytest restart test
  participant GRPCClient
  participant K8sHub as k8s_hub_client
  RestartTest->>GRPCClient: Update spec.restart_trigger
  GRPCClient->>GRPCClient: Call BareMetalInstances/Update
  RestartTest->>GRPCClient: Poll restart conditions and status.restartTrigger
  RestartTest->>K8sHub: Poll BMH power state
  RestartTest->>GRPCClient: Verify Running state and delete instance
Loading

Possibly related PRs

Suggested reviewers: tzvatot, rgolangh


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error _log_full_state calls logger.exception; a failed gRPC call renders CalledProcessError, whose command includes Authorization: Bearer <token> from _build_args, and INFO logs stream in CI. Redact the bearer token from subprocess exceptions or log only a sanitized error type; avoid logging raw BMH errorMessage and internal host identifiers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding an end-to-end test for bare-metal instance restart.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Hardcoded-Secrets ✅ Passed The PR additions contain no literal API keys, passwords, private keys, embedded credentials, or long encoded blobs; gRPC token and SSH key are injected parameters/fixtures.
No-Weak-Crypto ✅ Passed The PR adds only gRPC/test logic; targeted scans found no MD5, SHA1, DES, RC4, Blowfish, ECB, custom crypto, or non-constant-time secret/token comparisons.
No-Injection-Vectors ✅ Passed The feature diff contains no SQL concatenation, eval/exec, pickle/yaml unsafe loads, os.system, or dangerouslySetInnerHTML; kubectl calls use subprocess.run argv with shell=False by default.
Container-Privileges ✅ Passed The PR changes only Python test/client files and adds no container or Kubernetes manifests; scans found no privileged:true, hostPID, hostNetwork, hostIPC, SYS_ADMIN, or allowPrivilegeEscalation set...
Ai-Attribution ✅ Passed AI use is disclosed as Assisted-by: Claude Code in the PR and all 8 PR commits; no Co-Authored-By trailer appears.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bmaas/test_baremetal_instance_restart.py`:
- Around line 77-83: Update the restart verification around poll_until to assert
a restart-specific power-cycle transition rather than only the final powered-on
state; observe the host becoming powered off before confirming it returns to
"true", or use an equivalent restart-specific signal exposed by the test client.
- Around line 112-113: Update the broad exception handler in the cleanup flow of
the bare-metal instance restart test to retain the best-effort cleanup while
replacing the silent pass with logger.exception(...). Log the cleanup failure
and traceback before continuing, so cleanup errors are visible without changing
test flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 938418a0-bc60-4566-b459-731f1cd60273

📥 Commits

Reviewing files that changed from the base of the PR and between 7e647cb and 3c5d079.

📒 Files selected for processing (2)
  • tests/bmaas/test_baremetal_instance_restart.py
  • tests/core/grpc_client.py

Comment thread tests/bmaas/test_baremetal_instance_restart.py
Comment thread tests/bmaas/test_baremetal_instance_restart.py Outdated
Address CodeRabbit review feedback: replace bare `except: pass`
with `logger.exception()` so cleanup failures are visible for
same-session debugging.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
The RESTART_IN_PROGRESS condition can flip TRUE→FALSE between
polling intervals when the restart completes quickly, causing
a false timeout. Instead, wait for the durable end state
(status.restart_trigger echo) and verify RESTART_IN_PROGRESS
is cleared afterward.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
@mennyaboush

Copy link
Copy Markdown
Contributor Author

/retest

@github-actions

Copy link
Copy Markdown

Re-triggered failed runs:

  • E2E VMaaS Full Install (#30541447476)

Poll every 2s for the condition to appear with any status (TRUE
or FALSE), rather than requiring the exact TRUE state which is
transient and can be missed between polling intervals when the
restart completes quickly.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
Log BMH inventory (name, provisioning state, online, powered,
consumer) at test start and after BMH assignment to diagnose
why the restart test consistently fails to provision while the
lifecycle test succeeds on the same CI run.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/bmaas/test_baremetal_instance_restart.py (2)

90-96: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a true restart-in-progress condition.

The predicate v != "" also accepts CONDITION_STATUS_FALSE. A pre-existing false condition can satisfy this poll before the restart starts.

Use CONDITION_STATUS_TRUE or the matching project enum.

Suggested fix
-            until=lambda v: v != "",
+            until=lambda v: v == "CONDITION_STATUS_TRUE",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bmaas/test_baremetal_instance_restart.py` around lines 90 - 96, Update
the poll predicate in the restart test around _get_restart_condition_status so
it only succeeds when the RESTART_IN_PROGRESS condition equals
CONDITION_STATUS_TRUE, or the project’s equivalent enum value, rather than
accepting any non-empty status; preserve the existing polling parameters and
description.

134-142: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not skip cleanup when the CR name is empty.

The if bmi_cr: guard prevents deletion when failure occurs before wait_for_bmi_cr observes the CR. The BMI can remain in the backend and retain a BMH allocation.

Always call cli.delete_baremetal_instance and wait_for_bmi_grpc_removal using bmi_id. Make only wait_for_bmi_deletion conditional on bmi_cr. Keep cleanup steps independently guarded so one failed cleanup operation does not skip the remaining steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bmaas/test_baremetal_instance_restart.py` around lines 134 - 142, The
cleanup block in the BaseException handler must not be gated by the CR name
returned from get_baremetal_instance_name. In the cleanup logic, always call
cli.delete_baremetal_instance and wait_for_bmi_grpc_removal with bmi_id; invoke
wait_for_bmi_deletion only when bmi_cr is present, and keep each cleanup
operation independently guarded so a failure does not prevent subsequent
cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bmaas/test_baremetal_instance_lifecycle.py`:
- Around line 50-68: Update the BMI lifecycle flow after wait_for_bmi_cr and
before the assignment log so it polls
k8s_hub_client.get_baremetal_instance_external_host_id for a non-empty
external_host_id. Only after the ID is available should it log the BMH
assignment and call _log_bmh_inventory; keep the subsequent wait_for_bmi_running
flow unchanged.
- Around line 16-37: Update `_log_bmh_inventory` to remain best-effort by
catching `TimeoutExpired` and `FileNotFoundError` from `run_unchecked`, logging
a warning, and returning without propagating the error. Configure the inventory
`kubectl` call with a short diagnostic timeout instead of relying on the
300-second default, while preserving the existing non-zero return handling.

In `@tests/bmaas/test_baremetal_instance_restart.py`:
- Around line 74-78: Update the restart test flow around
get_baremetal_instance_external_host_id and wait_for_bmi_running to poll until
spec.externalHostID is non-empty before proceeding. After the ID becomes
available, validate its namespace and name, then retain the existing inventory
logging and running-state wait.

---

Outside diff comments:
In `@tests/bmaas/test_baremetal_instance_restart.py`:
- Around line 90-96: Update the poll predicate in the restart test around
_get_restart_condition_status so it only succeeds when the RESTART_IN_PROGRESS
condition equals CONDITION_STATUS_TRUE, or the project’s equivalent enum value,
rather than accepting any non-empty status; preserve the existing polling
parameters and description.
- Around line 134-142: The cleanup block in the BaseException handler must not
be gated by the CR name returned from get_baremetal_instance_name. In the
cleanup logic, always call cli.delete_baremetal_instance and
wait_for_bmi_grpc_removal with bmi_id; invoke wait_for_bmi_deletion only when
bmi_cr is present, and keep each cleanup operation independently guarded so a
failure does not prevent subsequent cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 71232b7f-d9e8-4e7f-9fd8-447d3f05c8cc

📥 Commits

Reviewing files that changed from the base of the PR and between 8583560 and 5bf959c.

📒 Files selected for processing (2)
  • tests/bmaas/test_baremetal_instance_lifecycle.py
  • tests/bmaas/test_baremetal_instance_restart.py

Comment thread tests/bmaas/test_baremetal_instance_lifecycle.py Outdated
Comment thread tests/bmaas/test_baremetal_instance_lifecycle.py
Comment on lines +74 to +78
external_host_id: str = k8s_hub_client.get_baremetal_instance_external_host_id(name=bmi_cr_name)
logger.info("BMI %s assigned to BMH: %s", bmi_id, external_host_id)
_log_bmh_inventory(bmh_namespace)

wait_for_bmi_running(grpc=grpc, bmi_id=bmi_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wait for spec.externalHostID before using it.

Line 71 waits only for CR creation. Line 74 performs one direct lookup of spec.externalHostID. The test can fail before wait_for_bmi_running if BMH assignment is still pending.

Poll until the external-host ID is non-empty, then validate its namespace and name.

Suggested fix
-        external_host_id: str = k8s_hub_client.get_baremetal_instance_external_host_id(name=bmi_cr_name)
+        external_host_id: str = poll_until(
+            fn=lambda: k8s_hub_client.get_baremetal_instance_external_host_id(name=bmi_cr_name),
+            until=lambda v: v != "",
+            retries=60,
+            delay=5,
+            description=f"{bmi_id} external host assignment",
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
external_host_id: str = k8s_hub_client.get_baremetal_instance_external_host_id(name=bmi_cr_name)
logger.info("BMI %s assigned to BMH: %s", bmi_id, external_host_id)
_log_bmh_inventory(bmh_namespace)
wait_for_bmi_running(grpc=grpc, bmi_id=bmi_id)
external_host_id: str = poll_until(
fn=lambda: k8s_hub_client.get_baremetal_instance_external_host_id(name=bmi_cr_name),
until=lambda v: v != "",
retries=60,
delay=5,
description=f"{bmi_id} external host assignment",
)
logger.info("BMI %s assigned to BMH: %s", bmi_id, external_host_id)
_log_bmh_inventory(bmh_namespace)
wait_for_bmi_running(grpc=grpc, bmi_id=bmi_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bmaas/test_baremetal_instance_restart.py` around lines 74 - 78, Update
the restart test flow around get_baremetal_instance_external_host_id and
wait_for_bmi_running to poll until spec.externalHostID is non-empty before
proceeding. After the ID becomes available, validate its namespace and name,
then retain the existing inventory logging and running-state wait.

Log BMI gRPC state, conditions, restart_trigger values, BMH
provisioning state, online/powered/error fields, and BMI CR
phase/conditions on every poll iteration while the BMI is stuck
in STARTING after restart. This will pinpoint whether the issue
is in the BMH (Ironic/sushy-tools), the BMI CR (operator), or
the fulfillment-service controller.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
Log gRPC state and CR PowerSynced condition after the stop/start
power cycle to compare with the restart test. If PowerSynced=False
appears here too, the bug affects all power operations, not just
restart_trigger.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bmaas/test_baremetal_instance_lifecycle.py`:
- Around line 110-126: Update the kubectl diagnostic flow in the lifecycle test
to unpack run_unchecked’s result into separate cr_output and cr_exit_code
values, then log cr_output as the CR data. When cr_exit_code is non-zero, emit a
warning containing the command failure context and exit code instead of treating
the result as a valid phase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 86deed97-27fc-4f05-bb19-9bd721845ba0

📥 Commits

Reviewing files that changed from the base of the PR and between 2b3d951 and 47b10ef.

📒 Files selected for processing (1)
  • tests/bmaas/test_baremetal_instance_lifecycle.py

Comment thread tests/bmaas/test_baremetal_instance_lifecycle.py Outdated
- Extract log_bmh_inventory to conftest.py (was duplicated in both tests)
- Restart test: replace inline poll with shared wait_for_bmi_running
  (adds FAILED-state fail-fast guard, matches lifecycle pattern)
- Lifecycle test: add wait_for_bmi_running after power-on to verify
  whether the PowerSynced bug affects stop/start too (not just restart)
- Both tests: consistent use of shared helpers, diagnostic logging on
  failure via _log_bmi_state

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
…_running

The BMI CR's externalHostID is empty until the operator assigns a BMH
during provisioning. Reading it before wait_for_bmi_running returns an
empty string. Move the read after the BMI reaches RUNNING state.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
@mennyaboush

Copy link
Copy Markdown
Contributor Author

Superseded by a clean PR — this branch contains investigation diagnostics for OSAC-3548. Keeping the branch for reference.

This branch had an error being deployed

1 failed deployment
e2e-test — 36a24be2 Deployed Aug 2, 2026 by mennyaboush via e2e-bmaas-full-install / e2e #386
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants