Skip to content

fix(K8sPipelineClient): tolerate pod/job deserialization errors from newer Kubernetes (fixes HTTP 400 on ingestionPipelines/status)#29791

Open
MichaelRBlack wants to merge 3 commits into
open-metadata:mainfrom
MichaelRBlack:fix/k8s-pipeline-client-tolerate-newer-k8s-pod-status
Open

fix(K8sPipelineClient): tolerate pod/job deserialization errors from newer Kubernetes (fixes HTTP 400 on ingestionPipelines/status)#29791
MichaelRBlack wants to merge 3 commits into
open-metadata:mainfrom
MichaelRBlack:fix/k8s-pipeline-client-tolerate-newer-k8s-pod-status

Conversation

@MichaelRBlack

@MichaelRBlack MichaelRBlack commented Jul 6, 2026

Copy link
Copy Markdown

Fixes #29792

Problem

On Kubernetes clusters newer than the bundled io.kubernetes:client-java models, K8sPipelineClient fails to read pod/job status. Listing pods/jobs throws an IllegalArgumentException during response deserialization because the pod status carries fields the client models don't define:

The field `allocatedResources` in the JSON string is not defined in the `V1PodStatus` properties.
JSON: {"observedGeneration":1,"phase":"Running","conditions":[...]}

That exception is a RuntimeException, not an ApiException, so it is uncaught and surfaces as HTTP 400 from GET /api/v1/services/ingestionPipelines/status. In the UI this blanks the database service Agents/Ingestion tab (no pipelines/status render). It also affects the queued-status and ingestion-log paths, which parse pods the same way.

Environment / repro

  • OpenMetadata 1.13 (bundled client-java 24.0.0), pipelineServiceClientConfig.type: k8s
  • AWS EKS Kubernetes 1.36; reproducible on any Kubernetes newer than the client models
  • Open a database service → Agents tab, or GET /api/v1/services/ingestionPipelines/status → 400

Why not just bump client-java?

allocatedResources / observedGeneration are recent Kubernetes pod-status additions. The latest client-java (26.0.0, ~k8s 1.34) still trails current clusters (e.g. 1.36), and the client's strict "reject unknown fields" deserialization means a version bump only defers the same break to the next Kubernetes release (a treadmill). A public lenient/ignore-unknown-fields switch is not exposed by client-java (the isLenientOnJson flag has no public setter).

Fix

Make the three pod/job read paths in K8sPipelineClient resilient to client-side deserialization failures. A RuntimeException from these calls means the API server responded (real access/RBAC errors are ApiException, still handled) but the bundled models couldn't parse a newer-Kubernetes field — cluster access is fine, so degrade gracefully instead of failing:

  • getServiceStatusInternal() — report healthy instead of 400-ing the status endpoint
  • getQueuedPipelineStatusInternal() — treat as no queued jobs
  • ingestion log fetch — return a graceful "logs unavailable" message

This keeps K8sPipelineClient's UI functional across Kubernetes upgrades rather than breaking whenever the cluster adds a status field ahead of client-java.

Notes

  • Additive catch (RuntimeException e) clauses only; no behavior change on the success or ApiException paths.
  • Opened as draft: contributed from a downstream deployment; I was not able to run the full openmetadata-service Maven build locally, so I'm relying on project CI. Happy to narrow the caught type (e.g. IllegalArgumentException) or add a unit test if maintainers prefer.

🤖 Generated with Claude Code

Greptile Summary

This PR makes Kubernetes pipeline status and log reads tolerate newer cluster fields. The main changes are:

  • Catch IllegalArgumentException from queued job status reads.
  • Return a healthy service status when pod or job deserialization fails.
  • Return a graceful log message when pod deserialization fails during log fetch.

Confidence Score: 4/5

This is close, but the fallback behavior should be fixed before merging.

  • The service status fallback can report Kubernetes as available before all required resource checks finish.
  • The log fallback can return a parsing error as normal log content.

openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java Adds Kubernetes client deserialization fallbacks for queued status, service status, and log retrieval.

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/k8s-pipelin..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used (3)

  • Context used - CLAUDE.md (source)
  • Context used - openmetadata-ui-core-components/CLAUDE.md (source)
  • Context used - AGENTS.md (source)

…newer Kubernetes

On clusters newer than the bundled kubernetes `client-java` models, listing
pods/jobs throws an `IllegalArgumentException` while deserializing the response,
because the pod status carries fields the models don't define, e.g.:

    The field `allocatedResources` in the JSON string is not defined in the
    `V1PodStatus` properties

That `IllegalArgumentException` is a `RuntimeException`, not an `ApiException`, so
it was uncaught and surfaced as HTTP 400 from
`GET /services/ingestionPipelines/status` — which blanks the service
Agents/Ingestion tab in the UI. Reproduced on EKS Kubernetes 1.36 with
OpenMetadata 1.13 (client-java 24.0.0). The latest client-java (26.0.0) still
trails recent Kubernetes, so bumping the dependency is not a durable fix.

Make the three pod/job read paths resilient to client-side deserialization
failures (the API call itself succeeded, so cluster access is fine):
- getServiceStatusInternal(): report healthy instead of failing the status endpoint
- getQueuedPipelineStatusInternal(): treat as no queued jobs
- the ingestion log-fetch path: return a graceful "logs unavailable" message

This keeps K8sPipelineClient's UI working across Kubernetes upgrades instead of
breaking whenever the cluster adds a status field ahead of client-java.

Signed-off-by: Michael Black <michael.black@pura.com>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@MichaelRBlack MichaelRBlack marked this pull request as ready for review July 6, 2026 20:36
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

✅ PR checks passed

The linked issue has a description and all required Shipping project fields set. Thanks!

…errors)

Per review feedback (gitar-bot): catching the whole RuntimeException hierarchy
could swallow genuine bugs (NPE, IllegalStateException, uninitialized API
clients) and, in getServiceStatusInternal(), report an unhealthy cluster as
healthy. Narrow all three handlers to IllegalArgumentException — the exact
exception client-java throws on unknown pod/job status fields — so real runtime
failures still propagate while version-skew deserialization degrades gracefully.

Signed-off-by: Michael Black <michael.black@pura.com>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment on lines +1013 to +1031
} catch (IllegalArgumentException e) {
// The list calls above reached the API server successfully (any real
// access/RBAC failure would be an ApiException, handled above). An
// IllegalArgumentException here is thrown by the bundled Kubernetes client
// (client-java) when it cannot DESERIALIZE a pod/job in the response —
// typically because the
// cluster runs a newer Kubernetes than the client models and the response
// carries an unmodeled status field, e.g.:
// IllegalArgumentException: The field `allocatedResources` in the JSON
// string is not defined in the `V1PodStatus` properties
// Cluster access is fine, so report healthy instead of failing the whole
// /services/ingestionPipelines/status endpoint (which blanks the UI).
LOG.warn(
"Kubernetes namespace {} is reachable but a pod/job could not be deserialized "
+ "(client-java models likely lag the cluster's Kubernetes version): {}",
namespace,
e.getMessage());
return buildHealthyStatus(getKubernetesVersion())
.withReason(String.format(K8S_AVAILABLE_FORMAT, namespace, serviceAccount));

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.

P1 Health check skips verification This branch returns a healthy status as soon as pod or job deserialization throws. Those list calls run before the ConfigMap and Secret reads, so a newer-cluster pod field can jump here before the service account proves it can read the required resources. In that case the status and validation endpoints report Kubernetes as available even when ConfigMap or Secret access is broken. The fallback should not mark the service healthy unless the remaining required checks have still been verified.

+ "the cluster's Kubernetes version): {}",
pipelineName,
e.getMessage());
return Map.of("logs", FAILED_LOGS_MESSAGE + e.getMessage());

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.

P1 Errors become log content This fallback still returns the deserialization failure under the normal logs payload key. The log response path treats that key as stream content, so users receive a regular log line containing the error message and the stream ends normally. A Kubernetes client parsing failure should be returned in an error-shaped response, not mixed into pipeline output.

@harshach harshach added the safe to test Add this label to run secure Github workflows on PRs label Jul 6, 2026
@harshach harshach added the To release Will cherry-pick this PR into the release branch label Jul 6, 2026
Comment on lines +1013 to +1031
} catch (IllegalArgumentException e) {
// The list calls above reached the API server successfully (any real
// access/RBAC failure would be an ApiException, handled above). An
// IllegalArgumentException here is thrown by the bundled Kubernetes client
// (client-java) when it cannot DESERIALIZE a pod/job in the response —
// typically because the
// cluster runs a newer Kubernetes than the client models and the response
// carries an unmodeled status field, e.g.:
// IllegalArgumentException: The field `allocatedResources` in the JSON
// string is not defined in the `V1PodStatus` properties
// Cluster access is fine, so report healthy instead of failing the whole
// /services/ingestionPipelines/status endpoint (which blanks the UI).
LOG.warn(
"Kubernetes namespace {} is reachable but a pod/job could not be deserialized "
+ "(client-java models likely lag the cluster's Kubernetes version): {}",
namespace,
e.getMessage());
return buildHealthyStatus(getKubernetesVersion())
.withReason(String.format(K8S_AVAILABLE_FORMAT, namespace, serviceAccount));

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.

P1 Health Checks Skip Verification This branch returns a healthy status as soon as pod or job deserialization throws. Those list calls run before the ConfigMap and Secret reads, so a newer-cluster pod field can jump here before the service account proves it can read the resources this client needs. The status endpoint can then report Kubernetes as available even when required ConfigMap or Secret access is broken.

Comment on lines +1158 to +1167
} catch (IllegalArgumentException e) {
// client-java could not deserialize a pod returned by a newer Kubernetes
// than its models (see getServiceStatusInternal). Return a graceful message
// instead of propagating the error.
LOG.warn(
"Could not deserialize pod while fetching logs for pipeline {} (client-java lags "
+ "the cluster's Kubernetes version): {}",
pipelineName,
e.getMessage());
return Map.of("logs", FAILED_LOGS_MESSAGE + e.getMessage());

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.

P1 Errors Become Logs This fallback returns the deserialization failure under the normal logs key. The log response path treats that key as stream content, so users receive the Kubernetes parsing error as an ordinary pipeline log line and the stream ends normally. A client-side parsing failure should use an error-shaped response instead of being mixed into pipeline output.

@gitar-bot

gitar-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Adds graceful handling for deserialization errors in K8sPipelineClient, ensuring UI functionality on newer Kubernetes clusters. Resolved the broad exception handling by narrowing the catch clause to IllegalArgumentException.

✅ 1 resolved
Bug: Broad catch(RuntimeException) masks real errors; reports cluster healthy

📄 openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java:933-940 📄 openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java:1013-1027 📄 openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java:1157-1166
All three new handlers catch the entire RuntimeException hierarchy, but the intent (per the comments and PR description) is only to tolerate client-java deserialization failures, which surface as IllegalArgumentException ("The field allocatedResources ... is not defined in the V1PodStatus properties"). Catching RuntimeException also swallows genuine programming/runtime bugs — e.g. NullPointerException from job.getMetadata()/latestPod.getMetadata(), IllegalStateException, NumberFormatException, or misconfiguration such as an uninitialized coreApi/batchApi.

The most dangerous case is getServiceStatusInternal() (line 1013): on ANY unexpected RuntimeException it now returns buildHealthyStatus(...). A real outage or bug would be reported to the UI as a healthy Kubernetes connection, hiding the failure instead of surfacing it. The queued-status path silently returns no jobs and the log path returns a generic message, similarly masking bugs unrelated to version skew.

Suggested fix: narrow the caught type to IllegalArgumentException (the actual deserialization exception thrown by client-java), as the author offered in the PR notes. This preserves the graceful-degradation intent while letting genuine runtime failures propagate/surface normally. If broader coverage is desired, at minimum log at ERROR (not WARN) and consider inspecting the exception type/message before treating it as version skew.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (20 flaky)

✅ 4090 passed · ❌ 0 failed · 🟡 20 flaky · ⏭️ 22 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 2 818 0 5 4
🟡 Shard 3 816 0 1 12
🟡 Shard 4 814 0 5 5
🟡 Shard 5 828 0 2 0
🟡 Shard 6 814 0 7 1
🟡 20 flaky test(s) (passed on retry)
  • Features/BulkImport.spec.ts › Database Schema (shard 2, 1 retry)
  • Features/ContextCenterArticles.spec.ts › Article listing search filters, clears, and shows empty state (shard 2, 1 retry)
  • Features/ContextCenterArticles.spec.ts › Article edit persistence and unsaved title behavior are correct (shard 2, 1 retry)
  • Features/ContextCenterMemories.spec.ts › closing the modal removes ?memory= param from the URL (shard 2, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with view-only permission sees no Add Memory button, no row actions, and no modal action buttons (shard 2, 1 retry)
  • Features/Table.spec.ts › should persist page size (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Integer (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Should search custom properties for storedProcedure in right panel (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Date (shard 4, 1 retry)
  • Pages/DataContractInheritance.spec.ts › Partial Contract Inheritance - Asset contract merges with Data Product contract (shard 4, 1 retry)
  • Pages/DataContractInheritance.spec.ts › Delete Asset Contract - Falls back to showing inherited contract from Data Product (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Subdomain rename does not affect parent domain and updates nested children (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should allow Data Steward to view all tabs for searchIndex (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary Bulk Import Export (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Import partial success - some terms pass, some fail (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 6, 1 retry)
  • Pages/TagPageRightPanel.spec.ts › Should open right panel when clicking asset in tag assets page (shard 6, 1 retry)
  • Pages/TestSuite.spec.ts › Logical TestSuite (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

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

Labels

safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

2 participants