Skip to content

CNTRLPLANE-3553: feat(nodepool): thread streamName through all platform callers#8834

Closed
sdminonne wants to merge 2 commits into
openshift:mainfrom
sdminonne:CNTRLPLANE-3553-thread-streamName-all-platforms
Closed

CNTRLPLANE-3553: feat(nodepool): thread streamName through all platform callers#8834
sdminonne wants to merge 2 commits into
openshift:mainfrom
sdminonne:CNTRLPLANE-3553-thread-streamName-all-platforms

Conversation

@sdminonne

@sdminonne sdminonne commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Wire the resolved RHEL stream name (streamName) through Azure, PowerVS, OpenStack, and KubeVirt platform resolvers so each platform selects boot image metadata from the correct OS stream (rhel-9 or rhel-10) instead of always using the legacy default StreamMetadata.

  • CAPI paths use c.resolvedRHELStreamForBootImage from ConfigGenerator; condition-setter paths resolve via getRHELStreamForBootImage().
  • All functions that previously accessed releaseImage.StreamMetadata directly now go through releaseImage.StreamForName(streamName).

PR Dependency Graph

#8675 (merged) ─► #8719 (merged) ─► #8730 (open) ─► This PR
                                        │
#8669 (merged) ─► #8699 (merged) ─► #8709 (open) ──┘
                                        │
                                    #8792 (open)
PR Title Status Relation
#8675 API: add OSImageStream field to NodePool merged grandparent
#8719 API: add GetRHELStream + stream constants merged parent
#8730 Wire osImageStream into NodePool controller (hash, token, status, validation) open direct parent
#8709 feat(nodepool): use stream metadata for platform image resolution open sibling
#8792 feat(nodepool): validate osImageStream in webhook open sibling
#8832 feat(nodepool): detect usesRunc from ContainerRuntimeConfig open (draft) sibling
This PR feat(nodepool): thread streamName through all platform callers open (draft) this PR

Platforms Updated

Platform Functions changed
Azure defaultAzureNodePoolImage, getAzureMarketplaceMetadata
PowerVS getPowerVSImage, ibmPowerVSMachineTemplateSpec, setPowerVSconditions
OpenStack OpenstackDefaultImage, OpenStackReleaseImage, MachineTemplateSpec, ReconcileOpenStackImageSpec, PrefixedClusterImageName, setOpenStackConditions, reconcileOpenStackImageCR
KubeVirt defaultImage, GetImage, MachineTemplateSpec, setKubevirtConditions

Test plan

  • All existing nodepool package tests pass with "" (empty/default) stream name
  • Verify multi-stream resolution with OSStreams populated on ReleaseImage (integration)
  • Run e2e-aws-upgrade-hypershift-operator to validate no spurious rollouts

Ref: CNTRLPLANE-3553

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Boot image selection now respects the configured RHEL stream across Azure, KubeVirt, OpenStack, and PowerVS platforms.
    • Multi-stream releases resolve the correct architecture-specific image metadata, including stream-specific versions and SKUs.
    • Older platform releases now default to the RHEL 9 stream when no stream is specified.
  • Bug Fixes

    • Invalid or unavailable stream metadata now produces a clear image resolution error instead of silently falling back.

@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 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 Jun 25, 2026
@openshift-ci

openshift-ci Bot commented Jun 25, 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

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 74de4e78-b167-4ab0-b316-91cc52831137

📥 Commits

Reviewing files that changed from the base of the PR and between 55f1400 and 5c532dc.

📒 Files selected for processing (13)
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
  • hypershift-operator/controllers/nodepool/config.go
  • hypershift-operator/controllers/nodepool/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go
  • hypershift-operator/controllers/nodepool/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack_test.go
  • hypershift-operator/controllers/nodepool/powervs.go
  • hypershift-operator/controllers/nodepool/powervs_test.go
  • hypershift-operator/controllers/nodepool/stream.go
  • hypershift-operator/controllers/nodepool/stream_test.go
✅ Files skipped from review due to trivial changes (2)
  • hypershift-operator/controllers/nodepool/powervs_test.go
  • hypershift-operator/controllers/nodepool/config.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/powervs.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go
  • hypershift-operator/controllers/nodepool/openstack/openstack.go
  • hypershift-operator/controllers/nodepool/stream.go

📝 Walkthrough

Walkthrough

Node pool image resolution now accepts an explicit RHEL stream across Azure, KubeVirt, OpenStack, and PowerVS paths. Platform helpers resolve architecture and release metadata through StreamForName, and machine template callers pass the resolved boot-image stream. OpenStack reconciliation also propagates the stream into image resource generation. Tests add multi-stream cases, while stream defaults and API/release constant consistency are validated.

Sequence Diagram(s)

sequenceDiagram
  participant NodePoolController
  participant PlatformHelper
  participant ReleaseImage
  participant MachineTemplate
  NodePoolController->>PlatformHelper: pass resolved RHEL stream
  PlatformHelper->>ReleaseImage: resolve stream metadata
  ReleaseImage-->>PlatformHelper: architecture and release data
  PlatformHelper->>MachineTemplate: construct stream-specific image spec
  MachineTemplate-->>NodePoolController: return platform template
Loading
🚥 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: threading streamName through nodepool platform callers.
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 tests use static table-driven names; no fmt.Sprintf/concatenated titles or dynamic identifiers were found in the touched files.
Test Structure And Quality ✅ Passed Touched tests are table-driven unit tests, not Ginkgo specs; no cluster ops, cleanup gaps, or waits were introduced, and assertions stay localized per case.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only threads streamName through image-resolution code and tests; it adds no replicas, affinity, topology spread, nodeSelectors, or PDB changes.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the touched files are unit tests and I found no IPv4-only or external-connectivity assumptions relevant to this check.
No-Weak-Crypto ✅ Passed No edited code imports or uses weak crypto; the only matches are test strings like DES_ID and a benign image-hash assertion.
Container-Privileges ✅ Passed Latest commit touches only 9 Go files; no YAML/JSON or Dockerfile manifests, and no touched file contains privileged/hostPID/hostNetwork/hostIPC/allowPrivilegeEscalation/SYS_ADMIN.
No-Sensitive-Data-In-Logs ✅ Passed No new runtime logs expose secrets/PII; the only added log is a generic controller-runtime result, and other changes only affect error/status text.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci openshift-ci Bot added area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform area/platform/gcp PR/issue for GCP (GCPPlatform) platform area/platform/kubevirt PR/issue for KubeVirt (KubevirtPlatform) platform area/platform/openstack PR/issue for OpenStack (OpenStackPlatform) platform area/platform/powervs PR/issue for PowerVS (PowerVSPlatform) platform and removed do-not-merge/needs-area labels Jun 25, 2026
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 38.88889% with 44 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.76%. Comparing base (94eb4f7) to head (5c532dc).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ypershift-operator/controllers/nodepool/powervs.go 23.07% 9 Missing and 1 partial ⚠️
...ershift-operator/controllers/nodepool/openstack.go 0.00% 9 Missing ⚠️
...operator/controllers/nodepool/kubevirt/kubevirt.go 36.36% 6 Missing and 1 partial ⚠️
...erator/controllers/nodepool/openstack/openstack.go 63.15% 5 Missing and 2 partials ⚠️
hypershift-operator/controllers/nodepool/stream.go 40.00% 4 Missing and 2 partials ⚠️
hypershift-operator/controllers/nodepool/azure.go 62.50% 1 Missing and 2 partials ⚠️
...pershift-operator/controllers/nodepool/kubevirt.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8834      +/-   ##
==========================================
+ Coverage   43.72%   43.76%   +0.03%     
==========================================
  Files         772      772              
  Lines       95933    95966      +33     
==========================================
+ Hits        41944    41995      +51     
+ Misses      51077    51053      -24     
- Partials     2912     2918       +6     
Files with missing lines Coverage Δ
hypershift-operator/controllers/nodepool/config.go 84.23% <ø> (ø)
...pershift-operator/controllers/nodepool/kubevirt.go 0.00% <0.00%> (ø)
hypershift-operator/controllers/nodepool/azure.go 88.46% <62.50%> (-0.06%) ⬇️
hypershift-operator/controllers/nodepool/stream.go 82.35% <40.00%> (-17.65%) ⬇️
...operator/controllers/nodepool/kubevirt/kubevirt.go 69.01% <36.36%> (-0.45%) ⬇️
...erator/controllers/nodepool/openstack/openstack.go 73.07% <63.15%> (-3.91%) ⬇️
...ershift-operator/controllers/nodepool/openstack.go 0.00% <0.00%> (ø)
...ypershift-operator/controllers/nodepool/powervs.go 5.66% <23.07%> (-0.84%) ⬇️

... and 3 files with indirect coverage changes

Flag Coverage Δ
cmd-support 37.38% <ø> (+0.15%) ⬆️
cpo-hostedcontrolplane 45.91% <ø> (ø)
cpo-other 45.11% <ø> (ø)
hypershift-operator 53.99% <38.88%> (-0.05%) ⬇️
other 32.08% <ø> (ø)

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.

🧹 Nitpick comments (2)
hypershift-operator/controllers/nodepool/openstack/openstack_test.go (1)

293-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a named-stream test case.

All updated call sites pass "", which routes through the legacy StreamMetadata fallback in StreamForName. The new OSStreams/named-stream branch (e.g. "rhel-9"/"rhel-10") — the actual purpose of this PR — isn't exercised here. A table entry that populates OSStreams and passes a concrete stream name would close that gap.

Also applies to: 358-358, 472-472

🤖 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/openstack/openstack_test.go` at line
293, Add a table-driven named-stream test case for OpenstackDefaultImage so the
new OSStreams path is covered instead of only the legacy StreamMetadata
fallback. Update the relevant test cases in openstack_test.go to populate
OSStreams and pass a concrete stream name like “rhel-9” or “rhel-10” through
OpenstackDefaultImage / StreamForName, and keep the existing empty-string cases
as fallback coverage. Ensure the added case exercises the branch introduced by
this PR at the updated call sites.
hypershift-operator/controllers/nodepool/config.go (1)

98-121: 📐 Maintainability & Code Quality | 🔵 Trivial

Factor out the default-stream derivation.

config.go re-parses the release version and recomputes the default RHEL stream with GetRHELStream("", version, false), duplicating the same version-aware logic used by getRHELStream. A shared helper, or returning the default alongside the resolved stream, would keep hash normalization and stream selection aligned if usesRunc handling changes later.

🤖 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/config.go` around lines 98 - 121,
The config hash normalization in config.go duplicates the version-based default
RHEL stream logic by re-parsing the release version and calling
GetRHELStream("", version, false) separately from getRHELStream. Refactor this
so the default stream is derived through a shared helper or returned together
with the resolved stream from getRHELStream, and update the normalization logic
in the same Config function to use that shared result so hash behavior stays
aligned if usesRunc handling changes.
🤖 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.

Nitpick comments:
In `@hypershift-operator/controllers/nodepool/config.go`:
- Around line 98-121: The config hash normalization in config.go duplicates the
version-based default RHEL stream logic by re-parsing the release version and
calling GetRHELStream("", version, false) separately from getRHELStream.
Refactor this so the default stream is derived through a shared helper or
returned together with the resolved stream from getRHELStream, and update the
normalization logic in the same Config function to use that shared result so
hash behavior stays aligned if usesRunc handling changes.

In `@hypershift-operator/controllers/nodepool/openstack/openstack_test.go`:
- Line 293: Add a table-driven named-stream test case for OpenstackDefaultImage
so the new OSStreams path is covered instead of only the legacy StreamMetadata
fallback. Update the relevant test cases in openstack_test.go to populate
OSStreams and pass a concrete stream name like “rhel-9” or “rhel-10” through
OpenstackDefaultImage / StreamForName, and keep the existing empty-string cases
as fallback coverage. Ensure the added case exercises the branch introduced by
this PR at the updated call sites.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 28adccd3-0671-4206-ac2d-917bb3007eb9

📥 Commits

Reviewing files that changed from the base of the PR and between 4c582e0 and ee9c4de.

📒 Files selected for processing (27)
  • hypershift-operator/controllers/nodepool/aws.go
  • hypershift-operator/controllers/nodepool/aws_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
  • hypershift-operator/controllers/nodepool/capi_test.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/config.go
  • hypershift-operator/controllers/nodepool/config_test.go
  • hypershift-operator/controllers/nodepool/gcp.go
  • hypershift-operator/controllers/nodepool/gcp_test.go
  • hypershift-operator/controllers/nodepool/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack_test.go
  • hypershift-operator/controllers/nodepool/osstream.go
  • hypershift-operator/controllers/nodepool/osstream_test.go
  • hypershift-operator/controllers/nodepool/powervs.go
  • hypershift-operator/controllers/nodepool/powervs_test.go
  • hypershift-operator/controllers/nodepool/stream.go
  • hypershift-operator/controllers/nodepool/stream_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/token_test.go
  • hypershift-operator/controllers/nodepool/version.go
  • hypershift-operator/controllers/nodepool/version_test.go

@sdminonne sdminonne changed the title feat(nodepool): thread streamName through all platform callers CNTRLPLANE-3553: feat(nodepool): thread streamName through all platform callers Jun 25, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 25, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 25, 2026

Copy link
Copy Markdown

@sdminonne: This pull request references CNTRLPLANE-3553 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:

Summary

  • Wire the resolved RHEL stream name (streamName) through Azure, PowerVS, OpenStack, and KubeVirt platform resolvers so each platform selects boot image metadata from the correct OS stream (rhel-9 or rhel-10) instead of always using the legacy default StreamMetadata.
  • CAPI paths use c.resolvedRHELStream from ConfigGenerator; condition-setter paths resolve via getRHELStream().
  • All functions that previously accessed releaseImage.StreamMetadata directly now go through releaseImage.StreamForName(streamName).

PR Dependency Graph

#8675 (merged) ─► #8719 (merged) ─► #8730 (open) ─► This PR
                                       │
#8669 (merged) ─► #8699 (merged) ─► #8709 (open) ──┘ (merge conflict resolution needed)
                                       │
                                   #8792 (open)
PR Title Status Relation
#8675 API: add OSImageStream field to NodePool merged grandparent
#8719 API: add GetRHELStream + stream constants merged parent
#8730 feat(nodepool): integrate osImageStream into NodePool controller open direct parent
#8709 feat(nodepool): use stream metadata for platform image resolution open sibling (merge conflict with #8730)
#8792 feat(nodepool): validate osImageStream in webhook open sibling
#8832 feat(nodepool): detect usesRunc from ContainerRuntimeConfig open (draft) sibling (PR A)
This PR feat(nodepool): thread streamName through all platform callers open (draft) PR B

Platforms Updated

Platform Functions changed
Azure defaultAzureNodePoolImage, getAzureMarketplaceMetadata
PowerVS getPowerVSImage, ibmPowerVSMachineTemplateSpec, setPowerVSconditions
OpenStack OpenstackDefaultImage, OpenStackReleaseImage, MachineTemplateSpec, ReconcileOpenStackImageSpec, PrefixedClusterImageName, setOpenStackConditions, reconcileOpenStackImageCR
KubeVirt defaultImage, GetImage, MachineTemplateSpec, setKubevirtConditions

Test plan

  • All existing nodepool package tests pass with "" (empty/default) stream name
  • Verify multi-stream resolution with OSStreams populated on ReleaseImage (integration)
  • Run e2e-aws-upgrade-hypershift-operator to validate no spurious rollouts

Ref: CNTRLPLANE-3553

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

  • Node pools can now detect and report the active OS image stream from running machines.

  • Image selection is now stream-aware across supported platforms, improving alignment with the chosen RHEL stream.

  • Bug Fixes

  • Improved validation so invalid or mismatched OS image stream settings fail fast with clearer errors.

  • Fixed default image and rollout handling to keep updates stable when the default stream is selected, while still triggering changes for non-default streams.

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 added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 28, 2026
@sdminonne
sdminonne force-pushed the CNTRLPLANE-3553-thread-streamName-all-platforms branch from ee9c4de to 55f1400 Compare July 4, 2026 10:35
@openshift-ci openshift-ci Bot added area/api Indicates the PR includes changes for the API area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Jul 4, 2026
@sdminonne
sdminonne marked this pull request as ready for review July 6, 2026 10:02
@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 6, 2026
@openshift-ci
openshift-ci Bot requested review from bryan-cox and cblecker July 6, 2026 10:03

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

Dropped some comments. Thanks!

ami, err := defaultNodePoolAMI(hcluster.Spec.Platform.AWS.Region, nodePool.Spec.Arch, "", releaseImage)
// Default behavior for Linux/RHCOS AMIs.
// TODO(CNTRLPLANE-3553): hardcode to rhel-9 until the MCO can install
// rhel-10 OS images. Use getRHELStreamForBootImage once MCO support lands.

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.

I notice setAWSConditions hardcodes StreamRHEL9 here, but the other platform condition-setters (setKubevirtConditions, setOpenStackConditions, setPowerVSconditions) all call getRHELStreamForBootImage() for version-aware resolution. Is there a reason AWS is different? If the intent is to hardcode everywhere until MCO rhel-10 lands, the others should match. If the others should be version-aware, AWS should too.

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.

No there's no reason it's the status of MCO that enforce us to hardcode to rhel-9. Need to be done across all the platforms

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.

Agreed with this observation. Worth noting that the inconsistency goes beyond cross-platform: within KubeVirt, OpenStack, and PowerVS themselves, the condition-setter path calls getRHELStreamForBootImage() (which resolves to rhel-10 for OCP 5.x) while the CAPI machine-template path uses c.resolvedRHELStreamForBootImage (hardcoded to rhel-9). So the condition can validate against rhel-10 metadata while machines actually boot with rhel-9. Hardcoding StreamRHEL9 in all condition-setters (matching AWS) would keep both paths consistent per-platform until MCO rhel-10 support lands.

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.

Good catch. Agreed with both your and @cblecker's observation — the inconsistency between condition-setter and CAPI machine-template paths within the same platform was the real
issue. Fixed in 5c532dc: all condition-setters (setKubevirtConditions, setOpenStackConditions, setPowerVSconditions) now hardcode StreamRHEL9 matching the AWS pattern, with the same
TODO(CNTRLPLANE-3553) marker so they all get unwound together once MCO rhel-10 support lands.

//
// This field is intentionally outside rolloutConfig because it does not
// participate in the config hash that drives rollouts.
resolvedRHELStreamForBootImage string

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.

Having two stream-related fields (resolvedRHELStreamForBootImage on ConfigGenerator and rhelStream on rolloutConfig) could trip up future contributors. The comment here explains the separation well, but rhelStream in rolloutConfig doesn't point back. A short "see also resolvedRHELStreamForBootImage" there would help.

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 a // See also ConfigGenerator.resolvedRHELStreamForBootImage comment on the rhelStream field in rolloutConfig.


const (
// OSImageStreamRHEL9 is the OS image stream name for RHEL 9.
// This value must match support/releaseinfo.StreamRHEL9.

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.

These "must match" cross-reference comments are a manual invariant that'll rot. Could we add a compile-time check instead? Something like:

func TestStreamConstantsMatch(t *testing.T) {
    assert.Equal(t, hyperv1.OSImageStreamRHEL9, releaseinfo.StreamRHEL9)
    assert.Equal(t, hyperv1.OSImageStreamRHEL10, releaseinfo.StreamRHEL10)
}

That way the comment isn't the only thing keeping them in sync.

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.

Added an init() assertion in stream.go that panics immediately at startup if hyperv1.OSImageStreamRHEL9 != releaseinfo.StreamRHEL9 (or RHEL10).

Go doesn't support true compile-time
string assertions, but init() runs before any controller code and will crash the binary if the constants diverge. Also added TestStreamConstantsMatch as a belt-and-suspenders check
in CI.


if !isOCP5Plus {
return "", nil
return StreamRHEL9, nil

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.

This changes the return for OCP < 5.0 from "" (legacy single-stream) to StreamRHEL9. For pre-5.0 payloads without OSStreams the fallback in StreamForName should handle it, but have you verified this doesn't break callers in the parent PRs (#8730, #8709)? They were written when this returned "".

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.

This is safe for the parent PRs. StreamForName("rhel-9") falls back to StreamMetadata for pre-5.0 payloads where OSStreams is nil, so the resolution path is the same as before. The
only difference is the name passed is "rhel-9" instead of "", and StreamForName handles both — empty string returns StreamMetadata directly, and a named string on a nil OSStreams
also falls back to StreamMetadata. Verified against #8730 and #8709.

g := NewGomegaWithT(t)

err := defaultAzureNodePoolImage(tc.nodePool, tc.releaseImage)
err := defaultAzureNodePoolImage(tc.nodePool, tc.releaseImage, "")

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.

The source changes in azure.go touch 3 functions (+8/-7), but the test only passes "" as stream. Could we add at least one case with a named stream and multi-stream ReleaseImage? The AWS tests do this well — same coverage here would be great.

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.

Added a test case "When named stream is used with multi-stream ReleaseImage it should resolve marketplace from the named stream" that populates OSStreams with a "rhel-9" entry and
passes it as streamName.

testRI = ri
}
img, digest, err := defaultImage(tt.arch, testRI)
img, digest, err := defaultImage(tt.arch, testRI, "")

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.

Same — defaultImage, GetImage, and MachineTemplateSpec all gained a streamName parameter but tests only pass "". One test with a named stream + OSStreams would confirm the wiring works for KubeVirt.

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.

Added "When named stream is used with multi-stream ReleaseImage it should resolve from the named stream" with OSStreams and streamName: "rhel-9".

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
url, hash, err := OpenstackDefaultImage(tc.releaseImage)
url, hash, err := OpenstackDefaultImage(tc.releaseImage, "")

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.

All five OpenStack functions only pass "" for the new parameter. Given OpenStack had the most changes (+19/-11), at least one happy-path test with a named stream would be reassuring.

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.

Added named-stream test cases to both TestOpenstackDefaultImage and TestOpenStackReleaseImage.

expectedCondType: string(hyperv1.NodePoolValidPlatformImageType),
expectedCondValue: corev1.ConditionFalse,
},
// TODO(CNTRLPLANE-3553): re-enable once getRHELStreamForBootImage is

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.

nit: Commented-out test code is easy to forget. Maybe worth adding a // TODO(CNTRLPLANE-3553): re-enable once hardcoding is removed on the commented block so a future grep picks it up — otherwise this will silently stay dead.

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.

Already has the grep-friendly TODO(CNTRLPLANE-3553) marker — left as-is.

sdminonne and others added 2 commits July 10, 2026 16:53
Wire the resolved RHEL stream name through Azure, PowerVS, OpenStack,
and KubeVirt platform resolvers so that each platform selects boot image
metadata from the correct OS stream (rhel-9 or rhel-10) instead of
always using the legacy default.

CAPI paths use c.resolvedRHELStream from ConfigGenerator. Condition
setter paths resolve the stream via getRHELStream(). All functions that
previously accessed releaseImage.StreamMetadata directly now go through
releaseImage.StreamForName(streamName).

Ref: CNTRLPLANE-3553

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Hardcode StreamRHEL9 in setKubevirtConditions, setOpenStackConditions,
  and setPowerVSconditions to match the AWS pattern, keeping condition-
  setter and CAPI machine-template paths consistent until MCO rhel-10
  support lands.
- Add init() assertion and TestStreamConstantsMatch to enforce that API
  constants (hyperv1.OSImageStreamRHEL9/10) match releaseinfo constants
  (releaseinfo.StreamRHEL9/10).
- Add cross-reference comment on rolloutConfig.rhelStream pointing to
  ConfigGenerator.resolvedRHELStreamForBootImage.
- Add named-stream test cases for Azure, KubeVirt, and OpenStack that
  exercise the OSStreams/StreamForName path with a concrete stream name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 10, 2026
@sdminonne
sdminonne force-pushed the CNTRLPLANE-3553-thread-streamName-all-platforms branch from 55f1400 to 5c532dc Compare July 10, 2026 16:20
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 10, 2026
@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: sdminonne
Once this PR has been reviewed and has the lgtm label, please assign awels, enxebre, nunnatsa 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 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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

@hypershift-jira-solve-ci

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

Copy link
Copy Markdown
Contributor

I now have a complete picture. Let me produce the final report.

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

codecov/patch — 38.88% of diff hit (target 43.72%)
44 lines in the patch are missing test coverage.

Summary

This is a code coverage threshold failure, not a test execution failure. PR #8834 threads a streamName parameter through platform-specific boot image resolution functions across Azure, KubeVirt, OpenStack, and PowerVS nodepool controllers. While the PR adds comprehensive unit tests for the pure-logic library functions, 44 of the ~72 changed non-test source lines lack coverage. The majority of these uncovered lines (~22) reside in NodePoolReconciler condition-setter methods and CAPI struct template-builder methods that are structurally untestable via unit tests — they require a full controller runtime with a Kubernetes API client. This pattern is consistent with how all existing reconciler methods in the codebase are tested (via integration/e2e tests, not unit tests). The remaining uncovered lines are error-return branches for StreamForName() calls (~14 lines) and intentionally-unreachable panic() guardrails in init() (~6 lines).

Root Cause

The coverage shortfall is structural, not a missing-test oversight. The 44 uncovered lines fall into three categories:

1. Reconciler/CAPI methods (0% file-level coverage, ~22 lines): These are methods on NodePoolReconciler or CAPI structs that interact with the Kubernetes API client and require a fully initialized controller runtime. They are only exercisable through integration/e2e tests. Affected methods:

  • openstack.go: setOpenStackConditions(), reconcileOpenStackImageCR(), openstackMachineTemplate()9 lines at 0% patch coverage
  • powervs.go: setPowerVSconditions(), ibmPowerVSMachineTemplate(), getPowerVSImage()9 missing + 1 partial at 23.07%
  • kubevirt.go: setKubevirtConditions(), kubevirtMachineTemplate()2 lines at 0%
  • azure.go: azureMachineTemplate()1 missing + 2 partials at 62.50%

2. Error-path branches in library functions (~16 lines): The new StreamForName() calls in kubevirt/kubevirt.go and openstack/openstack.go each add an if err != nil { return ..., fmt.Errorf(...) } block. Some of these error paths are not exercised by the existing test cases (which pass valid stream names or empty strings that resolve to StreamMetadata fallback). Affected:

  • kubevirt/kubevirt.go: 6 missing + 1 partial at 36.36%
  • openstack/openstack.go: 5 missing + 2 partials at 63.15%

3. Intentionally-unreachable panic guardrails (~6 lines): The init() function in stream.go asserts that hyperv1.OSImageStreamRHEL9 == releaseinfo.StreamRHEL9 (and RHEL10 equivalent). Since these constants currently match, the panic() branches are never executed. These exist as compile-time safety nets, not runtime logic.

  • stream.go: 4 missing + 2 partials at 40.00%

The pure-logic functions that the PR actually modifies — defaultAzureNodePoolImage(), getAzureMarketplaceMetadata(), kubevirt.defaultImage(), openstack.OpenstackDefaultImage(), openstack.OpenStackReleaseImage(), openstack.MachineTemplateSpec()are all unit-tested, including new multi-stream test cases added in this PR.

Recommendations
  1. Add error-path test cases for StreamForName() failures — The quickest coverage gain (~10-14 lines) would be adding test cases where streamName is set to a non-existent stream name (e.g., "nonexistent-stream") to exercise the new if err != nil branches in:

    • kubevirt/kubevirt_test.goTestDefaultImage — add a case with streamName: "bad-stream" and expectedError: true
    • openstack/openstack_test.goTestOpenstackDefaultImage and TestOpenStackReleaseImage — add similar error cases
    • powervs_test.goTestGetPowerVSImage — add a case with invalid stream name
  2. Consider adding stream.go init() to the codecov ignore list — The panic() guardrails are structural assertions, not testable logic. Add hypershift-operator/controllers/nodepool/stream.go to the ignore: section of codecov.yml if the init-only assertions remain the only executable code in this file, or accept the coverage gap since the companion TestStreamConstantsMatch test in stream_test.go already validates the same invariant.

  3. Reconciler method coverage is a known gap — The openstack.go, kubevirt.go, and powervs.go reconciler files already have 0% or near-0% overall file coverage. The new lines follow the same pattern as all existing code in these files. No action needed — this is a structural limitation of the codebase's test architecture.

  4. To pass the threshold with minimal changes: Adding ~6–8 error-path unit tests across the _test.go files for StreamForName() failures would cover enough lines to push patch coverage from 38.89% above the 43.72% target.

Evidence
Evidence Detail
Check run conclusion failure — codecov/patch
Patch coverage 38.89% (28 of 72 lines covered)
Target threshold 43.72% (auto-derived from project coverage)
Coverage gap 4.84 percentage points below target
Total uncovered lines 44 lines across 7 files
Largest gap: openstack.go 0.00% patch coverage, 9 missing lines (all in reconciler methods)
Largest gap: powervs.go 23.07% patch coverage, 9 missing + 1 partial (reconciler + getPowerVSImage)
kubevirt/kubevirt.go 36.36% patch coverage, 6 missing + 1 partial (error paths in defaultImage, GetImage, MachineTemplateSpec)
openstack/openstack.go 63.15% patch coverage, 5 missing + 2 partials (error paths in OpenstackDefaultImage, OpenStackReleaseImage)
stream.go 40.00% patch coverage, 4 missing + 2 partials (unreachable panic() in init())
azure.go 62.50% patch coverage, 1 missing + 2 partials (reconciler call in azureMachineTemplate)
kubevirt.go 0.00% patch coverage, 2 missing (reconciler calls in setKubevirtConditions, kubevirtMachineTemplate)
Project coverage trend 43.72% → 43.76% (+0.03%) — project coverage actually increased
codecov/project check ✅ Passed — overall project coverage is healthy
Codecov report Full report

@sdminonne

Copy link
Copy Markdown
Contributor Author

Closing in favor of #8832, which includes all commits from this PR plus runc detection.

@sdminonne sdminonne closed this Jul 12, 2026
sdminonne added a commit to sdminonne/hypershift that referenced this pull request Jul 13, 2026
- Hardcode StreamRHEL9 in setKubevirtConditions, setOpenStackConditions,
  and setPowerVSconditions to match the AWS pattern, keeping condition-
  setter and CAPI machine-template paths consistent until MCO rhel-10
  support lands.
- Add init() assertion and TestStreamConstantsMatch to enforce that API
  constants (hyperv1.OSImageStreamRHEL9/10) match releaseinfo constants
  (releaseinfo.StreamRHEL9/10).
- Add cross-reference comment on rolloutConfig.rhelStream pointing to
  ConfigGenerator.resolvedRHELStreamForBootImage.
- Add named-stream test cases for Azure, KubeVirt, and OpenStack that
  exercise the OSStreams/StreamForName path with a concrete stream name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api Indicates the PR includes changes for the API area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform area/platform/gcp PR/issue for GCP (GCPPlatform) platform area/platform/kubevirt PR/issue for KubeVirt (KubevirtPlatform) platform area/platform/openstack PR/issue for OpenStack (OpenStackPlatform) platform area/platform/powervs PR/issue for PowerVS (PowerVSPlatform) platform 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.

5 participants