Skip to content

TRT-2841: Revert "OCPBUGS-77056: Make external cert validation asynchronous (Resurrection)"#824

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:masterfrom
redhat-chai-bot:revert-822-trt-2841
Jul 26, 2026
Merged

TRT-2841: Revert "OCPBUGS-77056: Make external cert validation asynchronous (Resurrection)"#824
openshift-merge-bot[bot] merged 1 commit into
openshift:masterfrom
redhat-chai-bot:revert-822-trt-2841

Conversation

@redhat-chai-bot

@redhat-chai-bot redhat-chai-bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This reverts merge commit 13f62cb (PR #822),
which resurrected the asynchronous external certificate validation feature
originally introduced in PR #745.

Why

PR #745 was reverted via PR #817 after it caused RouteExternalCertificate
hypershift conformance test failures in the 5.0 nightly stream, tracked as
TRT-2813.

PR #822 re-introduced the same feature with bug fixes. However, after it
merged (2026-07-24 18:58 UTC), the 5.0 nightly stream began experiencing
x509: ECDSA verification failure errors in RouteExternalCertificate tests
— the router intermittently serves a broken certificate chain after secret
updates, causing TLS handshake failures. The historical pass rate for
aggregated-hypershift-ovn-conformance-5.0 degraded from 89–91% to 50–70%.

Investigation of the payload immediately before #822 merged
(5.0.0-0.nightly-2026-07-24-144712) confirmed that its hypershift
conformance failures were caused by infrastructure issues (pod scheduling
failures, insufficient aggregated runs), not by RouteExternalCertificate
test assertion failures. The actual x509 cert chain failures began in
subsequent payloads, aligning with #822's merge time.

As of 5.0.0-0.nightly-2026-07-25-093347, 5.0 has seen 5 consecutive
payload rejections with no accepted nightly in ~147.5 hours.

Tracking


@neisw requested in Slack thread

Summary by CodeRabbit

  • Bug Fixes

    • Improved TLS certificate validation to reject incorrectly formatted certificate and private-key data.
    • Improved route status synchronization and retry handling to reduce conflicting updates and status inconsistencies.
    • Improved external certificate secret event handling, including clearer status reporting for validation, updates, deletion, and retrieval failures.
  • Improvements

    • Updated external certificate authorization checks for more reliable access validation.
    • Simplified runtime lease processing for more predictable route status coordination.
    • Improved test infrastructure reliability, including temporary socket handling and asynchronous secret event testing.

…surrection)"

This reverts commit 13f62cb.

Reverting router#822 which reintroduced RouteExternalCertificate
test failures in the 5.0 nightly stream.

Tracked in TRT-2841.
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 25, 2026
@openshift-ci openshift-ci Bot added the ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review label Jul 25, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

@redhat-chai-bot: This pull request references TRT-2841 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 bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

