Merge pull request 'health_check' (#21) from health_check into master

Reviewed-on: #21
This commit is contained in:
Francesco Mangiacrapa 2024-10-28 15:51:22 +01:00
commit 3ec3738784
21 changed files with 664 additions and 109 deletions

View File

@ -3,6 +3,7 @@
## [v1.0.6] - 2024-10-01 ## [v1.0.6] - 2024-10-01
- Included the file size to reduce/optimize the time to upload files to the storage hub [#28150] - Included the file size to reduce/optimize the time to upload files to the storage hub [#28150]
- Checked if the user is `null` in the `UserUtils` class [#28301]
## [v1.0.5] - 2024-07-03 ## [v1.0.5] - 2024-07-03

View File

@ -28,21 +28,24 @@ public class UserUtils {
SecretManager cm = SecretManagerProvider.instance.get(); SecretManager cm = SecretManagerProvider.instance.get();
String context = cm.getContext(); String context = cm.getContext();
if(context==null) throw new SecurityException("Cannot determine context"); if (context == null)
org.gcube.common.authorization.utils.user.User user = cm.getUser(); throw new SecurityException("Cannot determine context");
log.info("Identified caller {} in context {}",user.getUsername(),context);
Set<String> roles = new HashSet<>(); Set<String> roles = new HashSet<>();
roles.addAll(user.getRoles()); org.gcube.common.authorization.utils.user.User user = cm.getUser();
AuthenticatedUser toReturn = if (user == null) {
new AuthenticatedUser(user,roles, AccessTokenProvider.instance.get(),SecurityTokenProvider.instance.get(),context); log.warn("No user found in the session work, context is {}", context);
} else {
log.info("Identified caller {} in context {}", user.getUsername(), context);
roles.addAll(user.getRoles());
}
AuthenticatedUser toReturn = new AuthenticatedUser(user, roles, AccessTokenProvider.instance.get(),
SecurityTokenProvider.instance.get(), context);
log.info("Current User is {} ", toReturn); log.info("Current User is {} ", toReturn);
return toReturn; return toReturn;
} }
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public static class AuthenticatedUser { public static class AuthenticatedUser {

View File

@ -1,5 +1,9 @@
# Changelog for org.gcube.application.geoportal-service # Changelog for org.gcube.application.geoportal-service
## [v1.2.2] - 2024-10-16
- Included the health check at https://{geoporta_endpoint}/health via `smallrye-health` (that implements the https://microprofile.io/specifications/microprofile-health/) [#28301]
## [v1.2.1] - 2024-10-02 ## [v1.2.1] - 2024-10-02
- Included the file size to reduce/optimize the time to upload files to the storage hub [#28150] - Included the file size to reduce/optimize the time to upload files to the storage hub [#28150]

View File

@ -3,13 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://enunciate.webcohesion.com/schemas/enunciate-2.14.0.xsd"> xsi:noNamespaceSchemaLocation="http://enunciate.webcohesion.com/schemas/enunciate-2.14.0.xsd">
<api-classes> <api-classes>
<!-- Use patterns to exclude classes... <!-- Use patterns to exclude classes... -->
e.g. for URI-Resolver
-->
<exclude pattern="org.gcube.application.geoportal.service.rest.DocsGenerator" /> <exclude pattern="org.gcube.application.geoportal.service.rest.DocsGenerator" />
<exclude pattern="org.gcube.application.geoportal.service.rest.ConcessioniOverMongo" /> <exclude pattern="org.gcube.application.geoportal.service.rest.ConcessioniOverMongo" />
<exclude pattern="org.gcube.application.geoportal.service.rest.GeoportalHealth" />
</api-classes> </api-classes>
<modules> <modules>
<gwt-json-overlay disabled="true" /> <gwt-json-overlay disabled="true" />

View File

@ -5,7 +5,9 @@
<group>${project.groupId}</group> <group>${project.groupId}</group>
<version>${project.version}</version> <version>${project.version}</version>
<description>${project.description}</description> <description>${project.description}</description>
<exclude>/srv/docs/*</exclude> <!-- <exclude>/srv/docs/*</exclude> -->
<exclude>/srv/api-docs/*</exclude> <!-- <exclude>/srv/api-docs/*</exclude> -->
<exclude>/srv/health</exclude>
<exclude>/srv/health/*</exclude>
</application> </application>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.gcube.application</groupId> <groupId>org.gcube.application</groupId>
<artifactId>geoportal-service</artifactId> <artifactId>geoportal-service</artifactId>
<version>1.2.1</version> <version>1.2.2</version>
<name>Geoportal Service</name> <name>Geoportal Service</name>
<packaging>war</packaging> <packaging>war</packaging>
@ -155,6 +155,17 @@
</dependency> <dependency> <groupId>javax.xml.ws</groupId> <artifactId>jaxws-api</artifactId> </dependency> <dependency> <groupId>javax.xml.ws</groupId> <artifactId>jaxws-api</artifactId>
<scope>test</scope> </dependency> --> <scope>test</scope> </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.eclipse.microprofile.health</groupId> -->
<!-- <artifactId>microprofile-health-api</artifactId> -->
<!-- <version>2.2</version> -->
<!-- </dependency> -->
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-health</artifactId>
<version>2.2.3</version>
</dependency>
<!-- Plugins related tests --> <!-- Plugins related tests -->

View File

@ -21,6 +21,7 @@ import org.gcube.application.geoportal.service.engine.providers.ucd.ProfileMap;
import org.gcube.application.geoportal.service.engine.providers.ucd.SingleISResourceUCDProvider; import org.gcube.application.geoportal.service.engine.providers.ucd.SingleISResourceUCDProvider;
import org.gcube.application.geoportal.service.engine.providers.ucd.UCDManager; import org.gcube.application.geoportal.service.engine.providers.ucd.UCDManager;
import org.gcube.application.geoportal.service.model.internal.db.Mongo; import org.gcube.application.geoportal.service.model.internal.db.Mongo;
import org.gcube.application.geoportal.service.rest.GeoportalHealth;
import org.gcube.application.geoportal.service.rest.Plugins; import org.gcube.application.geoportal.service.rest.Plugins;
import org.gcube.application.geoportal.service.rest.ProfiledDocuments; import org.gcube.application.geoportal.service.rest.ProfiledDocuments;
import org.gcube.application.geoportal.service.rest.UseCaseDescriptors; import org.gcube.application.geoportal.service.rest.UseCaseDescriptors;
@ -40,19 +41,19 @@ public class GeoPortalService extends ResourceConfig{
public GeoPortalService() { public GeoPortalService() {
super(); super();
//Register interrfaces // Register interfaces
log.info("Initializing serialization"); log.info("Initializing serialization");
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(Serialization.mapper); provider.setMapper(Serialization.mapper);
register(provider); register(provider);
registerClasses(RequestFilter.class); registerClasses(RequestFilter.class);
registerClasses(ProfiledDocuments.class); registerClasses(ProfiledDocuments.class);
registerClasses(UseCaseDescriptors.class); registerClasses(UseCaseDescriptors.class);
registerClasses(Plugins.class); registerClasses(Plugins.class);
registerClasses(GeoportalHealth.class);
// registerClasses(DocsGenerator.class); // registerClasses(DocsGenerator.class);
log.info("Setting implementations .. "); log.info("Setting implementations .. ");
@ -65,22 +66,16 @@ public class GeoPortalService extends ResourceConfig{
ImplementationProvider.get().setEngine(new UCDManager(), UCDManagerI.class); ImplementationProvider.get().setEngine(new UCDManager(), UCDManagerI.class);
ImplementationProvider.get().setEngine(new ConfigurationCache(), ConfigurationCache.ConfigurationMap.class); ImplementationProvider.get().setEngine(new ConfigurationCache(), ConfigurationCache.ConfigurationMap.class);
for (Map.Entry<Engine<Object>, Class<Object>> entry : customImplementations().entrySet()) { for (Map.Entry<Engine<Object>, Class<Object>> entry : customImplementations().entrySet()) {
log.warn("LOADING CUSTOM ENGINE : {} serving {}", entry.getKey(), entry.getValue()); log.warn("LOADING CUSTOM ENGINE : {} serving {}", entry.getKey(), entry.getValue());
ImplementationProvider.get().setEngine(entry.getKey(), entry.getValue()); ImplementationProvider.get().setEngine(entry.getKey(), entry.getValue());
} }
log.debug("ENGINES ARE : "); log.debug("ENGINES ARE : ");
ImplementationProvider.get().getManagerList().forEach( ImplementationProvider.get().getManagerList().forEach((aClass, s) -> log.debug("{} serving {} ", aClass, s));
(aClass, s) -> log.debug("{} serving {} ",aClass,s));
ImplementationProvider.get().initEngines(); ImplementationProvider.get().initEngines();
} }
} }

View File

@ -1,15 +1,17 @@
package org.gcube.application.geoportal.service; package org.gcube.application.geoportal.service;
public class ServiceConstants { public class ServiceConstants {
public static final String SE_GNA_DB_FLAG="GNA_DB"; // SE DB flagName
public static final String SE_GNA_DB_FLAGNAME = "GNA_DB";
// SE DB flagValue
public static final String SE_GNA_DB_FLAGVALUE = "Concessioni";
// SE DB category
public static final String SE_GNA_DB_CATEGORY = "Database"; public static final String SE_GNA_DB_CATEGORY = "Database";
// SE DB platform
public static final String SE_GNA_DB_PLATFORM = "postgis";
public static final String MONGO_SE_PLATFORM = "mongodb"; public static final String MONGO_SE_PLATFORM = "mongodb";
public static final String MONGO_SE_GNA_FLAG = "internal-db"; public static final String MONGO_SE_GNA_FLAG = "internal-db";
} }

View File

@ -29,7 +29,7 @@ public class MongoClientProvider extends AbstractScopedMap<Mongo> {
getProvidedObjectByClass(ISInterface.class), getProvidedObjectByClass(ISInterface.class),
ServiceConstants.SE_GNA_DB_CATEGORY, ServiceConstants.SE_GNA_DB_CATEGORY,
ServiceConstants.MONGO_SE_PLATFORM, ServiceConstants.MONGO_SE_PLATFORM,
ServiceConstants.SE_GNA_DB_FLAG, ServiceConstants.SE_GNA_DB_FLAGNAME,
ServiceConstants.MONGO_SE_GNA_FLAG); ServiceConstants.MONGO_SE_GNA_FLAG);
log.debug("Connecting to "+conn); log.debug("Connecting to "+conn);

View File

@ -0,0 +1,144 @@
package org.gcube.application.geoportal.service.rest;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponse.State;
import org.gcube.application.geoportal.service.rest.health.DatabaseHealthCheck;
import org.gcube.application.geoportal.service.rest.health.GeoportalHealthCheck;
import org.gcube.application.geoportal.service.rest.health.HealthCheckResponseSerializer;
import org.gcube.application.geoportal.service.rest.health.MongoHealthCheck;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
/**
* The Class GeoportalHealth.
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Oct 22, 2024
*/
@Path("/health")
@Slf4j
public class GeoportalHealth {
private ObjectMapper mapper = new ObjectMapper();
/**
* Instantiates a new geoportal health.
*/
public GeoportalHealth() {
SimpleModule module = new SimpleModule();
module.addSerializer(HealthCheckResponse.class, new HealthCheckResponseSerializer());
mapper.registerModule(module);
}
/**
* Service check.
*
* @return the response compliant to `microprofile-health` specification. 200 if
* is OK. Otherwise it fails.
* @throws JsonProcessingException the json processing exception
*/
@GET
@Path("")
@Produces({ MediaType.APPLICATION_JSON })
public Response serviceCheck() throws JsonProcessingException {
log.debug("serviceCheck called");
HealthCheckResponse response = new GeoportalHealthCheck().call();
String json = healthCheckSerializer(response);
log.info("serviceCheck response is {}", json);
return Response.ok().entity(json).build();
}
/**
* Mongo check.
*
* @param context the gcube context
* @param include_collections if the check has to include the mongo collections
* in the response
* @return the response compliant to `microprofile-health` specification
* @throws JsonProcessingException the json processing exception
*/
@GET
@Path("/mongo")
@Produces({ MediaType.APPLICATION_JSON })
public Response mongoCheck(@QueryParam("context") String context,
@DefaultValue("false") @QueryParam("include_collections") Boolean includeCollections)
throws JsonProcessingException {
log.debug("mongoCheck called in the context {}, includeCollections {}", context, includeCollections);
if (context == null) {
HealthCheckResponse response = HealthCheckResponse.named(MongoHealthCheck.SERVICE_NAME)
.withData("context", "is required parameter (e.g. context=/gcube/devsec/devVRE)").down().build();
String json = healthCheckSerializer(response);
log.info("mongoCheck error response is {}", json);
// Bad request
return Response.status(400).entity(json).build();
}
HealthCheckResponse response = new MongoHealthCheck(context, includeCollections).call();
ResponseBuilder responseBuilder = Response.ok();
if (response.getState().equals(State.DOWN)) {
responseBuilder = responseBuilder.status(503);
}
String json = healthCheckSerializer(response);
log.info("mongoCheck response is {}", json);
return responseBuilder.entity(json).build();
}
/**
* Database check.
*
* @param context the gcube context
* @return the response compliant to `microprofile-health` specification
* @throws JsonProcessingException the json processing exception
*/
@GET
@Path("/database")
@Produces({ MediaType.APPLICATION_JSON })
public Response databaseCheck(@QueryParam("context") String context) throws JsonProcessingException {
log.debug("databaseCheck called in the context {}", context);
if (context == null) {
HealthCheckResponse response = HealthCheckResponse.named(DatabaseHealthCheck.SERVICE_NAME)
.withData("context", "is required parameter (e.g. context=/gcube/devsec/devVRE)").down().build();
String json = healthCheckSerializer(response);
log.info("databaseCheck error response is {}", json);
// Bad request
return Response.status(400).entity(json).build();
}
HealthCheckResponse response = new DatabaseHealthCheck(context).call();
ResponseBuilder responseBuilder = Response.ok();
if (response.getState().equals(State.DOWN)) {
responseBuilder = responseBuilder.status(503);
}
String json = healthCheckSerializer(response);
log.info("databaseCheck response is {}", json);
return responseBuilder.entity(json).build();
}
/**
* Health check serializer.
*
* @param response the response
* @return the string
* @throws JsonProcessingException the json processing exception
*/
private String healthCheckSerializer(HealthCheckResponse response) throws JsonProcessingException {
// Serializes HealthCheckResponse in JSON with custom
// HealthCheckResponseSerializer
return mapper.writeValueAsString(response);
}
}

View File

@ -25,9 +25,6 @@ public abstract class GuardedMethod<T> {
preoperations.add(preoperation); preoperations.add(preoperation);
} }
private T result = null; private T result = null;
public GuardedMethod<T> execute() throws WebApplicationException { public GuardedMethod<T> execute() throws WebApplicationException {
@ -51,7 +48,8 @@ public abstract class GuardedMethod<T> {
throw new WebApplicationException("Environment is not properly configured", e, Status.EXPECTATION_FAILED); throw new WebApplicationException("Environment is not properly configured", e, Status.EXPECTATION_FAILED);
} catch (InsufficientPrivileges e) { } catch (InsufficientPrivileges e) {
log.error("Returning exception ", e); log.error("Returning exception ", e);
throw new WebApplicationException("User has insufficient privileges for requested action", e,Status.FORBIDDEN); throw new WebApplicationException("User has insufficient privileges for requested action", e,
Status.FORBIDDEN);
} catch (WebApplicationException e) { } catch (WebApplicationException e) {
log.error("Throwing Web Application Exception ", e); log.error("Throwing Web Application Exception ", e);
throw e; throw e;
@ -75,6 +73,5 @@ public abstract class GuardedMethod<T> {
return result; return result;
} }
protected abstract T run() throws Exception, WebApplicationException; protected abstract T run() throws Exception, WebApplicationException;
} }

View File

@ -0,0 +1,161 @@
package org.gcube.application.geoportal.service.rest.health;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
import org.gcube.application.cms.implementations.ISInterface;
import org.gcube.application.cms.implementations.ImplementationProvider;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.model.rest.DatabaseConnection;
import org.gcube.application.geoportal.service.ServiceConstants;
import org.gcube.common.scope.api.ScopeProvider;
import lombok.extern.slf4j.Slf4j;
/**
* The Class DatabaseHealthCheck.
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Oct 23, 2024
*/
@Readiness
@Liveness
@Slf4j
public class DatabaseHealthCheck implements HealthCheck {
private String context;
public static final String SERVICE_NAME = "database";
private static final int CONNECTION_TIMEOUT = 30;
/**
* Call.
*
* @return the health check response
*/
@Override
public HealthCheckResponse call() {
return checkDatabase(context);
}
/**
* Instantiates a new database health check.
*
* @param context the context
*/
public DatabaseHealthCheck(String context) {
this.context = context;
}
/**
* Check database.
*
* @param context the context
* @return the health check response
*/
private HealthCheckResponse checkDatabase(String context) {
log.debug("checkMongo in the context: {}", context);
HealthCheckResponseBuilder buildHCRBuilder = HealthCheckResponse.named(SERVICE_NAME);
ScopeProvider.instance.set(context);
try {
DatabaseConnection databaseConnection = null;
ISInterface isInterface = ImplementationProvider.get().getProvidedObjectByClass(ISInterface.class);
try {
if (isInterface == null)
throw new Exception(ISInterface.class.getSimpleName() + " configuration is null for "
+ DatabaseConnection.class.getSimpleName());
databaseConnection = isInterface.queryForDatabase(ServiceConstants.SE_GNA_DB_CATEGORY,
ServiceConstants.SE_GNA_DB_PLATFORM, ServiceConstants.SE_GNA_DB_FLAGNAME,
ServiceConstants.SE_GNA_DB_FLAGVALUE);
if (databaseConnection == null)
throw new Exception(DatabaseConnection.class.getSimpleName() + " configuration is null");
} catch (Exception e) {
log.error("Error on checking DB configuration: ", e);
buildHCRBuilder.state(false);
return buildHCRBuilder.build();
}
boolean connectionStatus = checkDatabaseConnection(databaseConnection);
buildHCRBuilder = appendDBInfo(buildHCRBuilder, databaseConnection);
buildHCRBuilder.state(connectionStatus);
log.info("checkDatabase is OK in the context: {}. State is {}", context, connectionStatus);
return buildHCRBuilder.build();
} catch (Exception e) {
log.error("Error on checkDatabase: ", e);
log.warn("checkDatabase is KO in the context: {}", context);
buildHCRBuilder.state(false);
return buildHCRBuilder.build();
} finally {
ScopeProvider.instance.reset();
}
}
/**
* Append DB info.
*
* @param buildHCRBuilder the build HCR builder
* @param connection the connection
* @return the health check response builder
*/
private HealthCheckResponseBuilder appendDBInfo(HealthCheckResponseBuilder buildHCRBuilder,
DatabaseConnection connection) {
buildHCRBuilder.withData("host", connection.getUrl() + "");
// anonymize the DB username
String userNotClear = "***";
if (connection.getUser() != null && connection.getUser().length() > 3) {
userNotClear = connection.getUser().substring(0, 3) + userNotClear;
}
buildHCRBuilder.withData("user ", userNotClear);
buildHCRBuilder.withData("pwd ", "****");
return buildHCRBuilder;
}
/**
* Check database connection.
*
* @param connectionParameters the connection parameters
* @return true, if successful
*/
private boolean checkDatabaseConnection(DatabaseConnection connectionParameters) {
try {
if (connectionParameters == null)
throw new ConfigurationException("connectionParameters is null");
// Getting connection
Connection connection = DriverManager.getConnection(connectionParameters.getUrl(),
connectionParameters.getUser(), connectionParameters.getPwd());
// Check if the connection is valid (timeout 30 seconds)
if (connection != null && connection.isValid(CONNECTION_TIMEOUT)) {
log.debug("Connection to DB " + connectionParameters.getUrl() + " is OK!");
return true;
} else {
log.debug("Connection to DB " + connectionParameters.getUrl() + " is KO!");
return false;
}
} catch (SQLException e) {
log.warn("Error on connecting to DB: " + connectionParameters, e);
return false;
} catch (ConfigurationException e1) {
log.warn("Error on reading connection configuration: " + connectionParameters, e1);
return false;
}
}
}

View File

@ -0,0 +1,37 @@
package org.gcube.application.geoportal.service.rest.health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
import lombok.extern.slf4j.Slf4j;
/**
* The Class GeoportalHealthCheck.
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Oct 22, 2024
*/
@Readiness
@Liveness
@Slf4j
public class GeoportalHealthCheck implements HealthCheck {
private static final String SERVICE_NAME = "geooportal-service";
/**
* Call.
*
* @return the health check response
*/
@Override
public HealthCheckResponse call() {
log.info(GeoportalHealthCheck.class.getSimpleName() + " call");
HealthCheckResponse response = HealthCheckResponse.named(SERVICE_NAME).state(true).build();
return response;
}
}

View File

@ -0,0 +1,40 @@
package org.gcube.application.geoportal.service.rest.health;
import java.io.IOException;
import java.util.Map;
import org.eclipse.microprofile.health.HealthCheckResponse;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HealthCheckResponseSerializer extends JsonSerializer<HealthCheckResponse> {
@Override
public void serialize(HealthCheckResponse response, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
if (response.getName() != null)
gen.writeStringField("name", response.getName());
if (response.getState() != null)
gen.writeStringField("state", response.getState().toString());
response.getData().ifPresent(data -> {
try {
gen.writeObjectFieldStart("data");
for (Map.Entry<String, Object> entry : data.entrySet()) {
gen.writeObjectField(entry.getKey(), entry.getValue());
}
gen.writeEndObject();
} catch (IOException e) {
log.warn("Error on serializing the data field", e);
}
});
gen.writeEndObject();
}
}

View File

@ -0,0 +1,117 @@
package org.gcube.application.geoportal.service.rest.health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
import org.gcube.application.cms.implementations.ImplementationProvider;
import org.gcube.application.geoportal.common.model.configuration.MongoConnection;
import org.gcube.application.geoportal.service.model.internal.db.Mongo;
import org.gcube.common.scope.api.ScopeProvider;
import com.mongodb.client.MongoIterable;
import lombok.extern.slf4j.Slf4j;
/**
* The Class MongoHealthCheck.
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Oct 22, 2024
*/
@Readiness
@Liveness
@Slf4j
public class MongoHealthCheck implements HealthCheck {
private String context;
private Boolean includeCollections = false;
public static final String SERVICE_NAME = "mongo";
/**
* Call.
*
* @return the health check response
*/
@Override
public HealthCheckResponse call() {
return checkMongo(context);
}
/**
* Instantiates a new mongo health check.
*
* @param context the context
* @param includeCollections the include collections
*/
public MongoHealthCheck(String context, Boolean includeCollections) {
this.context = context;
this.includeCollections = includeCollections;
}
/**
* Check mongo.
*
* @param context the context
* @return the health check response
*/
private HealthCheckResponse checkMongo(String context) {
log.debug("checkMongo in the context: {}", context);
HealthCheckResponseBuilder buildHCRBuilder = HealthCheckResponse.named(SERVICE_NAME);
Mongo mongo = null;
try {
ScopeProvider.instance.set(context);
mongo = ImplementationProvider.get().getProvidedObjectByClass(Mongo.class);
buildHCRBuilder = appendMongoInfo(buildHCRBuilder, mongo.getConnection());
buildHCRBuilder.state(true);
if (includeCollections!=null && includeCollections) {
MongoIterable<String> collections = mongo.getTheClient()
.getDatabase(mongo.getConnection().getDatabase()).listCollectionNames();
log.info("listCollectionNames is null: {}", collections == null);
int i = 1;
for (String coll : collections) {
log.debug("adding collection: {}", coll);
buildHCRBuilder.withData("collection_" + i, coll);
i++;
}
}
log.info("checkMongo is OK in the context: {}", context);
return buildHCRBuilder.build();
} catch (Exception e) {
log.error("Error on checkMongo: ", e);
log.warn("checkMongo is KO in the context: {}", context);
buildHCRBuilder.state(false);
if (mongo != null) {
MongoConnection connection = null;
try {
connection = mongo.getConnection();
buildHCRBuilder = appendMongoInfo(buildHCRBuilder, connection);
} catch (Exception e1) {
buildHCRBuilder.withData("hosts", connection.getHosts() + "");
}
}
return buildHCRBuilder.build();
} finally {
ScopeProvider.instance.reset();
}
}
/**
* Append mongo info.
*
* @param buildHCRBuilder the build HCR builder
* @param connection the connection
* @return the health check response builder
*/
private HealthCheckResponseBuilder appendMongoInfo(HealthCheckResponseBuilder buildHCRBuilder,
MongoConnection connection) {
buildHCRBuilder.withData("hosts", connection.getHosts() + "");
buildHCRBuilder.withData("db_name ", connection.getDatabase());
return buildHCRBuilder;
}
}

View File

@ -1,11 +1,13 @@
<web-app> <web-app>
<servlet> <servlet>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService</servlet-name> <servlet-name>org.gcube.application.geoportal.service.GeoPortalService
</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer <servlet-class>org.glassfish.jersey.servlet.ServletContainer
</servlet-class> </servlet-class>
<init-param> <init-param>
<param-name>javax.ws.rs.Application</param-name> <param-name>javax.ws.rs.Application</param-name>
<param-value>org.gcube.application.geoportal.service.GeoPortalService</param-value> <param-value>org.gcube.application.geoportal.service.GeoPortalService
</param-value>
</init-param> </init-param>
<init-param> <init-param>
<param-name>jersey.config.server.provider.classnames</param-name> <param-name>jersey.config.server.provider.classnames</param-name>
@ -29,7 +31,8 @@
</servlet-mapping> </servlet-mapping>
<servlet-mapping> <servlet-mapping>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService</servlet-name> <servlet-name>org.gcube.application.geoportal.service.GeoPortalService
</servlet-name>
<url-pattern>/srv/*</url-pattern> <url-pattern>/srv/*</url-pattern>
</servlet-mapping> </servlet-mapping>
</web-app> </web-app>

View File

@ -0,0 +1,32 @@
package org.gcube.application.geoportal.service.health;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.gcube.application.geoportal.service.rest.health.HealthCheckResponseSerializer;
public class HealthCheckSerializer {
public static void main(String[] args) throws Exception {
HealthCheckResponse response = HealthCheckResponse.named("geooportal-service")
.state(true)
.withData("status", "healthy")
.build();
// Configura ObjectMapper con il serializer personalizzato
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(HealthCheckResponse.class, new HealthCheckResponseSerializer());
mapper.registerModule(module);
// Serializza l'oggetto HealthCheckResponse in JSON
String json = mapper.writeValueAsString(response);
// Stampa il JSON serializzato
System.out.println(json);
}
}

View File

@ -3,6 +3,7 @@
## [v1.1.4] ## [v1.1.4]
- Improved logs - Improved logs
- Added fallback on /geoserver/rest path [#28150#note-8] - Added fallback on /geoserver/rest path [#28150#note-8]
- Improved some business logics
## [v1.1.3] ## [v1.1.3]
- Added apply regex business logic [#26322] - Added apply regex business logic [#26322]

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>sdi-plugins</artifactId> <artifactId>sdi-plugins</artifactId>
<version>1.1.4</version> <version>1.1.4-SNAPSHOT</version>
<name>gCube CMS - SDI Plugins</name> <name>gCube CMS - SDI Plugins</name>
<parent> <parent>

View File

@ -23,6 +23,10 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public abstract class SDIAbstractPlugin extends AbstractPlugin implements InitializablePlugin { public abstract class SDIAbstractPlugin extends AbstractPlugin implements InitializablePlugin {
public static final String POSTGIS_CREDENTIALS = "POSTGIS-CREDENTIALS";
public static final String SDI_CACHE = "SDI-CACHE";
protected static AbstractScopedMap<SDIManagerWrapper> sdiCache; protected static AbstractScopedMap<SDIManagerWrapper> sdiCache;
protected static AbstractScopedMap<DatabaseConnection> postgisCache; protected static AbstractScopedMap<DatabaseConnection> postgisCache;
@ -31,7 +35,7 @@ public abstract class SDIAbstractPlugin extends AbstractPlugin implements Initia
private static void initCache(){ private static void initCache(){
if(sdiCache==null) { if(sdiCache==null) {
log.info("Creating internal caches.. "); log.info("Creating internal caches.. ");
sdiCache = new AbstractScopedMap<SDIManagerWrapper>("SDI-CACHE") { sdiCache = new AbstractScopedMap<SDIManagerWrapper>(SDI_CACHE) {
@Override @Override
protected SDIManagerWrapper retrieveObject(String context) throws ConfigurationException { protected SDIManagerWrapper retrieveObject(String context) throws ConfigurationException {
try { try {
@ -44,7 +48,7 @@ public abstract class SDIAbstractPlugin extends AbstractPlugin implements Initia
sdiCache.setTTL(Duration.of(10, ChronoUnit.MINUTES)); sdiCache.setTTL(Duration.of(10, ChronoUnit.MINUTES));
} }
if(postgisCache==null) { if(postgisCache==null) {
postgisCache = new AbstractScopedMap<DatabaseConnection>("POSTGIS-CREDENTIALS") { postgisCache = new AbstractScopedMap<DatabaseConnection>(POSTGIS_CREDENTIALS) {
@Override @Override
protected DatabaseConnection retrieveObject(String context) throws ConfigurationException { protected DatabaseConnection retrieveObject(String context) throws ConfigurationException {
try { try {

View File

@ -463,6 +463,10 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
DatabaseConnection connectionParameters = null; DatabaseConnection connectionParameters = null;
try { try {
connectionParameters = postgisCache.getObject(); connectionParameters = postgisCache.getObject();
if(connectionParameters==null)
throw new ConfigurationException("connectionParameters is null");
// Getting connection // Getting connection
Connection connection = DriverManager.getConnection(connectionParameters.getUrl(), Connection connection = DriverManager.getConnection(connectionParameters.getUrl(),
connectionParameters.getUser(), connectionParameters.getPwd()); connectionParameters.getUser(), connectionParameters.getPwd());