Skip to content

Commit

Permalink
feat (jkube-kit) : Add HelidonHealthCheckEnricher to add Kubernetes h…
Browse files Browse the repository at this point in the history
…ealth checks for Helidon applications (eclipse-jkube#1713)

+ Add HelidonHealthCheckEnricher to automatically add liveness,
  readiness and start probes based on already existing microprofile
  health check enricher

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Apr 10, 2023
1 parent 355a982 commit 1720ee0
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Usage:
./scripts/extract-changelog-for-version.sh 1.3.37 5
```
### 1.13-SNAPSHOT
* Fix #1713: Add HelidonHealthCheckEnricher to add Kubernetes health checks for Helidon applications
* Fix #1714: Add HelidonGenerator to add opinionated container image for Helidon applications

### 1.12.0 (2023-04-03)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
- jkube-healthcheck-micronaut
- jkube-healthcheck-openliberty
- jkube-healthcheck-smallrye
- jkube-healthcheck-helidon
- jkube-prometheus
- jkube-service-discovery

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- jkube-healthcheck-micronaut
- jkube-healthcheck-openliberty
- jkube-healthcheck-smallrye
- jkube-healthcheck-helidon

# DeploymentConfigEnricher converts Deployment into DeploymentConfig
- jkube-openshift-deploymentconfig
Expand Down
5 changes: 5 additions & 0 deletions jkube-kit/jkube-kit-helidon/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<artifactId>jkube-kit-generator-java-exec</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.jkube</groupId>
<artifactId>jkube-kit-microprofile</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public static boolean hasHelidonGraalNativeImageExtension(JavaProject javaProjec
JKubeProjectUtil.hasDependency(javaProject, "io.helidon.integrations.graal", "helidon-mp-graal-native-image-extension");
}

public static boolean hasHelidonHealthDependency(JavaProject javaProject) {
return JKubeProjectUtil.hasTransitiveDependency(javaProject, "io.helidon.health", "helidon-health");
}

public static Properties getHelidonConfiguration(JavaProject javaProject) {
final URLClassLoader urlClassLoader = getClassLoader(javaProject);
final List<Supplier<Properties>> sources = Arrays.asList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.helidon.enricher;

import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext;
import org.eclipse.jkube.microprofile.enricher.AbstractMicroprofileHealthCheckEnricher;

import java.util.Optional;

import static org.eclipse.jkube.helidon.HelidonUtils.hasHelidonHealthDependency;
import static org.eclipse.jkube.kit.common.Configs.asInteger;

public class HelidonHealthCheckEnricher extends AbstractMicroprofileHealthCheckEnricher {
private static final String DEFAULT_HELIDON_PORT = "8080";
public HelidonHealthCheckEnricher(JKubeEnricherContext buildContext) {
super(buildContext, "jkube-healthcheck-helidon");
}

@Override
protected boolean shouldAddProbe() {
return hasHelidonHealthDependency(getContext().getProject());
}

@Override
protected int getPort() {
return asInteger(Optional.ofNullable(getPortFromConfiguration()).orElse(DEFAULT_HELIDON_PORT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.eclipse.jkube.helidon.enricher.HelidonHealthCheckEnricher
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,30 @@ void extractPort_whenServerPortPropertyAbsent_thenReturnDefaultPort() {
assertThat(result).isEqualTo("8080");
}

@Test
void hasHelidonHealthDependency_whenNoDependency_thenReturnFalse() {
// Given
JavaProject javaProject = createNewJKubeProjectWithDeps("io.helidon.webserver", "helidon-webserver");

// When
boolean result = HelidonUtils.hasHelidonHealthDependency(javaProject);

// Then
assertThat(result).isFalse();
}

@Test
void hasHelidonHealthDependency_whenHelidonHealthDependency_thenReturnTrue() {
// Given
JavaProject javaProject = createNewJKubeProjectWithDeps("io.helidon.health", "helidon-health");

// When
boolean result = HelidonUtils.hasHelidonHealthDependency(javaProject);

// Then
assertThat(result).isTrue();
}

private JavaProject createNewJKubeProjectWithDeps(String groupId, String artifactId) {
List<Dependency> dependencyList = Collections.singletonList(Dependency.builder()
.groupId(groupId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.helidon.enricher;

import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.Probe;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import org.assertj.core.api.AssertionsForInterfaceTypes;
import org.eclipse.jkube.helidon.HelidonUtils;
import org.eclipse.jkube.kit.common.Dependency;
import org.eclipse.jkube.kit.common.JavaProject;
import org.eclipse.jkube.kit.config.resource.PlatformMode;
import org.eclipse.jkube.kit.config.resource.ProcessorConfig;
import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.io.File;
import java.util.Collections;
import java.util.Properties;
import java.util.function.Supplier;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class HelidonHealthCheckEnricherTest {
private JKubeEnricherContext context;
private JavaProject javaProject;
private Properties properties;
private KubernetesListBuilder klb;

@BeforeEach
void setup() {
properties = new Properties();
ProcessorConfig processorConfig = new ProcessorConfig();
klb = new KubernetesListBuilder();
klb.addToItems(new DeploymentBuilder()
.editOrNewSpec()
.editOrNewTemplate()
.editOrNewMetadata()
.withName("template-name")
.endMetadata()
.editOrNewSpec()
.addNewContainer()
.withImage("container/image")
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build());

context = mock(JKubeEnricherContext.class, RETURNS_DEEP_STUBS);
javaProject = mock(JavaProject.class, RETURNS_DEEP_STUBS);
when(context.getProject()).thenReturn(javaProject);
when(context.getProperties()).thenReturn(properties);
when(context.getConfiguration().getProcessorConfig()).thenReturn(processorConfig);
when(javaProject.getProperties()).thenReturn(properties);
when(javaProject.getBaseDirectory()).thenReturn(new File("/tmp/ignore"));
when(javaProject.getOutputDirectory()).thenReturn(new File("/tmp/ignore"));
}

@Test
void create_withNoMicroprofileDependency_shouldNotAddProbes() {
// Given
HelidonHealthCheckEnricher helidonHealthCheckEnricher = new HelidonHealthCheckEnricher(context);

// When
helidonHealthCheckEnricher.create(PlatformMode.kubernetes, klb);

// Then
assertNoProbesAdded();
}

@Test
void create_withMicroprofileDependencyAndMicroprofileFeatureDisabled_shouldNotAddProbes() {
// Given
withMicroprofileDependency("5.0");
HelidonHealthCheckEnricher helidonHealthCheckEnricher = new HelidonHealthCheckEnricher(context);

// When
helidonHealthCheckEnricher.create(PlatformMode.kubernetes, klb);

// Then
assertNoProbesAdded();
}

@Test
void create_withMicroprofileDependencyAndMicroprofileFeatureEnabled_shouldAddProbes() {
try (MockedStatic<HelidonUtils> mockStatic = Mockito.mockStatic(HelidonUtils.class)) {
// Given
mockStatic.when(() -> HelidonUtils.hasHelidonHealthDependency(javaProject)).thenReturn(true);
withMicroprofileDependency("5.0");
HelidonHealthCheckEnricher helidonHealthCheckEnricher = new HelidonHealthCheckEnricher(context);
// When
helidonHealthCheckEnricher.create(PlatformMode.kubernetes, klb);
// Then
assertProbesAdded("HTTP", "/health/live", "HTTP", "/health/ready", "HTTP", "/health/started");
}
}

@Test
void create_withLegacyMicroprofileDependencyAndMicroprofileFeatureEnabled_shouldOnlyAddLivenessReadinessProbes() {
try (MockedStatic<HelidonUtils> mockStatic = Mockito.mockStatic(HelidonUtils.class)) {
// Given
mockStatic.when(() -> HelidonUtils.hasHelidonHealthDependency(javaProject)).thenReturn(true);
withMicroprofileDependency("2.2");
HelidonHealthCheckEnricher helidonHealthCheckEnricher = new HelidonHealthCheckEnricher(context);
// When
helidonHealthCheckEnricher.create(PlatformMode.kubernetes, klb);
// Then
assertProbesAdded("HTTP", "/health/live", "HTTP", "/health/ready", null, null);
assertThat(getFirstContainerFromDeployment())
.extracting(Container::getStartupProbe)
.isNull();
}
}

@Test
void create_withMicroprofileEnabledAndEnricherConfiguration_shouldProbesAsConfigured() {
try (MockedStatic<HelidonUtils> mockStatic = Mockito.mockStatic(HelidonUtils.class)) {
// Given
mockStatic.when(() -> HelidonUtils.hasHelidonHealthDependency(javaProject)).thenReturn(true);
withMicroprofileDependency("5.0");
properties.put("jkube.enricher.jkube-healthcheck-helidon.scheme", "HTTPS");
properties.put("jkube.enricher.jkube-healthcheck-helidon.port", "8080");
properties.put("jkube.enricher.jkube-healthcheck-helidon.livenessPath", "/custom/health/live");
properties.put("jkube.enricher.jkube-healthcheck-helidon.livenessFailureThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.livenessSuccessThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.livenessInitialDelay", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.livenessPeriodSeconds", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.readinessPath", "/custom/health/ready");
properties.put("jkube.enricher.jkube-healthcheck-helidon.readinessFailureThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.readinessSuccessThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.readinessInitialDelay", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.readinessPeriodSeconds", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.startupPath", "/custom/health/startup");
properties.put("jkube.enricher.jkube-healthcheck-helidon.startupFailureThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.startupSuccessThreshold", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.startupInitialDelay", "5");
properties.put("jkube.enricher.jkube-healthcheck-helidon.startupPeriodSeconds", "5");
HelidonHealthCheckEnricher helidonHealthCheckEnricher = new HelidonHealthCheckEnricher(context);
// When
helidonHealthCheckEnricher.create(PlatformMode.kubernetes, klb);
// Then
assertProbesAdded("HTTPS", "/custom/health/live", "HTTPS", "/custom/health/ready", "HTTPS", "/custom/health/startup");
assertThat(getFirstContainerFromDeployment())
.hasFieldOrPropertyWithValue("livenessProbe.failureThreshold", 5)
.hasFieldOrPropertyWithValue("livenessProbe.successThreshold", 5)
.hasFieldOrPropertyWithValue("livenessProbe.initialDelaySeconds", 5)
.hasFieldOrPropertyWithValue("livenessProbe.periodSeconds", 5)
.hasFieldOrPropertyWithValue("livenessProbe.httpGet.port.IntVal", 8080)
.hasFieldOrPropertyWithValue("readinessProbe.failureThreshold", 5)
.hasFieldOrPropertyWithValue("readinessProbe.successThreshold", 5)
.hasFieldOrPropertyWithValue("readinessProbe.initialDelaySeconds", 5)
.hasFieldOrPropertyWithValue("readinessProbe.periodSeconds", 5)
.hasFieldOrPropertyWithValue("readinessProbe.httpGet.port.IntVal", 8080)
.hasFieldOrPropertyWithValue("startupProbe.failureThreshold", 5)
.hasFieldOrPropertyWithValue("startupProbe.successThreshold", 5)
.hasFieldOrPropertyWithValue("startupProbe.initialDelaySeconds", 5)
.hasFieldOrPropertyWithValue("startupProbe.periodSeconds", 5)
.hasFieldOrPropertyWithValue("startupProbe.httpGet.port.IntVal", 8080);
}
}

private void withMicroprofileDependency(String microProfileVersion) {
when(javaProject.getDependenciesWithTransitive()).thenReturn(Collections.singletonList(Dependency.builder()
.groupId("org.eclipse.microprofile")
.artifactId("microprofile")
.version(microProfileVersion)
.build()));
}

private void assertProbesAdded(String livenessScheme, String livenessPath, String readyScheme, String readyPath, String startedScheme, String startedPath) {
Container container = getFirstContainerFromDeployment();
assertProbe(container::getLivenessProbe, livenessScheme, livenessPath);
assertProbe(container::getReadinessProbe, readyScheme, readyPath);
assertProbe(container::getStartupProbe, startedScheme, startedPath);
}

private void assertNoProbesAdded() {
Container container = getFirstContainerFromDeployment();
assertThat(container.getLivenessProbe()).isNull();
assertThat(container.getReadinessProbe()).isNull();
assertThat(container.getStartupProbe()).isNull();
}

private void assertProbe(Supplier<Probe> probeSupplier, String scheme, String path) {
if (scheme != null && path != null) {
assertThat(probeSupplier.get())
.hasFieldOrPropertyWithValue("httpGet.scheme", scheme)
.hasFieldOrPropertyWithValue("httpGet.path", path);
}
}

private Container getFirstContainerFromDeployment() {
Deployment deployment = (Deployment) klb.buildFirstItem();
AssertionsForInterfaceTypes.assertThat(deployment.getSpec().getTemplate().getSpec().getContainers()).hasSize(1);
return deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
- jkube-healthcheck-micronaut
- jkube-healthcheck-openliberty
- jkube-healthcheck-smallrye
- jkube-healthcheck-helidon
- jkube-prometheus
- jkube-service-discovery

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- jkube-healthcheck-micronaut
- jkube-healthcheck-openliberty
- jkube-healthcheck-smallrye
- jkube-healthcheck-helidon

# DeploymentConfigEnricher converts Deployment into DeploymentConfig
- jkube-openshift-deploymentconfig
Expand Down

0 comments on commit 1720ee0

Please sign in to comment.