This reverts merge commit 13f62cb (PR #822),
which resurrected the asynchronous external certificate validation feature
originally introduced in PR #745.

Why

PR #745 was reverted via PR #817 after it caused RouteExternalCertificate
hypershift conformance test failures in the 5.0 nightly stream, tracked as
TRT-2813.

PR #822 re-introduced the same feature with bug fixes. However, after it
merged (2026-07-24 18:58 UTC), the 5.0 nightly stream began experiencing
x509: ECDSA verification failure errors in RouteExternalCertificate tests
— the router intermittently serves a broken certificate chain after secret
updates, causing TLS handshake failures. The historical pass rate for
aggregated-hypershift-ovn-conformance-5.0 degraded from 89–91% to 50–70%.

Investigation of the payload immediately before #822 merged
(5.0.0-0.nightly-2026-07-24-144712) confirmed that its hypershift
conformance failures were caused by infrastructure issues (pod scheduling
failures, insufficient aggregated runs), not by RouteExternalCertificate
test assertion failures. The actual x509 cert chain failures began in
subsequent payloads, aligning with #822's merge time.

As of 5.0.0-0.nightly-2026-07-25-093347, 5.0 has seen 5 consecutive
payload rejections with no accepted nightly in ~147.5 hours.

Tracking


@neisw requested in Slack thread

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

Copy link
Copy Markdown

Walkthrough

The PR updates external-certificate validation, replaces the shared secret manager, changes secret-event status handling, serializes route status admission, simplifies writer leases, and adjusts build defaults and test infrastructure.

Changes

Router validation and runtime behavior

Layer / File(s) Summary
External certificate validation
pkg/router/routeapihelpers/validation.go, pkg/router/routeapihelpers/validation_test.go
PEM block types are validated explicitly, and external-certificate validation authorizes and retrieves referenced TLS Secrets.
Secret manager and informer flow
pkg/cmd/infra/router/template.go, pkg/router/controller/route_secret_manager.go, pkg/router/controller/route_secret_manager_test.go, pkg/router/controller/contention.go
The library secret manager is wired in, status reasons and secret handlers are updated, and secret-event tests use informers and completion signaling.
Status admission and conflict handling
pkg/router/controller/status.go
Status handling is serialized, conflicts release the writer lease while retrying, and ignored condition reason preservation is removed.
Single-worker writer leases
pkg/router/writerlease/*, pkg/router/router_test.go, pkg/cmd/infra/router/template.go
Lease constructors no longer accept worker counts; execution uses one worker, shuts down queues, and changes follower requeue behavior.
Runtime defaults and test harness wiring
hack/Makefile.debug, pkg/cmd/infra/router/clientcmd.go, pkg/router/controller/factory/factory_endpointslices_test.go, pkg/router/template/configmanager/haproxy/testing/haproxy.go
Build architecture and client throttling overrides are removed, while endpoint-slice and fake HAProxy test setup are adjusted.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Route
  participant authorizationutil
  participant SecretsGetter
  Route->>authorizationutil: Authorize secret access
  Route->>SecretsGetter: Retrieve referenced TLS Secret
  SecretsGetter-->>Route: Return Secret or validation error
Loading

Possibly related PRs

  • openshift/router#817: Applies related external-certificate status, secret-handler, and ingress-condition equality changes.

Suggested reviewers: bentito, jcmoraisjr

🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning FAIL: New tests use indefinite informer waits (context.TODO().Done(), bare <-doneCh) and global os.Setenv without cleanup, violating timeout/cleanup requirements. Add bounded waits (e.g. time.After), close informer stop channels with defer, and replace os.Setenv with t.Setenv/t.Cleanup so feature-gate state is restored.
✅ Passed checks (13 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies this as a revert of the asynchronous external certificate validation change, matching the PR’s main objective.
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 modified test titles use dynamic data or Ginkgo-style names; the touched tests use static literal names only.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e specs were added; the diff has no It/Describe/Context/When or ginkgo imports, and touched tests are unit tests only.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the diff only changes unit tests/helpers and contains no multi-node or HA cluster assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Touched controller/build code only; no nodeSelector, affinity, topologySpreadConstraints, PDB, or topology-derived replica logic was introduced.
Ote Binary Stdout Contract ✅ Passed PASS: The patch only changes helper logic; no new stdout writes were introduced in main/init/TestMain or suite setup, and existing router_test.go prints were untouched.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo/e2e tests were added; the changed tests are unit tests, and their IPv4 literals don’t indicate disconnected-network or public-internet dependencies.
No-Weak-Crypto ✅ Passed No added MD5/SHA1/DES/RC4/etc. The touched crypto-adjacent code only adds PEM-type validation and auth checks; no custom crypto or insecure comparisons were introduced.
Container-Privileges ✅ Passed Changed files are code/tests only; no new privileged, hostPID/IPC, hostNetwork, allowPrivilegeEscalation, or SYS_ADMIN settings were found in the PR.
No-Sensitive-Data-In-Logs ✅ Passed No new logs expose secret contents, tokens, PII, or hostnames; added messages only include route/secret names and versions (object metadata).
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@openshift-ci
openshift-ci Bot requested review from bentito and grzpiotrowski July 25, 2026 20:06
@openshift-ci

openshift-ci Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

@neisw

neisw commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/payload-aggregate periodic-ci-openshift-hypershift-release-5.0-periodics-e2e-aws-ovn-conformance 10

@openshift-ci

openshift-ci Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

@neisw: trigger 1 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-hypershift-release-5.0-periodics-e2e-aws-ovn-conformance

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/99d2eb40-8864-11f1-822b-6a1747442f40-0

@neisw

neisw commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/payload-aggregate periodic-ci-openshift-release-main-ci-5.0-upgrade-from-stable-4.22-e2e-gcp-ovn-rt-upgrade 10

@openshift-ci

openshift-ci Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

@neisw: trigger 1 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-main-ci-5.0-upgrade-from-stable-4.22-e2e-gcp-ovn-rt-upgrade

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/b5f22b10-8864-11f1-855d-aaa086abd364-0

@neisw

neisw commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-metal-ipi-ovn-ipv4 periodic-ci-openshift-release-main-ci-5.0-e2e-aws-ovn-techpreview

@openshift-ci

openshift-ci Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

@neisw: trigger 2 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-main-nightly-5.0-e2e-metal-ipi-ovn-ipv4
  • periodic-ci-openshift-release-main-ci-5.0-e2e-aws-ovn-techpreview

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/0a333ac0-8865-11f1-955f-076351bfea4c-0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
pkg/router/controller/contention.go (1)

296-303: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep conditionsEqual faithful for status updates.

isIngressConditionUpdateRequired also calls conditionsEqual to decide whether to write a condition. With the current ignore-set shortcut, changes like Admitted=TrueAdmitted=False can be treated as unchanged when one condition reason is in the ignore set, skipping a required status write. Use this ignore behavior only in the contention/path where it is needed.

🤖 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 `@pkg/router/controller/contention.go` around lines 296 - 303, Update
conditionsEqual to always compare Type, Status, Reason, and Message, removing
the ignoreIngressConditionReason shortcut so status changes such as
Admitted=True to Admitted=False are detected. Preserve the ignore-set behavior
in the specific contention/path that requires it, rather than applying it
through the shared equality helper.
pkg/router/template/configmanager/haproxy/testing/haproxy.go (1)

110-132: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle post-start listener errors.

go func() error discards the goroutine return: startup listener failures are already sent on listenErr, but later listener.Accept() failures also return from the goroutine and are thrown away, silently stopping the accept loop. Send those errors on listenErr or otherwise report them. Also use a context.Context for the listener call instead of net.Listen for any noctx lint requirement.

🤖 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 `@pkg/router/template/configmanager/haproxy/testing/haproxy.go` around lines
110 - 132, Update the listener goroutine to report errors from both startup and
post-start `listener.Accept()` failures through `listenErr` instead of
discarding its return value. Replace `net.Listen` with the context-aware
listener API, using the surrounding test context, while preserving normal
shutdown behavior without reporting it as an error.

Sources: Path instructions, Linters/SAST tools

🧹 Nitpick comments (2)
pkg/router/controller/route_secret_manager_test.go (1)

53-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test helper mutates process env and starts an informer that never stops.

os.Setenv has no cleanup (use t.Setenv by passing t), and context.TODO().Done() returns nil, so every informer.Run goroutine lives for the whole test binary.

♻️ Suggested change
-func fakeSecretInformer(fakeKubeClient *testclient.Clientset, namespace, name string) cache.SharedInformer {
+func fakeSecretInformer(t *testing.T, fakeKubeClient *testclient.Clientset, namespace, name string) cache.SharedInformer {
 	// WatchListClient featuregate is enabled by default since v0.35. Fake client does not support
 	// initializing its cache from Watch, so falling back to use List instead. The envvar below
 	// configures the featuregate state.
-	os.Setenv("KUBE_FEATURE_"+string(features.WatchListClient), "False")
+	t.Setenv("KUBE_FEATURE_"+string(features.WatchListClient), "False")

and drive Run with a per-test cancellable context:

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
go informer.Run(ctx.Done())
🤖 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 `@pkg/router/controller/route_secret_manager_test.go` around lines 53 - 75,
Update fakeSecretInformer to accept *testing.T, replace os.Setenv with t.Setenv
so the feature gate is restored automatically, and use a per-test cancellable
context for informer.Run. Register cancel with t.Cleanup and pass ctx.Done() to
ensure the informer goroutine stops when the test completes.
pkg/router/routeapihelpers/validation.go (1)

293-317: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comment doesn't match the implementation.

The comment claims only the first block is checked, but the loop validates every decoded block. Also, "cannot be null" is misleading for a byte slice — "empty" is accurate.

♻️ Suggested wording fix
-// validatePEMContent takes content and pemType, PEM decodes content and
-// validates that the first block matches the expected pemType. This validation
-// 1. Ensures that the required pemType is first in the order
+// validatePEMContent takes content and pemType, PEM decodes content and
+// validates that every decoded block matches the expected pemType. This validation
+// 1. Ensures that the required pemType is the only type present (and therefore first in order)
 // 2. Blocks attempts of passing certificates where keys should be used, and
 // 3. Blocks attempts of passing keys where certificates should be used.
@@
 	if len(content) == 0 {
-		return fmt.Errorf("the PEM content cannot be null")
+		return fmt.Errorf("the PEM content cannot be empty")
 	}
🤖 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 `@pkg/router/routeapihelpers/validation.go` around lines 293 - 317, Update the
validatePEMContent doc comment to state that validation checks every decoded PEM
block, not only the first block, and replace the “cannot be null” wording with
“cannot be empty” to accurately describe the []byte input. Keep the
implementation unchanged.
🤖 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 `@pkg/router/controller/factory/factory_endpointslices_test.go`:
- Around line 80-88: Update newEndpointSliceTestSetup to accept *testing.T and
replace the global os.Setenv call for the WatchListClient feature gate with
t.Setenv, preserving the existing disabled value while ensuring automatic
test-scoped cleanup.

In `@pkg/router/controller/route_secret_manager_test.go`:
- Around line 115-133: The statusRecorder completion signalling and state access
are not concurrency-safe. In
pkg/router/controller/route_secret_manager_test.go:115-133, add mutex protection
for rejections and updates, restore guarded getters, and use sync.Once when
closing doneCh. In pkg/router/controller/route_secret_manager_test.go:1409-1417,
stop reassigning recorder.doneCh; instead use a buffered event channel sent by
statusRecorder so each phase can be awaited without mutating shared state.

In `@pkg/router/controller/route_secret_manager.go`:
- Around line 311-314: Update the DeleteFunc callback to safely handle informer
tombstones before accessing the deleted secret. Unwrap
cache.DeletedFinalStateUnknown and extract its underlying *kapi.Secret, while
preserving direct *kapi.Secret handling and avoiding a panic for unexpected
object types; then continue generating the existing deletion message.

In `@pkg/router/writerlease/writerlease.go`:
- Around line 253-255: Replace the blocking time.Sleep(remaining) in the
follower lease-expiration path with the delaying queue mechanism, reusing
l.queue to schedule key after the remaining duration. Preserve the existing log
and avoid directly blocking the worker so Run shutdown can interrupt it.

---

Outside diff comments:
In `@pkg/router/controller/contention.go`:
- Around line 296-303: Update conditionsEqual to always compare Type, Status,
Reason, and Message, removing the ignoreIngressConditionReason shortcut so
status changes such as Admitted=True to Admitted=False are detected. Preserve
the ignore-set behavior in the specific contention/path that requires it, rather
than applying it through the shared equality helper.

In `@pkg/router/template/configmanager/haproxy/testing/haproxy.go`:
- Around line 110-132: Update the listener goroutine to report errors from both
startup and post-start `listener.Accept()` failures through `listenErr` instead
of discarding its return value. Replace `net.Listen` with the context-aware
listener API, using the surrounding test context, while preserving normal
shutdown behavior without reporting it as an error.

---

Nitpick comments:
In `@pkg/router/controller/route_secret_manager_test.go`:
- Around line 53-75: Update fakeSecretInformer to accept *testing.T, replace
os.Setenv with t.Setenv so the feature gate is restored automatically, and use a
per-test cancellable context for informer.Run. Register cancel with t.Cleanup
and pass ctx.Done() to ensure the informer goroutine stops when the test
completes.

In `@pkg/router/routeapihelpers/validation.go`:
- Around line 293-317: Update the validatePEMContent doc comment to state that
validation checks every decoded PEM block, not only the first block, and replace
the “cannot be null” wording with “cannot be empty” to accurately describe the
[]byte input. Keep the implementation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ea1c099-020a-4a3c-b967-4403f1e85ad8

📥 Commits

Reviewing files that changed from the base of the PR and between 13f62cb and c15a20a.

⛔ Files ignored due to path filters (3)
  • vendor/github.com/openshift/library-go/pkg/authorization/authorizationutil/subject.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/library-go/pkg/authorization/authorizationutil/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/modules.txt is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (17)
  • hack/Makefile.debug
  • pkg/cmd/infra/router/clientcmd.go
  • pkg/cmd/infra/router/template.go
  • pkg/router/controller/contention.go
  • pkg/router/controller/factory/factory_endpointslices_test.go
  • pkg/router/controller/route_secret_manager.go
  • pkg/router/controller/route_secret_manager_test.go
  • pkg/router/controller/shared_secret_manager.go
  • pkg/router/controller/shared_secret_manager_test.go
  • pkg/router/controller/status.go
  • pkg/router/controller/status_test.go
  • pkg/router/routeapihelpers/validation.go
  • pkg/router/routeapihelpers/validation_test.go
  • pkg/router/router_test.go
  • pkg/router/template/configmanager/haproxy/testing/haproxy.go
  • pkg/router/writerlease/writerlease.go
  • pkg/router/writerlease/writerlease_test.go
💤 Files with no reviewable changes (4)
  • pkg/router/controller/shared_secret_manager_test.go
  • pkg/cmd/infra/router/clientcmd.go
  • pkg/router/controller/shared_secret_manager.go
  • pkg/router/controller/status_test.go

Comment on lines +80 to +88
func newEndpointSliceTestSetup(plugin router.Plugin, initialObjects ...runtime.Object) (*fakekubeclient.Clientset, chan struct{}) {
stopCh := make(chan struct{})
client := fakekubeclient.NewSimpleClientset(initialObjects...)
fakeProject := &fakeproject.FakeProjectV1{}

// WatchListClient featuregate is enabled by default since v0.35. Fake client does not support
// initializing its cache from Watch, so falling back to use List instead. The envvar below
// configures the featuregate state.
t.Setenv("KUBE_FEATURE_"+string(features.WatchListClient), "False")
os.Setenv("KUBE_FEATURE_"+string(features.WatchListClient), "False")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching factory_endpointslices_test.go:\n'
fd -a 'factory_endpointslices_test.go' . || true

file="$(fd 'factory_endpointslices_test.go' . | head -n 1 || true)"
if [ -n "${file:-}" ]; then
  printf '\nFile: %s\n' "$file"
  wc -l "$file"
  printf '\nLines 1-140:\n'
  sed -n '1,140p' "$file" | nl -ba
fi

printf '\nSearch for newEndpointSliceTestSetup and related env usage:\n'
rg -n "newEndpointSliceTestSetup|WatchListClient|KUBE_FEATURE_WatchListClient|Setenv" . --glob '*.go'

Repository: openshift/router

Length of output: 473


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="./pkg/router/controller/factory/factory_endpointslices_test.go"

printf 'File: %s\n' "$file"
wc -l "$file"

printf '\nLines 1-140:\n'
sed -n '1,140p' "$file" | awk '{printf "%6d  %s\n", NR, $0}'

printf '\nSearch for watcher helper and env usage:\n'
rg -n "newEndpointSliceTestSetup|WatchListClient|KUBE_FEATURE_WatchListClient|Setenv|CheckError" "$file" ./pkg/router/controller/factory --glob '*.go' || true

printf '\nDiff/stat context if available:\n'
git diff --stat HEAD^..HEAD 2>/dev/null || true
git diff -- pkg/router/controller/factory/factory_endpointslices_test.go 2>/dev/null | sed -n '1,220p' || true

Repository: openshift/router

Length of output: 8208


🌐 Web query:

Go testing.T.Setenv environment variable set automatically restored after test error no ignore os.Setenv documentation

💡 Result:

Yes, the environment variable changes made using testing.T.Setenv (as well as B.Setenv and F.Setenv) are automatically restored after the test finishes, regardless of whether the test passes or errors out [1][2][3]. When you call t.Setenv(key, value), the testing package internally performs the following steps: 1. It records the current state of the environment variable (if it exists) [2]. 2. It calls os.Setenv to update the variable [1][2]. 3. It registers a cleanup function using the t.Cleanup method to restore the variable to its original state (or unset it if it did not exist) once the test completes [1][2]. Because t.Cleanup is used, the restoration logic is guaranteed to run after the test function returns, even if the test fails or calls T.Fatal/T.Fatalf [1][2]. Important considerations regarding t.Setenv: - Process-Global Impact: Since environment variables are process-wide, t.Setenv cannot be used in parallel tests [1][4]. If you attempt to call t.Setenv in a test that has called T.Parallel (or if any of its ancestors are parallel), the testing framework will panic [5][2][4]. This safeguard prevents cross-test interference and potential race conditions associated with changing environment variables in a concurrent environment [6][4]. - No Manual Cleanup Required: You do not need to manually call os.Unsetenv or attempt to restore the variable yourself when using t.Setenv, as the testing framework handles this automatically and reliably [6][4][3].

Citations:


Keep feature-gate configuration test-scoped.

newEndpointSliceTestSetup sets WatchListClient=False globally with os.Setenv, so later endpoint-slice tests can inherit this state, and the error return is ignored. Change the helper to accept *testing.T and use t.Setenv(...).

🤖 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 `@pkg/router/controller/factory/factory_endpointslices_test.go` around lines 80
- 88, Update newEndpointSliceTestSetup to accept *testing.T and replace the
global os.Setenv call for the WatchListClient feature gate with t.Setenv,
preserving the existing disabled value while ensuring automatic test-scoped
cleanup.

Source: Path instructions

Comment on lines 115 to 133
type statusRecorder struct {
sync.Mutex
rejections []string
updates []string
unservableInFutureVersions map[string]string
doneCh chan struct{}
}

func (r *statusRecorder) routeKey(route *routev1.Route) string {
return route.Namespace + "-" + route.Name
}
func (r *statusRecorder) RecordRouteRejection(route *routev1.Route, reason, message string) {
r.Lock()
defer r.Unlock()
defer close(r.doneCh)
r.rejections = append(r.rejections, fmt.Sprintf("%s:%s", r.routeKey(route), reason))
}

func (r *statusRecorder) RecordRouteUpdate(route *routev1.Route, reason, message string) {
r.Lock()
defer r.Unlock()
defer close(r.doneCh)
r.updates = append(r.updates, fmt.Sprintf("%s:%s", r.routeKey(route), reason))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

statusRecorder completion signalling and state are not concurrency-safe. Both sites stem from one root cause: the informer goroutine mutates recorder state and closes doneCh while the test goroutine reads that state and swaps the channel.

  • pkg/router/controller/route_secret_manager_test.go#L115-L133: guard rejections/updates with a mutex (restore guarded getters) and close doneCh via sync.Once.
  • pkg/router/controller/route_secret_manager_test.go#L1409-L1417: stop reassigning recorder.doneCh; use a buffered event channel the recorder sends on so each phase can be awaited without mutating shared state.
📍 Affects 1 file
  • pkg/router/controller/route_secret_manager_test.go#L115-L133 (this comment)
  • pkg/router/controller/route_secret_manager_test.go#L1409-L1417
🤖 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 `@pkg/router/controller/route_secret_manager_test.go` around lines 115 - 133,
The statusRecorder completion signalling and state access are not
concurrency-safe. In pkg/router/controller/route_secret_manager_test.go:115-133,
add mutex protection for rejections and updates, restore guarded getters, and
use sync.Once when closing doneCh. In
pkg/router/controller/route_secret_manager_test.go:1409-1417, stop reassigning
recorder.doneCh; instead use a buffered event channel sent by statusRecorder so
each phase can be awaited without mutating shared state.

Comment on lines 311 to +314
DeleteFunc: func(obj interface{}) {
secret, ok := obj.(*kapi.Secret)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
log.Error(nil, "Couldn't get object from tombstone", "type", fmt.Sprintf("%T", obj))
return
}
secret, ok = tombstone.Obj.(*kapi.Secret)
if !ok {
log.Error(nil, "Tombstone contained object that is not a secret", "type", fmt.Sprintf("%T", tombstone.Obj))
return
}
}
secret := obj.(*kapi.Secret)
key := generateKey(namespace, routeName)
msg := fmt.Sprintf("external certificate validation failed: secret %q deleted for route %q", secret.Name, key)
msg := fmt.Sprintf("secret %q deleted for route %q", secret.Name, key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Unchecked type assertion in DeleteFunc can panic on a tombstone.

Shared informers deliver cache.DeletedFinalStateUnknown when a delete is missed (watch gap / resync), so obj.(*kapi.Secret) will panic and take down the router process. The tombstone unwrapping that previously guarded this was dropped here.

🛡️ Proposed fix
 		DeleteFunc: func(obj interface{}) {
-			secret := obj.(*kapi.Secret)
+			secret, ok := obj.(*kapi.Secret)
+			if !ok {
+				tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+				if !ok {
+					log.V(4).Info("unexpected object type in DeleteFunc", "namespace", namespace, "route", routeName)
+					return
+				}
+				secret, ok = tombstone.Obj.(*kapi.Secret)
+				if !ok {
+					log.V(4).Info("unexpected tombstone object type in DeleteFunc", "namespace", namespace, "route", routeName)
+					return
+				}
+			}
 			key := generateKey(namespace, routeName)
 			msg := fmt.Sprintf("secret %q deleted for route %q", secret.Name, key)
📝 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
DeleteFunc: func(obj interface{}) {
secret, ok := obj.(*kapi.Secret)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
log.Error(nil, "Couldn't get object from tombstone", "type", fmt.Sprintf("%T", obj))
return
}
secret, ok = tombstone.Obj.(*kapi.Secret)
if !ok {
log.Error(nil, "Tombstone contained object that is not a secret", "type", fmt.Sprintf("%T", tombstone.Obj))
return
}
}
secret := obj.(*kapi.Secret)
key := generateKey(namespace, routeName)
msg := fmt.Sprintf("external certificate validation failed: secret %q deleted for route %q", secret.Name, key)
msg := fmt.Sprintf("secret %q deleted for route %q", secret.Name, key)
DeleteFunc: func(obj interface{}) {
secret, ok := obj.(*kapi.Secret)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
log.V(4).Info("unexpected object type in DeleteFunc", "namespace", namespace, "route", routeName)
return
}
secret, ok = tombstone.Obj.(*kapi.Secret)
if !ok {
log.V(4).Info("unexpected tombstone object type in DeleteFunc", "namespace", namespace, "route", routeName)
return
}
}
key := generateKey(namespace, routeName)
msg := fmt.Sprintf("secret %q deleted for route %q", secret.Name, key)
🤖 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 `@pkg/router/controller/route_secret_manager.go` around lines 311 - 314, Update
the DeleteFunc callback to safely handle informer tombstones before accessing
the deleted secret. Unwrap cache.DeletedFinalStateUnknown and extract its
underlying *kapi.Secret, while preserving direct *kapi.Secret handling and
avoiding a panic for unexpected object types; then continue generating the
existing deletion message.

Comment on lines +253 to +255
log.V(4).Info("follower awaiting lease expiration", "worker", l.name, "key", key, "leaseTimeRemaining", remaining)
time.Sleep(remaining)
l.queue.Add(key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)writerlease\.go$|pkg/router/writerlease'

echo "== file outline =="
ast-grep outline pkg/router/writerlease/writerlease.go --view expanded || true

echo "== relevant lines 220-275 =="
sed -n '220,275p' pkg/router/writerlease/writerlease.go | nl -ba -v220

echo "== search worker/run fields and queue usage =="
rg -n "type .*Queue|struct Worker|func .*Worker|l\.queue\.AddAfter|time\.Sleep|Run\(\)|stop|quit|context|Done\(key\)" pkg/router/writerlease/writerlease.go

Repository: openshift/router

Length of output: 2411


🌐 Web query:

kubernetes client-go workqueue Type.DelayingInterface AddAfter Sleep context stopable example

💡 Result:

In Kubernetes client-go, the workqueue's DelayingInterface (and its Typed equivalent) provides an AddAfter method to enqueue items after a specified delay [1][2][3]. Crucially, the workqueue implementation is designed to handle stop signals gracefully via its ShutDown method rather than through a context.Context passed to individual AddAfter calls [1][2][4]. When you call AddAfter(item, duration), the implementation internally uses a select block with a stop channel (stopCh) [1][2]: select { case <-q.stopCh: // Stops the addition if the queue has been shut down case q.waitingForAddCh <- &waitFor[T]{data: item, readyAt: q.clock.Now.Add(duration)}: // Adds the item to the delayed processing queue } Key points regarding your requirements: 1. Stopability: The queue becomes "stopable" by calling its ShutDown method [4]. This closes the stopCh, which allows pending AddAfter operations to unblock and prevents new items from being added [1][2][4]. There is no native support for passing a context.Context to AddAfter to interrupt it externally. 2. Avoiding Blocking: AddAfter is designed to be non-blocking under normal operation; it sends the item to a channel (waitingForAddCh) consumed by a dedicated "waitingLoop" goroutine [1][2]. It only blocks if the channel is full or if the system is shutting down, at which point the stopCh case takes precedence [1][2]. 3. Best Practice for Workers: To stop worker goroutines that are blocked on the standard queue.Get call, the standard pattern is to call ShutDown on the queue [4][5]. This causes Get to return a shutdown flag (true), signaling your worker loop to terminate gracefully [4][5]. If you need to implement a complex shutdown involving external contexts, you must manage this outside the workqueue by checking your context before calling AddAfter or by triggering the queue's ShutDown when your context is cancelled. Note that attempts to add custom context-awareness to the queue itself (such as making Get interruptible) have been discussed in the community, but are not currently supported features [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== writerlease lines 136-265 =="
sed -n '136,265p' pkg/router/writerlease/writerlease.go

echo "== test shutdown references =="
sed -n '1,260p' pkg/router/writerlease/writerlease_test.go

Repository: openshift/router

Length of output: 6213


Do not block the sole worker while deferring follower work.

time.Sleep(remaining) cannot be interrupted by Run’s shutdown path, so the worker remains stuck until the remaining lease time elapses. Requeue with the delaying queue so shutdown can stop the worker.

Proposed fix
-			time.Sleep(remaining)
-			l.queue.Add(key)
+			l.queue.AddAfter(key, remaining)
 			l.queue.Done(key)
📝 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
log.V(4).Info("follower awaiting lease expiration", "worker", l.name, "key", key, "leaseTimeRemaining", remaining)
time.Sleep(remaining)
l.queue.Add(key)
log.V(4).Info("follower awaiting lease expiration", "worker", l.name, "key", key, "leaseTimeRemaining", remaining)
l.queue.AddAfter(key, remaining)
l.queue.Done(key)
🤖 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 `@pkg/router/writerlease/writerlease.go` around lines 253 - 255, Replace the
blocking time.Sleep(remaining) in the follower lease-expiration path with the
delaying queue mechanism, reusing l.queue to schedule key after the remaining
duration. Preserve the existing log and avoid directly blocking the worker so
Run shutdown can interrupt it.

@neisw

neisw commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/retest-required

@neisw

neisw commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

/lgtm
/label approved
/label docs-approved
/label px-approved
/verified by payload-jobs

@openshift-ci openshift-ci Bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. docs-approved Signifies that Docs has signed off on this PR px-approved Signifies that Product Support has signed off on this PR labels Jul 26, 2026
@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Jul 26, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@neisw: This PR has been marked as verified by payload-jobs.

Details

In response to this:

/lgtm
/label approved
/label docs-approved
/label px-approved
/verified by payload-jobs

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 lgtm Indicates that a PR is ready to be merged. label Jul 26, 2026
@openshift-merge-bot
openshift-merge-bot Bot merged commit 42b2fe7 into openshift:master Jul 26, 2026
13 checks passed
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. docs-approved Signifies that Docs has signed off on this PR 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. px-approved Signifies that Product Support has signed off on this PR ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants