Health extension implemented

This commit is contained in:
lucio 2023-02-01 14:40:31 +01:00
parent 98c41e7463
commit b873f97187
14 changed files with 318 additions and 111 deletions

View File

@ -38,9 +38,9 @@
</scm> </scm>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>io.github.classgraph</groupId>
<artifactId>reflections</artifactId> <artifactId>classgraph</artifactId>
<version>0.9.10</version> <version>4.8.28</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.gcube.common</groupId> <groupId>org.gcube.common</groupId>

View File

@ -6,6 +6,7 @@ import org.gcube.common.events.Hub;
import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.configuration.application.ApplicationConfiguration;
import org.gcube.smartgears.context.Properties; import org.gcube.smartgears.context.Properties;
import org.gcube.smartgears.context.container.ContainerContext; import org.gcube.smartgears.context.container.ContainerContext;
import org.gcube.smartgears.health.HealthManager;
import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle;
import org.gcube.smartgears.persistence.PersistenceWriter; import org.gcube.smartgears.persistence.PersistenceWriter;
import org.gcube.smartgears.security.AuthorizationProvider; import org.gcube.smartgears.security.AuthorizationProvider;

View File

@ -51,4 +51,5 @@ public interface ContainerContext {
AuthorizationProvider authorizationProvider(); AuthorizationProvider authorizationProvider();
} }

View File

