Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Microprofile Health #25186

Draft
wants to merge 31 commits into
base: master
Choose a base branch
from
Draft

Conversation

Thihup
Copy link
Contributor

@Thihup Thihup commented Oct 16, 2024

This change supports the deployment of MicroProfile Health 4.0 compatible applications, and modifies GF full profile to pass the MP Health 4.0.1 TCK by implementing the specification from scratch.

The MicroProfile Health uses the MicroProfile Config to check for two properties, so instead of requiring an explicit dependency,it handles the absence of the MP Config by returning a default value.

Currently this implementation does not add any default procedure.
Each application now contains four new endpoints: /health, /health/live, /health/ready and /health/started.

The TCK requires a single property (mp.health.disable-default-procedures) to be set (although currently unused by the implementation)

Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@arjantijms
Copy link
Contributor

Wow, super cool! Just curious, how many of the TCK tests are passing at the moment?

Maybe the manifest.mf file can be generated? It usually is, but I didn't check yet if you're doing anything special. Just a quick glance.

@Thihup
Copy link
Contributor Author

Thihup commented Oct 17, 2024

Tests passed/Failed/Skipped: | 27/1/0
The only test failing is the testEmptyReadinessWithConfig.

The main issue related to this failure is use of the Microprofile Config. As I'm creating a new MpConfigProviderResolver, it is not reading the microprofile-config.properties.

As I understand, the correct use should be ConfigProvider.getConfig(), but using it fails with No ConfigProviderResolver implementation found!. I couldn't understand why it was happening as I required the implementation (Import-Package: io.helidon.config.mp;).

Related to the manifest, I had to change it to allow the use of a higher CDI version. I followed the pattern of the microprofile-jwt-auth-api.

@Thihup
Copy link
Contributor Author

Thihup commented Oct 17, 2024

Another issue: right now I'm registering the Servlet as the following http://localhost:8080/my-app/health, but the spec requires it to be http://localhost:8080/my-app/health.

@OndroMih
Copy link
Contributor

OndroMih commented Oct 17, 2024

@Thihup, regarding "As I understand, the correct use should be ConfigProvider.getConfig(), but using it fails with No ConfigProviderResolver implementation found!", this is probably related to classloading or the order in which MP Health and Config are initialized. Config in GF is initialized in https://github.com/eclipse-ee4j/glassfish/blob/master/appserver/microprofile/config/src/main/java/org/glassfish/microprofile/config/ConfigDeployer.java. Maybe it happens after MP Health is initialized and thus Config is not yet available. If it's the case, we need to ensure that Health initializes after Config.

Alternatively, refactor ConfigDeployer.java to extract the initialization into a separate method that initializes MP Config if it's not initialized, and call the method directly from the MP Health initializer or via a CDI event triggered from the Health initializer. I prepared something like that in my local branch with experiments related to MP Config, which I only have locally and haven't prepared as a PR yet:

@Override
    public ApplicationContainer load(Container container, DeploymentContext deploymentContext) {
        initializeConfigProviders();
        return new ConfigApplicationContainer(deploymentContext);
    }

    /**
     * Initialise Config providers if they haven't been initialized
     */
    public void initializeConfigProviders() {
        if (!configProvidersInitialized) {
            final var resolver = new MpConfigProviderResolver();
            ConfigProviderResolver.setInstance(resolver);
            configProvidersInitialized = true;
        }
    }

And then ConfigDeployer could be injected into the Health initializer and call the initializeConfigProviders() method from there to ensure Config is initialized. Or, even better, fire an HK2 event from Health initializer and handle the event by calling the method.

@arjantijms
Copy link
Contributor

Related to the manifest, I had to change it to allow the use of a higher CDI version. I followed the pattern of the microprofile-jwt-auth-api.

You are absolutely right. I missed it being in a patches folder.

(maybe for the future we can add some extension somewhere in OSGi so that we can more directly specify we allow higher versions, without patching the entire manifest)

Thihup added 10 commits October 28, 2024 09:34
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@Thihup Thihup force-pushed the microprofile-health branch from 0544532 to 7d13b7d Compare October 29, 2024 18:00
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@Thihup Thihup marked this pull request as ready for review October 29, 2024 18:54
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@dmatej dmatej added this to the 7.1.0 milestone Oct 31, 2024
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@dmatej dmatej added the New feature A major new user functionality was added label Nov 20, 2024
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
Signed-off-by: Thiago Henrique Hüpner <[email protected]>
@dmatej dmatej requested a review from OndroMih December 30, 2024 09:18
@Thihup Thihup marked this pull request as draft December 30, 2024 11:11
Copy link
Contributor

