Skip to content

CNTRLPLANE-3694: validate nodepool upgrade type against known values#8989

Open
hypershift-jira-solve-ci[bot] wants to merge 1 commit into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-3694
Open

CNTRLPLANE-3694: validate nodepool upgrade type against known values#8989
hypershift-jira-solve-ci[bot] wants to merge 1 commit into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-3694

Conversation

@hypershift-jira-solve-ci

@hypershift-jira-solve-ci hypershift-jira-solve-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Validates the NodePool UpgradeType field against known values (Replace, InPlace) in the validateManagement function.

Previously, the function skipped all validation when UpgradeType was InPlace and produced confusing error messages for unknown upgrade types. This replaces the if-chain with a switch statement that explicitly validates the upgrade type, returning a clear error for unsupported values.

Additionally normalizes error messages to a consistent format and adds test coverage:

  • Unit test for empty-string UpgradeType
  • Envtest cases for UpgradeType enum validation and immutability CEL rules

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/CNTRLPLANE-3694

Special notes for your reviewer:

Resolves the TODO that was at the former line 638 of nodepool_controller.go.

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin


Note: This PR was auto-generated by the jira-agent periodic CI job in response to CNTRLPLANE-3694. See the full report for token usage, cost breakdown, and detailed phase output.

Summary by CodeRabbit

  • Bug Fixes
    • Improved node pool management validation for upgrade configurations.
    • Added explicit handling for in-place upgrades and clearer “unsupported” errors for invalid upgrade/replace combinations.
    • Strengthened validation of replacement strategies and ensured required rolling update settings are present.
  • Tests
    • Updated table-driven validation tests with clearer scenario names and more precise error-message assertions for upgrade and replace cases.

@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 13, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 13, 2026

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references CNTRLPLANE-3694 which is a valid jira issue.

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

Details

In response to this:

What this PR does / why we need it:

Validates the NodePool UpgradeType field against known values (Replace, InPlace) in the validateManagement function.

Previously, the function skipped all validation when UpgradeType was InPlace and produced confusing error messages for unknown upgrade types. This replaces the if-chain with a switch statement that explicitly validates the upgrade type, returning a clear error for unsupported values.

Additionally normalizes error messages to a consistent format and adds test coverage:

  • Unit test for empty-string UpgradeType
  • Envtest cases for UpgradeType enum validation and immutability CEL rules

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/CNTRLPLANE-3694

Special notes for your reviewer:

Resolves the TODO that was at the former line 638 of nodepool_controller.go.

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin

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 requested review from devguyio and muraee July 13, 2026 10:54
@openshift-ci openshift-ci Bot added the area/cli Indicates the PR includes changes for CLI label Jul 13, 2026
@openshift-ci

openshift-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: hypershift-jira-solve-ci[bot]
Once this PR has been reviewed and has the lgtm label, please assign csrwng 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/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release and removed do-not-merge/needs-area labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

validateManagement now explicitly returns successfully for InPlace upgrades while retaining validation for Replace strategies and rolling-update configuration. The related table-driven test cases use more explicit names for invalid, missing, and valid management configurations.

Suggested reviewers: devguyio, muraee

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: validating NodePool UpgradeType against supported values.
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 The updated test names are static literal strings; no generated IDs, timestamps, or runtime data appear in the changed titles.
Test Structure And Quality ✅ Passed The changed test is a simple table-driven unit test; no cluster setup, waits, cleanup, or multi-behavior It blocks are involved.
Topology-Aware Scheduling Compatibility ✅ Passed Touched code only changes NodePool management validation and test names; no pod affinity, node selectors, tolerations, replica, or topology-aware scheduling logic was added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the touched test file is table-driven unit tests, and the CRD suite asset only contains static manifest data.
No-Weak-Crypto ✅ Passed Touched files only change NodePool validation/tests; no weak crypto, custom crypto, or secret/token comparisons found.
Container-Privileges ✅ Passed PASS: The PR only changes Go controller/test logic; no container/K8s manifests or privileged settings (privileged, hostPID, hostNetwork, hostIPC, SYS_ADMIN, allowPrivilegeEscalation) appear in the...
No-Sensitive-Data-In-Logs ✅ Passed No new log statements or sensitive-value logging were added; the diff only changes validation logic/comments, test names, and CRD validation cases.
✨ 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 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.80%. Comparing base (8ea96d7) to head (07fd764).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8989      +/-   ##
==========================================
+ Coverage   43.78%   43.80%   +0.02%     
==========================================
  Files         772      772              
  Lines       96000    96099      +99     
==========================================
+ Hits        42033    42096      +63     
- Misses      51055    51084      +29     
- Partials     2912     2919       +7     
Files with missing lines Coverage Δ
...erator/controllers/nodepool/nodepool_controller.go 43.28% <ø> (-0.07%) ⬇️

... and 6 files with indirect coverage changes

Flag Coverage Δ
cmd-support 37.45% <ø> (+0.02%) ⬆️
cpo-hostedcontrolplane 45.90% <ø> (-0.01%) ⬇️
cpo-other 45.13% <ø> (+0.01%) ⬆️
hypershift-operator 54.06% <ø> (+0.02%) ⬆️
other 32.12% <ø> (+0.03%) ⬆️

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.

case hyperv1.UpgradeTypeInPlace:
return nil
case hyperv1.UpgradeTypeReplace:
// validated below

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we not encapsulate that in a function and then add complete behavioral unit tests for it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Extracted validateInPlaceManagement and validateReplaceManagement sub-functions and added behavioral unit tests with error message assertions for each validation path.


AI-assisted response via Claude Code

if nodePool.Spec.Management.UpgradeType == hyperv1.UpgradeTypeInPlace {
switch nodePool.Spec.Management.UpgradeType {
case hyperv1.UpgradeTypeInPlace:
return nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't there be some validation here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Added validateInPlaceManagement which now rejects conflicting Replace config when upgrade type is InPlace.


AI-assisted response via Claude Code

case hyperv1.UpgradeTypeReplace:
// validated below
default:
return fmt.Errorf("unsupported upgrade type %q, supported values are %q and %q",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The jira ticket acceptance criteria says: If an invalid upgrade type is provided, set an appropriate NodePool condition (e.g., ValidConfiguration or similar) with a clear message indicating the unsupported value

Where is that done?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The condition is set in conditions.go:221-243 via updateManagementEnabledCondition. When validateManagement returns an error, it sets the UpdateManagementEnabled condition to False with reason ValidationFailed and the error message as the condition message. The error is not returned to the reconciler (only logged) since it's a user input problem that reconciling won't solve.


AI-assisted response via Claude Code

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@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.

Caution

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

⚠️ Outside diff range comments (1)
hypershift-operator/controllers/nodepool/nodepool_controller.go (1)

657-676: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require MaxUnavailable and MaxSurge for RollingUpdate.
replace defaults only when the whole field is omitted. An explicit rollingUpdate: {} still passes this check, but capi.go later reads MaxUnavailable and MaxSurge directly. Validate both inner fields here or add schema defaults/required markers.

🤖 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 `@hypershift-operator/controllers/nodepool/nodepool_controller.go` around lines
657 - 676, Update validateReplaceManagement to validate both MaxUnavailable and
MaxSurge within Replace.RollingUpdate when the strategy is
UpgradeStrategyRollingUpdate, rejecting configurations where either field is
unset; preserve the existing validation for missing Replace and unsupported
strategies.
🤖 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.

Outside diff comments:
In `@hypershift-operator/controllers/nodepool/nodepool_controller.go`:
- Around line 657-676: Update validateReplaceManagement to validate both
MaxUnavailable and MaxSurge within Replace.RollingUpdate when the strategy is
UpgradeStrategyRollingUpdate, rejecting configurations where either field is
unset; preserve the existing validation for missing Replace and unsupported
strategies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 52e64e8e-9de5-4fab-82df-c71861f18b54

📥 Commits

Reviewing files that changed from the base of the PR and between 8ea96d7 and e15a163.

⛔ Files ignored due to path filters (1)
  • cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/stable.nodepools.management.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
📒 Files selected for processing (2)
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/nodepool_controller_test.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Test cases are not using the unit test format, given-when-it. Please review CLAUDE.md for more details.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Renamed all test cases to use "When , it should " format per the code-formatting convention.


AI-assisted response via Claude Code

nodePool.Spec.Management.Replace == nil {
return fmt.Errorf("this is unsupported. %q upgrade type and a strategy: %q or %q are required",
func validateReplaceManagement(nodePool *hyperv1.NodePool) error {
if nodePool.Spec.Management.Replace == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this really the only validation we can do here? Did you consider what else should be checked?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Added the symmetric cross-field check: validateReplaceManagement now also rejects InPlace config when upgrade type is Replace, mirroring the existing check in validateInPlaceManagement that rejects Replace config with InPlace type. Beyond cross-field consistency, the InPlaceUpgrade struct only has the optional MaxUnavailable field (defaults to 1), and value-level validation for IntOrString is handled at the API layer. The CEL rule on NodePoolManagement (!has(self.inPlace) || self.upgradeType == 'InPlace') covers the API-level guard for InPlace config, so the backend validation here completes the picture by catching the reverse case for Replace config.


AI-assisted response via Claude Code

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

nodePool.Spec.Management.Replace == nil {
return fmt.Errorf("this is unsupported. %q upgrade type and a strategy: %q or %q are required",
func validateInPlaceManagement(nodePool *hyperv1.NodePool) error {
if nodePool.Spec.Management.Replace != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this really the only validation we can do here? Did you consider what else should be checked?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Added deeper validation in validateReplaceManagement:

  1. RollingUpdate + nil MaxUnavailable/MaxSurge: if RollingUpdate is set but MaxUnavailable or MaxSurge is nil, it now rejects — prevents nil pointers from reaching CAPI (capi.go:482-484).
  2. OnDelete + RollingUpdate conflict: if strategy is OnDelete but RollingUpdate config is set, it now rejects the conflicting configuration.

Refactored the strategy validation into a switch to cleanly separate RollingUpdate vs OnDelete vs unknown paths.

For validateInPlaceManagement: InPlaceUpgrade has only MaxUnavailable (optional, defaults to 1 in getInPlaceMaxUnavailable at capi.go:1120-1135). The conflict check (no Replace config) is the appropriate backend validation — the downstream consumer handles nil/zero values gracefully.


AI-assisted response via Claude Code

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@@ -635,29 +635,53 @@ func (r *NodePoolReconciler) deleteNodePoolSecrets(ctx context.Context, nodePool
// validateManagement does additional backend validation. API validation/default should
// prevent this from ever fail.
func validateManagement(nodePool *hyperv1.NodePool) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@bryan-cox I don't think we need any new server side validation https://redhat.atlassian.net/browse/CNTRLPLANE-3694.
If anything we should translate this server side validation into CEL and add envtest cases

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Reverted the new server-side validation (removed validateInPlaceManagement and validateReplaceManagement sub-functions) and restored validateManagement to its original behavior. The existing CEL rules already cover the InPlace validation: Enum=Replace;InPlace on UpgradeType, self == oldSelf for immutability, and !has(self.inPlace) || self.upgradeType == 'InPlace' for cross-field validation. Added envtest cases covering these existing CEL rules plus the rollingUpdate/strategy cross-field rule on ReplaceUpgrade.


AI-assisted response via Claude Code

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

Remove the TODO comment from validateManagement (the InPlace upgrade type
is already validated by existing CEL rules: Enum on UpgradeType, XValidation
on NodePoolManagement for inPlace/upgradeType cross-field check, and
immutability on UpgradeType). Rename unit test cases to "When..., it should..."
format. Add envtest cases covering the existing CEL rules for UpgradeType
enum validation, immutability, inPlace cross-field validation, and
rollingUpdate/strategy cross-field validation.

Ref: CNTRLPLANE-3694

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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.

Caution

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

⚠️ Outside diff range comments (1)
hypershift-operator/controllers/nodepool/nodepool_controller_test.go (1)

191-273: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add explicit empty and InPlace cases.

This table exercises invalid and Replace configurations, but not the empty UpgradeType path or the successful InPlace branch in validateManagement. Add both cases so the new validation contract is protected against regressions.

🤖 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 `@hypershift-operator/controllers/nodepool/nodepool_controller_test.go` around
lines 191 - 273, Add table-driven cases covering an empty UpgradeType and a
valid InPlace upgrade in the validation tests around validateManagement.
Configure the empty UpgradeType case with the expected error result, and
configure the InPlace case with valid management settings and no Replace
configuration so it expects success.
🤖 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.

Outside diff comments:
In `@hypershift-operator/controllers/nodepool/nodepool_controller_test.go`:
- Around line 191-273: Add table-driven cases covering an empty UpgradeType and
a valid InPlace upgrade in the validation tests around validateManagement.
Configure the empty UpgradeType case with the expected error result, and
configure the InPlace case with valid management settings and no Replace
configuration so it expects success.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: c4cbb96d-e7d4-408a-84ff-c4971f9695b2

📥 Commits

Reviewing files that changed from the base of the PR and between 27c274f and 07fd764.

⛔ Files ignored due to path filters (1)
  • cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/stable.nodepools.management.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
📒 Files selected for processing (2)
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/nodepool_controller_test.go
💤 Files with no reviewable changes (1)
  • hypershift-operator/controllers/nodepool/nodepool_controller.go

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@openshift-ci

openshift-ci Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@hypershift-jira-solve-ci[bot]: all tests passed!

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.

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/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release 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