Skip to content

OCPBUGS-97830: Add wait-for-etcd init container to oauth-apiserver - #8940

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
bryan-cox:OCPBUGS-97830
Jul 15, 2026
Merged

OCPBUGS-97830: Add wait-for-etcd init container to oauth-apiserver#8940
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
bryan-cox:OCPBUGS-97830

Conversation

@bryan-cox

@bryan-cox bryan-cox commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Add wait-for-etcd init container to the oauth-apiserver deployment, mirroring the existing kube-apiserver pattern
  • The init container DNS-polls etcd-client.$NAMESPACE.svc until it resolves before the main container starts
  • For unmanaged etcd, the init container is removed at reconciliation time (same guard as KAS)

Problem

The openshift-oauth-apiserver pod frequently restarts once during HCP startup because it attempts to connect to etcd before the etcd-client service is routable. The container exits with "error building REST storage: context deadline exceeded" after a 20-second connection timeout, gets restarted, and succeeds on the second attempt.

This is the primary flake in the AKS e2e CI job (pull-ci-openshift-hypershift-main-e2e-aks), causing EnsureNoCrashingPods to fail when it sees restartCount=1. The job has a ~50% failure rate due to this issue.

Root Cause

The kube-apiserver has a wait-for-etcd init container that prevents this exact race condition. The openshift-oauth-apiserver connects to etcd the same way but lacked this init container. The WithDependencies controller chain only gates Deployment creation at the controller level, not pod-level startup — so the etcd Service can exist but not yet be routable when the oauth-apiserver container starts.

Log evidence

PR #8849 — pod openshift-oauth-apiserver-66b8fdb877-vr549 (artifacts):

Previous container logs (ran exactly 20s before crashing):

W0706 13:01:23.962078  1 logging.go:55] [core] [Channel #7 SubChannel #8]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp: lookup etcd-client: operation was canceled"
W0706 13:01:43.452877  1 logging.go:55] [core] [Channel #1 SubChannel #3]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.68.198:2379: operation was canceled"
E0706 13:01:43.960390  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
  terminated:
    exitCode: 1
    reason: Error
    startedAt: "2026-07-06T13:01:23Z"
    finishedAt: "2026-07-06T13:01:43Z"
restartCount: 1

PR #8924 — pod openshift-oauth-apiserver-55785bf8cc-z2sxz (artifacts):

Previous container logs (ran 22s before crashing):

W0706 08:14:42.800789  1 logging.go:55] [core] [Channel #3 SubChannel #5]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: operation was canceled"
W0706 08:14:44.060546  1 logging.go:55] [core] [Channel #7 SubChannel #9]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: i/o timeout"
E0706 08:14:44.060879  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
  terminated:
    exitCode: 1
    reason: Error
    startedAt: "2026-07-06T08:14:22Z"
    finishedAt: "2026-07-06T08:14:44Z"
restartCount: 1

Both crashes follow the same sequence: oauth-apiserver starts → gRPC connections to etcd-client:2379 fail (DNS lookup canceled or TCP dial timeout) → 20s etcd client context expires → fatal "error building REST storage: context deadline exceeded" → exit code 1 → restart succeeds. Both PRs (#8849 is a dependabot update, #8924 is an unrelated nil-map guard fix) are completely unrelated to this failure.

Test plan

  • make lint-fix passes (0 issues)
  • make verify passes (pre-existing verify-crd-schema failure unrelated to this change)
  • AKS e2e job should stop flaking on EnsureNoCrashingPods for oauth-apiserver restarts

/jira:OCPBUGS-97830

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved startup reliability for the OAuth API server by adding an init step that waits until the required etcd service can be discovered before the server starts.
    • Updated deployment behavior for environments with externally managed etcd to skip the waiting step, avoiding unnecessary delays.

@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 jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 6, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-97830, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Summary

  • Add wait-for-etcd init container to the oauth-apiserver deployment, mirroring the existing kube-apiserver pattern
  • The init container DNS-polls etcd-client.$NAMESPACE.svc until it resolves before the main container starts
  • For unmanaged etcd, the init container is removed at reconciliation time (same guard as KAS)

Problem

The openshift-oauth-apiserver pod frequently restarts once during HCP startup because it attempts to connect to etcd before the etcd-client service is routable. The container exits with "error building REST storage: context deadline exceeded" after a 20-second connection timeout, gets restarted, and succeeds on the second attempt.

This is the primary flake in the AKS e2e CI job (pull-ci-openshift-hypershift-main-e2e-aks), causing EnsureNoCrashingPods to fail when it sees restartCount=1. The job has a ~50% failure rate due to this issue.

Root Cause

The kube-apiserver has a wait-for-etcd init container that prevents this exact race condition. The openshift-oauth-apiserver connects to etcd the same way but lacked this init container. The WithDependencies controller chain only gates Deployment creation at the controller level, not pod-level startup — so the etcd Service can exist but not yet be routable when the oauth-apiserver container starts.

Test plan

  • make lint-fix passes (0 issues)
  • make verify passes (pre-existing verify-crd-schema failure unrelated to this change)
  • AKS e2e job should stop flaking on EnsureNoCrashingPods for oauth-apiserver restarts

/jira:OCPBUGS-97830

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

@coderabbitai

coderabbitai Bot commented Jul 6, 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: 4b3868ef-1001-4792-873d-bcbdc530bce2

📥 Commits

Reviewing files that changed from the base of the PR and between 07b2c59 and afce795.

⛔ Files ignored due to path filters (5)
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/AROSwift/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/GCP/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/IBMCloud/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
📒 Files selected for processing (2)
  • control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go

📝 Walkthrough

Walkthrough

The openshift-oauth-apiserver deployment manifest adds a wait-for-etcd init container that repeatedly resolves etcd-client.$(POD_NAMESPACE).svc before the main containers start. The hosted control plane deployment adapter removes that init container when Etcd.ManagementType is Unmanaged.

🚥 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 accurately summarizes the main change: adding a wait-for-etcd init container to oauth-apiserver.
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 No Ginkgo test files or titles were changed; the PR only modifies deployment assets and generated testdata.
Test Structure And Quality ✅ Passed No Ginkgo tests were added or modified; oauth_apiserver tests here are table-driven unit tests, so this check is not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed Only a wait-for-etcd init container was added/removed; no nodeSelectors, anti-affinity, topology spread, or replica logic was introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the only DNS use is cluster-internal etcd-client.$(POD_NAMESPACE).svc.
No-Weak-Crypto ✅ Passed PR diff only adds/removes a wait-for-etcd init container; no weak-crypto APIs, custom crypto, or secret comparisons appear in changed files or fixtures.
Container-Privileges ✅ Passed No privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation settings were added; fixture securityContexts are readOnlyRootFilesystem only.
No-Sensitive-Data-In-Logs ✅ Passed No new logging of secrets/PII/internal data was added; the init-container nslookup gate mirrors kube-apiserver, and the Go change only removes it for unmanaged etcd.
✨ 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 requested review from jparrill and muraee July 6, 2026 17:06
@openshift-ci openshift-ci Bot added the area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release label Jul 6, 2026
@openshift-ci

openshift-ci Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox

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

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@bryan-cox

Copy link
Copy Markdown
Member Author

/jira refresh

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 6, 2026
@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 6, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-97830, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

/jira refresh

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

@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-97830, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

Summary

  • Add wait-for-etcd init container to the oauth-apiserver deployment, mirroring the existing kube-apiserver pattern
  • The init container DNS-polls etcd-client.$NAMESPACE.svc until it resolves before the main container starts
  • For unmanaged etcd, the init container is removed at reconciliation time (same guard as KAS)

Problem

The openshift-oauth-apiserver pod frequently restarts once during HCP startup because it attempts to connect to etcd before the etcd-client service is routable. The container exits with "error building REST storage: context deadline exceeded" after a 20-second connection timeout, gets restarted, and succeeds on the second attempt.

This is the primary flake in the AKS e2e CI job (pull-ci-openshift-hypershift-main-e2e-aks), causing EnsureNoCrashingPods to fail when it sees restartCount=1. The job has a ~50% failure rate due to this issue.

Root Cause

The kube-apiserver has a wait-for-etcd init container that prevents this exact race condition. The openshift-oauth-apiserver connects to etcd the same way but lacked this init container. The WithDependencies controller chain only gates Deployment creation at the controller level, not pod-level startup — so the etcd Service can exist but not yet be routable when the oauth-apiserver container starts.

Log evidence

PR #8849 — pod openshift-oauth-apiserver-66b8fdb877-vr549 (artifacts):

Previous container logs (ran exactly 20s before crashing):

W0706 13:01:23.962078  1 logging.go:55] [core] [Channel #7 SubChannel #8]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp: lookup etcd-client: operation was canceled"
W0706 13:01:43.452877  1 logging.go:55] [core] [Channel #1 SubChannel #3]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.68.198:2379: operation was canceled"
E0706 13:01:43.960390  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
 terminated:
   exitCode: 1
   reason: Error
   startedAt: "2026-07-06T13:01:23Z"
   finishedAt: "2026-07-06T13:01:43Z"
restartCount: 1

PR #8924 — pod openshift-oauth-apiserver-55785bf8cc-z2sxz (artifacts):

Previous container logs (ran 22s before crashing):

W0706 08:14:42.800789  1 logging.go:55] [core] [Channel #3 SubChannel #5]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: operation was canceled"
W0706 08:14:44.060546  1 logging.go:55] [core] [Channel #7 SubChannel #9]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: i/o timeout"
E0706 08:14:44.060879  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
 terminated:
   exitCode: 1
   reason: Error
   startedAt: "2026-07-06T08:14:22Z"
   finishedAt: "2026-07-06T08:14:44Z"
restartCount: 1

Both crashes follow the same sequence: oauth-apiserver starts → gRPC connections to etcd-client:2379 fail (DNS lookup canceled or TCP dial timeout) → 20s etcd client context expires → fatal "error building REST storage: context deadline exceeded" → exit code 1 → restart succeeds. Both PRs (#8849 is a dependabot update, #8924 is an unrelated nil-map guard fix) are completely unrelated to this failure.

Test plan

  • make lint-fix passes (0 issues)
  • make verify passes (pre-existing verify-crd-schema failure unrelated to this change)
  • AKS e2e job should stop flaking on EnsureNoCrashingPods for oauth-apiserver restarts

/jira:OCPBUGS-97830

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

@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 (3)
control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml (2)

129-145: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Missing resource requests on the init container.

Every other container in this manifest declares resources.requests (cpu/memory). Add equivalent requests for wait-for-etcd for consistent scheduling/QoS behavior.

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml`
around lines 129 - 145, The wait-for-etcd init container is missing resource
requests while the rest of the manifest defines them, so update the
initContainers entry in the openshift-oauth-apiserver deployment asset to
include resources.requests for cpu and memory on wait-for-etcd. Use the existing
container resource patterns in this deployment as the reference and add
equivalent requests in the same init container block.

Source: Path instructions


132-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Shebang/interpreter mismatch.

Script declares #!/bin/sh but is invoked via command: /bin/bash. Since it's run as bash -c "<script>", the shebang line is a no-op comment; harmless but confusing.

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml`
around lines 132 - 136, The startup script in the deployment manifest has a
shebang/interpreter mismatch: the inline script under the container command is
executed by /bin/bash, so the leading #!/bin/sh line is just a misleading
comment. Update the script in the openshift-oauth-apiserver deployment asset to
use a consistent interpreter choice by either removing the shebang from the
inline block or aligning the command to sh, and keep the command/script pair
under the container spec consistent with the existing nslookup loop.
control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go (1)

29-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the duplicate Unmanaged condition checks.

The same cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged condition is evaluated twice in adjacent blocks (lines 30 and 36). Combine them into a single branch for clarity.

♻️ Proposed refactor
 	var err error
 	etcdHostname := "etcd-client"
 	if cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged {
 		etcdHostname, err = netutil.HostFromURL(cpContext.HCP.Spec.Etcd.Unmanaged.Endpoint)
 		if err != nil {
 			return err
 		}
+		podspec.RemoveInitContainer("wait-for-etcd", &deployment.Spec.Template.Spec)
 	}
-	if cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged {
-		podspec.RemoveInitContainer("wait-for-etcd", &deployment.Spec.Template.Spec)
-	}
🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go`
around lines 29 - 38, The OAuth apiserver deployment logic evaluates
cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged twice in adjacent
blocks, which should be merged for clarity. Update the deployment.go logic
around the etcdHostname handling so the unmanaged branch both derives the host
with netutil.HostFromURL and removes the "wait-for-etcd" init container in a
single conditional, using the same cpContext.HCP.Spec.Etcd.ManagementType check
and the related deployment.Spec.Template.Spec/podspec.RemoveInitContainer path.

Source: Coding guidelines

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml`:
- Around line 129-145: The wait-for-etcd init container is missing resource
requests while the rest of the manifest defines them, so update the
initContainers entry in the openshift-oauth-apiserver deployment asset to
include resources.requests for cpu and memory on wait-for-etcd. Use the existing
container resource patterns in this deployment as the reference and add
equivalent requests in the same init container block.
- Around line 132-136: The startup script in the deployment manifest has a
shebang/interpreter mismatch: the inline script under the container command is
executed by /bin/bash, so the leading #!/bin/sh line is just a misleading
comment. Update the script in the openshift-oauth-apiserver deployment asset to
use a consistent interpreter choice by either removing the shebang from the
inline block or aligning the command to sh, and keep the command/script pair
under the container spec consistent with the existing nslookup loop.

In
`@control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go`:
- Around line 29-38: The OAuth apiserver deployment logic evaluates
cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged twice in adjacent
blocks, which should be merged for clarity. Update the deployment.go logic
around the etcdHostname handling so the unmanaged branch both derives the host
with netutil.HostFromURL and removes the "wait-for-etcd" init container in a
single conditional, using the same cpContext.HCP.Spec.Etcd.ManagementType check
and the related deployment.Spec.Template.Spec/podspec.RemoveInitContainer path.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: cad16ca2-4cc8-478e-a9bd-e3eabef1c676

📥 Commits

Reviewing files that changed from the base of the PR and between e4d4711 and 24e4bf8.

⛔ Files ignored due to path filters (5)
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/AROSwift/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/GCP/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/IBMCloud/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
  • control-plane-operator/controllers/hostedcontrolplane/testdata/openshift-oauth-apiserver/zz_fixture_TestControlPlaneComponents_openshift_oauth_apiserver_deployment.yaml is excluded by !**/testdata/**
📒 Files selected for processing (2)
  • control-plane-operator/controllers/hostedcontrolplane/v2/assets/openshift-oauth-apiserver/deployment.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.53%. Comparing base (3e6ecb5) to head (afce795).
⚠️ Report is 89 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8940      +/-   ##
==========================================
+ Coverage   43.37%   43.53%   +0.15%     
==========================================
  Files         771      771              
  Lines       95718    95799      +81     
==========================================
+ Hits        41520    41708     +188     
+ Misses      51313    51192     -121     
- Partials     2885     2899      +14     
Files with missing lines Coverage Δ
...ostedcontrolplane/v2/oauth_apiserver/deployment.go 100.00% <100.00%> (ø)

... and 6 files with indirect coverage changes

Flag Coverage Δ
cmd-support 37.22% <ø> (+0.34%) ⬆️
cpo-hostedcontrolplane 45.51% <100.00%> (+0.29%) ⬆️
cpo-other 45.10% <ø> (ø)
hypershift-operator 53.65% <ø> (ø)
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.

- -c
- |
#!/bin/sh
while ! nslookup etcd-client.$(POD_NAMESPACE).svc; do sleep 1; done

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.

Is the goal to check for DNS resolution or actual connectivity to the endpoint?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

DNS resolution, not connectivity. The failure mode is that oauth-apiserver tries to connect to etcd-client before the Service has a backing Endpoints object (DNS returns NXDOMAIN), then exits with "context deadline exceeded" after 20s. Once DNS resolves, kube-proxy rules handle L4 connectivity. This mirrors the existing kube-apiserver wait-for-etcd init container which uses the same nslookup check.

Also merged the duplicate Unmanaged condition checks in deployment.go per a coderabbit nit.


AI-assisted response via Claude Code

…iserver

The oauth-apiserver pod frequently restarts once during HCP startup
because it connects to etcd before the etcd-client service is routable,
exiting with "context deadline exceeded" after 20s. This is the primary
flake in the AKS e2e job (~50% failure rate).

Mirror the kube-apiserver pattern: add a wait-for-etcd init container
that DNS-polls etcd-client.$NAMESPACE.svc until it resolves. For
unmanaged etcd, strip the init container at reconciliation time.

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

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

Note on deployment_test.go:68 — The existing test case "When etcd is unmanaged, it should configure custom etcd endpoint" exercises the unmanaged etcd path but doesn't verify the new RemoveInitContainer behavior. Could you add an assertion here? Something like:

initContainer := podspec.FindContainer("wait-for-etcd", deployment.Spec.Template.Spec.InitContainers)
g.Expect(initContainer).To(BeNil(), "wait-for-etcd init container should be removed for unmanaged etcd")

(I know KAS doesn't test this either, but since you're already touching this code it's a good opportunity to close the gap.)

if err != nil {
return err
}
podspec.RemoveInitContainer("wait-for-etcd", &deployment.Spec.Template.Spec)

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 placement works, but KAS keeps init container removal in its own self-documenting block with an explanatory comment (kas/deployment.go:105-110). Nesting it inside the hostname-resolution block makes it look like a side-effect of URL parsing rather than an intentional design decision.

Consider:

// With managed etcd, we should wait for the known etcd client service name to
// at least resolve before starting up to avoid futile connection attempts and
// pod crashing. For unmanaged, make no assumptions.
if cpContext.HCP.Spec.Etcd.ManagementType == hyperv1.Unmanaged {
    podspec.RemoveInitContainer("wait-for-etcd", &deployment.Spec.Template.Spec)
}

Nit, not blocking — just consistency with KAS.

@jparrill

Copy link
Copy Markdown
Contributor

Dropped some comments. Thanks!

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 13, 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
/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

The PR does NOT touch any cluster creation code. Now I have enough evidence for the report. Let me compile the final analysis:

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

Failed to create cluster: failed to apply object "/clusters": Timeout: request did not complete within requested timeout - context deadline exceeded

Summary

The create-management-cluster pre-phase step failed because the CI cluster's (build01) Kubernetes API server timed out when the hypershift create cluster azure command attempted to apply the HostedCluster CR via client.Create(). All Azure infrastructure (resource groups, VNet, NSG, DNS zones, load balancer, role assignments) was provisioned successfully between 16:24:18–16:25:59Z, but the subsequent HostedCluster object creation request at ~16:25:59Z did not complete within the API server's request timeout, failing at 16:26:46Z. The API server remained unavailable 4+ minutes later ("the server is currently unable to handle the request" at 16:31:09Z during the dump step). All 6 downstream step failures (destroy-guests, destroy-management-cluster, dump-management-cluster, hypershift-k8sgpt, and both phase-level failures) are cascading consequences of the management cluster never being created. This failure is unrelated to PR #8940's changes, which only add a wait-for-etcd init container to the oauth-apiserver deployment template — code that is never reached during management cluster creation.

Root Cause

The root cause is a transient CI infrastructure issue — the build01.ci.openshift.org API server was unable to handle requests during the window when the create-management-cluster step attempted to create the HostedCluster custom resource.

The hypershift create cluster azure command uses client.Create() (a direct Kubernetes API create call) to apply the HostedCluster object. The error "Timeout: request did not complete within requested timeout - context deadline exceeded" originates from the Kubernetes API server itself — not from a client-side timeout. This indicates the API server could not complete admission/webhook processing within its server-side request timeout (~60s by default).

Supporting evidence that this is an infrastructure issue, not a code issue:

  1. All Azure infrastructure provisioned successfully — resource groups, VNet, NSG, DNS zones, role assignments, and load balancer were all created without error
  2. The API server was still unavailable 4 minutes later — the dump-management-cluster step at 16:31:09Z also got "the server is currently unable to handle the request", indicating a sustained API server availability issue on the CI cluster
  3. PR OCPBUGS-97830: Add wait-for-etcd init container to oauth-apiserver #8940 does not modify any cluster creation code — the diff only touches control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment.go and the corresponding deployment YAML template/test fixtures, none of which are in the cluster creation path
  4. The HostedCluster object was never persistedhostedclusters.hypershift.openshift.io "21bddc751d-mgmt" not found confirms the create call never completed
Recommendations
  1. Retry the job — This is a transient CI infrastructure failure on build01.ci.openshift.org. The failure is not caused by PR OCPBUGS-97830: Add wait-for-etcd init container to oauth-apiserver #8940's code changes and a rerun should succeed if the CI cluster API server is healthy.

  2. No code changes needed — PR OCPBUGS-97830: Add wait-for-etcd init container to oauth-apiserver #8940 only adds a wait-for-etcd init container to the oauth-apiserver deployment, which is applied by the control-plane-operator after the hosted control plane is already up. This code path is never exercised during the create-management-cluster pre-phase step that failed.

  3. If the failure recurs on retry, investigate build01 CI cluster health — check for API server load, webhook latency, or node pressure on the CI cluster at the time of the next run.

Evidence
Evidence Detail
Failed step create-management-cluster (pre-phase) — failed after 3m28s
Error failed to apply object "/clusters": Timeout: request did not complete within requested timeout - context deadline exceeded
Error time 2026-07-13T16:26:46Z
Infra created OK Resource groups, VNet, NSG, DNS, LB all created successfully (16:24:18–16:25:59Z)
API server still down dump-management-cluster at 16:31:09Z: "the server is currently unable to handle the request"
HostedCluster never created hostedclusters.hypershift.openshift.io "21bddc751d-mgmt" not found
CI cluster build01.ci.openshift.org, namespace ci-op-2nz1jr4z
Cascading failures 6 downstream steps failed: destroy-guests, destroy-management-cluster, dump-management-cluster, hypershift-k8sgpt, post phase, pre phase
PR relevance PR #8940 modifies only oauth-apiserver deployment template (init container) — does not touch cluster creation path
Stack trace cmd/cluster/azure/create.go:64core.CreateCluster()client.Create() server-side timeout

@bryan-cox

Copy link
Copy Markdown
Member Author

/test e2e-azure-v2-self-managed

@bryan-cox

Copy link
Copy Markdown
Member Author

/label acknowledge-critical-fixes-only

@openshift-ci openshift-ci Bot added the acknowledge-critical-fixes-only Indicates if the issuer of the label is OK with the policy. label Jul 15, 2026
@bryan-cox

Copy link
Copy Markdown
Member Author

/verified by e2e

The wait-for-etcd init container was verified running in the e2e-aws job. From the TestCreateCluster pod artifacts, pod openshift-oauth-apiserver-84c5cb89c8-jhjdt:

    name: wait-for-etcd
    ready: true
    restartCount: 0
    state:
      terminated:
        containerID: cri-o://16044e11be598d5fe419f74bc393860b4f1da4652d258c1237dd2c3b17d7d8b5
        exitCode: 0
        finishedAt: "2026-07-13T17:38:01Z"
        reason: Completed
        startedAt: "2026-07-13T17:38:01Z"

The init container resolved etcd DNS and completed successfully with zero restarts before the oauth-apiserver main container started.

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

Copy link
Copy Markdown

@bryan-cox: This PR has been marked as verified by e2e.

Details

In response to this:

/verified by e2e

The wait-for-etcd init container was verified running in the e2e-aws job. From the TestCreateCluster pod artifacts, pod openshift-oauth-apiserver-84c5cb89c8-jhjdt:

   name: wait-for-etcd
   ready: true
   restartCount: 0
   state:
     terminated:
       containerID: cri-o://16044e11be598d5fe419f74bc393860b4f1da4652d258c1237dd2c3b17d7d8b5
       exitCode: 0
       finishedAt: "2026-07-13T17:38:01Z"
       reason: Completed
       startedAt: "2026-07-13T17:38:01Z"

The init container resolved etcd DNS and completed successfully with zero restarts before the oauth-apiserver main container started.

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

@openshift-ci

openshift-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@bryan-cox: 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.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 4fa09b1 into openshift:main Jul 15, 2026
41 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: Jira Issue Verification Checks: Jira Issue OCPBUGS-97830
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-97830 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

Summary

  • Add wait-for-etcd init container to the oauth-apiserver deployment, mirroring the existing kube-apiserver pattern
  • The init container DNS-polls etcd-client.$NAMESPACE.svc until it resolves before the main container starts
  • For unmanaged etcd, the init container is removed at reconciliation time (same guard as KAS)

Problem

The openshift-oauth-apiserver pod frequently restarts once during HCP startup because it attempts to connect to etcd before the etcd-client service is routable. The container exits with "error building REST storage: context deadline exceeded" after a 20-second connection timeout, gets restarted, and succeeds on the second attempt.

This is the primary flake in the AKS e2e CI job (pull-ci-openshift-hypershift-main-e2e-aks), causing EnsureNoCrashingPods to fail when it sees restartCount=1. The job has a ~50% failure rate due to this issue.

Root Cause

The kube-apiserver has a wait-for-etcd init container that prevents this exact race condition. The openshift-oauth-apiserver connects to etcd the same way but lacked this init container. The WithDependencies controller chain only gates Deployment creation at the controller level, not pod-level startup — so the etcd Service can exist but not yet be routable when the oauth-apiserver container starts.

Log evidence

PR #8849 — pod openshift-oauth-apiserver-66b8fdb877-vr549 (artifacts):

Previous container logs (ran exactly 20s before crashing):

W0706 13:01:23.962078  1 logging.go:55] [core] [Channel #7 SubChannel #8]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp: lookup etcd-client: operation was canceled"
W0706 13:01:43.452877  1 logging.go:55] [core] [Channel #1 SubChannel #3]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.68.198:2379: operation was canceled"
E0706 13:01:43.960390  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
 terminated:
   exitCode: 1
   reason: Error
   startedAt: "2026-07-06T13:01:23Z"
   finishedAt: "2026-07-06T13:01:43Z"
restartCount: 1

PR #8924 — pod openshift-oauth-apiserver-55785bf8cc-z2sxz (artifacts):

Previous container logs (ran 22s before crashing):

W0706 08:14:42.800789  1 logging.go:55] [core] [Channel #3 SubChannel #5]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: operation was canceled"
W0706 08:14:44.060546  1 logging.go:55] [core] [Channel #7 SubChannel #9]grpc: addrConn.createTransport failed to connect to {Addr: "etcd-client:2379", ...}. Err: connection error: desc = "transport: Error while dialing: dial tcp 10.0.143.145:2379: i/o timeout"
E0706 08:14:44.060879  1 run.go:72] "command failed" err="error building REST storage: context deadline exceeded"
lastState:
 terminated:
   exitCode: 1
   reason: Error
   startedAt: "2026-07-06T08:14:22Z"
   finishedAt: "2026-07-06T08:14:44Z"
restartCount: 1

Both crashes follow the same sequence: oauth-apiserver starts → gRPC connections to etcd-client:2379 fail (DNS lookup canceled or TCP dial timeout) → 20s etcd client context expires → fatal "error building REST storage: context deadline exceeded" → exit code 1 → restart succeeds. Both PRs (#8849 is a dependabot update, #8924 is an unrelated nil-map guard fix) are completely unrelated to this failure.

Test plan

  • make lint-fix passes (0 issues)
  • make verify passes (pre-existing verify-crd-schema failure unrelated to this change)
  • AKS e2e job should stop flaking on EnsureNoCrashingPods for oauth-apiserver restarts

/jira:OCPBUGS-97830

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
  • Improved startup reliability for the OAuth API server by adding an init step that waits until the required etcd service can be discovered before the server starts.
  • Updated deployment behavior for environments with externally managed etcd to skip the waiting step, avoiding unnecessary delays.

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.

@bryan-cox
bryan-cox deleted the OCPBUGS-97830 branch July 15, 2026 16:08
devguyio added a commit to devguyio/hypershift that referenced this pull request Jul 21, 2026
ModernTLS fixtures from openshift#8871 landed stale due to Tide merge skew.
GHA tested against a base that predated openshift#8772, openshift#8940, openshift#8971, openshift#8705
which changed oauth masterURL, wait-for-etcd init container, router
ordering, and etcd job label regex respectively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ahmed Abdalla <aabdelre@redhat.com>
vsolanki12 pushed a commit to vsolanki12/hypershift that referenced this pull request Aug 25, 2026
ModernTLS fixtures from openshift#8871 landed stale due to Tide merge skew.
GHA tested against a base that predated openshift#8772, openshift#8940, openshift#8971, openshift#8705
which changed oauth masterURL, wait-for-etcd init container, router
ordering, and etcd job label regex respectively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ahmed Abdalla <aabdelre@redhat.com>
rutvik23 pushed a commit to rutvik23/hypershift that referenced this pull request Aug 26, 2026
ModernTLS fixtures from openshift#8871 landed stale due to Tide merge skew.
GHA tested against a base that predated openshift#8772, openshift#8940, openshift#8971, openshift#8705
which changed oauth masterURL, wait-for-etcd init container, router
ordering, and etcd job label regex respectively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ahmed Abdalla <aabdelre@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acknowledge-critical-fixes-only Indicates if the issuer of the label is OK with the policy. 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 jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. 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. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants