feat(QTDI-2030): Add health and readiness endpoints to component-server - #1245
feat(QTDI-2030): Add health and readiness endpoints to component-server#1245undx wants to merge 11 commits into
Conversation
Introduces GET /api/v1/health (liveness probe) and GET /api/v1/readiness
(readiness probe) as dedicated Kubernetes-style endpoints, replacing the
insufficient /environment endpoint for cluster health monitoring.
Health checks: heap memory availability (configurable threshold),
component index validity, and Vault connectivity via VaultClient.ping().
Readiness check: component index load state (isStarted()).
Both endpoints return {"status":"UP"|"DOWN","cause":"..."} with HTTP 200/503.
Closes QTDI-2030
Co-Authored-By: GitHub Copilot <copilot@noreply.github.com>
Scope & Design Review — Round 1 — APPROVEDFindingsBLOCKER B1 — Wrong path for readiness endpoint (FIXED ✅)
MAJOR M1 — Memory check divide-by-zero risk with unbounded heap (FIXED ✅)
MINOR Mi1 —
|
| Severity | Count |
|---|---|
| Critical (fixed) | 1 |
| Warning (accepted) | 2 |
CRITICAL C1 — catch (final Exception e) in VaultClient.ping() (FIXED ✅)
- Fix: Changed to
catch (final javax.ws.rs.ProcessingException e)— the correct transport-level exception for JAX-RS client failures.
WARNING W1 — catch (final Throwable t) in HealthService.checkComponentIndex() (ACCEPTED)
- Justification: Intentional per approved plan — OOME can only be caught with
Throwable.
WARNING W2 — MockitoAnnotations.openMocks() instead of @ExtendWith(MockitoExtension.class) (ACCEPTABLE)
- Justification:
mockito-junit-jupiter:4.8.1is incompatible with JUnit 5.10.0 on the test classpath.MockitoAnnotations.openMocks()achieves the same result;@InjectMocksand@Mockannotations are still used as prescribed.
AC Coverage
| AC | Covered? |
|---|---|
| GET /api/v1/health → 200 when healthy | ✅ UT + IT |
| GET /api/v1/readiness → 200 when ready | ✅ UT + IT |
| /health → 503 + cause when memory low | ✅ UT |
| /health → 503 + cause when Vault fails | ✅ UT |
| /readiness → 503 + cause when index not ready | ✅ UT |
| /environment independent from health/readiness | ✅ IT |
| Memory threshold is configurable | ✅ config property |
| Vault check uses VaultClient.ping() | ✅ new method added |
| Error message with cause in JSON | ✅ HealthStatus.cause field |
…ess/readiness - Rename /health → /liveness (LivenessResource + LivenessResourceImpl) The /health endpoint was misleading; /liveness matches Kubernetes naming - Add FatalState singleton to record VirtualMachineError occurrences LivenessResourceImpl now returns DOWN if a fatal JVM error was recorded, instead of performing active synchronous checks that could themselves trigger OOMEs - Add DefaultExceptionHandler VirtualMachineError interception → FatalState Any OOME or other VirtualMachineError during a request marks the JVM as fatal - Add RequestContextFilter + RequestContextHolder to expose request path on the current thread, used by DefaultExceptionHandler for richer error messages - Move Vault check from liveness to ReadinessResourceImpl with 5s cache Synchronous Vault ping on the liveness path risked cascading pod restarts; moved to readiness with a TTL cache and catch(Throwable) + Response.close() - Add VaultClient.ping() timeout (configurable, default 2s) via talend.vault.cache.vault.ping.timeout - Remove HealthService (logic split into LivenessResourceImpl/ReadinessResourceImpl) - New unit tests: FatalStateTest, RequestContextHolderTest, ReadinessResourceImplTest - Rewrite HealthResourceImplIT → LivenessResourceImplIT
Review round 1 — summary
Fixes pushed: What changed
Pending clarifications: 0 Round summary generated by AI. Please resolve threads after verifying the fixes. |
There was a problem hiding this comment.
Pull request overview
This PR introduces Kubernetes probe endpoints for the component-server by adding new liveness/readiness JAX-RS resources, tracking fatal JVM errors to force restarts, and adding a Vault connectivity “ping” used by readiness logic.
Changes:
- Add
/api/v1/livenessand/api/v1/readinessendpoints with a sharedHealthStatusresponse model. - Record
VirtualMachineErroroccurrences viaDefaultExceptionHandlerinto an application-scopedFatalStateso liveness can return503after fatal JVM errors. - Add a
VaultClient.ping()method (with configurable ping timeout) and use it from readiness with simple caching.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
vault-client/src/main/java/org/talend/sdk/components/vault/client/VaultClient.java |
Adds Vault connectivity ping() and a ping-timeout config property. |
component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/service/RequestContextHolderTest.java |
Unit tests for the new request-path ThreadLocal holder. |
component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/service/FatalStateTest.java |
Unit tests for fatal JVM error tracking behavior. |
component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ReadinessResourceImplTest.java |
Unit tests for readiness behavior (index readiness + Vault ping). |
component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/LivenessResourceImplIT.java |
Integration tests validating probe endpoints respond successfully in the container. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/RequestContextHolder.java |
Introduces ThreadLocal request-path storage for error context. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/FatalState.java |
Adds application-scoped fatal error latch used by liveness endpoint. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/ComponentManagerService.java |
Exposes isStarted() for readiness gating. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/ReadinessResourceImpl.java |
Implements readiness endpoint with component-index check and cached Vault ping. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/LivenessResourceImpl.java |
Implements liveness endpoint based on FatalState. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/filter/RequestContextFilter.java |
Adds servlet filter that populates/clears RequestContextHolder. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/error/DefaultExceptionHandler.java |
Records VirtualMachineError into FatalState including request-path context. |
component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/configuration/ComponentServerConfiguration.java |
Adds (currently unused) heap-threshold config property for health checks. |
component-server-parent/component-server/pom.xml |
Adds mockito-core test dependency for new unit tests. |
component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/HealthStatus.java |
Adds HealthStatus DTO used by probe responses. |
component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/ReadinessResource.java |
Defines readiness API contract and OpenAPI metadata. |
component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/LivenessResource.java |
Defines liveness API contract and OpenAPI metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ural refinements - LivenessResource: rename JVM → Application in API response descriptions (U1, U2) - ComponentServerConfiguration: remove unused healthMemoryThreshold field (U3) - ComponentServerConfiguration: add healthVaultEnabled flag (default false) (U4) - ReadinessResourceImpl: gate Vault check behind healthVaultEnabled flag (U4) - ReadinessResourceImpl: fix race — update lastVaultCheck after result (C1) - ReadinessResourceImpl: re-throw VirtualMachineError before catch(Throwable) (C1) - ComponentManagerService: make started field volatile (C5) - DefaultExceptionHandler: replace RequestContextHolder with @context HttpServletRequest (C6) - VaultClient: use dedicated ClientBuilder for ping with connect/read timeout (C7) - FatalState: add reset() method for test isolation (U5) - LivenessResourceImplIT: add test for FatalState.markFatal() → /liveness 503 (U5)
Review round 2 — summary
Fixes pushed: Round summary generated by AI. Please resolve threads after verifying the fixes. |
…eption handler hardening, TTL race, resource leaks - VaultClient.ping(): return true only on HTTP 200 (initialized, unsealed, active) instead of unconditionally for any response. - DefaultExceptionHandler: drop exception.getMessage() from the VirtualMachineError cause string to avoid allocation during an already-failing JVM. - ReadinessResourceImpl.checkVaultCached(): synchronize the TTL refresh path (double-checked) so only one thread pings Vault per TTL window. - LivenessResourceImplIT: wrap all Response usages in try-with-resources. - Add unit test coverage for the above (VaultClientTest, new DefaultExceptionHandlerTest, ReadinessResourceImplTest concurrency test) to satisfy the Sonar quality gate. Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Review round 3 — summary
Fixes pushed: Round summary generated by AI. Please resolve threads after verifying the fixes. Signed: Claude Sonnet 4.5 |
…ig key prefix, vault error leak Guard against request.getRequestURI() returning null in DefaultExceptionHandler, rename the health.vault.enabled config key to the talend.component.server.* prefix used elsewhere in ComponentServerConfiguration, and stop leaking low-level transport/SSL details via t.getMessage() in the readiness response cause. Extend unit test coverage for the new branches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review round 4 — summary
Fixes pushed: Round summary generated by AI. Please resolve threads after verifying the fixes. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ReadinessResourceImplTest.java:85
returns200WhenIndexReadyAndVaultReachable()disables the Vault check (healthVaultEnabled=false), so the test name is misleading (it doesn’t assert anything about Vault reachability). Renaming it will make intent and coverage clearer.
void returns200WhenIndexReadyAndVaultReachable() {
when(componentManagerService.isStarted()).thenReturn(true);
when(configuration.getHealthVaultEnabled()).thenReturn(false);
Extract the duplicated "Vault is not reachable" string into a VAULT_NOT_REACHABLE constant in ReadinessResourceImpl, replacing all 3 call sites. No behavioural change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review round 5 — summary
Fixes pushed: Round summary generated by AI. Please resolve threads after verifying the fixes. |
|

0 New Issues
0 Fixed Issues
1 Accepted Issue
Summary
Implements two new Kubernetes-style probe endpoints for the component-server, resolving recurring cluster restart failures in Talend Cloud (AU/AP incidents).
The existing
/environmentendpoint was insufficient because it returns 200 even when the server cannot process requests. This PR introduces:503 {"status":"DOWN","cause":"..."\}if any check fails, triggering a pod restart.503while the index is being built, preventing premature traffic routing.Key design decisions:
talend.server.health.memory.threshold(default: 10%)Runtime.maxMemory() == Long.MAX_VALUEguard prevents false DOWN on unbounded heapvaultUrl == "no-vault"(standalone mode)catch (Throwable t)in component index check is intentional — required to catch OOME (documented in PR comment below)Jira: QTDI-2030
Checklist
mvn clean verifypasses locallymvn spotless:apply)AI generated code
https://internal.qlik.dev/general/ways-of-working/code-reviews/#guidelines-for-ai-generated-code