@OndroMih OndroMih left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome!

Stream<HealthCheck> healthChecks = applicationHealthChecks.values()
.stream()
.flatMap(Collection::stream);
// Stream.concat(reportKind.healthChecks(), applicationHealthChecks.stream());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment, please

Comment on lines +60 to +73
<Import-Package>
jakarta.enterprise.context.spi;
jakarta.enterprise.event;
jakarta.enterprise.inject;
jakarta.enterprise.inject.spi;
jakarta.json.bind;
jakarta.servlet;
jakarta.servlet.http;
org.eclipse.microprofile.health;
org.glassfish.internal.api;
org.glassfish.microprofile.health;
org.glassfish.hk2.api;
org.glassfish.hk2.utilities;
</Import-Package>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <Import-Package> block can be removed. By default, all imported packages are added automatically: https://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html#_import_package

Suggested change
<Import-Package>
jakarta.enterprise.context.spi;
jakarta.enterprise.event;
jakarta.enterprise.inject;
jakarta.enterprise.inject.spi;
jakarta.json.bind;
jakarta.servlet;
jakarta.servlet.http;
org.eclipse.microprofile.health;
org.glassfish.internal.api;
org.glassfish.microprofile.health;
org.glassfish.hk2.api;
org.glassfish.hk2.utilities;
</Import-Package>


@Service(name = "healthcheck-service")
@RunLevel(StartupRunLevel.VAL)
public class HealthService implements EventListener {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's required to register the event listener, otherwise the event method won't be called:

import org.glassfish.api.event.Events;
import org.glassfish.hk2.api.PostConstruct;

public class HealthService implements PostConstruct, EventListener {

    @Inject
    Events events;

    @Override
    public void postConstruct() {
        events.register(this);
    }

HealthReporter service = Globals.getDefaultHabitat().getService(HealthReporter.class);

if (event.is(Deployment.APPLICATION_UNLOADED) && event.hook() instanceof ApplicationInfo appInfo) {
service.removeAllHealthChecksFrom(appInfo.getName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The healthchecks are registered with "" context root but here we want to remove them by appInfo.getName(), which is not "". As a result, the health checks are not removed on undeployment, remain leaking, and even remain in the output of the /health endpoint.

public void afterDeploymentValidation(@Observes AfterDeploymentValidation adv, BeanManager beanManager) {
healthChecks.forEach(bean -> {
CreationalContext<HealthCheck> creationalContext = beanManager.createCreationalContext(bean);
service.addHealthCheck("", bean.create(creationalContext));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should bind the health checks to application name, which has to be unique. When undeployed, HealthService.event should unbind health checks for the same application name.

The problem with the current approach is that when 2 applications are deployed, they deploy all health checks under the same context name. When one of the applications is undeployed, it would remove health checks also from other applications. We need to add and remove health checks for a single application under a unique context.

public void addHealthCheck(String contextPath, HealthCheck healthCheck) {
applicationHealthChecks.computeIfAbsent(contextPath, k -> new CopyOnWriteArrayList<>())
.add(healthCheck);
// applicationHealthChecks.add(healthCheck);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

}


public void addHealthCheck(String contextPath, HealthCheck healthCheck) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename contextPath to contextName. It has nothing to do with a "path", it's just a context to group several checks. In GlassFish, it would be application name.

org.glassfish.hk2.api;
org.glassfish.hk2.utilities;
</Import-Package>
</instructions>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hk2-locator resources need to be included, otherwise HK2 will not find the HealthService service.

Suggested change
</instructions>
<Include-Resource>
{maven-resources},
META-INF/hk2-locator/=target/classes/META-INF/hk2-locator/,
</Include-Resource>
</instructions>

*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.microprofile.impl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this package. It doesn't even mention "health". For example:

Suggested change
package org.glassfish.microprofile.impl;
package org.glassfish.microprofile.health.service;

If the package name is not "impl", then it can even be removed from <Export-Package> in pom.xml.

service.removeAllHealthChecksFrom(appInfo.getName());
}

if (event.is(Deployment.APPLICATION_STARTED) && event.hook() instanceof ApplicationInfo appInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either remove this block, or use it to find out the application name to be used in the CDI extension for the application context name


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove maven.compiler.release setting, it's ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New feature A major new user functionality was added
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants