added `database` health_check

This commit is contained in:
Francesco Mangiacrapa 2024-10-23 15:41:04 +02:00
parent ed7d1e36da
commit faffb42483
8 changed files with 223 additions and 23 deletions

View File

@ -1,15 +1,17 @@
package org.gcube.application.geoportal.service;
public class ServiceConstants {
public static final String SE_GNA_DB_FLAG="GNA_DB";
public static final String SE_GNA_DB_CATEGORY="Database";
public static final String MONGO_SE_PLATFORM="mongodb";
public static final String MONGO_SE_GNA_FLAG="internal-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";
// 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_GNA_FLAG = "internal-db";
}

View File

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

View File

@ -10,6 +10,7 @@ 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;
@ -25,7 +26,7 @@ import lombok.extern.slf4j.Slf4j;
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Oct 22, 2024
* Oct 22, 2024
*/
@Path("/health")
@Slf4j
@ -45,7 +46,8 @@ public class GeoportalHealth {
/**
* Service check.
*
* @return the response compliant to `microprofile-health` specification. 200 if is OK. Otherwise it fails.
* @return the response compliant to `microprofile-health` specification. 200 if
* is OK. Otherwise it fails.
* @throws JsonProcessingException the json processing exception
*/
@GET
@ -60,17 +62,50 @@ public class GeoportalHealth {
}
/**
* Database check.
* Mongo check.
*
* @param context the gcube context
* @param include_collections if the check has to include the mongo collections in the response
* @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 databaseCheck(@QueryParam("context") String context, @QueryParam("include_collections") Boolean includeCollections) throws JsonProcessingException {
public Response mongoCheck(@QueryParam("context") String context,
@QueryParam("include_collections") Boolean includeCollections) throws JsonProcessingException {
log.debug("mongoCheck called in the context {}", context);
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(MongoHealthCheck.SERVICE_NAME)
@ -81,7 +116,7 @@ public class GeoportalHealth {
return Response.status(400).entity(json).build();
}
HealthCheckResponse response = new MongoHealthCheck(context, includeCollections).call();
HealthCheckResponse response = new DatabaseHealthCheck(context).call();
ResponseBuilder responseBuilder = Response.ok();
if (response.getState().equals(State.DOWN)) {
responseBuilder = responseBuilder.status(503);

View File

@ -0,0 +1,155 @@
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() + "");
buildHCRBuilder.withData("user ", connection.getUser());
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

@ -1,6 +1,6 @@
# Changelog for org.gcube.application.cms.sdi-plugins
## [v1.1.4]
## [v1.1.4-SNAPSHOT]
- Improved logs
- Added fallback on /geoserver/rest path [#28150#note-8]

View File

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

View File

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

View File

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