Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.api;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.talend.sdk.component.server.front.model.HealthStatus;

@Path("health")
@Tag(name = "Health", description = "Kubernetes liveness probe endpoint.")
public interface HealthResource {

@GET
@Produces(APPLICATION_JSON)
@Operation(operationId = "getLiveness",
description = "Liveness probe: returns 200 when the server is healthy (heap, component index, Vault). "
+ "Returns 503 with a cause when any check fails.")
@APIResponses({

Check notice on line 42 in component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/HealthResource.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/HealthResource.java#L42

Remove the 'APIResponses' wrapper from this annotation group
@APIResponse(responseCode = "200",
description = "Server is healthy.",
content = @Content(mediaType = APPLICATION_JSON,
schema = @Schema(implementation = HealthStatus.class))),
@APIResponse(responseCode = "503",
description = "Server is not healthy.",
content = @Content(mediaType = APPLICATION_JSON,
schema = @Schema(implementation = HealthStatus.class)))
})
Response getLiveness();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.api;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.talend.sdk.component.server.front.model.HealthStatus;

@Path("readiness")
@Tag(name = "Health", description = "Kubernetes readiness probe endpoint.")
public interface ReadinessResource {

@GET
@Produces(APPLICATION_JSON)
@Operation(operationId = "getReadiness",
description = "Readiness probe: returns 200 when the component index is loaded and the server is ready "
+ "to serve traffic. Returns 503 with a cause otherwise.")
@APIResponses({

Check notice on line 42 in component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/ReadinessResource.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server-api/src/main/java/org/talend/sdk/component/server/api/ReadinessResource.java#L42

Remove the 'APIResponses' wrapper from this annotation group
@APIResponse(responseCode = "200",
description = "Server is ready.",
content = @Content(mediaType = APPLICATION_JSON,
schema = @Schema(implementation = HealthStatus.class))),
@APIResponse(responseCode = "503",
description = "Server is not ready.",
content = @Content(mediaType = APPLICATION_JSON,
schema = @Schema(implementation = HealthStatus.class)))
})
Response getReadiness();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.front.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HealthStatus {

private String status;

private String cause;
}
6 changes: 6 additions & 0 deletions component-server-parent/component-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@
<version>${meecrowave.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito4.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>ziplock</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@
@ConfigProperty(name = "talend.component.server.plugins.reloading.marker")
private Optional<String> pluginsReloadFileMarker;

@Inject

Check warning on line 204 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/configuration/ComponentServerConfiguration.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/configuration/ComponentServerConfiguration.java#L204

Remove this field injection and use constructor injection instead.
@Documentation("Minimum percentage of available JVM heap required for the liveness probe to report UP. "
+ "If the available heap drops below this threshold the health endpoint returns 503.")
@ConfigProperty(name = "talend.server.health.memory.threshold", defaultValue = "10")
private Integer healthMemoryThreshold;

@PostConstruct
private void init() {
if (logRequests != null && logRequests) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.front;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.core.Response;

import org.talend.sdk.component.server.api.HealthResource;
import org.talend.sdk.component.server.front.model.HealthStatus;
import org.talend.sdk.component.server.service.HealthService;

@ApplicationScoped
public class HealthResourceImpl implements HealthResource {

@Inject

Check warning on line 29 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/HealthResourceImpl.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/HealthResourceImpl.java#L29

Remove this field injection and use constructor injection instead.
private HealthService healthService;

@Override
public Response getLiveness() {
final HealthStatus status = healthService.checkLiveness();
if ("UP".equals(status.getStatus())) {
return Response.ok(status).build();
}
return Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(status).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.front;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.core.Response;

import org.talend.sdk.component.server.api.ReadinessResource;
import org.talend.sdk.component.server.front.model.HealthStatus;
import org.talend.sdk.component.server.service.HealthService;

@ApplicationScoped
public class ReadinessResourceImpl implements ReadinessResource {

@Inject

Check warning on line 29 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/ReadinessResourceImpl.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/front/ReadinessResourceImpl.java#L29

Remove this field injection and use constructor injection instead.
private HealthService healthService;

@Override
public Response getReadiness() {
final HealthStatus status = healthService.checkReadiness();
if ("UP".equals(status.getStatus())) {
return Response.ok(status).build();
}
return Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(status).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,8 @@ public ComponentManager manager() {
return instance;
}

public boolean isStarted() {
return started;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Copyright (C) 2006-2026 Talend Inc. - www.talend.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.talend.sdk.component.server.service;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.talend.sdk.component.server.configuration.ComponentServerConfiguration;
import org.talend.sdk.component.server.front.model.HealthStatus;
import org.talend.sdk.components.vault.client.VaultClient;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@ApplicationScoped
public class HealthService {

private static final String STATUS_UP = "UP";

private static final String STATUS_DOWN = "DOWN";

@Inject

Check warning on line 35 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java#L35

Remove this field injection and use constructor injection instead.
private ComponentManagerService componentManagerService;

@Inject

Check warning on line 38 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java#L38

Remove this field injection and use constructor injection instead.
private VaultClient vaultClient;

@Inject

Check warning on line 41 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java#L41

Remove this field injection and use constructor injection instead.
private ComponentServerConfiguration configuration;

/**
* Performs the liveness check: heap availability, component index, and Vault connectivity.
*
* @return {@link HealthStatus} with {@code status="UP"} if all checks pass,
* or {@code status="DOWN"} with a human-readable cause on failure.
*/
public HealthStatus checkLiveness() {
final HealthStatus memoryStatus = checkMemory();
if (STATUS_DOWN.equals(memoryStatus.getStatus())) {
return memoryStatus;
}
final HealthStatus indexStatus = checkComponentIndex();
if (STATUS_DOWN.equals(indexStatus.getStatus())) {
return indexStatus;
}
final HealthStatus vaultStatus = checkVault();
if (STATUS_DOWN.equals(vaultStatus.getStatus())) {
return vaultStatus;
}
return new HealthStatus(STATUS_UP, null);
}

/**
* Performs the readiness check: verifies that the component index is loaded.
*
* @return {@link HealthStatus} with {@code status="UP"} if ready,
* or {@code status="DOWN"} with cause otherwise.
*/
public HealthStatus checkReadiness() {
if (!componentManagerService.isStarted()) {
return new HealthStatus(STATUS_DOWN, "Component index not ready");
}
return new HealthStatus(STATUS_UP, null);
}

private HealthStatus checkMemory() {
final Runtime runtime = Runtime.getRuntime();
final long maxMemory = runtime.maxMemory();
if (maxMemory == Long.MAX_VALUE) {
return new HealthStatus(STATUS_UP, null);
}
final long availableMemory = maxMemory - runtime.totalMemory() + runtime.freeMemory();
final int availablePercent = (int) (availableMemory * 100L / maxMemory);
final int threshold = configuration.getHealthMemoryThreshold();
if (availablePercent < threshold) {
final String cause = String
.format("Available heap is %d%% which is below the configured threshold of %d%%",
availablePercent, threshold);
log.warn("Liveness check failed: {}", cause);

Check failure on line 92 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java#L92

Define a constant instead of duplicating this literal "Liveness check failed: {}" 3 times.
return new HealthStatus(STATUS_DOWN, cause);
}
return new HealthStatus(STATUS_UP, null);
}

private HealthStatus checkComponentIndex() {
try {
componentManagerService.manager().getContainer().findAll();
return new HealthStatus(STATUS_UP, null);
} catch (final Throwable t) {

Check warning on line 102 in component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java

View check run for this annotation

sonar-rnd / SonarQube Code Analysis

component-server-parent/component-server/src/main/java/org/talend/sdk/component/server/service/HealthService.java#L102

Catch Exception instead of Throwable.
final String cause = "Component index check failed: " + t.getMessage();
log.warn("Liveness check failed: {}", cause);
return new HealthStatus(STATUS_DOWN, cause);
}
}

private HealthStatus checkVault() {
if (!vaultClient.ping()) {
final String cause = "Vault is not reachable";
log.warn("Liveness check failed: {}", cause);
return new HealthStatus(STATUS_DOWN, cause);
}
return new HealthStatus(STATUS_UP, null);
}
}
Loading
Loading