Skip to content

[DO NOT MERGE]fix: use available+progressing for pool satisfaction and fail stuck N… - #1058

Draft
dakotalongRH wants to merge 1 commit into
openshift:masterfrom
dakotalongRH:fix/pool-satisfaction-and-account-limit-guard
Draft

[DO NOT MERGE]fix: use available+progressing for pool satisfaction and fail stuck N…#1058
dakotalongRH wants to merge 1 commit into
openshift:masterfrom
dakotalongRH:fix/pool-satisfaction-and-account-limit-guard

Conversation

@dakotalongRH

@dakotalongRH dakotalongRH commented Jul 30, 2026

Copy link
Copy Markdown

…oState accounts

The pool controller used unclaimedAccounts to determine if the pool was satisfied, which included NoState zombie CRs that will never provision. This prevented new accounts from being created when the pool was full of stuck accounts.

Additionally, accounts stuck on the account-limit requeued every 5 minutes forever without being marked Failed, inflating the unclaimed count permanently. They are now failed after 25 minutes (matching the existing creation timeout).

The pool also created all Account CRs in rapid succession via the ownership watch re-reconcile, causing burst CreateAccount API calls that overwhelmed the single-threaded account controller. A 30-second delay between creates prevents this.

What is being added?

Is this a fix for a bug? What's the bug? Is this a new feature? Please describe it. Is this just a small typo fix? That's fine too!

Checklist before requesting review

  • I have tested this locally
  • I have included unit tests
  • I have updated any corresponding documentation

Steps To Manually Test

Please provide us steps to reproduce the scenarios needed to test this. If an integration test is provided, let us know how to run it. If not, enumerate the steps to validate this does what it's supposed to.

  1. Start the operator
  2. Run the thing
  3. Observe X in the spec for the thing
  4. Clean up the thing

Ref OSD-0000

Summary by CodeRabbit

  • Bug Fixes
    • Account provisioning now fails with a clear timeout when the account creation limit remains blocked too long.
    • Account pools no longer count incomplete or “zombie” accounts toward the required pool size.
    • Account creation is throttled to prevent burst provisioning and improve pool reconciliation reliability.
  • Tests
    • Added coverage verifying that incomplete accounts are excluded and replacement accounts are created as needed.

…oState accounts

The pool controller used unclaimedAccounts to determine if the pool was
satisfied, which included NoState zombie CRs that will never provision.
This prevented new accounts from being created when the pool was full of
stuck accounts.

Additionally, accounts stuck on the account-limit requeued every 5
minutes forever without being marked Failed, inflating the unclaimed
count permanently. They are now failed after 25 minutes (matching the
existing creation timeout).

The pool also created all Account CRs in rapid succession via the
ownership watch re-reconcile, causing burst CreateAccount API calls that
overwhelmed the single-threaded account controller. A 30-second delay
between creates prevents this.
@dakotalongRH
dakotalongRH marked this pull request as draft July 30, 2026 00:30
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Walkthrough

Account pool reconciliation now excludes no-state zombie accounts from effective capacity, spaces account creation by 30 seconds, and tests the behavior. Account reconciliation also fails accounts that remain blocked by the global account limit beyond createPendTime.

Changes

Account provisioning reconciliation

Layer / File(s) Summary
Effective pool counting and zombie handling
controllers/accountpool/accountpool_controller.go, controllers/accountpool/accountpool_controller_test.go
Pool satisfaction uses available plus progressing accounts, with coverage confirming no-state zombie accounts do not satisfy the pool size.
Account creation pacing
controllers/accountpool/accountpool_controller.go
Account creation logs pool counts and requeues after a 30-second delay.
Account limit timeout
controllers/account/account_controller.go
Eligible accounts blocked by the global account limit are failed with AccountLimitTimeout after exceeding createPendTime, except in FedRAMP mode.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: pool satisfaction logic and stuck account failure handling.
Description check ✅ Passed The description includes the bug summary, checklist, manual test steps, and reference, so it is mostly complete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Stable And Deterministic Test Names ✅ Passed Added test title is static ('NoState zombies do not satisfy pool'); no formatted, generated, or run-specific values appear in touched test titles.
Test Structure And Quality ✅ Passed The changed tests are plain table-driven unit tests, not Ginkgo; they use a fake client, no Eventually/cluster waits, and gomock cleanup is handled.
Microshift Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the changed test file is a plain Go unit test with no MicroShift-unsupported APIs or features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the only new test is a plain Go unit test with t.Run and no multi-node assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Only reconcile logic and tests changed; no pod scheduling, topology, node selector, affinity, PDB, or replica assumptions were added.
Ote Binary Stdout Contract ✅ Passed PASS: The commit only touches controller/test files; no stdout writes were added in main/init/TestMain/suite setup, and the fmt.Printf calls are confined to test functions.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the only test change is a plain Go unit test, with no IPv4 or external-network assumptions found.
No-Weak-Crypto ✅ Passed Touched files only adjust account/pool reconciliation; no crypto imports, weak algorithms, or secret-comparison code were added.
Container-Privileges ✅ Passed PR only changes Go controllers/tests; no manifest privilege settings or host* / allowPrivilegeEscalation hits were found.
No-Sensitive-Data-In-Logs ✅ Passed New logs only emit durations, counts, and generated Account CR names; I found no passwords, tokens, PII, customer data, or other sensitive fields.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 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.

@openshift-ci
openshift-ci Bot requested review from BATMAN-JD and eth1030 July 30, 2026 00:30
@openshift-ci

openshift-ci Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dakotalongRH

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

The pull request process is described 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

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 30, 2026

@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 `@controllers/account/account_controller.go`:
- Around line 160-185: Update the account-limit handling around
AccountsCanBeCreated so every account blocked by the global limit returns a
retry result, including FedRAMP mode. Keep the stuck-duration timeout and
setAccountFailed flow gated by !config.IsFedramp(), then move the existing retry
logging and return outside that condition to prevent nonCCSAssignAccount from
running.

In `@controllers/accountpool/accountpool_controller.go`:
- Around line 117-121: Update Reconcile so accountCreationDelay is enforced
across all reconciliation paths, including reconciliations triggered by the
Account ownership watch. After creating an Account, ensure every return path
applies the delayed result, or persist and validate a next-creation time at
Reconcile entry before permitting another creation; preserve the required
30-second gap between account creations.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 830754b9-cf7d-42ba-b676-d7185f353877

📥 Commits

Reviewing files that changed from the base of the PR and between dabda29 and 44524b3.

📒 Files selected for processing (3)
  • controllers/account/account_controller.go
  • controllers/accountpool/accountpool_controller.go
  • controllers/accountpool/accountpool_controller_test.go

Comment on lines +160 to +185
// Check account limit before doing any expensive work.
// If the account has been stuck waiting for the limit to clear for too long,
// fail it so the pool controller's satisfaction check isn't inflated by
// accounts that will never provision.
if !currentAcctInstance.IsPendingDeletion() && !currentAcctInstance.IsBYOC() && currentAcctInstance.IsUnclaimedAndHasNoState() && !currentAcctInstance.HasAwsAccountID() {
if !totalaccountwatcher.TotalAccountWatcher.AccountsCanBeCreated() {
if !config.IsFedramp() {
reqLogger.Info("AWS Account limit reached. This does not always indicate a problem, it's a limit we enforce in the configmap to prevent runaway account creation")
stuckDuration := time.Since(currentAcctInstance.CreationTimestamp.Time)
if stuckDuration > createPendTime {
errMsg := fmt.Sprintf("Account limit reached for longer than %d minutes, failing account", utils.WaitTime)
reqLogger.Info(errMsg, "stuckFor", stuckDuration.String())
_, stateErr := r.setAccountFailed( //nolint:contextcheck // pre-existing function signature
reqLogger,
currentAcctInstance,
awsv1alpha1.AccountCreationFailed,
"AccountLimitTimeout",
errMsg,
AccountFailed,
)
if stateErr != nil {
reqLogger.Error(stateErr, "failed setting account state", "desiredState", AccountFailed)
return reconcile.Result{}, stateErr
}
return reconcile.Result{}, errors.New(errMsg)
}
reqLogger.Info("AWS Account limit reached, will retry", "stuckFor", stuckDuration.String())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the account-limit requeue in FedRAMP mode.

When AccountsCanBeCreated() is false and FedRAMP mode is enabled, this block falls through instead of returning. Reconciliation can then reach Lines 374-377 and call nonCCSAssignAccount, attempting provisioning despite the global limit. Keep the timeout failure gated by !config.IsFedramp(), but move the retry return outside that condition.

Proposed structure
-            if !config.IsFedramp() {
-                stuckDuration := time.Since(currentAcctInstance.CreationTimestamp.Time)
+            stuckDuration := time.Since(currentAcctInstance.CreationTimestamp.Time)
+            if !config.IsFedramp() {
                 if stuckDuration > createPendTime {
                     // existing setAccountFailed path
                 }
-                reqLogger.Info("AWS Account limit reached, will retry", "stuckFor", stuckDuration.String())
-                return reconcile.Result{Requeue: true, RequeueAfter: time.Duration(5) * time.Minute}, nil
             }
+            reqLogger.Info("AWS Account limit reached, will retry", "stuckFor", stuckDuration.String())
+            return reconcile.Result{Requeue: true, RequeueAfter: 5 * time.Minute}, nil
📝 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
// Check account limit before doing any expensive work.
// If the account has been stuck waiting for the limit to clear for too long,
// fail it so the pool controller's satisfaction check isn't inflated by
// accounts that will never provision.
if !currentAcctInstance.IsPendingDeletion() && !currentAcctInstance.IsBYOC() && currentAcctInstance.IsUnclaimedAndHasNoState() && !currentAcctInstance.HasAwsAccountID() {
if !totalaccountwatcher.TotalAccountWatcher.AccountsCanBeCreated() {
if !config.IsFedramp() {
reqLogger.Info("AWS Account limit reached. This does not always indicate a problem, it's a limit we enforce in the configmap to prevent runaway account creation")
stuckDuration := time.Since(currentAcctInstance.CreationTimestamp.Time)
if stuckDuration > createPendTime {
errMsg := fmt.Sprintf("Account limit reached for longer than %d minutes, failing account", utils.WaitTime)
reqLogger.Info(errMsg, "stuckFor", stuckDuration.String())
_, stateErr := r.setAccountFailed( //nolint:contextcheck // pre-existing function signature
reqLogger,
currentAcctInstance,
awsv1alpha1.AccountCreationFailed,
"AccountLimitTimeout",
errMsg,
AccountFailed,
)
if stateErr != nil {
reqLogger.Error(stateErr, "failed setting account state", "desiredState", AccountFailed)
return reconcile.Result{}, stateErr
}
return reconcile.Result{}, errors.New(errMsg)
}
reqLogger.Info("AWS Account limit reached, will retry", "stuckFor", stuckDuration.String())
// Check account limit before doing any expensive work.
// If the account has been stuck waiting for the limit to clear for too long,
// fail it so the pool controller's satisfaction check isn't inflated by
// accounts that will never provision.
if !currentAcctInstance.IsPendingDeletion() && !currentAcctInstance.IsBYOC() && currentAcctInstance.IsUnclaimedAndHasNoState() && !currentAcctInstance.HasAwsAccountID() {
if !totalaccountwatcher.TotalAccountWatcher.AccountsCanBeCreated() {
stuckDuration := time.Since(currentAcctInstance.CreationTimestamp.Time)
if !config.IsFedramp() {
if stuckDuration > createPendTime {
errMsg := fmt.Sprintf("Account limit reached for longer than %d minutes, failing account", utils.WaitTime)
reqLogger.Info(errMsg, "stuckFor", stuckDuration.String())
_, stateErr := r.setAccountFailed( //nolint:contextcheck // pre-existing function signature
reqLogger,
currentAcctInstance,
awsv1alpha1.AccountCreationFailed,
"AccountLimitTimeout",
errMsg,
AccountFailed,
)
if stateErr != nil {
reqLogger.Error(stateErr, "failed setting account state", "desiredState", AccountFailed)
return reconcile.Result{}, stateErr
}
return reconcile.Result{}, errors.New(errMsg)
}
}
reqLogger.Info("AWS Account limit reached, will retry", "stuckFor", stuckDuration.String())
return reconcile.Result{Requeue: true, RequeueAfter: 5 * time.Minute}, nil
🤖 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 `@controllers/account/account_controller.go` around lines 160 - 185, Update the
account-limit handling around AccountsCanBeCreated so every account blocked by
the global limit returns a retry result, including FedRAMP mode. Keep the
stuck-duration timeout and setAccountFailed flow gated by !config.IsFedramp(),
then move the existing retry logging and return outside that condition to
prevent nonCCSAssignAccount from running.

Comment on lines +117 to +121
// Delay before creating the next account to prevent burst-provisioning
// many AWS accounts at once. Without this, each Account CR creation triggers
// an immediate re-reconcile (via ownership watch), causing the pool to
// rapidly create all CRs in quick succession.
return reconcile.Result{RequeueAfter: accountCreationDelay}, nil

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
fd -a 'accountpool_controller.go|accountpool' . | sed 's#^\./##' | head -50

echo
echo "Outline:"
if [ -f controllers/accountpool/accountpool_controller.go ]; then
  ast-grep outline controllers/accountpool/accountpool_controller.go --view compact || true
fi

echo
echo "Relevant source:"
if [ -f controllers/accountpool/accountpool_controller.go ]; then
  sed -n '1,220p' controllers/accountpool/accountpool_controller.go | cat -n
fi

echo
echo "Search for accountCreationDelay and RequeueAfter/owner references:"
rg -n "accountCreationDelay|RequeueAfter|owner|Ownership|CreateAccount|Create|Reconcile" controllers/accountpool/controllers/accountpool/accountpool_controller.go controllers/accountpool -S

echo
echo "Diff/stat if available:"
git diff --stat controllers/accountpool/accountpool_controller.go || true
git diff -- controllers/accountpool/accountpool_controller.go | sed -n '1,240p' || true

Repository: openshift/aws-account-operator

Length of output: 14215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "SetupWithManager:"
sed -n '226,290p' controllers/accountpool/accountpool_controller.go | cat -n

echo
echo "Tests around Setup/Reconcile/enqueues:"
sed -n '240,380p' controllers/accountpool/accountpool_controller_test.go | cat -n

echo
echo "Search owner watch / Owns references in accountpool files:"
rg -n "Owns|Build|Watches|Create(\(|\()|RequeueAfter|Requeue|Result\\{" controllers/accountpool -S

echo
echo "Read-only semantic probe of reconcile.Result behavior described in controller-runtime docs/source text if vendored:
github module availability?"
rg -n "func Result RequeueAfter|RequeueAfter|Requeue bool" -S go.mod vendor 2>/dev/null || true

Repository: openshift/aws-account-operator

Length of output: 7376


Enforce creation throttling on every reconcile path

Reconcile creates a new Account and then returns RequeueAfter: accountCreationDelay, but the .Owns(&awsv1alpha1.Account{}) watch enqueues another reconciliation on that create/update. Return only the delayed result after every creation, or persist a next-creation time and check it at the start of Reconcile, so owner-watch events cannot bypass the 30-second gap.

🤖 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 `@controllers/accountpool/accountpool_controller.go` around lines 117 - 121,
Update Reconcile so accountCreationDelay is enforced across all reconciliation
paths, including reconciliations triggered by the Account ownership watch. After
creating an Account, ensure every return path applies the delayed result, or
persist and validate a next-creation time at Reconcile entry before permitting
another creation; preserve the required 30-second gap between account creations.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 1, 2026
@openshift-ci

openshift-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR needs rebase.

Details

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 kubernetes-sigs/prow repository.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant