Skip to content

CNTRLPLANE-3430: Make HA break-glass credentials test infraless - #8546

Closed
sdminonne wants to merge 1 commit into
openshift:mainfrom
sdminonne:CNTRLPLANE-3430/infraless-ha-break-glass-creds
Closed

CNTRLPLANE-3430: Make HA break-glass credentials test infraless#8546
sdminonne wants to merge 1 commit into
openshift:mainfrom
sdminonne:CNTRLPLANE-3430/infraless-ha-break-glass-creds

Conversation

@sdminonne

@sdminonne sdminonne commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Make TestCreateClusterHABreakGlassCredentials work on non-OpenShift management clusters (e.g. AKS) by setting explicit hostnames on Route-type services in BeforeApply, so the CPO can resolve infrastructure readiness without a default ingress domain.
  • Add early KAS pod scheduling failure detection (10-minute timeout) to fail fast under resource pressure instead of waiting for the full WaitForGuestClient timeout.
  • Skip the test on release versions < 4.23 via e2eutil.AtLeast(t, e2eutil.Version423) because older CPO versions lack the LoadBalancer.Hostname bypass fix (merged in PR OCPBUGS-87028: Fix KAS ReconcileServiceStatus to check LoadBalancer.Hostname before LB provisioning #8663).
  • Poll SSR in validateCertificateAuth instead of failing immediately on Unauthorized, allowing time for the control-plane-pki-operator to reconcile the break-glass signer CA into the client-ca trust bundle.

Depends on: #8663

Test plan

  • e2e-aks-4-22: test should be skipped (not failed) due to AtLeast(Version423)
  • e2e-aks (4.23+): test should pass as before
  • e2e-aws: test should pass (cloud LB provisions external endpoints)

🤖 Generated with Claude Code

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

openshift-ci-robot commented May 19, 2026

Copy link
Copy Markdown

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

Switches TestCreateClusterHABreakGlassCredentials from the cloud platform to NonePlatform.

This test validates control-plane PKI operations (CSR creation/approval/signing, certificate
revocation, per-pod KAS cert reload) with NodePoolReplicas=0 and an HA control plane (3 KAS
replicas). None of these operations require cloud infrastructure.

The management cluster's cloud controller manager provisions a real LoadBalancer for the
kube-apiserver service regardless of the hosted cluster's platform type, so the guest API
server remains reachable. This follows the same pattern used by TestHAEtcdChaos.

Removing cloud infrastructure provisioning reduces cost, execution time, and flakiness from
cloud API interactions — particularly under resource pressure when multiple hosted clusters
compete for management cluster capacity (as seen in PR #8338's e2e-aks job).

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3430

Special notes for your reviewer:

Single-line change. The NonePlatform pattern is already proven by TestHAEtcdChaos
(test/e2e/chaos_test.go:32-66) which runs an HA control plane on NonePlatform with
NodePoolReplicas=0 and successfully calls WaitForGuestClient.

The framework handles NonePlatform correctly for this test:

  • before(): skips ValidatePublicCluster — OK, the test has explicit waits
  • after(): skips EnsureAdmissionPolicies — OK (OCPBUGS-61291)
  • after(): skips ValidateHostedClusterConditions — OK for NonePlatform
  • RunTestControlPlanePKIOperatorBreakGlassCredentials: only skips KubeVirt and GCP

Checklist:

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

🤖 Generated with Claude Code

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 May 19, 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-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 area/testing Indicates the PR includes changes for e2e testing and removed do-not-merge/needs-area labels May 19, 2026
@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@coderabbitai

coderabbitai Bot commented May 19, 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

Adds support function netutil.RouteHost that chooses the best hostname from a Route (Spec.Host, then Status.Ingress[0].Host, then RouterCanonicalHostname). Reconciler code in OAuth and KAS now uses this helper to populate service status (setting host and port or returning an error when no host is found). Unit tests for RouteHost and additional route-resolution cases were added. A single e2e test was updated to pass hyperv1.NonePlatform and to wait for hosted control plane bootstrap before running break-glass checks.

Sequence Diagram(s)

sequenceDiagram
  participant Reconciler as ReconcileServiceStatus / ReconcileKonnectivityServerServiceStatus
  participant Route as routev1.Route
  participant RouteHost as netutil.RouteHost
  participant ServiceStatus as ServiceStatusUpdater
  Reconciler->>Route: read route resource
  Reconciler->>RouteHost: call RouteHost(route)
  RouteHost-->>Reconciler: host (or "")
  alt host != ""
    Reconciler->>ServiceStatus: set host and port (443 / RouteExternalPort)
  else host == ""
    Reconciler-->>ServiceStatus: set error message and return
  end
Loading

Suggested reviewers

  • muraee
  • Nirshal
🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Microshift Test Compatibility ⚠️ Warning Test uses HighlyAvailable control plane (3 KAS replicas) which violates MicroShift's single-node architecture. No protective [Skipped:MicroShift] or [apigroup:...] labels present. Add [Skipped:MicroShift] label to test name or guard with exutil.IsMicroShiftCluster() check, since HighlyAvailable control planes unsupported on single-node MicroShift.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: converting the HA break-glass credentials test to use NonePlatform instead of cloud platform.
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 All test names in the PR are stable and deterministic. TestRouteHost (7 cases) and new Route cases in TestReconcileServiceStatus (2 cases) use static, descriptive strings with no dynamic content.
Test Structure And Quality ✅ Passed Test meets quality standards: single responsibility, proper cleanup, explicit 20-min timeout, meaningful assertion messages, consistent patterns.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added. The PR modifies an existing test (TestCreateClusterHABreakGlassCredentials) and adds only a unit test (TestRouteHost), which is outside the scope of this check.
Topology-Aware Scheduling Compatibility ✅ Passed PR introduces no scheduling constraints. Changes are for route hostname resolution, infrastructure logging, and e2e test platform configuration only.
Ote Binary Stdout Contract ✅ Passed PR uses logr.Logger for all logging (framework-controlled). No fmt.Print/klog/stdout writes in process-level code. ValidateHostedClusterConditions is called within test bodies.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added. New unit tests use domain names, not IPv4 hardcoding, and have no external connectivity requirements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 41.44%. Comparing base (6a6d8c1) to head (c08ad8d).
⚠️ Report is 463 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8546      +/-   ##
==========================================
+ Coverage   40.68%   41.44%   +0.75%     
==========================================
  Files         755      756       +1     
  Lines       93368    93644     +276     
==========================================
+ Hits        37985    38807     +822     
+ Misses      52649    52114     -535     
+ Partials     2734     2723      -11     

see 56 files with indirect coverage changes

Flag Coverage Δ
cmd-support 34.87% <ø> (+0.17%) ⬆️
cpo-hostedcontrolplane 43.50% <ø> (+1.69%) ⬆️
cpo-other 42.79% <ø> (+1.40%) ⬆️
hypershift-operator 51.56% <ø> (+0.75%) ⬆️
other 31.64% <ø> (+0.02%) ⬆️

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.

@sdminonne

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2056721417461829632 | Cost: $4.631663149999999 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@cwbotbot

cwbotbot commented May 19, 2026

Copy link
Copy Markdown

Test Results

e2e-aws

e2e-aks

Failed Tests

Total failed tests: 15

  • TestNodePool
  • TestNodePool/HostedCluster0
  • TestNodePool/HostedCluster0/Main
  • TestNodePool/HostedCluster0/Main/TestMirrorConfigs
  • TestNodePool/HostedCluster0/Main/TestNTOMachineConfigAppliedInPlace

... and 10 more failed tests

@openshift-ci openshift-ci Bot added 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 labels May 19, 2026

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@control-plane-operator/controllers/hostedcontrolplane/oauth/service.go`:
- Around line 95-98: The code currently calls netutil.RouteHost(route) and then
dereferences route.ObjectMeta.CreationTimestamp.Time which can panic if route is
nil; add an explicit nil check for route before calling netutil.RouteHost (e.g.,
if route == nil set message to a clear "OAuth service route is missing" or
similar including the duration since creation and return), otherwise proceed to
compute routeHost and use route.ObjectMeta.CreationTimestamp.Time; ensure you
update the branch that currently handles empty routeHost to avoid dereferencing
route when route is nil.

In `@support/netutil/route_test.go`:
- Around line 472-559: Add a test case in TestRouteHost that asserts RouteHost
prefers a non-empty host from a later ingress when the first ingress is empty:
create a routev1.Route with Status.Ingress containing two entries where
Ingress[0] is empty and Ingress[1].Host (or RouterCanonicalHostname) is set, and
expect RouteHost(route) to return that second ingress host; ensure the new case
references RouteHost and the routev1.Route.Status.Ingress slice so it guards
multi-ingress fallback behavior.

In `@support/netutil/route.go`:
- Around line 163-169: The current logic only inspects route.Status.Ingress[0]
and returns its Host or RouterCanonicalHostname, which misses later ingress
entries; update the code that references route.Status.Ingress[0] to iterate over
all entries in route.Status.Ingress, checking each entry's Host first and then
RouterCanonicalHostname, and return the first non-empty value found (falling
back to empty/no-host if none exist); ensure you reference the same symbols
(route.Status.Ingress, Host, RouterCanonicalHostname) so the loop replaces the
existing index-0 checks.
🪄 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: 815d35bd-ddc0-4a15-b370-ec61f7400400

📥 Commits

Reviewing files that changed from the base of the PR and between 880b44f and 4a99314.

📒 Files selected for processing (5)
  • control-plane-operator/controllers/hostedcontrolplane/kas/service.go
  • control-plane-operator/controllers/hostedcontrolplane/oauth/service.go
  • control-plane-operator/controllers/hostedcontrolplane/oauth/service_test.go
  • support/netutil/route.go
  • support/netutil/route_test.go

Comment thread control-plane-operator/controllers/hostedcontrolplane/oauth/service.go Outdated
Comment thread support/netutil/route_test.go Outdated
Comment on lines +472 to +559
func TestRouteHost(t *testing.T) {
testCases := []struct {
name string
route *routev1.Route
expectedHost string
}{
{
name: "When route is nil, it should return empty string",
route: nil,
expectedHost: "",
},
{
name: "When Spec.Host is set, it should return Spec.Host",
route: &routev1.Route{
Spec: routev1.RouteSpec{
Host: "oauth.example.com",
},
},
expectedHost: "oauth.example.com",
},
{
name: "When Spec.Host is set and Status.Ingress is also populated, it should prefer Spec.Host",
route: &routev1.Route{
Spec: routev1.RouteSpec{
Host: "oauth.example.com",
},
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
Host: "ingress.example.com",
RouterCanonicalHostname: "canonical.example.com",
},
},
},
},
expectedHost: "oauth.example.com",
},
{
name: "When Spec.Host is empty and Status.Ingress[0].Host is set, it should return Ingress Host",
route: &routev1.Route{
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
Host: "ingress.example.com",
},
},
},
},
expectedHost: "ingress.example.com",
},
{
name: "When Spec.Host and Ingress Host are empty but RouterCanonicalHostname is set, it should return RouterCanonicalHostname",
route: &routev1.Route{
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
RouterCanonicalHostname: "router-canonical.example.com",
},
},
},
},
expectedHost: "router-canonical.example.com",
},
{
name: "When all host fields are empty, it should return empty string",
route: &routev1.Route{
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{},
},
},
},
expectedHost: "",
},
{
name: "When Status.Ingress slice is empty, it should return empty string",
route: &routev1.Route{},
expectedHost: "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
g.Expect(RouteHost(tc.route)).To(Equal(tc.expectedHost))
})
}
}

@coderabbitai coderabbitai Bot May 19, 2026

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a multi-ingress test case to lock expected fallback behavior.

TestRouteHost should include a case where Status.Ingress[0] is empty and Status.Ingress[1] has Host (or RouterCanonicalHostname). This guards against regressions in real multi-router status layouts.

🤖 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 `@support/netutil/route_test.go` around lines 472 - 559, Add a test case in
TestRouteHost that asserts RouteHost prefers a non-empty host from a later
ingress when the first ingress is empty: create a routev1.Route with
Status.Ingress containing two entries where Ingress[0] is empty and
Ingress[1].Host (or RouterCanonicalHostname) is set, and expect RouteHost(route)
to return that second ingress host; ensure the new case references RouteHost and
the routev1.Route.Status.Ingress slice so it guards multi-ingress fallback
behavior.

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 follows from the multi-ingress comment — since a route in HyperShift is admitted by exactly one router, there is no real scenario where Ingress[0] is empty but Ingress[1] has the host. Adding a test for that would codify fallback behavior we intentionally do not support and do not want to guarantee.

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.

@sdminonne, that's a clear and valid constraint — since HyperShift routes are admitted by exactly one router, Ingress[0] being empty while Ingress[1] carries the host is not a real scenario, and locking in fallback behavior that isn't intended would be misleading. Happy to drop this suggestion.


✏️ Learnings added
Learnt from: sdminonne
Repo: openshift/hypershift PR: 8546
File: support/netutil/route_test.go:472-559
Timestamp: 2026-05-19T18:45:39.602Z
Learning: In HyperShift, a Route is admitted by exactly one router, so `Status.Ingress` will never have a meaningful host in a later index while `Ingress[0]` is empty. Multi-ingress fallback behavior in `RouteHost` (support/netutil/route.go) is intentionally not supported and should not be tested or codified.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread support/netutil/route.go Outdated
Comment on lines +163 to +169
if len(route.Status.Ingress) > 0 {
if route.Status.Ingress[0].Host != "" {
return route.Status.Ingress[0].Host
}
if route.Status.Ingress[0].RouterCanonicalHostname != "" {
return route.Status.Ingress[0].RouterCanonicalHostname
}

@coderabbitai coderabbitai Bot May 19, 2026

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle multi-ingress routes instead of only index 0.

Line 163 assumes Status.Ingress[0] is the only useful entry. Routes can have multiple ingress records, so this may return empty even when a later entry has a usable host, which can stall readiness.

🔧 Suggested fix
 func RouteHost(route *routev1.Route) string {
 	if route == nil {
 		return ""
 	}
 	if route.Spec.Host != "" {
 		return route.Spec.Host
 	}
-	if len(route.Status.Ingress) > 0 {
-		if route.Status.Ingress[0].Host != "" {
-			return route.Status.Ingress[0].Host
-		}
-		if route.Status.Ingress[0].RouterCanonicalHostname != "" {
-			return route.Status.Ingress[0].RouterCanonicalHostname
-		}
-	}
-	return ""
+	var canonicalFallback string
+	for _, ingress := range route.Status.Ingress {
+		if ingress.Host != "" {
+			return ingress.Host
+		}
+		if canonicalFallback == "" && ingress.RouterCanonicalHostname != "" {
+			canonicalFallback = ingress.RouterCanonicalHostname
+		}
+	}
+	return canonicalFallback
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(route.Status.Ingress) > 0 {
if route.Status.Ingress[0].Host != "" {
return route.Status.Ingress[0].Host
}
if route.Status.Ingress[0].RouterCanonicalHostname != "" {
return route.Status.Ingress[0].RouterCanonicalHostname
}
func RouteHost(route *routev1.Route) string {
if route == nil {
return ""
}
if route.Spec.Host != "" {
return route.Spec.Host
}
var canonicalFallback string
for _, ingress := range route.Status.Ingress {
if ingress.Host != "" {
return ingress.Host
}
if canonicalFallback == "" && ingress.RouterCanonicalHostname != "" {
canonicalFallback = ingress.RouterCanonicalHostname
}
}
return canonicalFallback
}
🤖 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 `@support/netutil/route.go` around lines 163 - 169, The current logic only
inspects route.Status.Ingress[0] and returns its Host or
RouterCanonicalHostname, which misses later ingress entries; update the code
that references route.Status.Ingress[0] to iterate over all entries in
route.Status.Ingress, checking each entry's Host first and then
RouterCanonicalHostname, and return the first non-empty value found (falling
back to empty/no-host if none exist); ensure you reference the same symbols
(route.Status.Ingress, Host, RouterCanonicalHostname) so the loop replaces the
existing index-0 checks.

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.

In HyperShift, a route is admitted by exactly one router — either the OpenShift default router or the HyperShift shared ingress controller. There is no scenario where Ingress[0] is empty but Ingress[1] contains the host. Multiple ingress entries represent different routers admitting the same route, and the first entry is always the relevant one for the controller that owns the route.

Iterating all entries would add complexity for a scenario that does not exist in this codebase and could mask bugs by silently falling through to an unrelated router's entry.

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.

@sdminonne, thank you for the clear explanation of HyperShift's routing model. That's a strong argument — since a route in HyperShift is always admitted by exactly one router, Status.Ingress[0] is definitively the correct (and only) entry to inspect. Iterating further would indeed add unnecessary complexity and could silently mask bugs by falling through to an unrelated router's entry. I'll withdraw the suggestion.


✏️ Learnings added
Learnt from: sdminonne
Repo: openshift/hypershift PR: 8546
File: support/netutil/route.go:163-169
Timestamp: 2026-05-19T18:45:30.452Z
Learning: In HyperShift, a Route is admitted by exactly one router — either the OpenShift default router or the HyperShift shared ingress controller. There is no scenario where `Status.Ingress[0]` is empty but a later entry (e.g. `Status.Ingress[1]`) contains the host. Multiple ingress entries would represent different routers admitting the same route, but that does not occur in HyperShift. Therefore, reading only `Status.Ingress[0]` (as in `netutil.RouteHost` in `support/netutil/route.go`) is intentional and correct; iterating over all entries is unnecessary and could mask bugs.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@sdminonne

Copy link
Copy Markdown
Contributor Author

/retest

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2056808766044639232 | Cost: $6.988275500000003 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2056995254766145536 | Cost: $2.7953644999999994 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2057054221647220736 | Cost: $3.1325262499999997 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2057122430584885248 | Cost: $3.4875462500000003 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aks | Build: 2061798353355673600 | Cost: $2.8396047500000003 | Failed step: hypershift-azure-run-e2e

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks

@sdminonne

Copy link
Copy Markdown
Contributor Author

/pipeline-required

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aws-4-22

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aks-4-22

@sdminonne

Copy link
Copy Markdown
Contributor Author

/test e2e-aws-upgrade-hypershift-operator

@sdminonne
sdminonne force-pushed the CNTRLPLANE-3430/infraless-ha-break-glass-creds branch from 2f43cbe to 618a7ba Compare June 4, 2026 08:50
… < 4.23

- Set explicit hostnames on Route-type services in BeforeApply so the CPO
  can resolve infrastructure readiness on non-OpenShift management clusters
  (e.g. AKS) without a default ingress domain.
- Skip the test on release versions < 4.23 via AtLeast(Version423) because
  older CPO versions lack the LoadBalancer.Hostname bypass fix.
- Add early KAS pod scheduling failure detection (10-minute timeout) to
  fail fast under resource pressure.
- Poll SSR in validateCertificateAuth instead of failing immediately on
  Unauthorized, allowing time for the control-plane-pki-operator to
  reconcile the break-glass signer CA into the client-ca trust bundle.
- Increase WaitForGuestClient timeout to 15 minutes to accommodate cloud
  LB provisioning time on Azure/AWS.

Signed-off-by: Salvatore Dario Minonne <sminonne@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sdminonne
sdminonne force-pushed the CNTRLPLANE-3430/infraless-ha-break-glass-creds branch from 618a7ba to c08ad8d Compare June 4, 2026 09:57
@sdminonne

Copy link
Copy Markdown
Contributor Author

/retest

1 similar comment
@sdminonne

Copy link
Copy Markdown
Contributor Author

/retest

@sdminonne

Copy link
Copy Markdown
Contributor Author

/pipeline-required

@Nirshal

Nirshal commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 2, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks-4-22
/test e2e-aws-4-22
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-azure-v2-self-managed
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-gke

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

AI Test Failure Analysis

Job: pull-ci-openshift-hypershift-main-e2e-aws | Build: 2072694408209240064 | Cost: $2.2094277500000006 | Failed step: hypershift-aws-run-e2e-nested

View full analysis report


Generated by hypershift-analyze-e2e-failure post-step using Claude claude-opus-4-6

@sdminonne

Copy link
Copy Markdown
Contributor Author

/retest-required

@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@sdminonne: The following tests 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-azure-v2-self-managed c08ad8d link true /test e2e-azure-v2-self-managed
ci/prow/e2e-aws-4-22 c08ad8d link true /test e2e-aws-4-22

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

Copy link
Copy Markdown
Contributor

Now I have all the evidence I need. Let me compile the final report:

Test Failure Analysis Complete

Job Information

Job 1: e2e-aws-4-22

  • Prow Job: pull-ci-openshift-hypershift-main-e2e-aws-4-22
  • Build ID: 2073052531847598080
  • Target: e2e-aws-4-22
  • Failed Test: TestKarpenter/Main/Parallel_provisioning_tests/OpenshiftEC2NodeClass_Kubelet_propagation

Job 2: e2e-azure-v2-self-managed

  • Prow Job: pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed
  • Build ID: 2073052531864375296
  • Target: e2e-azure-v2-self-managed
  • Failed Test: [sig-hypershift] Control Plane Workloads / No crashing pods / openshift-oauth-apiserver

Test Failure Analysis

Error

Job 1 (e2e-aws-4-22):
Timed out after 120.001s.
Get "https://10.0.128.122:10250/containerLogs/kube-system/kubelet-config-checker/checker": remote error: tls: internal error
--- FAIL: TestKarpenter/Main/Parallel_provisioning_tests/OpenshiftEC2NodeClass_Kubelet_propagation (445.10s)

Job 2 (e2e-azure-v2-self-managed):
[FAILED] container audit-logs in pod openshift-oauth-apiserver-657df4ddfb-jxvnb has too many restarts (1 > 0)
Expected <int32>: 1 to be <= <int32>: 0
--- FAIL: TestE2EV2 (5063.33s)

Summary

Both failures are pre-existing flaky tests unrelated to PR #8546. The PR modifies TestCreateClusterHABreakGlassCredentials (making it infraless by switching to NonePlatform), WaitForGuestClient timeout (10→15 min), and the break-glass SSR validation (adding retry polling). None of the changed files (create_cluster_test.go, util/util.go, control_plane_pki_operator.go) are involved in the failing tests. In the e2e-aws-4-22 job, TestCreateClusterHABreakGlassCredentials was correctly skipped due to the version gate (AtLeast(Version423)). The actual failures are: (1) a Karpenter kubelet-config-checker pod that cannot fetch container logs due to a TLS handshake error on the kubelet port 10250, and (2) a single audit-logs container restart in the openshift-oauth-apiserver pod on Azure.

Root Cause

Job 1 — e2e-aws-4-22 (TestKarpenter/OpenshiftEC2NodeClass_Kubelet_propagation):

The test creates an OpenshiftEC2NodeClass with custom kubelet configuration, provisions a Karpenter node, deploys a kubelet-config-checker pod, and then attempts to read container logs via the kubelet API at https://<node-ip>:10250/containerLogs/kube-system/kubelet-config-checker/checker. The request fails with tls: internal error after polling for 120 seconds. This is a TLS handshake failure between the KAS (acting as proxy for the logs request) and the kubelet on the newly-provisioned Karpenter node. The root cause is that the kubelet's serving certificate was not yet trusted or properly signed by the time the test attempted to read logs — a known race condition with Karpenter-provisioned nodes where kubelet certificate bootstrap may not complete within the test's timeout window. This is a pre-existing flaky test — the Karpenter node came up (1 node ready in 5m15s), but the kubelet TLS serving cert wasn't fully established for log streaming.

Job 2 — e2e-azure-v2-self-managed (openshift-oauth-apiserver crashing pods):

The ControlPlaneWorkloads test checks that all control plane pods have zero container restarts. The audit-logs sidecar container in pod openshift-oauth-apiserver-657df4ddfb-jxvnb had exactly 1 restart (restartCount: 1 > 0). The test also noted previous terminated container "audit-logs" not found when trying to stream previous logs, meaning the restart happened early in the pod lifecycle and the previous container logs were already garbage-collected. A single audit-logs sidecar restart is a known transient issue — the sidecar may start before the main container's audit log directory is ready, causing it to exit and restart once. This is a pre-existing flaky test — 425 of 426 tests passed, and the single restart is a transient container startup ordering issue.

Recommendations
  1. Retry / Retest: Both failures are pre-existing flakes unrelated to PR CNTRLPLANE-3430: Make HA break-glass credentials test infraless #8546's changes. A /retest should clear them.

  2. For the Karpenter TLS issue: The OpenshiftEC2NodeClass_Kubelet_propagation test could be hardened by:

    • Increasing the Eventually timeout beyond 120s for the container log fetch
    • Adding a pre-check that the kubelet serving cert is signed before attempting log reads
    • Retrying on tls: internal error specifically (treating it as a transient condition)
  3. For the Azure oauth-apiserver restart: The ControlPlaneWorkloads crash detection test is overly strict — a single audit-logs sidecar restart during initial pod startup is expected behavior. Consider:

    • Allowing ≤1 restart for known sidecar containers (audit-logs)
    • Or excluding restarts that occurred before the pod reached Ready state
  4. No code changes needed in PR CNTRLPLANE-3430: Make HA break-glass credentials test infraless #8546: The PR's changes to TestCreateClusterHABreakGlassCredentials, WaitForGuestClient timeout, and validateCertificateAuth retry logic are correct and uninvolved in either failure.

Evidence
Evidence Detail
PR #8546 changed files test/e2e/create_cluster_test.go, test/e2e/util/util.go, test/integration/control_plane_pki_operator.go
PR's test in e2e-aws-4-22 TestCreateClusterHABreakGlassCredentialsSKIPPED (version gate AtLeast(Version423))
PR's test in e2e-azure Not present in the v2 test suite — did not run
Job 1 failing test TestKarpenter/Main/Parallel_provisioning_tests/OpenshiftEC2NodeClass_Kubelet_propagation in karpenter_test.go:1336
Job 1 error tls: internal error on kubelet port 10250 at 10.0.128.122 after 120s timeout
Job 1 test result 556 tests, 29 skipped, 4 failures (all 4 are parent-child cascade from 1 leaf failure)
Job 2 failing test [sig-hypershift] Control Plane Workloads / No crashing pods / openshift-oauth-apiserver in control_plane_workloads_test.go:905
Job 2 error audit-logs container in openshift-oauth-apiserver-657df4ddfb-jxvnb had 1 restart (threshold: 0)
Job 2 test result 426 of 962 specs run, 425 passed, 1 failed, 535 skipped
Job 2 other groups external-oidc: PASS, oauth-lb: PASS, private: PASS, upgrade: PASS, etcd-chaos: PASS, autoscaling: PASS

@openshift-ci

openshift-ci Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Stale PRs are closed after 21d of inactivity.

If this PR is still relevant, comment to refresh it or remove the stale label.
Mark the PR as fresh by commenting /remove-lifecycle stale.

If this PR is safe to close now please do so with /close.

/lifecycle stale

@openshift-ci openshift-ci Bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Aug 5, 2026
@openshift-ci

openshift-ci Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Stale PRs rot after 14d of inactivity.

Mark the PR as fresh by commenting /remove-lifecycle rotten.
Rotten PRs close after an additional 7d of inactivity.

If this PR is safe to close now please do so with /close.

/lifecycle rotten
/remove-lifecycle stale

@openshift-ci openshift-ci Bot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Aug 19, 2026
@openshift-ci

openshift-ci Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Rotten PRs close after 7d of inactivity.

Reopen the PR by commenting /reopen.
Mark the PR as fresh by commenting /remove-lifecycle rotten.

/close

@openshift-ci openshift-ci Bot closed this Aug 26, 2026
@openshift-ci

openshift-ci Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

@openshift-ci[bot]: Closed this PR.

Details

In response to this:

Rotten PRs close after 7d of inactivity.

Reopen the PR by commenting /reopen.
Mark the PR as fresh by commenting /remove-lifecycle rotten.

/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. 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/testing Indicates the PR includes changes for e2e testing jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants