Skip to content

CNTRLPLANE-3604: Capi v1beta2 storage migration#8938

Open
clebs wants to merge 5 commits into
openshift:mainfrom
clebs:capi-v1beta2-storage-migration
Open

CNTRLPLANE-3604: Capi v1beta2 storage migration#8938
clebs wants to merge 5 commits into
openshift:mainfrom
clebs:capi-v1beta2-storage-migration

Conversation

@clebs

@clebs clebs commented Jul 6, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

This PR enables CAPI CRD storage version migration from v1beta1 to v1beta2. The migration is necessary because upstream Cluster API is deprecating v1beta1, and all existing CAPI resources stored in etcd must be re-stored using the v1beta2 schema before v1beta1 can be removed.
Additionally, keeping the old storage version also has shown to block the ability to migrate clients to the new API because of various conversion issues.

Upstream, the supported path is migrating the storage version first while clients stay on the old API and afterwards migrating the clients. Therefore, we will follow the same procedure.

Safety guards require explicit acknowledgement before migration and prevent accidental downgrades after migration completes.

When a user runs hypershift install, the CLI applies all CAPI CRDs with v1beta2 as the storage version, and the hypershift operator starts a CRD migrator controller that performs no-op server-side applies on every existing CAPI resource to force re storage. CAPI's built-in migrator is disabled on the CAPI manager deployment to avoid conflicts and managing the process centrally on the hypershift operator, gated by the flags and guardrails described.

The migration can be disabled by passing the --disable-capi-migration flag.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3604

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • New Features
    • Added conditional CAPI CRD storage version migration support during operator startup and CRD installation.
    • Introduced a --disable-capi-migration installation flag and optional CAPI_STORAGE_VERSION wiring for the migrator.
  • Bug Fixes
    • Improved CRD override generation to apply storage-version-specific behavior and adjusted migration guards for safer updates.
    • Updated manager startup args to skip specific CRD migration phases.
  • Tests
    • Expanded unit coverage for storage-version overrides, migration status reporting, and added gated e2e validation.
  • Documentation
    • Replaced the storage migration guide with updated default behavior and how to disable it.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 6, 2026
@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 6, 2026
@openshift-ci

openshift-ci Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot

openshift-ci-robot commented Jul 6, 2026

Copy link
Copy Markdown

@clebs: This pull request references CNTRLPLANE-3604 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 story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

This PR enables CAPI CRD storage version migration from v1beta1 to v1beta2. The migration is necessary because upstream Cluster API is deprecating v1beta1, and all existing CAPI resources stored in etcd must be re-stored using the v1beta2 schema before v1beta1 can be removed.
Additionally, keeping the old storage version also has shown to block the ability to migrate clients to the new API because of various conversion issues.

Upstream, the supported path is migrating the storage version first while clients stay on the old API and afterwards migrating the clients. Therefore, we will follow the same procedure.

Safety guards require explicit acknowledgement before migration and prevent accidental downgrades after migration completes.

When a user runs hypershift install --capi-storage-version=v1beta2, the CLI applies all CAPI CRDs with v1beta2 as the storage version, and the hypershift operator starts a CRD migrator controller that performs no-op server-side applies on every existing CAPI resource to force re storage. CAPI's built-in migrator is disabled on the CAPI manager deployment to avoid conflicts and managing the process centrally on the hypershift operator, gated by the flags and guardrails described.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3604

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

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 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: clebs
Once this PR has been reviewed and has the lgtm label, please assign devguyio 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

@openshift-ci openshift-ci Bot added area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/testing Indicates the PR includes changes for e2e testing and removed do-not-merge/needs-area labels Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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

Walkthrough

This PR adds CAPI CRD storage-version migration support. It introduces storage-version-specific CRD override generation, installer flags and migration guards, operator wiring for CAPI_STORAGE_VERSION, a CRD migrator controller with status reporting, updated deployment arguments, expanded tests, end-to-end validation, and documentation for the migration flow.

Sequence Diagram(s)

sequenceDiagram
  participant Installer
  participant HyperShiftOperator
  participant CRDMigrator
  participant KubernetesAPI
  Installer->>KubernetesAPI: Apply CAPI CRDs with v1beta2 storage
  Installer->>HyperShiftOperator: Configure CAPI_STORAGE_VERSION
  HyperShiftOperator->>CRDMigrator: Start migration controller
  CRDMigrator->>KubernetesAPI: Migrate resources and update CRD status
  CRDMigrator->>KubernetesAPI: Persist migration status
Loading
🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
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 Changed tests use static literal names; no Ginkgo titles with dynamic identifiers, and no pod/namespace/IP/timestamp data appears in titles.
Test Structure And Quality ✅ Passed No Ginkgo Describe/It blocks were added; the new tests use repo-standard t.Run/Gomega patterns, have bounded Eventually timeouts, and use envtest/framework cleanup.
Topology-Aware Scheduling Compatibility ✅ Passed Touched deployment code only adds env/args; no new nodeSelectors, topology spreads, required anti-affinity, or replica logic was introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed New e2e test only uses Kubernetes/guest-cluster clients and pod logs; scans found no hardcoded IPs, CIDRs, IPv4-only parsing, or external URLs in its path.
No-Weak-Crypto ✅ Passed Exact scans of the changed files found no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB or ConstantTimeCompare/hmac.Equal usages; changes are migration/config code.
Container-Privileges ✅ Passed PASS: Reviewed the new manifest and deployment builders; no privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation=true or root settings were added.
No-Sensitive-Data-In-Logs ✅ Passed Only generic migration/status logs and debug-level CR object refs were added; no passwords, tokens, API keys, PII, session IDs, or hostnames are logged.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: CAPI storage migration to v1beta2, and it references the linked work item.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.19%. Comparing base (7eb0da0) to head (613eea0).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
hypershift-operator/main.go 0.00% 26 Missing ⚠️
support/capi-crdmigrator/status.go 88.39% 10 Missing and 3 partials ⚠️
cmd/install/install.go 75.00% 4 Missing and 2 partials ⚠️
cmd/install/assets/crds/assets.go 86.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8938      +/-   ##
==========================================
+ Coverage   44.11%   44.19%   +0.07%     
==========================================
  Files         772      774       +2     
  Lines       96226    96396     +170     
==========================================
+ Hits        42448    42599     +151     
- Misses      50832    50845      +13     
- Partials     2946     2952       +6     
Files with missing lines Coverage Δ
cmd/install/assets/hypershift_operator.go 47.56% <100.00%> (+0.08%) ⬆️
...s/hostedcontrolplane/v2/capi_manager/deployment.go 100.00% <100.00%> (ø)
support/capi-crdmigrator/metrics.go 100.00% <100.00%> (ø)
cmd/install/assets/crds/assets.go 73.68% <86.66%> (+36.72%) ⬆️
cmd/install/install.go 69.48% <75.00%> (+0.59%) ⬆️
support/capi-crdmigrator/status.go 88.39% <88.39%> (ø)
hypershift-operator/main.go 0.00% <0.00%> (ø)
Flag Coverage Δ
cmd-support 38.53% <86.62%> (+0.29%) ⬆️
cpo-hostedcontrolplane 46.18% <100.00%> (+0.01%) ⬆️
cpo-other 45.13% <ø> (ø)
hypershift-operator 54.04% <0.00%> (-0.06%) ⬇️
other 32.12% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
cmd/install/assets/crds/assets.go (1)

169-173: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject unknown --capi-storage-version values before applying overrides. In cmd/install/assets/crds/assets.go:169-173, an unexpected version string makes every entry in crd.Spec.Versions non-storage and produces an invalid CRD. Validate the input up front so the install fails fast with a clear error.

🤖 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 `@cmd/install/assets/crds/assets.go` around lines 169 - 173, Validate the
`--capi-storage-version` override before `loadCRDs` applies it in `assets.go` so
unknown values are rejected early instead of marking every entry in
`crd.Spec.Versions` as non-storage. Update the override handling around
`overrides[crd.Name]` and `crd.Spec.Versions` to check that
`override.StorageVersion` matches one of the version names for that CRD, and
return a clear error from the install flow if it does not.
🧹 Nitpick comments (1)
cmd/install/assets/crds/assets_test.go (1)

45-99: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider asserting exactly one storage version per CRD.

The current switch only checks the two known version names, so it would not detect the failure mode where the selected version name matches none of a CRD's versions and every version ends up Storage=false. Counting storage versions and asserting == 1 per CAPI CRD would harden this test against the risk flagged in assets.go.

🤖 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 `@cmd/install/assets/crds/assets_test.go` around lines 45 - 99, The
storage-version checks in TestCustomResourceDefinitionsStorageVersion only
verify the known version names, so they can miss cases where no version is
marked as storage. Update both subtests to count the number of true Storage
entries for each CAPI CRD produced by CustomResourceDefinitions and assert that
exactly one version is storage, while still checking that the selected version
name (v1beta1 or v1beta2) is the one marked true and the other known version is
false.
🤖 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/install/assets/crds/assets_test.go`:
- Around line 12-98: Update the new subtest labels in assets_test.go to match
the repository’s test naming convention used by TestCapiResources: rename each
t.Run description in CAPICRDOverridesWithStorageVersion and
CustomResourceDefinitionsStorageVersion to the “When … it should …” format. Keep
the test logic the same, but make the names describe the input version and
expected storage behavior in that phrasing.

In `@cmd/install/install_test.go`:
- Around line 2442-2535: Update the table-driven subtest names in the install
tests to match the required “When … it should …” convention used by this suite.
Reword each case in the test block around the existing storage-version scenarios
(e.g. the fresh install, upgrade, re-install, and downgrade cases in the test
using makeCAPICRD and Options) so every name starts with “When” and describes
the condition plus expected outcome with “it should …”.

In `@cmd/install/install.go`:
- Around line 443-445: Validate the `--capi-storage-version` value before CRD
rendering so only `v1beta1` or `v1beta2` are accepted. Add an allow-list check
in `Options.Validate` or at the start of `setupCRDs`, using the existing
`opts.CAPIStorageVersion` path, and reject any other value with a clear error
before continuing into CRD setup.

In `@support/capi-crdmigrator/crd_migrator.go`:
- Around line 497-529: The ttlCache implementation only evicts expired keys
during has(), so stale CR/generation entries can accumulate in memory. Update
ttlCache.add to sweep and remove expired entries before inserting the new key,
using the existing ttlCache fields and mutex so the map stays bounded even when
old keys are never read again.

In `@test/e2e/capi_storage_migration_test.go`:
- Line 51: The test setup has leftover unused scaffolding: hcpNamespace is
computed in the CAPI storage migration test but only discarded later, and the
redundant _ = g no-op is unnecessary. Remove the unused hcpNamespace assignment
and the _ = g discard, or fully wire hcpNamespace into the intended CAPI
resource checks in the test body if that was the original goal; reference the
existing test function and its g.Expect(t.Run(...)) usage to update the right
spots consistently.
- Around line 140-174: The log stream in checkHOLogsForMigratorErrors is
deferred inside the pod loop, so each PodLogs stream stays open until the
function exits instead of being closed after each pod is processed. Update the
loop to close each stream immediately after scanning that pod’s logs, and keep
the existing stream creation and log-scanning logic in
checkHOLogsForMigratorErrors unchanged otherwise.

---

Outside diff comments:
In `@cmd/install/assets/crds/assets.go`:
- Around line 169-173: Validate the `--capi-storage-version` override before
`loadCRDs` applies it in `assets.go` so unknown values are rejected early
instead of marking every entry in `crd.Spec.Versions` as non-storage. Update the
override handling around `overrides[crd.Name]` and `crd.Spec.Versions` to check
that `override.StorageVersion` matches one of the version names for that CRD,
and return a clear error from the install flow if it does not.

---

Nitpick comments:
In `@cmd/install/assets/crds/assets_test.go`:
- Around line 45-99: The storage-version checks in
TestCustomResourceDefinitionsStorageVersion only verify the known version names,
so they can miss cases where no version is marked as storage. Update both
subtests to count the number of true Storage entries for each CAPI CRD produced
by CustomResourceDefinitions and assert that exactly one version is storage,
while still checking that the selected version name (v1beta1 or v1beta2) is the
one marked true and the other known version is false.
🪄 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: Enterprise

Run ID: 8bd87e84-55f5-4d56-aa66-25868c88e041

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6ecb5 and 4b6cf33.

⛔ Files ignored due to path filters (1)
  • hypershift-operator/controllers/hostedcluster/testdata/cluster-api/zz_fixture_TestReconcileComponents.yaml is excluded by !**/testdata/**
📒 Files selected for processing (17)
  • cmd/install/assets/crds/assets.go
  • cmd/install/assets/crds/assets_test.go
  • cmd/install/assets/hypershift_operator.go
  • cmd/install/assets/hypershift_operator_test.go
  • cmd/install/install.go
  • cmd/install/install_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment_test.go
  • docs/content/how-to/capi-storage-migration.md
  • docs/mkdocs.yml
  • hypershift-operator/main.go
  • support/capi-crdmigrator/crd_migrator.go
  • support/capi-crdmigrator/crd_migrator_test.go
  • test/e2e/capi_storage_migration_test.go
  • test/e2e/e2e_test.go
  • test/e2e/util/install.go
  • test/e2e/util/options.go

Comment thread cmd/install/assets/crds/assets_test.go Outdated
Comment thread cmd/install/install_test.go
Comment thread cmd/install/install.go Outdated
Comment thread support/capi-crdmigrator/crd_migrator.go
Comment thread test/e2e/capi_storage_migration_test.go Outdated
Comment thread test/e2e/capi_storage_migration_test.go
@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch from 4b6cf33 to 19841ae Compare July 6, 2026 19:15

@coderabbitai coderabbitai Bot left a comment

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.

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 `@docs/content/how-to/capi-storage-migration.md`:
- Around line 69-73: Add a language tag to the fenced Markdown snippets in the
migration doc so markdownlint treats them as text blocks. Update the plain
fenced blocks in the affected sections of the document to use the same syntax as
the other output/error snippets, keeping the content unchanged and applying the
fix in both locations referenced by the comment.
- Line 83: Fix the typo in the documentation sentence so the CLI flag name and
the following word are separated correctly; update the text in the migration
guide sentence that mentions --capi-storage-version and
--acknowledge-capi-migration so it reads with a space between the flag value and
the word flag, preserving the rest of the wording unchanged.
🪄 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: Enterprise

Run ID: 296b59f6-396c-483e-9bdc-fae567985130

📥 Commits

Reviewing files that changed from the base of the PR and between 4b6cf33 and 19841ae.

⛔ Files ignored due to path filters (1)
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
📒 Files selected for processing (2)
  • docs/content/how-to/capi-storage-migration.md
  • docs/mkdocs.yml
✅ Files skipped from review due to trivial changes (1)
  • docs/mkdocs.yml

Comment thread docs/content/how-to/capi-storage-migration.md Outdated
Comment thread docs/content/how-to/capi-storage-migration.md Outdated
@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch 3 times, most recently from a6838eb to d72bb04 Compare July 7, 2026 08:48

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
cmd/install/install_test.go (1)

2476-2577: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add coverage for mixed storedVersions.

The table only covers singleton storedVersions. During CRD storage migration, Kubernetes can report both v1beta1 and v1beta2; add a case for that intermediate state with the intended guard behavior. As per coding guidelines, **/*_test.go: Unit test any code changes and additions.

🤖 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 `@cmd/install/install_test.go` around lines 2476 - 2577, Add a test case in the
install test table to cover a CRD whose storedVersions contains both v1beta1 and
v1beta2, since the current scenarios only use a single version. Update the
existing table-driven test around the install/migration checks to build the
mixed-state object with makeCAPICRD and assert the intended guard behavior for
the relevant path in the install flow, using the same Options fields such as
CAPIStorageVersion, AcknowledgeCAPIMigration, and ForceCAPIStorageDowngrade as
needed.

Source: Coding guidelines

cmd/install/assets/crds/assets.go (1)

51-54: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Return a defensive copy from CAPICRDNames.

CAPICRDNames exposes the backing array for capiCRDNames; any caller that sorts, appends, or edits the returned slice can change global CRD override behavior for the process.

Proposed fix
 func CAPICRDNames() []string {
-	return capiCRDNames
+	names := make([]string, len(capiCRDNames))
+	copy(names, capiCRDNames)
+	return names
 }
🤖 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 `@cmd/install/assets/crds/assets.go` around lines 51 - 54, CAPICRDNames is
returning the shared capiCRDNames slice directly, which lets callers mutate
global state. Update CAPICRDNames to return a defensive copy of capiCRDNames
instead of the backing slice, so any sorting, appending, or editing by callers
cannot affect process-wide CRD behavior.
🤖 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/install/assets/crds/assets.go`:
- Around line 62-67: CAPICRDOverridesWithStorageVersion currently allows any
non-empty storageVersion to flow through, which can later mark every CRD version
as non-storage and produce an invalid CRD; make the handling explicit by
validating the requested version against the available CRD versions before
building overrides and fail fast when no match is found. Update the CRD
construction path around CAPICRDOverridesWithStorageVersion and the
storage-version selection logic that sets the version Storage field so it tracks
whether a match was found and returns an error instead of implicitly falling
back to all false, keeping the exported API behavior explicit.

---

Nitpick comments:
In `@cmd/install/assets/crds/assets.go`:
- Around line 51-54: CAPICRDNames is returning the shared capiCRDNames slice
directly, which lets callers mutate global state. Update CAPICRDNames to return
a defensive copy of capiCRDNames instead of the backing slice, so any sorting,
appending, or editing by callers cannot affect process-wide CRD behavior.

In `@cmd/install/install_test.go`:
- Around line 2476-2577: Add a test case in the install test table to cover a
CRD whose storedVersions contains both v1beta1 and v1beta2, since the current
scenarios only use a single version. Update the existing table-driven test
around the install/migration checks to build the mixed-state object with
makeCAPICRD and assert the intended guard behavior for the relevant path in the
install flow, using the same Options fields such as CAPIStorageVersion,
AcknowledgeCAPIMigration, and ForceCAPIStorageDowngrade as needed.
🪄 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: Enterprise

Run ID: d05b1d1b-4f73-4acd-adf3-1b233a6bdf33

📥 Commits

Reviewing files that changed from the base of the PR and between a6838eb and d72bb04.

⛔ Files ignored due to path filters (2)
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • hypershift-operator/controllers/hostedcluster/testdata/cluster-api/zz_fixture_TestReconcileComponents.yaml is excluded by !**/testdata/**
📒 Files selected for processing (17)
  • cmd/install/assets/crds/assets.go
  • cmd/install/assets/crds/assets_test.go
  • cmd/install/assets/hypershift_operator.go
  • cmd/install/assets/hypershift_operator_test.go
  • cmd/install/install.go
  • cmd/install/install_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment_test.go
  • docs/content/how-to/capi-storage-migration.md
  • docs/mkdocs.yml
  • hypershift-operator/main.go
  • support/capi-crdmigrator/crd_migrator.go
  • support/capi-crdmigrator/crd_migrator_test.go
  • test/e2e/capi_storage_migration_test.go
  • test/e2e/e2e_test.go
  • test/e2e/util/install.go
  • test/e2e/util/options.go
✅ Files skipped from review due to trivial changes (4)
  • test/e2e/util/install.go
  • test/e2e/util/options.go
  • docs/content/how-to/capi-storage-migration.md
  • docs/mkdocs.yml
🚧 Files skipped from review as they are similar to previous changes (11)
  • cmd/install/assets/hypershift_operator_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment_test.go
  • cmd/install/assets/hypershift_operator.go
  • hypershift-operator/main.go
  • cmd/install/assets/crds/assets_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment.go
  • cmd/install/install.go
  • test/e2e/e2e_test.go
  • test/e2e/capi_storage_migration_test.go
  • support/capi-crdmigrator/crd_migrator_test.go
  • support/capi-crdmigrator/crd_migrator.go

Comment thread cmd/install/assets/crds/assets.go
@clebs

clebs commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/test e2e-aws

@cwbotbot

cwbotbot commented Jul 7, 2026

Copy link
Copy Markdown

Test Results

e2e-aws

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 9, 2026
@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch 2 times, most recently from 125c9bf to edcd70d Compare July 14, 2026 09:15
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 14, 2026
@clebs

clebs commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

/test e2e-aws

@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch from 2507829 to 9c59bda Compare July 14, 2026 10:35
@clebs

clebs commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

/test e2e-aws

1 similar comment
@clebs

clebs commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

/test e2e-aws

@hypershift-jira-solve-ci

hypershift-jira-solve-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Now I have a clear picture. Let me summarize the 4 distinct root causes:

  1. EnsureAllRoutesUseHCPRouter (TestAutoscaling, TestKarpenterUpgradeControlPlane): the server is currently unable to handle the request (get routes.route.openshift.io) — API server 503 during teardown phase
  2. EnsureGlobalPullSecret (TestCreateCluster): kubelet-config-verifier DaemonSet stuck 2/3 ready → context deadline exceeded; then second sub-test fails because DaemonSet "already exists"
  3. TestNodePool/HostedCluster2/Teardown: namespace still exists after deletion timeout: context deadline exceeded — HostedCluster wasn't finalized
  4. Teardown failures cascade from above API server unavailability: the server is currently unable to handle the request, KeyPairLimitExceeded: Maximum of 5000 keypairs

All of these failures are infrastructure/environment issues (API server 503s, AWS keypair quota exhaustion, DaemonSet scheduling issues) — none are related to the CAPI v1beta2 storage migration code changes.

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

1. TestAutoscaling/EnsureHostedCluster/EnsureAllRoutesUseHCPRouter:
   "failed to list routes: the server is currently unable to handle the request (get routes.route.openshift.io)"

2. TestKarpenterUpgradeControlPlane/EnsureHostedCluster/EnsureAllRoutesUseHCPRouter:
   "failed to list routes: the server is currently unable to handle the request (get routes.route.openshift.io)"

3. TestCreateCluster/Main/EnsureGlobalPullSecret:
   "failed to wait for DaemonSet kubelet-config-verifier to be ready: context deadline exceeded"
   "daemonsets.apps \"kubelet-config-verifier\" already exists" (cascading)

4. TestNodePool/HostedCluster2/Teardown:
   "namespace still exists after deletion timeout: context deadline exceeded"
   "hostedcluster wasn't finalized, aborting delete: context deadline exceeded"

Summary

All 17 test failures (4 distinct root failures with cascading parent failures) are caused by CI infrastructure/environment issues unrelated to the PR's code changes. The management cluster's API server returned HTTP 503 ("the server is currently unable to handle the request") 16 times throughout the run, affecting the EnsureAllRoutesUseHCPRouter check for TestAutoscaling and TestKarpenterUpgradeControlPlane, cluster dump operations during teardown, and namespace cleanup. Separately, the EnsureGlobalPullSecret test hit a DaemonSet scheduling issue (kubelet-config-verifier stuck at 2/3 pods ready for >20 minutes until deadline), and the AWS account hit the 5000 keypair quota limit. The actual test for the PR's feature (TestCAPIStorageVersionMigration) was skipped (not failed). None of the failures are related to the CAPI v1beta2 storage migration changes.

Root Cause

The failures stem from three independent CI infrastructure/environment issues:

1. Management cluster API server instability (HTTP 503): The shared management cluster API server returned "the server is currently unable to handle the request" errors at least 16 times during the test run. This directly caused:

  • TestAutoscaling/EnsureHostedCluster/EnsureAllRoutesUseHCPRouter — failed to list OpenShift Routes via the guest API
  • TestKarpenterUpgradeControlPlane/EnsureHostedCluster/EnsureAllRoutesUseHCPRouter — same 503 error on route listing
  • Multiple Teardown failures — failed to dump hosted cluster: failed to dump cluster: the server is currently unable to handle the request
  • These failures cascade up to mark the parent test (TestAutoscaling, TestKarpenterUpgradeControlPlane) and their Teardown sub-tests as FAIL

2. DaemonSet scheduling issue (EnsureGlobalPullSecret): The kubelet-config-verifier DaemonSet deployed during the EnsureGlobalPullSecret test could only schedule 2 out of 3 pods. It remained stuck at "2/3 pods ready" for the entire 20+ minute timeout period, resulting in context deadline exceeded. The subsequent sub-test Check_if_the_config.json_is_correct_in_all_of_the_nodes then tried to create the same DaemonSet and failed with "kubelet-config-verifier" already exists (HTTP 409) because the previous sub-test's DaemonSet was not cleaned up. This is a node scheduling/resource contention issue, not a product bug.

3. AWS EC2 keypair quota exhaustion: The AWS account hit the 5000 keypair limit: api error KeyPairLimitExceeded: Maximum of 5000 keypairs reached. This prevented machine journal dumping during teardown for TestKarpenter and TestKarpenterUpgradeControlPlane, and caused the TestNodePool/HostedCluster2/Teardown to fail when the HostedCluster couldn't be finalized within the deadline (hostedcluster wasn't finalized, aborting delete: context deadline exceedednamespace still exists after deletion timeout).

Not related to PR changes: The PR's feature test TestCAPIStorageVersionMigration was skipped (not failed), which is expected behavior when the test's preconditions aren't met in this e2e-aws configuration. The CAPI v1beta2 storage migration code is not exercised by any of the failing tests.

Recommendations
  1. Retry the job — All failures are transient infrastructure issues (API server 503s, AWS quota, DaemonSet scheduling). A re-run on a healthier management cluster with available AWS keypair quota should pass cleanly.

  2. AWS keypair cleanup — The KeyPairLimitExceeded error indicates stale CI keypairs accumulating in the us-east-1 region. The CI team should run a keypair garbage collection job to clean up orphaned keypairs from previous test runs.

  3. Management cluster health — The 16 occurrences of "the server is currently unable to handle the request" suggest the shared management cluster was under pressure during this run (possibly due to concurrent CI jobs). This is an infrastructure concern, not actionable by the PR author.

  4. EnsureGlobalPullSecret test improvement — The Check_if_the_config.json_is_correct_in_all_of_the_nodes sub-test should handle the case where kubelet-config-verifier DaemonSet already exists (e.g., delete-and-recreate pattern) to avoid cascading failures from a previous sub-test's leftover resources.

  5. Consider re-running only — Since TestCAPIStorageVersionMigration was skipped rather than failed, verify that the test has adequate coverage in another job (e.g., a unit test or a different e2e variant that satisfies the skip conditions).

Evidence
Evidence Detail
API Server 503 count 16 occurrences of "the server is currently unable to handle the request" in build-log.txt
EnsureAllRoutesUseHCPRouter error util.go:1214: failed to list routes: the server is currently unable to handle the request (get routes.route.openshift.io) — affects TestAutoscaling and TestKarpenterUpgradeControlPlane
EnsureGlobalPullSecret error globalps.go:220: failed to wait for DaemonSet kubelet-config-verifier to be ready: context deadline exceeded — DaemonSet stuck at 2/3 pods ready
DaemonSet already exists globalps.go:211: daemonsets.apps "kubelet-config-verifier" already exists — cascading failure from above
AWS Keypair Quota api error KeyPairLimitExceeded: Maximum of 5000 keypairs reached — prevents keypair import for machine journal dumping
HostedCluster2 Teardown hypershift_framework.go:576: hostedcluster wasn't finalized, aborting delete: context deadline exceeded and hypershift_framework.go:605: namespace still exists after deletion timeout: context deadline exceeded
Teardown dump failures hypershift_framework.go:613: Failed to dump cluster: failed to dump hosted cluster: failed to dump cluster: the server is currently unable to handle the request — affects TestAutoscaling, TestCreateCluster, TestKarpenterUpgradeControlPlane
TestCAPIStorageVersionMigration Skipped (not failed) — PR's feature test not exercised in this configuration
Test summary 618 tests, 31 skipped, 17 failures — 4 distinct root failures with cascading parents
PR relevance None of the 17 failures touch CAPI storage migration code paths; all are infrastructure/environment

@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch from 9c59bda to e4918e5 Compare July 15, 2026 09:12
@clebs

clebs commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

/test e2e-aws

clebs added 5 commits July 15, 2026 15:34
Add storage version migration to the hypershift install command.

The migration runs by default and can be disabled with the
--disable-capi-migration flag.

The migration is performed by CAPI's CRD migrator controller, setup and
executed centrally by the hypershift operator.

Additionally migration progress is persisted in a ConfigMap by the
migrator controller and metrics for migrated CRDs and total CRDs to migrate are
exposed.

Signed-off-by: Borja Clemente <bclement@redhat.com>

new opt-out approach
Add unit tests for the CAPI v1beta2 storage migration and for all new or
changed flags in hypershift install.

Add new tests for the adjusted CRD migrator copied from upstream CAPI.

Signed-off-by: Borja Clemente <bclement@redhat.com>
Add an e2e test that verifies the CAPI storage version migration from
v1beta1 to v1beta2.

The test creates a hosted cluster with v1beta1 as storage versiona and
then runs `hypershift install` again with the
`--capi-storage-migration=v1beta2` flag.
Then it verifies that the migrator runs, all CRDs have v1beta2 as
storage version and the cluster is still running.

This e2e test is gated behind a flag to avoid interferring with other
suites and be tested in isolation.

Signed-off-by: Borja Clemente <bclement@redhat.com>
Add a new how-to guide for the storage version migration explaining the
process, all the new flags and all scenarios this feature interacts with
and what the result for each is.

Signed-off-by: Borja Clemente <bclement@redhat.com>
Add crd_migrator.go to the codecov ignore list. This is acceptable
because it has been adopted from upstream CAPI and is tested via envtest
as well.

Signed-off-by: Borja Clemente <bclement@redhat.com>
@clebs
clebs force-pushed the capi-v1beta2-storage-migration branch from e4918e5 to 613eea0 Compare July 15, 2026 13:34
@clebs
clebs marked this pull request as ready for review July 15, 2026 14:38
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 15, 2026
@openshift-ci
openshift-ci Bot requested review from cblecker and ironcladlou July 15, 2026 14:40
@openshift-ci

openshift-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@clebs: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws e4918e5 link true /test e2e-aws

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Jul 16, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@clebs: This PR has been marked as verified by @clebs.

Details

In response to this:

/verified by @clebs
Ran hypershift install --dummy to provoke an error (unknown flag).
Output on main:

Error: unknown flag: --dummy
unknown flag: --dummy

Output on this PR:

unknown flag: --dummy

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-robot

Copy link
Copy Markdown

@clebs: The /verified command must be used with one of the following actions: by, later, remove, or bypass. See https://docs.ci.openshift.org/docs/architecture/jira/#premerge-verification for more information.

Details

In response to this:

/verified cancel

The previous tagging was intended for a different PR

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.

@clebs

clebs commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

/verified remove

The previous tag was intended for a different PR.

@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Jul 16, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@clebs: The verified label has been removed.

Details

In response to this:

/verified remove

The previous tag was intended for a different PR.

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.

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

Labels

area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/testing Indicates the PR includes changes for e2e testing jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants