Skip to content

OCPBUGS-81512: Use ETag conditional requests for OpenAPI v2 fetching#16457

Merged
openshift-merge-bot[bot] merged 3 commits into
openshift:mainfrom
rhamilto:OCPBUGS-81512
May 19, 2026
Merged

OCPBUGS-81512: Use ETag conditional requests for OpenAPI v2 fetching#16457
openshift-merge-bot[bot] merged 3 commits into
openshift:mainfrom
rhamilto:OCPBUGS-81512

Conversation

@rhamilto
Copy link
Copy Markdown
Member

@rhamilto rhamilto commented May 18, 2026

Analysis / Root cause:

The console fetches the full OpenAPI v2 schema (/api/kubernetes/openapi/v2) at startup, after every API discovery cycle, and on a 5-minute polling interval — all without HTTP conditional request headers (ETag / If-None-Match). On clusters with 1000+ CRDs, the OpenAPI spec grows to 50MB+, resulting in repeated full downloads of an unchanged payload.

The fetchSwagger() function used coFetchJSON, which discards the Response object (and its headers) after parsing JSON. This made it impossible to capture the ETag header for conditional requests.

Solution description:

  • Switch from coFetchJSON to coFetch to access response headers
  • Cache the ETag header from each successful response
  • Send If-None-Match with the cached ETag on subsequent requests
  • On 304 Not Modified, return the already-cached definitions without re-downloading or re-parsing the payload
  • Falls back gracefully when the server doesn't return an ETag (behavior identical to before)
  • Remove the legacy localStorage cleanup code that was marked as safe to remove in a future release

Screenshots / screen recording:
Screenshot 2026-05-19 at 9 52 22 AM
Screenshot 2026-05-19 at 9 52 55 AM
Screenshot 2026-05-19 at 9 53 02 AM

Test setup:
A running OpenShift cluster. No special setup required for unit tests (yarn test public/module/k8s/__tests__/swagger.spec.ts).

Test cases:

  • First load: 200 response with ETag header present, definitions returned
  • Subsequent polls: If-None-Match header sent, 304 response, no payload transferred, cached definitions returned
  • After CRD install/uninstall: server returns 200 with new ETag, definitions update
  • Server without ETag support: no If-None-Match sent, behaves same as before
  • Missing definitions in response: returns null
  • Network error: returns null

Browser conformance:

  • Chrome
  • Firefox
  • Safari (or Epiphany on Linux)

Additional info:

Jira: https://redhat.atlassian.net/browse/OCPBUGS-81512

Summary by CodeRabbit

  • Bug Fixes
    • Improved Kubernetes OpenAPI specification caching behavior
    • Resolved unintended removal of locally cached specifications
    • Added support for efficient conditional requests to minimize bandwidth usage

@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 18, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-81512, which is invalid:

  • expected the bug to target either version "5.0." or "openshift-5.0.", but it targets "4.22.0" instead

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:

Analysis / Root cause:

The console fetches the full OpenAPI v2 schema (/api/kubernetes/openapi/v2) at startup, after every API discovery cycle, and on a 5-minute polling interval — all without HTTP conditional request headers (ETag / If-None-Match). On clusters with 1000+ CRDs, the OpenAPI spec grows to 50MB+, resulting in repeated full downloads of an unchanged payload.

The fetchSwagger() function used coFetchJSON, which discards the Response object (and its headers) after parsing JSON. This made it impossible to capture the ETag header for conditional requests.

Solution description:

  • Switch from coFetchJSON to coFetch to access response headers
  • Cache the ETag header from each successful response
  • Send If-None-Match with the cached ETag on subsequent requests
  • On 304 Not Modified, return the already-cached definitions without re-downloading or re-parsing the payload
  • Falls back gracefully when the server doesn't return an ETag (behavior identical to before)
  • Remove the legacy localStorage cleanup code that was marked as safe to remove in a future release

Screenshots / screen recording:

Test setup:
A running OpenShift cluster. No special setup required for unit tests (yarn test public/module/k8s/__tests__/swagger.spec.ts).

Test cases:

  • First load: 200 response with ETag header present, definitions returned
  • Subsequent polls: If-None-Match header sent, 304 response, no payload transferred, cached definitions returned
  • After CRD install/uninstall: server returns 200 with new ETag, definitions update
  • Server without ETag support: no If-None-Match sent, behaves same as before
  • Missing definitions in response: returns null
  • Network error: returns null

Browser conformance:

  • Chrome
  • Firefox
  • Safari (or Epiphany on Linux)

Additional info:

Jira: https://redhat.atlassian.net/browse/OCPBUGS-81512

Reviewers and assignees:

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 openshift-ci-robot added the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label May 18, 2026
@rhamilto
Copy link
Copy Markdown
Member Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added the jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. label May 18, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-81512, 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)
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 openshift-ci-robot removed the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label May 18, 2026
@openshift-ci openshift-ci Bot requested review from TheRealJon and sg00dwin May 18, 2026 19:14
@openshift-ci openshift-ci Bot added component/core Related to console core functionality approved Indicates a PR has been approved by an approver from all required OWNERS files. labels May 18, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-81512, 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:

Analysis / Root cause:

The console fetches the full OpenAPI v2 schema (/api/kubernetes/openapi/v2) at startup, after every API discovery cycle, and on a 5-minute polling interval — all without HTTP conditional request headers (ETag / If-None-Match). On clusters with 1000+ CRDs, the OpenAPI spec grows to 50MB+, resulting in repeated full downloads of an unchanged payload.

The fetchSwagger() function used coFetchJSON, which discards the Response object (and its headers) after parsing JSON. This made it impossible to capture the ETag header for conditional requests.

Solution description:

  • Switch from coFetchJSON to coFetch to access response headers
  • Cache the ETag header from each successful response
  • Send If-None-Match with the cached ETag on subsequent requests
  • On 304 Not Modified, return the already-cached definitions without re-downloading or re-parsing the payload
  • Falls back gracefully when the server doesn't return an ETag (behavior identical to before)
  • Remove the legacy localStorage cleanup code that was marked as safe to remove in a future release

Screenshots / screen recording:

Test setup:
A running OpenShift cluster. No special setup required for unit tests (yarn test public/module/k8s/__tests__/swagger.spec.ts).

Test cases:

  • First load: 200 response with ETag header present, definitions returned
  • Subsequent polls: If-None-Match header sent, 304 response, no payload transferred, cached definitions returned
  • After CRD install/uninstall: server returns 200 with new ETag, definitions update
  • Server without ETag support: no If-None-Match sent, behaves same as before
  • Missing definitions in response: returns null
  • Network error: returns null

Browser conformance:

  • Chrome
  • Firefox
  • Safari (or Epiphany on Linux)

Additional info:

Jira: https://redhat.atlassian.net/browse/OCPBUGS-81512

Reviewers and assignees:

Summary by CodeRabbit

  • Bug Fixes
  • Improved Kubernetes OpenAPI specification caching behavior
  • Resolved unintended removal of locally cached specifications
  • Added support for efficient conditional requests to minimize bandwidth usage

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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR implements ETag-based HTTP caching for Kubernetes OpenAPI specification fetching. The implementation updates fetchSwagger to use coFetch with explicit JSON headers, maintains an in-memory ETag, and includes If-None-Match headers on conditional requests. When the server responds with HTTP 304, the function reuses cached definitions; otherwise, it parses the response and updates both the cache and ETag. The change removes prior localStorage cleanup behavior and adds console_swagger_refresh event dispatch on successful responses. A comprehensive Jest test suite verifies first-call fetching, ETag header propagation, 304 cache hits, definition updates, event dispatch timing, error cases, and server responses lacking ETags.

🚥 Pre-merge checks | ✅ 12
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically references the Jira issue (OCPBUGS-81512) and accurately summarizes the main technical change: implementing ETag-based conditional requests for OpenAPI v2 schema fetching.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Jest test names in swagger.spec.ts are stable and deterministic with no dynamic content (pod names, timestamps, UUIDs, namespaces, IPs). All test titles use static, descriptive BDD language.
Test Structure And Quality ✅ Passed Custom check not applicable. Specifies Ginkgo test review, but PR contains Jest tests (TypeScript/JavaScript frontend) not Go tests.
Microshift Test Compatibility ✅ Passed This PR adds Jest unit tests, not Ginkgo e2e tests. The custom check applies only to Ginkgo tests. These are frontend unit tests with mocked dependencies—not cluster-facing.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This PR adds only Jest unit tests (frontend/public/module/k8s/tests/swagger.spec.ts), not Ginkgo e2e tests. The SNO compatibility check applies to Ginkgo e2e tests only. Not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed PR modifies frontend UI utilities for OpenAPI spec caching. No deployment manifests, operator code, controllers, or scheduling constraints are introduced. Check is not applicable.
Ote Binary Stdout Contract ✅ Passed Not applicable: PR modifies frontend TypeScript code, not OTE Go binaries. Check targets process-level stdout in Go binary entry points. No violations found.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Not applicable. PR adds Jest unit tests for TypeScript frontend module, not Ginkgo e2e tests. Tests use mocked HTTP with no IPv4 assumptions or external connectivity.
Description check ✅ Passed PR description is comprehensive and well-structured, covering root cause, solution, test cases, and links to Jira tracking. All required sections are filled with substantive detail.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@frontend/public/module/k8s/__tests__/swagger.spec.ts`:
- Around line 31-37: The test's 304 mock (create304Response) incorrectly sets
ok: true and the assertion checks a new Response instead of the actual mock,
causing a false-positive; update create304Response to set ok: false, keep json:
jest.fn() on the returned object, and change the test assertion in the swagger
spec to verify that the json mock on the returned create304Response instance was
called (i.e., assert the json jest.fn() of the same Response object passed to
fetchSwagger was invoked) so the test truly verifies fetchSwagger called
response.json() on the real 304 response.

In `@frontend/public/module/k8s/swagger.ts`:
- Line 25: The code has a race where cachedETag and swaggerDefinitions are
assigned separately (symbols: cachedETag, swaggerDefinitions), allowing a
request to observe the new ETag but old definitions; fix by coupling them into a
single atomic cache object (e.g., cached = { etag, definitions }) and update all
reads/writes to use that single object so assignments and returns are atomic,
then replace all usages that read cachedETag or swaggerDefinitions (including
the If-None-Match check and the return path) to read from the unified cached
object.
🪄 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: 72b85227-8bda-471f-a35e-d86d5b1e7c69

📥 Commits

Reviewing files that changed from the base of the PR and between c943917 and ec67f7b.

📒 Files selected for processing (2)
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
  • frontend/public/module/k8s/swagger.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (11)
frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx,js,jsx}: Never import from package index files (e.g., @console/shared) in new code, as they can create circular dependencies and slow builds. Import from specific file paths instead.
Do not use backticks in t() calls for i18n strings, as the i18n parser cannot extract keys from template literals. Use single or double quotes instead.

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import from deprecated packages or use code with the @deprecated TSdoc tag in new code.

**/*.{ts,tsx}: Use React functional components with hooks instead of class components
State Management: Use React hooks and Context API (migrating away from legacy Redux/Immutable.js)
Hooks: Use existing hooks from console-shared when possible (useK8sWatchResource, useUserSettings, etc.)
API calls: Use k8s resource hooks for data fetching, consoleFetchJSON for HTTP requests
Extensions: Use console extension points for plugin integration
Types: Check existing types in console-shared before creating new ones
Dynamic Plugins: Use console extension points for plugin integration
Styling: Use SCSS modules co-located with components, PatternFly design system components, avoid any SCSS/CSS if possible
Accessibility: Follow WCAG 2.1 AA standards, use semantic HTML, ARIA labels where needed, ensure keyboard navigation, test with screen readers
i18n: Use useTranslation('namespace') hook with key format for translation keys
Error Handling: Use ErrorBoundary components and graceful degradation patterns
Optimize re-renders: Use useCallback for memoized callbacks to avoid function recreation every render
Optimize re-renders: Use useMemo for expensive computations to avoid recalculating on every render
Lazy loading: Use React.lazy() to lazy load heavy components
TypeScript type safety: Avoid using any type; suggest proper type definitions and verify null/undefined are handled properly
Type component props properly: Reuse existing component prop types instead of duplicating type definitions
Use proper hooks: Use specialized hooks like usePluginInfo for plugin data instead of generic data fetching patterns
Avoid deprecated components: Check for JSDoc @deprecated tags, import paths containing /deprecated, and DEPRECATED_ file name prefix before using components
Importing from barrel files and circular dependencies: Import directly from specific files instead...

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
frontend/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Never use absolute URLs or paths in the console code. The console runs behind a proxy under an arbitrary path.

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

When writing code for static plugins, ensure that all $codeRef reference the corresponding extension type from the @console/dynamic-plugin-sdk package.

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (TESTING.md)

**/*.{tsx,ts}: Always use page.getByTestId('x') for Playwright selectors which queries [data-test="x"]. If a React element only has a legacy test attribute, add data-test to the element. Never remove legacy attributes
Prefer data-test attributes in Cypress selectors (e.g., cy.get('[data-test="create-deployment"]')) over brittle CSS/ARIA selectors

File Naming: PascalCase for components, kebab-case for utilities, *.spec.ts(x) for tests

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.{go,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Use lowercase dash-separated names for all files to avoid git issues with case-insensitive file systems

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

**/*.{ts,tsx,js,jsx}: New code MUST be written in TypeScript, not JavaScript
Prefer functional programming patterns and immutable data structures
Run the linter and follow all rules defined in .eslintrc
Never use absolute paths in code - the app should be able to run behind a proxy under an arbitrary path

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.ts

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Plugin SDK Changes: Any updates to console-dynamic-plugin-sdk should aim to maintain backward compatibility as it's a public API - use the plugin-api-review skill to vet changes for public API impact and ensure proper documentation updates

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
frontend/**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (README.md)

frontend/**/*.{js,ts,tsx}: Support only the latest versions of Edge, Chrome, Safari, and Firefox browsers; IE 11 and earlier are not supported
CSP violations should be automatically reported to telemetry by parsing dynamic plugin names from securitypolicyviolation events, with throttling to prevent duplicate reports within a day

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (INTERNATIONALIZATION.md)

For dynamic translation keys that cannot be parsed by i18next-parser (t(key), t('key' + id), t(key${id})), specify possible static values in comments for the parser to extract

Files:

  • frontend/public/module/k8s/swagger.ts
  • frontend/public/module/k8s/__tests__/swagger.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Tests should follow a similar 'test tables' convention as used in Go where applicable

Files:

  • frontend/public/module/k8s/__tests__/swagger.spec.ts
🔀 Multi-repo context openshift/console-operator

Linked repositories findings

openshift/console-operator

  • Kubernetes API server and kube-openapi handlers set and compute ETag and honor If-None-Match / 304 behaviour:
    • vendor/k8s.io/kube-openapi/pkg/handler3/handler.go — computeETag and ETag-related comments/logic. [::openshift/console-operator::vendor/k8s.io/kube-openapi/pkg/handler3/handler.go]
    • vendor/k8s.io/kube-openapi/pkg/handler/handler.go — computeETag usage and ETag comments. [::openshift/console-operator::vendor/k8s.io/kube-openapi/pkg/handler/handler.go]
    • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/etag.go — ServeHTTPWithETag, sets ETag header and returns 304 when If-None-Match matches. [::openshift/console-operator::vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/etag.go]
    • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/peer_aggregated_handler.go — cachedResponseETag usage. [::openshift/console-operator::vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/peer_aggregated_handler.go]

(Repository inspected: file list count and README/go.mod shown.)

Conclusion: server-side components in this repo already compute and expose ETag and support If-None-Match/304 semantics for discovery/OpenAPI endpoints — relevant positive signal for the PR's switch to conditional requests.

🔇 Additional comments (2)
frontend/public/module/k8s/swagger.ts (2)

3-3: LGTM!


29-29: Strong caching strategy for large cluster payloads.

The ETag-based conditional request flow correctly optimizes repeated OpenAPI fetches:

  • Explicit Accept: application/json header clarifies expected response format.
  • Relative path and coFetch usage follow console proxy conventions.
  • Graceful fallback when server omits ETag (sets null, next request sends no If-None-Match).
  • Error handling and event dispatch preserve existing semantics.

This addresses the 50MB+ repeated download problem on clusters with 1000+ CRDs. Once the ETag/definitions synchronization issue is resolved, this will deliver significant bandwidth and parse-time savings.

Also applies to: 33-33, 37-42, 45-46

Comment thread frontend/public/module/k8s/__tests__/swagger.spec.ts
Comment thread frontend/public/module/k8s/swagger.ts
The console fetches the full OpenAPI v2 schema (~50MB+ on large
clusters) at startup, after API discovery, and every 5 minutes without
HTTP conditional request headers. This causes repeated full downloads
of an unchanged payload.

Switch from coFetchJSON to coFetch to access response headers, cache
the ETag from successful responses, and send If-None-Match on
subsequent requests. When the server returns 304 Not Modified, return
the cached definitions without re-downloading or re-parsing the payload.

Also removes the legacy localStorage cleanup code that was marked as
safe to remove in a future release.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@logonoff
Copy link
Copy Markdown
Member

logonoff commented May 19, 2026

QA Verification Evidence

Details
Branch OCPBUGS-81512
Baseline main @ ac110743c8
Candidate OCPBUGS-81512 @ 197eb72ac8
Verified 2026-05-19
Browser Playwright 1.60.0 / Chromium 148
OS Darwin 25.5.0
Jira OCPBUGS-81512

Verification Steps

# Route Action Status
1 /dashboards Navigate, wait for load ✅ pass
2 /api-explorer Navigate, wait for API list ✅ pass
3 /api-resource/ns/default/core~v1~Pod Navigate to Pod API resource ✅ pass
4 /k8s/ns/default/pods Navigate, wait for pod list ✅ pass
5 /k8s/ns/default/core~v1~Pod/~new Navigate to create Pod YAML ✅ pass
6 /dashboards (network) Monitor openapi/v2 requests for ETag ✅ pass
7 /api-explorer?q=QAVerifyWidget Create CRD via CLI, verify cache invalidation ✅ pass

Network Analysis: ETag Caching Behavior

The key change in this PR is adding ETag / If-None-Match conditional request headers to the OpenAPI v2 fetch. Here is the observed network behavior on a cluster with 1000+ CRDs:

Baseline (main) — no conditional requests:

Request Headers Status Payload
Initial load accept: application/json (no If-None-Match) 200 OK Full ~50MB+ spec
Every subsequent poll Same — no conditional headers 200 OK Full ~50MB+ spec re-downloaded

Candidate (OCPBUGS-81512) — ETag caching enabled:

Request Headers Status Payload
Initial load accept: application/json 200 OK Full spec (ETag cached)
Subsequent polls if-none-match: "D838E645..." 304 Not Modified No body transferred
After CRD creation if-none-match: "D838E645..." (old ETag) 200 OK New spec with updated ETag "31FACF97..."
Polls after CRD if-none-match: "31FACF97..." (new ETag) 304 Not Modified No body transferred

CRD Cache Invalidation Test

Created a test CRD (QAVerifyWidget) via CLI to verify the OpenAPI spec updates correctly:

  1. Created CRD qaverifywidgets.qa.openshift.io with oc apply
  2. Reloaded console — server returned 200 OK with new ETag (spec regenerated with new CRD)
  3. Verified QAVerifyWidget appears in API Explorer
  4. Subsequent polls returned 304 Not Modified with the new ETag
  5. Cleaned up test CRD with oc delete

Unit Tests

8 of 9 tests pass. 1 test fails:

FAIL public/module/k8s/__tests__/swagger.spec.ts
  fetchSwagger
    ✓ should fetch and return swagger definitions on first call
    ✓ should send If-None-Match header on subsequent requests
    ✓ should return cached definitions on 304
    ✓ should update definitions when server returns new data
    ✓ should dispatch console_swagger_refresh on successful fetch
    ✓ should not dispatch console_swagger_refresh on 304
    ✓ should return null when definitions are missing from response
    ✓ should return null on fetch error
    ✕ should work without ETag header from server

Failing test: should work without ETag header from server — when the server doesn't return an ETag header, response.headers.get('ETag') returns null, which is stored in cachedETag. The test expects no If-None-Match header on the next request, but receives If-None-Match: null. This is also visible in the browser network requests where the first request (before any ETag is cached) sends if-none-match: undefined.

Note

Minor bug: The if (cachedETag) guard should prevent sending If-None-Match when cachedETag is undefined or null, but the header is still being sent. The server correctly ignores invalid values and returns 200 OK, so there is no functional impact — but the guard logic should be investigated.

Visual Regression Check

No visual regressions detected. Screenshots are identical between baseline and candidate (expected — this is a logic-only change).

Animated overview (click to expand)
Baseline Candidate
Step 1: Dashboard overview (pass)
Baseline (main) Candidate (OCPBUGS-81512)
Step 2: API Explorer resource list (pass)
Baseline (main) Candidate (OCPBUGS-81512)
Step 3: Pod API resource detail (pass)
Baseline (main) Candidate (OCPBUGS-81512)
Step 4: Pod list page (pass)
Baseline (main) Candidate (OCPBUGS-81512)
Step 5: YAML editor with schema validation (pass)
Baseline (main) Candidate (OCPBUGS-81512)
Step 6: Network ETag evidence (pass)

Candidate-only — showing the dashboard page where network requests were monitored:

Console Errors

No new console errors introduced by this change. Both baseline and candidate show the same 3 pre-existing cluster-specific errors:

  • 404 on metal3.io provisioning (cluster config)
  • 403 on accounts_mgmt subscription check (expected without OCM)
  • API call to get support level has failed (expected without OCM)

Warning

This verification was performed by an AI agent. Results may contain false positives or miss
regressions that require human judgment. Always review the screenshots manually before approving.

Automated QA verification by Claude Code

Comment thread frontend/public/module/k8s/swagger.ts Outdated
Co-authored-by: logonoff <git@logonoff.co>
@rhamilto
Copy link
Copy Markdown
Member Author

/label tide/merge-method-squash

@openshift-ci openshift-ci Bot added the tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. label May 19, 2026
Comment thread frontend/public/module/k8s/swagger.ts Outdated
Copy link
Copy Markdown
Member

@logonoff logonoff left a comment

Choose a reason for hiding this comment

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

/lgtm

@logonoff
Copy link
Copy Markdown
Member

/verified by claude

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label May 19, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@logonoff: This PR has been marked as verified by claude.

Details

In response to this:

/verified by claude

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.

@logonoff
Copy link
Copy Markdown
Member

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label May 19, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 19, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: logonoff, rhamilto

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

@rhamilto
Copy link
Copy Markdown
Member Author

/cherry-pick release-4.22

@openshift-cherrypick-robot
Copy link
Copy Markdown

@rhamilto: once the present PR merges, I will cherry-pick it on top of release-4.22 in a new PR and assign it to you.

Details

In response to this:

/cherry-pick release-4.22

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.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 19, 2026

@rhamilto: 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 f6cdcba into openshift:main May 19, 2026
9 checks passed
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@rhamilto: Jira Issue Verification Checks: Jira Issue OCPBUGS-81512
✔️ 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-81512 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:

Analysis / Root cause:

The console fetches the full OpenAPI v2 schema (/api/kubernetes/openapi/v2) at startup, after every API discovery cycle, and on a 5-minute polling interval — all without HTTP conditional request headers (ETag / If-None-Match). On clusters with 1000+ CRDs, the OpenAPI spec grows to 50MB+, resulting in repeated full downloads of an unchanged payload.

The fetchSwagger() function used coFetchJSON, which discards the Response object (and its headers) after parsing JSON. This made it impossible to capture the ETag header for conditional requests.

Solution description:

  • Switch from coFetchJSON to coFetch to access response headers
  • Cache the ETag header from each successful response
  • Send If-None-Match with the cached ETag on subsequent requests
  • On 304 Not Modified, return the already-cached definitions without re-downloading or re-parsing the payload
  • Falls back gracefully when the server doesn't return an ETag (behavior identical to before)
  • Remove the legacy localStorage cleanup code that was marked as safe to remove in a future release

Screenshots / screen recording:
Screenshot 2026-05-19 at 9 52 22 AM
Screenshot 2026-05-19 at 9 52 55 AM
Screenshot 2026-05-19 at 9 53 02 AM

Test setup:
A running OpenShift cluster. No special setup required for unit tests (yarn test public/module/k8s/__tests__/swagger.spec.ts).

Test cases:

  • First load: 200 response with ETag header present, definitions returned
  • Subsequent polls: If-None-Match header sent, 304 response, no payload transferred, cached definitions returned
  • After CRD install/uninstall: server returns 200 with new ETag, definitions update
  • Server without ETag support: no If-None-Match sent, behaves same as before
  • Missing definitions in response: returns null
  • Network error: returns null

Browser conformance:

  • Chrome
  • Firefox
  • Safari (or Epiphany on Linux)

Additional info:

Jira: https://redhat.atlassian.net/browse/OCPBUGS-81512

Summary by CodeRabbit

  • Bug Fixes
  • Improved Kubernetes OpenAPI specification caching behavior
  • Resolved unintended removal of locally cached specifications
  • Added support for efficient conditional requests to minimize bandwidth usage

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-cherrypick-robot
Copy link
Copy Markdown

@rhamilto: #16457 failed to apply on top of branch "release-4.22":

Applying: OCPBUGS-81512: Use ETag conditional requests for OpenAPI v2 fetching
Using index info to reconstruct a base tree...
M	frontend/public/module/k8s/swagger.ts
Falling back to patching base and 3-way merge...
Auto-merging frontend/public/module/k8s/swagger.ts
CONFLICT (content): Merge conflict in frontend/public/module/k8s/swagger.ts
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 OCPBUGS-81512: Use ETag conditional requests for OpenAPI v2 fetching

Details

In response to this:

/cherry-pick release-4.22

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. component/core Related to console core functionality 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. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. 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