Skip to content

Replace internal/ironic with backend-agnostic internal/management interface#9

Merged
tzumainn merged 1 commit into
osac-project:mainfrom
DanNiESh:host-management-interface
Jun 5, 2026
Merged

Replace internal/ironic with backend-agnostic internal/management interface#9
tzumainn merged 1 commit into
osac-project:mainfrom
DanNiESh:host-management-interface

Conversation

@DanNiESh
Copy link
Copy Markdown
Contributor

@DanNiESh DanNiESh commented Jun 3, 2026

Closes osac-project/issues#404

Introduce a management.Client interface with GetPowerState/SetPowerState methods and a config-driven factory/registry pattern, mirroring the inventory interface in bare-metal-fulfillment-operator. The OpenStack implementation wraps gophercloud Ironic calls. The controller and cmd/main.go are updated to use management.Client, and internal/ironic/ is removed.

Summary by CodeRabbit

  • Refactor
    • Reworked host-lease power management to use a backend-agnostic management client.
  • New Features
    • Added a generic management client API and an OpenStack-backed implementation for power operations.
  • Bug Fixes / Cleanup
    • Removed the legacy Ironic client and its associated tests.
  • Tests
    • Added management tests and updated controller tests to validate the new management model.

@DanNiESh DanNiESh marked this pull request as draft June 3, 2026 16:20
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@DanNiESh, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 4741f30c-49e7-4386-a13e-a919e0a1a9d3

📥 Commits

Reviewing files that changed from the base of the PR and between 07d2684 and b3fa6bc.

📒 Files selected for processing (9)
  • cmd/main.go
  • internal/controller/hostlease_controller.go
  • internal/controller/hostlease_controller_test.go
  • internal/ironic/client.go
  • internal/ironic/client_internal_test.go
  • internal/ironic/client_test.go
  • internal/management/client.go
  • internal/management/management_test.go
  • internal/management/openstack.go

Walkthrough

Migrates power management to a backend-agnostic management.Client, implements an OpenStack backend, updates HostLeaseReconciler to use management.PowerStatus semantics, rewrites reconcile/sync logic and tests, and wires the new client into cmd/main.

Changes

Management client abstraction and controller migration

Layer / File(s) Summary
Application wiring (startup & wiring)
cmd/main.go
Adds context import, creates a 30s context, constructs management.NewOpenStackClient(...), logs failure/success, and passes managementClient to NewHostLeaseReconciler.
Management API abstraction
internal/management/client.go
Adds PowerState (PowerOn/PowerOff), PowerStatus{State,IsTransitioning}, Config, Client interface (GetPowerState/SetPowerState), NewClient factory registry, and ErrTransitioning. NewClient errors on unsupported types.
Management tests
internal/management/management_test.go
Ginkgo suite validating PowerState strings, PowerStatus transitioning flag, NewClient factory error behavior for unknown type, and conditional OpenStack client creation tests based on environment credentials.
OpenStack management implementation
internal/management/openstack.go
Implements OpenStackClient using gophercloud: parses cfg.Options["openstack"], builds provider and Bare Metal v1 client, maps node state/target to PowerStatus, and translates 409 Conflict into ErrTransitioning.
Host lease controller refactor
internal/controller/hostlease_controller.go
Replaces IronicClient with ManagementClient management.Client. handleUpdate fetches management.PowerStatus and may re-fetch after reconciliation; reconcilePower and syncHostLeaseStatus operate on management.PowerStatus and handle transitioning via ErrTransitioning.
Controller test updates
internal/controller/hostlease_controller_test.go
Replaces Ironic mocks with mockManagementClient, updates BeforeEach, reconcilePower, delete, finalizer, provisioning, and syncHostLeaseStatus tests to use GetPowerState/SetPowerState and management.PowerStatus values.

Sequence Diagram

sequenceDiagram
  participant Main as cmd/main
  participant MgmtFactory as management.NewOpenStackClient
  participant OpenStackClient
  participant IronicAPI
  participant Reconciler as HostLeaseReconciler

  Main->>MgmtFactory: NewOpenStackClient(ctx, Config{Type:"openstack"})
  MgmtFactory->>OpenStackClient: construct OpenStackClient
  OpenStackClient->>IronicAPI: authenticate / build nodes client
  OpenStackClient-->>Main: return client or error
  Main->>Reconciler: NewHostLeaseReconciler(managementClient)

  Reconciler->>OpenStackClient: GetPowerState(ctx, externalHostID)
  OpenStackClient->>IronicAPI: fetch node
  IronicAPI-->>OpenStackClient: node state + target
  OpenStackClient-->>Reconciler: PowerStatus{State, IsTransitioning}

  Reconciler->>OpenStackClient: SetPowerState(ctx, hostID, target)
  OpenStackClient->>IronicAPI: change node power
  IronicAPI-->>OpenStackClient: success or 409 conflict
  OpenStackClient-->>Reconciler: nil or ErrTransitioning / wrapped error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ajamias
  • tzumainn
  • larsks

Security & Risk Assessment

  • Risk Severity: Low — core Ironic interactions are moved behind an adapter; new cfg.Options parsing increases surface area.
  • Impact: Low — authentication remains delegated to gophercloud; incorrect or unvalidated cfg.Options could cause misconfiguration.

Poem

⚙️ A client born to manage power's art,
OpenStack wired to play its part.
Controllers speak cleaner, tests sing along,
Old Ironic hooks replaced, neat and strong.
Small change, steady rhythm, code marches on.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error cmd/main.go line 242 logs errors from management.NewOpenStackClient(), which wraps gophercloud errors containing endpoint URLs, service catalog info, and region names via line 60's %w formatter. Use generic error message without chain in cmd/main.go:242, or sanitize line 60 in openstack.go by removing %w wrapper like line 52 does.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Ai-Attribution ⚠️ Warning Commit uses Co-Authored-By trailer for AI tool (Claude), which implies legal personhood and undermines license chain of title. Should use Assisted-by trailer per open source community standards. Replace commit trailer: change "Co-Authored-By: Claude Opus 4.6" to "Assisted-by: Claude Opus 4.6" to properly reflect AI assistance rather than co-authorship.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: replacing the Ironic client with a backend-agnostic management interface.
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 No hardcoded secrets found. Code properly sources credentials from environment variables and cloud configuration, with authentication errors sanitized at trust boundaries.
No-Weak-Crypto ✅ Passed No weak cryptography (MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB), custom crypto implementations, or non-constant-time secret comparisons detected in the PR changes.
No-Injection-Vectors ✅ Passed No injection vectors detected. No SQL, shell, eval, pickle, yaml.Load, os.system calls found. String concatenation uses hardcoded constants. Config instantiation hardcoded.
Container-Privileges ✅ Passed Container uses distroless nonroot base and runs as UID 65532. K8s manifest enforces runAsNonRoot, disables privilege escalation, and drops all capabilities. No risky privilege flags detected.
✨ 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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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 `@cmd/main.go`:
- Around line 235-237: Replace the unbounded startup context used when creating
the OpenStack client: instead of calling management.NewOpenStackClient with
context.TODO(), create a context with a reasonable timeout (e.g., using
context.WithTimeout) and pass that context into management.NewOpenStackClient so
the underlying clientconfig.AuthenticatedClient call cannot block startup
indefinitely; ensure you cancel the context after use and add the time import in
cmd/main.go for the timeout value (referencing management.NewOpenStackClient and
the current use of context.TODO()).

In `@internal/controller/hostlease_controller.go`:
- Around line 291-313: The controller should not treat backend "transition in
progress" errors as terminal: update internal/management/openstack.go (the
OpenStackClient.SetPowerState path) to detect API conflict/409 or
transition-specific error text and return a normalized sentinel
(management.ErrTransitioning); then update reconcilePower in
hostlease_controller.go to handle management.ErrTransitioning the same way it
handles powerStatus.IsTransitioning (log that the node is transitioning and
return without marking the HostLease failed so the controller can
requeue/progress instead of setting HostLeasePhaseFailed). Ensure you reference
the management.ErrTransitioning sentinel in both places so the behavior is
consistent.

In `@internal/management/client.go`:
- Around line 35-41: The NewClient function currently returns nil, nil when no
factory exists for cfg.Type which silently swallows misconfiguration; update
NewClient (and its lookup of newClientFuncs) to return a descriptive error
instead of nil when ok is false (e.g., fmt.Errorf("unknown client type %q",
cfg.Type) or similar), so callers receive a non-nil error on unregistered
cfg.Type rather than a nil Client.
- Around line 10-13: The constants PowerOn and PowerOff in PowerState are
hardcoded string literals; replace their values with the canonical gophercloud
constants by assigning them using string(nodes.PowerOn) and
string(nodes.PowerOff) (referencing the existing PowerOn, PowerOff and
PowerState symbols and the external nodes.PowerOn/nodes.PowerOff constants) so
the code uses gophercloud’s authoritative values and avoids future drift.

In `@internal/management/management_test.go`:
- Line 71: Define a test constant (e.g. const testProviderOpenstack =
"openstack") in management_test.go and replace the three hard-coded occurrences
of "openstack" where a resource's Type is set (the struct initializers using
Type: "openstack") with that constant; ensure all three call sites in the tests
now reference testProviderOpenstack to satisfy goconst and keep semantics
unchanged.

In `@internal/management/openstack.go`:
- Around line 62-72: GetPowerState currently casts Ironic's node.PowerState
directly into the local PowerState allowing unexpected values through; change
GetPowerState to validate node.PowerState with an allow-list using the canonical
strings string(nodes.PowerOn) and string(nodes.PowerOff) (map those to your
local PowerState constants) and return a clear error for any unknown value, keep
IsTransitioning as node.TargetPowerState != "" and also verify the PowerState
constants in client.go match the canonical gophercloud values before returning
the PowerStatus.
🪄 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: Enterprise

Run ID: 05116e8c-18ef-4064-b6c2-554606281295

📥 Commits

Reviewing files that changed from the base of the PR and between 3ad9e1a and 0a07073.

📒 Files selected for processing (8)
  • cmd/main.go
  • internal/controller/hostlease_controller.go
  • internal/controller/hostlease_controller_test.go
  • internal/ironic/client.go
  • internal/ironic/client_test.go
  • internal/management/client.go
  • internal/management/management_test.go
  • internal/management/openstack.go
💤 Files with no reviewable changes (2)
  • internal/ironic/client.go
  • internal/ironic/client_test.go

Comment thread cmd/main.go Outdated
Comment thread internal/controller/hostlease_controller.go
Comment on lines +10 to +13
const (
PowerOn PowerState = "power on"
PowerOff PowerState = "power off"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Extract PowerOn and PowerOff constant values from gophercloud nodes package.

# Search for the PowerState constant declarations in gophercloud
rg -nP --type=go 'const\s+Power(On|Off)\s+\w+\s*=\s*"[^"]+"' -A1 -B1

Repository: osac-project/host-management-openstack

Length of output: 64


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show PowerState type + constant definitions and nearby usage
FILE="internal/management/client.go"
nl -ba "$FILE" | sed -n '1,120p'

# Find all uses of PowerOn/PowerOff/PowerState in the repo
rg -n --hidden --glob '!*vendor/*' 'PowerOn|PowerOff|PowerState' .

Repository: osac-project/host-management-openstack

Length of output: 125


🌐 Web query:

gophercloud nodes.PowerOn PowerOff constant value "power on" "power off"

💡 Result:

In gophercloud’s Bare Metal nodes API, the power action constants map to these exact string values: - nodes.PowerOn = "power on" [1] - nodes.PowerOff = "power off" [1] The OpenStack Bare Metal API documentation likewise describes power actions/states as usually the strings “power on” or “power off” [2], and the corresponding enum in the OpenStack SDK uses POWER_ON = 'power on' and POWER_OFF = 'power off' [3].

Citations:


Use gophercloud nodes.PowerOn/nodes.PowerOff (values already match)

internal/management/client.go hardcodes PowerOn = "power on" and PowerOff = "power off", which match gophercloud’s nodes.PowerOn/nodes.PowerOff string values, so power-transition mismatch risk is low. For maintainability and to prevent future drift, switch to string(nodes.PowerOn) / string(nodes.PowerOff) instead of raw string literals.

🤖 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 `@internal/management/client.go` around lines 10 - 13, The constants PowerOn
and PowerOff in PowerState are hardcoded string literals; replace their values
with the canonical gophercloud constants by assigning them using
string(nodes.PowerOn) and string(nodes.PowerOff) (referencing the existing
PowerOn, PowerOff and PowerState symbols and the external
nodes.PowerOn/nodes.PowerOff constants) so the code uses gophercloud’s
authoritative values and avoids future drift.

Comment thread internal/management/client.go
Comment thread internal/management/management_test.go Outdated
Comment thread internal/management/openstack.go
@DanNiESh DanNiESh force-pushed the host-management-interface branch from 0a07073 to a748b8e Compare June 3, 2026 18:21
@DanNiESh DanNiESh requested review from ajamias and tzumainn June 3, 2026 18:29
@DanNiESh DanNiESh marked this pull request as ready for review June 3, 2026 18:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
internal/controller/hostlease_controller.go (1)

160-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Major: don't mark a transitioning node as Ready / PowerSynced=True.

reconcilePower correctly recognizes powerStatus.IsTransitioning, but the final convergence check and syncHostLeaseStatus ignore that flag and look only at State. If the backend reports a transition in progress while State already equals the desired target, this path sets HostConditionPowerSynced=True, advances the lease to Ready, and stops requeueing before the power operation actually finishes. Risk: major correctness/availability impact from prematurely declaring host power convergence.

Suggested fix
-	r.syncHostLeaseStatus(hostLease, powerStatus, nil, log)
+	r.syncHostLeaseStatus(hostLease, powerStatus, nil, log)

 	if hostLease.Spec.PoweredOn != nil {
-		if *hostLease.Spec.PoweredOn != (powerStatus.State == management.PowerOn) {
+		if powerStatus.IsTransitioning || *hostLease.Spec.PoweredOn != (powerStatus.State == management.PowerOn) {
 			return ctrl.Result{RequeueAfter: r.RecheckInterval}, nil
 		}
 	}
@@
 	if powerStatus == nil {
 		return
 	}

 	poweredOn := powerStatus.State == management.PowerOn
 	hostLease.Status.PoweredOn = &poweredOn
+
+	if powerStatus.IsTransitioning {
+		hostLease.Status.Phase = v1alpha1.HostLeasePhaseProgressing
+		hostLease.SetStatusCondition(
+			v1alpha1.HostConditionPowerSynced,
+			metav1.ConditionFalse,
+			v1alpha1.HostConditionReasonProgressing,
+			"waiting for node power transition to complete",
+		)
+		return
+	}

 	if hostLease.Spec.PoweredOn != nil && *hostLease.Spec.PoweredOn != poweredOn {

Based on learnings: reconcilePower should use the external host identifier field consistently; this path already does that, so the remaining contract to preserve is the management.PowerStatus transition semantics.

Also applies to: 380-415

🤖 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 `@internal/controller/hostlease_controller.go` around lines 160 - 167, The code
marks HostLease as Ready and sets PowerSynced without honoring
management.PowerStatus.IsTransitioning; update the convergence check in the
block that sets hostLease.Status.Phase (and the similar logic at lines ~380-415)
to treat any powerStatus.IsTransitioning as "not yet converged": if
powerStatus.IsTransitioning is true, return ctrl.Result{RequeueAfter:
r.RecheckInterval}, nil (do not set HostLeasePhaseReady or PowerSynced=True) and
ensure syncHostLeaseStatus (and any code referencing HostConditionPowerSynced)
uses management.PowerStatus.IsTransitioning when computing convergence so the
controller continues requeuing until the transition completes.
🤖 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 `@internal/controller/hostlease_controller.go`:
- Around line 136-142: The code dereferences powerStatus.State without checking
for a nil powerStatus returned by r.ManagementClient.GetPowerState; update the
reconcile logic around the GetPowerState call so you first check if powerStatus
== nil (treating nil as a valid “no data” result) before accessing
powerStatus.State, call r.syncHostLeaseStatus(hostLease, nil, nil, log) or the
existing nil-path handling as appropriate, and only log or compare
powerStatus.State when powerStatus is non-nil; apply the same nil-guard fix to
both usages of GetPowerState / powerStatus.State (the block calling
r.syncHostLeaseStatus and the later block at the second occurrence).

In `@internal/management/client.go`:
- Around line 39-44: NewClient currently dereferences cfg.Type without
validating cfg and will panic if cfg is nil; update NewClient to first check
whether cfg is nil and return a descriptive error (e.g., "nil config" or
"invalid configuration") instead of proceeding, then validate cfg.Type (ensure
it's non-empty) before looking up newClientFuncs[cfg.Type] and returning the
chosen fn(ctx, cfg) or the unsupported-backend error; reference the NewClient
function and the newClientFuncs map when making this change.

In `@internal/management/management_test.go`:
- Around line 65-79: The test is flaky because hasOpenStackCredentials() returns
true if either OS_CLOUD or OS_AUTH_URL is set, causing partial envs to run real
integration logic; change the test to only run the real OpenStack client
creation when a strict integration flag or full set of required env vars is
present (e.g., require TEST_OPENSTACK_INTEGRATION=true or require both OS_CLOUD
and OS_AUTH_URL and any other mandatory vars), or alternatively replace the real
call to management.NewClient in these specs with a mocked client; update the
BeforeEach that calls hasOpenStackCredentials() (and any duplicate checks around
lines 111-136) to use the stricter predicate or flag so the suite remains
deterministic.

In `@internal/management/openstack.go`:
- Around line 42-57: The NewBareMetalV1 call is ignoring the region/endpoint
type from clientOpts; update the call to pass gophercloud.EndpointOpts populated
from cloud.RegionName and cloud.EndpointType so Ironic endpoint discovery uses
the intended region/interface. Replace openstack.NewBareMetalV1(providerClient,
gophercloud.EndpointOpts{}) with openstack.NewBareMetalV1(providerClient,
gophercloud.EndpointOpts{Region: cloud.RegionName, Availability:
cloud.EndpointType}) (referencing clientOpts, cloud.RegionName,
cloud.EndpointType, and openstack.NewBareMetalV1).

---

Outside diff comments:
In `@internal/controller/hostlease_controller.go`:
- Around line 160-167: The code marks HostLease as Ready and sets PowerSynced
without honoring management.PowerStatus.IsTransitioning; update the convergence
check in the block that sets hostLease.Status.Phase (and the similar logic at
lines ~380-415) to treat any powerStatus.IsTransitioning as "not yet converged":
if powerStatus.IsTransitioning is true, return ctrl.Result{RequeueAfter:
r.RecheckInterval}, nil (do not set HostLeasePhaseReady or PowerSynced=True) and
ensure syncHostLeaseStatus (and any code referencing HostConditionPowerSynced)
uses management.PowerStatus.IsTransitioning when computing convergence so the
controller continues requeuing until the transition completes.
🪄 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: Enterprise

Run ID: ca0a030d-4a71-47b9-bf8d-b4a4143b60c9

📥 Commits

Reviewing files that changed from the base of the PR and between 0a07073 and a748b8e.

📒 Files selected for processing (8)
  • cmd/main.go
  • internal/controller/hostlease_controller.go
  • internal/controller/hostlease_controller_test.go
  • internal/ironic/client.go
  • internal/ironic/client_test.go
  • internal/management/client.go
  • internal/management/management_test.go
  • internal/management/openstack.go
💤 Files with no reviewable changes (2)
  • internal/ironic/client_test.go
  • internal/ironic/client.go

Comment thread internal/controller/hostlease_controller.go
Comment thread internal/management/client.go
Comment thread internal/management/management_test.go
Comment thread internal/management/openstack.go Outdated
@DanNiESh DanNiESh force-pushed the host-management-interface branch 2 times, most recently from 351a480 to 4adf46b Compare June 4, 2026 20:51
@tzumainn
Copy link
Copy Markdown

tzumainn commented Jun 4, 2026

/retest

@tzumainn tzumainn closed this Jun 4, 2026
@tzumainn tzumainn reopened this Jun 4, 2026
Copy link
Copy Markdown

@tzumainn tzumainn left a comment

Choose a reason for hiding this comment

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

Thanks!

@tzumainn
Copy link
Copy Markdown

tzumainn commented Jun 4, 2026

Test failures seem unrelated (possibly a bug with files that are deleted in a PR); forcing merge

@tzumainn
Copy link
Copy Markdown

tzumainn commented Jun 4, 2026

@coderabbitai have all issues been addressed?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments on PR `#9` (including resolved state)
gh api repos/osac-project/host-management-openstack/pulls/9/comments \
  --jq '.[] | {id, user: .user.login, body: .body, path: .path, line: .line, resolved: (.resolved // false)}' 2>/dev/null | head -200

echo "--- REVIEWS ---"
gh api repos/osac-project/host-management-openstack/pulls/9/reviews \
  --jq '.[] | {id, user: .user.login, state, body: .body}' 2>/dev/null | head -100

echo "--- ISSUE COMMENTS ---"
gh api repos/osac-project/host-management-openstack/issues/9/comments \
  --jq '.[] | {id, user: .user.login, body: .body}' 2>/dev/null | head -200

Length of output: 107274



@tzumainn — Almost, but not quite. Here's the status breakdown:

✅ Fully addressed (confirmed)

Issue Severity File
Unbounded context.TODO() startup hang Major cmd/main.go
ErrTransitioning sentinel + 409 normalization Major hostlease_controller.go / openstack.go
NewClient returns nil, nil for unknown type Critical management/client.go
NewClient panics on nil config Major management/client.go
GetPowerState nil-guard before deref Major hostlease_controller.go
Validate PowerState at trust boundary (allow-list) Major openstack.go
openstack.NewBareMetalV1 ignoring region/endpoint Critical openstack.go
goconst lint failure ("openstack" repeated 3×) Minor management_test.go
Flaky integration tests gated on ambient env credentials Major management_test.go
Hardcoded PowerOn/PowerOff string literals Nitpick management/client.go

❌ Still outstanding

1. Convergence check ignores powerStatus.IsTransitioning — Major (correctness/availability)

This was posted as an "outside diff range" comment in the second review and has no confirmation of being addressed. If IsTransitioning == true but State already equals the desired target, the controller currently sets HostConditionPowerSynced=True and advances the lease to Ready prematurely — before the power operation actually finishes.

  • Risk: Major. Hosts could be declared power-converged while Ironic is still in the middle of a transition, potentially handing off a host in an inconsistent state.
  • Affected locations: hostlease_controller.go ~lines 160–167 (convergence requeue decision) and syncHostLeaseStatus ~lines 380–415.

Suggested check:

// In the convergence block
if powerStatus.IsTransitioning || *hostLease.Spec.PoweredOn != (powerStatus.State == management.PowerOn) {
    return ctrl.Result{RequeueAfter: r.RecheckInterval}, nil
}
// In syncHostLeaseStatus, before setting PowerSynced=True
if powerStatus.IsTransitioning {
    // set Progressing condition, don't advance to Ready
    return
}

2. Pre-merge check failure — ❌ Error (sensitive data in logs)

  • No-Sensitive-Data-In-Logs: cmd/main.go line 242 logs a wrapped error from OpenStack authentication. Gophercloud error chains can carry credential/token details, so wrapping and logging them verbatim creates a credential-exposure risk (Medium severity).
  • Resolution: Sanitize or omit error details from auth failures before logging (e.g., log a fixed message and the error type without the full chain).

Warnings (non-blocking but flagged by CI):

  • Docstring coverage: 12.5% vs. 80% threshold.
  • AI attribution: commit uses Co-Authored-By instead of Red Hat Assisted-by/Generated-by trailers.

🧠 Learnings used
Learnt from: larsks
Repo: osac-project/host-management-openstack PR: 1
File: internal/controller/host_controller.go:105-125
Timestamp: 2026-04-06T21:51:20.735Z
Learning: In `internal/controller/host_controller.go` (Go, controller-runtime operator), the `reconcilePower` method should use `host.Spec.ExternalID` (not `host.Spec.ID`) when referencing the Ironic node UUID, consistent with the Host CR spec field `externalID`. Power state constants should use `string(nodes.PowerOn)` and `string(nodes.PowerOff)` from gophercloud rather than raw string literals.

@DanNiESh DanNiESh force-pushed the host-management-interface branch from 4adf46b to 07d2684 Compare June 4, 2026 23:36
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@cmd/main.go`:
- Around line 241-243: The current call to setupLog.Error(err, "failed to create
management client") may log sensitive OpenStack credentials/tokens returned by
management.NewOpenStackClient; change this to log a generic failure message
(e.g., setupLog.Error(nil, "failed to create management client")) or build and
pass a sanitized error string instead of the raw err; implement a small
sanitizer that strips Keystone URLs, auth tokens and known sensitive keys from
err.Error() (or only log err.Error() at debug/trace level), and use that
sanitized string in the setupLog.Error call so sensitive data is never emitted
to normal logs.

In `@internal/controller/hostlease_controller.go`:
- Around line 422-424: syncHostLeaseStatus currently returns early when
powerStatus.IsTransitioning and skips updating the PowerSynced condition; change
this to explicitly set the PowerSynced condition to False with
Reason="Progressing" (and an appropriate Message) before returning so the status
reflects an ongoing transition. Locate syncHostLeaseStatus and the block that
checks powerStatus.IsTransitioning and replace the early return with code that
updates the HostLease status conditions (PowerSynced condition) to
False/Progressing, persists the status update path used elsewhere in the
function, then return to keep behavior consistent.

In `@internal/management/openstack.go`:
- Around line 85-88: Validate the incoming PowerState in SetPowerState before
calling nodes.ChangePowerState: check that the target parameter is one of the
allowed constants (PowerOn or PowerOff) and return a descriptive error if not;
then only call nodes.ChangePowerState(ctx, c.client, hostID,
nodes.PowerStateOpts{Target: nodes.TargetPowerState(target)}) when the target is
valid. This keeps validation symmetric with GetPowerState and prevents sending
invalid targets to Ironic.
🪄 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: Enterprise

Run ID: 0d371344-ea01-4d1a-8d9e-850ccb2d273e

📥 Commits

Reviewing files that changed from the base of the PR and between 351a480 and 07d2684.

📒 Files selected for processing (8)
  • cmd/main.go
  • internal/controller/hostlease_controller.go
  • internal/controller/hostlease_controller_test.go
  • internal/ironic/client.go
  • internal/ironic/client_test.go
  • internal/management/client.go
  • internal/management/management_test.go
  • internal/management/openstack.go
💤 Files with no reviewable changes (2)
  • internal/ironic/client_test.go
  • internal/ironic/client.go

Comment thread cmd/main.go
Comment thread internal/controller/hostlease_controller.go
Comment thread internal/management/openstack.go
@DanNiESh DanNiESh force-pushed the host-management-interface branch from 07d2684 to b891357 Compare June 4, 2026 23:58
…erface

Introduce a management.Client interface with GetPowerState/SetPowerState
methods and a config-driven factory/registry pattern, mirroring the
inventory interface in bare-metal-fulfillment-operator. The OpenStack
implementation wraps gophercloud Ironic calls. The controller and
cmd/main.go are updated to use management.Client, and internal/ironic/
is removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DanNiESh DanNiESh force-pushed the host-management-interface branch from b891357 to b3fa6bc Compare June 5, 2026 00:08
@DanNiESh
Copy link
Copy Markdown
Contributor Author

DanNiESh commented Jun 5, 2026

@coderabbitai have all issues been addressed?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check convergence logic in hostlease_controller.go for IsTransitioning guard
echo "=== hostlease_controller.go: syncHostLeaseStatus and convergence block ==="
cat internal/controller/hostlease_controller.go

echo ""
echo "=== cmd/main.go: auth error logging ==="
cat cmd/main.go

Length of output: 29815



@DanNiESh — Yes, all previously outstanding issues have now been addressed. Here's the final status:

✅ Issue 1 — Convergence check ignores powerStatus.IsTransitioning (was: Major)

Fixed. Two guards are in place:

  1. handleUpdate (Lines 161–165): The requeue decision now correctly checks transitioning state — the host will never advance to Ready while a power operation is in flight:

    if powerStatus.IsTransitioning || *hostLease.Spec.PoweredOn != (powerStatus.State == management.PowerOn) {
        hostLease.Status.Phase = v1alpha1.HostLeasePhaseProgressing
        return ctrl.Result{RequeueAfter: r.RecheckInterval}, nil
    }
  2. syncHostLeaseStatus: When IsTransitioning is true, a ConditionFalse/Progressing condition is set and the function returns early — PowerSynced=True is never written while the node is mid-transition.


✅ Issue 2 — Sensitive data in logs (cmd/main.go) (was: Pre-merge check failure / Medium risk)

Fixed. The gophercloud error chain (which can carry auth tokens/credentials) is now only emitted at the verbose debug level (V(1)), while the standard error log path passes nil with a sanitized message:

setupLog.V(1).Info("management client creation failed", "error", err)  // debug only
setupLog.Error(nil, "failed to create management client (check cloud credentials and endpoint configuration)")

No credential exposure at the default log level.


All critical, major, and pre-merge blocking issues are resolved. The remaining warnings (docstring coverage threshold and AI attribution trailer style) are non-blocking CI notes.


🧠 Learnings used
Learnt from: larsks
Repo: osac-project/host-management-openstack PR: 1
File: internal/controller/host_controller.go:105-125
Timestamp: 2026-04-06T21:51:20.735Z
Learning: In `internal/controller/host_controller.go` (Go, controller-runtime operator), the `reconcilePower` method should use `host.Spec.ExternalID` (not `host.Spec.ID`) when referencing the Ironic node UUID, consistent with the Host CR spec field `externalID`. Power state constants should use `string(nodes.PowerOn)` and `string(nodes.PowerOff)` from gophercloud rather than raw string literals.

@tzumainn tzumainn dismissed coderabbitai[bot]’s stale review June 5, 2026 03:22

Looks like all requested changes have been made!

@tzumainn tzumainn merged commit a126183 into osac-project:main Jun 5, 2026
5 checks passed
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.

Create bare metal management interface (with OpenStack implementation)

2 participants