@ -22,6 +22,8 @@ public interface ApplicationExtension extends Servlet {
*/ */
void init(ApplicationContext context) throws Exception; void init(ApplicationContext context) throws Exception;
void stop();
/** /**
* Returns the name of this extension. * Returns the name of this extension.
* @return the name * @return the name

View File

@ -76,6 +76,9 @@ public abstract class HttpExtension extends HttpServlet implements ApplicationEx
this.context=context; this.context=context;
} }
@Override
public void stop() {}
@Override @Override
public Set<Exclude> excludes() { public Set<Exclude> excludes() {
return new HashSet<Exclude>(); //all managed by default return new HashSet<Exclude>(); //all managed by default

View File

@ -4,41 +4,108 @@ import static org.gcube.smartgears.Constants.application_json;
import static org.gcube.smartgears.extensions.HttpExtension.Method.GET; import static org.gcube.smartgears.extensions.HttpExtension.Method.GET;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
import java.util.Timer;
import java.util.stream.Collectors;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.gcube.common.health.api.response.HealthResponse; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.common.health.api.HealthCheck;
import org.gcube.common.health.api.ReadinessChecker;
import org.gcube.smartgears.context.application.ApplicationContext;
import org.gcube.smartgears.extensions.ApiResource; import org.gcube.smartgears.extensions.ApiResource;
import org.gcube.smartgears.extensions.ApiSignature; import org.gcube.smartgears.extensions.ApiSignature;
import org.gcube.smartgears.handlers.application.request.RequestError;
import org.gcube.smartgears.health.HealthManager;
import org.gcube.smartgears.health.HealthResponse;
import org.gcube.smartgears.health.HealthTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
public class HealthResource extends ApiResource { public class HealthResource extends ApiResource {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(HealthResource.class);
public static final String mapping = "/health"; public static final String mapping = "/health";
private static final ApiSignature signature = handles(mapping).with(method(GET).produces(application_json)); private static final ApiSignature signature = handles(mapping).with(method(GET).produces(application_json));
private HealthManager manager;
private HealthTask task;
private Timer timer;
HealthResource() { HealthResource() {
super(signature); super(signature);
} }
@Override
public void init(ApplicationContext context) throws Exception {
Set<Class<?>> annotatedReadiness;
try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo().scan()) {
ClassInfoList classInfos = result.getClassesWithAnnotation(ReadinessChecker.class.getName());
annotatedReadiness = classInfos.stream().map(ClassInfo::loadClass)
.filter(c -> HealthCheck.class.isAssignableFrom(c)).collect(Collectors.toSet());
}
manager = new HealthManager();
for (Class<?> readnessClass : annotatedReadiness)
try {
manager.register((HealthCheck) readnessClass.getDeclaredConstructor().newInstance());
log.info("added class {} to health manager", readnessClass.getCanonicalName());
} catch (Throwable e) {
log.error("healthChecher class {} cannot be instantiated", readnessClass.getCanonicalName(), e);
}
task = new HealthTask(manager);
timer = new Timer(true);
timer.scheduleAtFixedRate(task, 0, 10000);
super.init(context);
}
public void stop() {
timer.cancel();
}
@Override @Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
Object response = null;
} String contextpath = req.getContextPath();
log.info("context path is {}", req.getContextPath());
if (contextpath.endsWith("/readiness"))
response = readiness();
else
RequestError.resource_notfound_error.fire();
new ObjectMapper().writeValue(out, response);
public HealthResponse liveness() { out.flush();
return null;
} }
public HealthResponse readiness() { public HealthResponse readiness() {
return null; return task.getResponse();
} }
} }

View File

@ -0,0 +1,18 @@
package org.gcube.smartgears.health;
import java.util.List;
import org.gcube.common.health.api.HealthCheck;
public class HealthManager {
private List<HealthCheck> checks;
public void register(HealthCheck check){
checks.add(check);
}
public List<HealthCheck> getChecks() {
return checks;
}
}

View File

@ -0,0 +1,35 @@
package org.gcube.smartgears.health;
import java.util.List;
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude;
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.gcube.common.health.api.Status;
import org.gcube.common.health.api.response.HealthCheckResponse;
import org.gcube.common.validator.annotations.NotNull;
@JsonInclude(Include.NON_NULL)
public class HealthResponse {
@NotNull
private Status status;
private List<HealthCheckResponse> checks;
public HealthResponse(Status status, List<HealthCheckResponse> checks) {
super();
this.status = status;
this.checks = checks;
}
public Status getStatus() {
return status;
}
public List<HealthCheckResponse> getChecks() {
return checks;
}
}

View File

@ -0,0 +1,43 @@
package org.gcube.smartgears.health;
import java.util.List;
import java.util.TimerTask;
import java.util.stream.Collectors;
import org.gcube.common.health.api.HealthCheck;
import org.gcube.common.health.api.Status;
import org.gcube.common.health.api.response.HealthCheckResponse;
public class HealthTask extends TimerTask{
private HealthResponse response;
private HealthManager healthManager;
public HealthTask(HealthManager healthManager) {
this.healthManager = healthManager;
}
@Override
public void run() {
List<HealthCheck> checks = healthManager.getChecks();
List<HealthCheckResponse> responses = checks.stream().map(c -> this.wrap(c)).collect(Collectors.toList());
Status totalStatus = responses.stream().anyMatch(r -> r.getStatus().equals(Status.DOWN)) ? Status.DOWN : Status.UP;
this.response = new HealthResponse(totalStatus, responses);
}
public HealthResponse getResponse() {
return response;
}
private HealthCheckResponse wrap(HealthCheck check){
try {
return check.check();
}catch (Throwable t) {
return HealthCheckResponse.builder(check.getName()).down().withMessage("unexpected error executing check").build();
}
}
}

View File

@ -12,6 +12,10 @@ public class KeyCloakHealthCheck implements HealthCheck{
private static final String CHECK_NAME = "authorization-check" ; private static final String CHECK_NAME = "authorization-check" ;
public String getName(){
return CHECK_NAME;
}
@Override @Override
public HealthCheckResponse check() { public HealthCheckResponse check() {
try { try {

View File

@ -13,6 +13,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -52,6 +53,8 @@ public class ApplicationManager {
private ApplicationContext context; private ApplicationContext context;
private ApplicationExtensions extensions;
/** /**
* Starts application management. * Starts application management.
* *
@ -80,7 +83,7 @@ public class ApplicationManager {
ApplicationHandlers handlers = provider().handlersFor(context); ApplicationHandlers handlers = provider().handlersFor(context);
ApplicationExtensions extensions = provider().extensionsFor(context); extensions = provider().extensionsFor(context);
extensions.validate(); extensions.validate();
List<ApplicationLifecycleHandler> lifecycleHandlers = handlers.lifecycleHandlers(); List<ApplicationLifecycleHandler> lifecycleHandlers = handlers.lifecycleHandlers();
@ -151,6 +154,9 @@ public class ApplicationManager {
context.events().fire(context, ApplicationLifecycle.stop); context.events().fire(context, ApplicationLifecycle.stop);
if (extensions != null)
unregister(extensions);
stopLifecycleHandlers(); stopLifecycleHandlers();
log.info("stopping application events for {}", context.name()); log.info("stopping application events for {}", context.name());
@ -228,6 +234,15 @@ public class ApplicationManager {
} }
private void unregister(ApplicationExtensions extensions) {
for (ApplicationExtension extension : extensions.extensions())
extension.stop();
}
private void start(List<ApplicationLifecycleHandler> handlers) { private void start(List<ApplicationLifecycleHandler> handlers) {
try { try {

View File

@ -14,12 +14,11 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
@ -48,14 +47,14 @@ import org.gcube.smartgears.publishing.SmartgearsProfilePublisher;
import org.gcube.smartgears.security.AuthorizationProvider; import org.gcube.smartgears.security.AuthorizationProvider;
import org.gcube.smartgears.security.AuthorizationProviderFactory; import org.gcube.smartgears.security.AuthorizationProviderFactory;
import org.gcube.smartgears.utils.Utils; import org.gcube.smartgears.utils.Utils;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
/** /**
* Default implementation of the {@link Provider} interface. * Default implementation of the {@link Provider} interface.
* *
@ -69,7 +68,6 @@ public class DefaultProvider implements Provider {
private ContainerContext containerContext; private ContainerContext containerContext;
// TODO: do the same with applicationContext (with a map) // TODO: do the same with applicationContext (with a map)
private File configFile = null; private File configFile = null;
protected DefaultProvider(File configFile) { protected DefaultProvider(File configFile) {
@ -78,8 +76,8 @@ public class DefaultProvider implements Provider {
List<Publisher> publishers; List<Publisher> publishers;
protected DefaultProvider() {
protected DefaultProvider(){}; };
@Override @Override
public ContainerContext containerContext() { public ContainerContext containerContext() {
@ -109,12 +107,14 @@ public class DefaultProvider implements Provider {
} }
AuthorizationProviderFactory<?> authfactory = configuration.getauthorizationConfiguration().getAuthProviderFactory(); AuthorizationProviderFactory<?> authfactory = configuration.getauthorizationConfiguration()
.getAuthProviderFactory();
Credentials credentials = configuration.getauthorizationConfiguration().getCredentials(); Credentials credentials = configuration.getauthorizationConfiguration().getCredentials();
AuthorizationProvider authProvider = authfactory.connect(credentials); AuthorizationProvider authProvider = authfactory.connect(credentials);
containerContext = new DefaultContainerContext(id, configuration, hub, lifecycle, authProvider, new Properties()); containerContext = new DefaultContainerContext(id, configuration, hub, lifecycle, authProvider,
new Properties());
} }
return containerContext; return containerContext;
} }
@ -127,7 +127,8 @@ public class DefaultProvider implements Provider {
ContainerConfigurationBinder binder = new ContainerConfigurationBinder(); ContainerConfigurationBinder binder = new ContainerConfigurationBinder();
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
if (currentClassLoader.getParent()!=null && !currentClassLoader.getParent().equals(ClassLoader.getSystemClassLoader())){ if (currentClassLoader.getParent() != null
&& !currentClassLoader.getParent().equals(ClassLoader.getSystemClassLoader())) {
log.trace("probably i'm in a webapp classloader"); log.trace("probably i'm in a webapp classloader");
currentClassLoader = currentClassLoader.getParent(); currentClassLoader = currentClassLoader.getParent();
} }
@ -153,28 +154,31 @@ public class DefaultProvider implements Provider {
// shouldn't happen: management shouldn't have started at all // shouldn't happen: management shouldn't have started at all
if (embedded == null && external == null) if (embedded == null && external == null)
throw new AssertionError("application @ " + application.getContextPath() + " is not distributed with " throw new AssertionError("application @ " + application.getContextPath() + " is not distributed with "
+ configuration_file_path+" and there is no external configuration for it in "+container_configuraton_file_path); + configuration_file_path + " and there is no external configuration for it in "
+ container_configuraton_file_path);
// no embedded configuration // no embedded configuration
if (embedded == null) { if (embedded == null) {
configuration = external; configuration = external;
log.info("loaded configuration for application "+configuration.name()+" from "+container_configuraton_file_path); log.info("loaded configuration for application " + configuration.name() + " from "
} + container_configuraton_file_path);
else { } else {
configuration = embedded; configuration = embedded;
if (external == null) if (external == null)
log.info("loaded configuration for application "+configuration.name()+" from "+configuration_file_path); log.info("loaded configuration for application " + configuration.name() + " from "
+ configuration_file_path);
else { else {
configuration.merge(external); configuration.merge(external);
log.info("loaded configuration for application "+configuration.name()+" from "+configuration_file_path+" and "+container_configuraton_file_path); log.info("loaded configuration for application " + configuration.name() + " from "
+ configuration_file_path + " and " + container_configuraton_file_path);
} }
} }
@ -208,14 +212,13 @@ public class DefaultProvider implements Provider {
try { try {
ApplicationConfigurationBinder binder = new ApplicationConfigurationBinder(); ApplicationConfigurationBinder binder = new ApplicationConfigurationBinder();
// searching for smartegars related application handlers in the common
// classloader
//searching for smartegars related application handlers in the common classloader
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
if (currentClassLoader.getParent()!=null && !currentClassLoader.getParent().equals(ClassLoader.getSystemClassLoader())){ if (currentClassLoader.getParent() != null
&& !currentClassLoader.getParent().equals(ClassLoader.getSystemClassLoader())) {
log.trace("probably i'm in a webapp classloader"); log.trace("probably i'm in a webapp classloader");
currentClassLoader = currentClassLoader.getParent(); currentClassLoader = currentClassLoader.getParent();
} }
@ -224,15 +227,14 @@ public class DefaultProvider implements Provider {
return defaultHandlers; return defaultHandlers;
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new RuntimeException("cannot install handlers for application @ " + context.name()+" (see cause) ", e); throw new RuntimeException("cannot install handlers for application @ " + context.name() + " (see cause) ",
e);
} }
} }
@Override @Override
public ApplicationExtensions extensionsFor(ApplicationContext context) { public ApplicationExtensions extensionsFor(ApplicationContext context) {
@ -248,7 +250,8 @@ public class DefaultProvider implements Provider {
config = getClass().getResourceAsStream(default_extensions_file_path); config = getClass().getResourceAsStream(default_extensions_file_path);
if (config == null) if (config == null)
throw new IllegalStateException("invalid distribution: cannot find " + default_extensions_file_path); throw new IllegalStateException(
"invalid distribution: cannot find " + default_extensions_file_path);
} else } else
log.info("{} uses custom extensions @ {}", context.name(), extensions_file_path); log.info("{} uses custom extensions @ {}", context.name(), extensions_file_path);
@ -259,12 +262,12 @@ public class DefaultProvider implements Provider {
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new RuntimeException("cannot install extensions for application @ " + context.name()+" (see cause) ", e); throw new RuntimeException(
"cannot install extensions for application @ " + context.name() + " (see cause) ", e);
} }
} }
@Override @Override
public SmartGearsConfiguration smartgearsConfiguration() { public SmartGearsConfiguration smartgearsConfiguration() {
@ -324,22 +327,24 @@ public class DefaultProvider implements Provider {
File homeDir = new File(home); File homeDir = new File(home);
if (!(homeDir.exists() && homeDir.isDirectory() && homeDir.canRead() && homeDir.canWrite())) if (!(homeDir.exists() && homeDir.isDirectory() && homeDir.canRead() && homeDir.canWrite()))
throw new IllegalStateException("invalid node configuration: home "+home+" does not exist or is not a directory or cannot be accessed in read/write mode"); throw new IllegalStateException("invalid node configuration: home " + home
+ " does not exist or is not a directory or cannot be accessed in read/write mode");
configFile = new File(homeDir, container_configuraton_file_path); configFile = new File(homeDir, container_configuraton_file_path);
log.trace("reading container configuration @ {} ", configFile.getAbsolutePath()); log.trace("reading container configuration @ {} ", configFile.getAbsolutePath());
} }
if (!(configFile.exists() && configFile.canRead())) if (!(configFile.exists() && configFile.canRead()))
throw new IllegalStateException("invalid node configuration: file "+configFile.getAbsolutePath()+" does not exist or cannot be accessed"); throw new IllegalStateException("invalid node configuration: file " + configFile.getAbsolutePath()
+ " does not exist or cannot be accessed");
ContainerConfiguration configuration; ContainerConfiguration configuration;
try (InputStream stream = new FileInputStream(configFile)) { try (InputStream stream = new FileInputStream(configFile)) {
configuration = new ContainerConfigurationBinder().load(stream); configuration = new ContainerConfigurationBinder().load(stream);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("invalid node configuration: file "+configFile.getAbsolutePath()+" is invalid", e); throw new IllegalStateException(
"invalid node configuration: file " + configFile.getAbsolutePath() + " is invalid", e);
} }
return configuration; return configuration;
@ -348,27 +353,42 @@ public class DefaultProvider implements Provider {
@Override @Override
public synchronized List<Publisher> publishers() { public synchronized List<Publisher> publishers() {
if (this.publishers == null) { if (this.publishers == null) {
//retrieve from root class loader
Collection<URL> urls = ClasspathHelper.forClassLoader(Thread.currentThread().getContextClassLoader());
urls.removeIf(url -> url.toString().endsWith(".so") || url.toString().endsWith(".zip") );
Set<Class<?>> annotatedPublishers;
ConfigurationBuilder reflectionConf = new ConfigurationBuilder().addUrls(urls).setScanners(new TypeAnnotationsScanner(), new SubTypesScanner()); try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo()
.addClassLoader(Thread.currentThread().getContextClassLoader()).scan()) {
Reflections reflection = new Reflections(reflectionConf); ClassInfoList classInfos = result.getClassesWithAnnotation(SmartgearsProfilePublisher.class.getName());
annotatedPublishers = classInfos.stream().map(ClassInfo::loadClass)
.filter(c -> Publisher.class.isAssignableFrom(c)).collect(Collectors.toSet());
}
/*
* Collection<URL> urls =
* ClasspathHelper.forClassLoader(Thread.currentThread().getContextClassLoader()
* ); urls.removeIf(url -> url.toString().endsWith(".so") ||
* url.toString().endsWith(".zip") );
*
*
* ConfigurationBuilder reflectionConf = new
* ConfigurationBuilder().addUrls(urls).setScanners(new
* TypeAnnotationsScanner(), new SubTypesScanner());
*
* Reflections reflection = new Reflections(reflectionConf);
*
* = reflection.getTypesAnnotatedWith(SmartgearsProfilePublisher.class);
*/
Set<Class<?>> annotatedPublishers = reflection.getTypesAnnotatedWith(SmartgearsProfilePublisher.class);
List<Publisher> foundPublishers = new ArrayList<Publisher>(); List<Publisher> foundPublishers = new ArrayList<Publisher>();
for (Class<?> annotatedPublisher : annotatedPublishers) { for (Class<?> annotatedPublisher : annotatedPublishers) {
if (Publisher.class.isAssignableFrom(annotatedPublisher))
try { try {
foundPublishers.add((Publisher)annotatedPublisher.newInstance()); foundPublishers.add((Publisher) annotatedPublisher.getDeclaredConstructor().newInstance());
log.info("added class {} to publishers", annotatedPublisher); log.info("added class {} to publishers", annotatedPublisher);
} catch (Exception e) { } catch (Throwable e) {
log.error("publisher class {} cannot be instantiated", annotatedPublisher.getCanonicalName(), e); log.error("publisher class {} cannot be instantiated", annotatedPublisher.getCanonicalName(), e);
} }
else
log.warn("publisher class {} discarded, it doesn't implements Publisher class", annotatedPublisher.getCanonicalName());
} }
this.publishers = foundPublishers; this.publishers = foundPublishers;
@ -381,10 +401,8 @@ public class DefaultProvider implements Provider {
} }
/* /*
@Override * @Override public AuthorizationProvider authorizationProvider() { return
public AuthorizationProvider authorizationProvider() { * containerContext.authorizationProvider(); }
return containerContext.authorizationProvider();
}
*/ */
} }