Health extension implemented

master
lucio 1 year ago
parent 98c41e7463
commit b873f97187

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

@ -82,5 +82,5 @@ public interface ApplicationContext {
* @return the AuhtorizationProvider
**/
AuthorizationProvider authorizationProvider();
}

@ -6,6 +6,7 @@ import org.gcube.common.events.Hub;
import org.gcube.smartgears.configuration.application.ApplicationConfiguration;
import org.gcube.smartgears.context.Properties;
import org.gcube.smartgears.context.container.ContainerContext;
import org.gcube.smartgears.health.HealthManager;
import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle;
import org.gcube.smartgears.persistence.PersistenceWriter;
import org.gcube.smartgears.security.AuthorizationProvider;
@ -42,7 +43,7 @@ public class DefaultApplicationContext implements ApplicationContext {
this.configuration=configuration;
this.hub=hub;
this.lifecycle = lifecycle;
this.properties=properties;
this.properties=properties;
}
/**
@ -106,6 +107,6 @@ public class DefaultApplicationContext implements ApplicationContext {
**/
public AuthorizationProvider authorizationProvider() {
return container().authorizationProvider();
}
}
}

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

@ -56,7 +56,7 @@ public abstract class ApiResource extends HttpExtension {
public Set<Exclude> excludes() {
return Collections.singleton(new Exclude(Constants.root_mapping+mapping()));
}
/**
* Returns <code>true</code> if this resource supports a given method.
* @param method the method

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

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

@ -4,41 +4,108 @@ import static org.gcube.smartgears.Constants.application_json;
import static org.gcube.smartgears.extensions.HttpExtension.Method.GET;
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.http.HttpServletRequest;
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.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 {
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(HealthResource.class);
public static final String mapping = "/health";
private static final ApiSignature signature = handles(mapping).with(method(GET).produces(application_json));
private HealthManager manager;
private HealthTask task;
private Timer timer;
HealthResource() {
super(signature);
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
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 HealthResponse liveness() {
return null;
public void stop() {
timer.cancel();
}
@Override
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);
out.flush();
}
public HealthResponse readiness() {
return null;
return task.getResponse();
}
}

@ -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;
}
}

@ -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;
}
}

@ -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();
}
}
}

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

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

@ -14,12 +14,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
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.AuthorizationProviderFactory;
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.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.
*
@ -67,8 +66,7 @@ public class DefaultProvider implements Provider {
private static Logger log = LoggerFactory.getLogger(Provider.class);
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;
@ -78,13 +76,13 @@ public class DefaultProvider implements Provider {
List<Publisher> publishers;
protected DefaultProvider(){};
protected DefaultProvider() {
};
@Override
public ContainerContext containerContext() {
if(containerContext==null){
if (containerContext == null) {
ContainerConfiguration configuration = containerConfiguration();
Hub hub = new DefaultHub();
@ -94,27 +92,29 @@ public class DefaultProvider implements Provider {
File file = configuration.persistence().file(container_profile_file_path);
String id = null;
if (file.exists()){
if (file.exists()) {
log.info("loading persisted state for container");
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){
id = (String)ois.readObject();
}catch(Exception e){
log.error("error loading persisted state, creating new uuid",e);
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
id = (String) ois.readObject();
} catch (Exception e) {
log.error("error loading persisted state, creating new uuid", e);
}
}
if (id==null){
}
if (id == null) {
id = UUID.randomUUID().toString();
log.info("container id created is {}",id);
log.info("container id created is {}", id);
}
}
AuthorizationProviderFactory<?> authfactory = configuration.getauthorizationConfiguration().getAuthProviderFactory();
AuthorizationProviderFactory<?> authfactory = configuration.getauthorizationConfiguration()
.getAuthProviderFactory();
Credentials credentials = configuration.getauthorizationConfiguration().getCredentials();
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;
}
@ -127,12 +127,13 @@ public class DefaultProvider implements Provider {
ContainerConfigurationBinder binder = new ContainerConfigurationBinder();
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");
currentClassLoader = currentClassLoader.getParent();
}
List<ContainerHandler> defaultHandlers = binder.bindHandlers(currentClassLoader);
List<ContainerHandler> defaultHandlers = binder.bindHandlers(currentClassLoader);
return defaultHandlers;
@ -150,34 +151,37 @@ public class DefaultProvider implements Provider {
ApplicationConfiguration embedded = configurationFor(application);
ApplicationConfiguration external = context.configuration().app(application.getContextPath());
//shouldn't happen: management shouldn't have started at all
if (embedded==null && external==null)
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);
// shouldn't happen: management shouldn't have started at all
if (embedded == null && external == null)
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);
//no embedded configuration
// no embedded configuration
if (embedded == null) {
configuration = external ;
configuration = external;
log.info("loaded configuration for application "+configuration.name()+" from "+container_configuraton_file_path);
}
else {
log.info("loaded configuration for application " + configuration.name() + " from "
+ container_configuraton_file_path);
} else {
configuration = embedded;
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 {
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);
}
}
}
ApplicationConfiguration bridgedConfiguration = new BridgedApplicationConfiguration(context.configuration(),
configuration);
@ -187,16 +191,16 @@ public class DefaultProvider implements Provider {
ApplicationLifecycle lifecycle = new ApplicationLifecycle(hub, configuration.name());
File file = bridgedConfiguration.persistence().file(profile_file_path);
String id= null;
if (file.exists()){
String id = null;
if (file.exists()) {
log.info("loading persisted state for application {}", application.getContextPath());
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){
id = (String)ois.readObject();
}catch(Exception e){
log.error("error loading persisted state, creating new uuid",e);
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
id = (String) ois.readObject();
} catch (Exception e) {
log.error("error loading persisted state, creating new uuid", e);
}
}
if (id==null)
}
if (id == null)
id = UUID.randomUUID().toString();
return new DefaultApplicationContext(id, context, application, bridgedConfiguration, hub, lifecycle,
@ -208,31 +212,29 @@ public class DefaultProvider implements Provider {
try {
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();
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");
currentClassLoader = currentClassLoader.getParent();
}
ApplicationHandlers defaultHandlers = binder.bindHandlers(currentClassLoader);
return defaultHandlers;
return defaultHandlers;
} 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
public ApplicationExtensions extensionsFor(ApplicationContext context) {
@ -248,7 +250,8 @@ public class DefaultProvider implements Provider {
config = getClass().getResourceAsStream(default_extensions_file_path);
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
log.info("{} uses custom extensions @ {}", context.name(), extensions_file_path);
@ -259,12 +262,12 @@ public class DefaultProvider implements Provider {
} 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
public SmartGearsConfiguration smartgearsConfiguration() {
@ -297,7 +300,7 @@ public class DefaultProvider implements Provider {
InputStream config = application.getResourceAsStream(configuration_file_path);
if (config == null)
if (config == null)
return null;
ApplicationConfigurationBinder binder = new ApplicationConfigurationBinder();
@ -313,7 +316,7 @@ public class DefaultProvider implements Provider {
private ContainerConfiguration containerConfiguration() {
if (configFile==null) {
if (configFile == null) {
String home = Utils.home();
@ -324,22 +327,24 @@ public class DefaultProvider implements Provider {
File homeDir = new File(home);
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");
configFile = new File(homeDir,container_configuraton_file_path);
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);
log.trace("reading container configuration @ {} ", configFile.getAbsolutePath());
}
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;
try (InputStream stream = new FileInputStream(configFile)){
configuration= new ContainerConfigurationBinder().load(stream);
}catch (Exception e) {
throw new IllegalStateException("invalid node configuration: file "+configFile.getAbsolutePath()+" is invalid", e);
try (InputStream stream = new FileInputStream(configFile)) {
configuration = new ContainerConfigurationBinder().load(stream);
} catch (Exception e) {
throw new IllegalStateException(
"invalid node configuration: file " + configFile.getAbsolutePath() + " is invalid", e);
}
return configuration;
@ -348,43 +353,56 @@ public class DefaultProvider implements Provider {
@Override
public synchronized List<Publisher> publishers() {
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;
try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo()
.addClassLoader(Thread.currentThread().getContextClassLoader()).scan()) {
ClassInfoList classInfos = result.getClassesWithAnnotation(SmartgearsProfilePublisher.class.getName());
ConfigurationBuilder reflectionConf = new ConfigurationBuilder().addUrls(urls).setScanners(new TypeAnnotationsScanner(), new SubTypesScanner());
annotatedPublishers = classInfos.stream().map(ClassInfo::loadClass)
.filter(c -> Publisher.class.isAssignableFrom(c)).collect(Collectors.toSet());
Reflections reflection = new Reflections(reflectionConf);
}
/*
* 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>();
for (Class<?> annotatedPublisher: annotatedPublishers) {
if (Publisher.class.isAssignableFrom(annotatedPublisher))
try {
foundPublishers.add((Publisher)annotatedPublisher.newInstance());
log.info("added class {} to publishers",annotatedPublisher);
} catch (Exception 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());
for (Class<?> annotatedPublisher : annotatedPublishers) {
try {
foundPublishers.add((Publisher) annotatedPublisher.getDeclaredConstructor().newInstance());
log.info("added class {} to publishers", annotatedPublisher);
} catch (Throwable e) {
log.error("publisher class {} cannot be instantiated", annotatedPublisher.getCanonicalName(), e);
}
}
this.publishers = foundPublishers;
if (foundPublishers.isEmpty())
log.warn("no publishers found in classloader");
}
return this.publishers;
}
/*
@Override
public AuthorizationProvider authorizationProvider() {
return containerContext.authorizationProvider();
}
*/
/*
* @Override public AuthorizationProvider authorizationProvider() { return
* containerContext.authorizationProvider(); }
*/
}

Loading…
Cancel
Save