From 575f035e5ffed8be20cbb1e0b57410e9ab6b2b28 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Mon, 18 May 2026 13:55:52 +0200 Subject: [PATCH 1/8] fix health indicator Signed-off-by: Pablo Carle --- .../config/GatewayHealthIndicator.java | 13 ++++++-- .../config/GatewayHealthIndicatorTest.java | 33 ++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index 5512d1b912..018a79a822 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -39,6 +39,9 @@ @ConditionalOnMissingBean(name = "modulithConfig") public class GatewayHealthIndicator extends AbstractHealthIndicator { + @Value("${apiml.service.hostname") + private String hostname; + protected final DiscoveryClient discoveryClient; private final String apiCatalogServiceId; @InjectApimlLogger @@ -60,8 +63,8 @@ protected void doHealthCheck(Health.Builder builder) { // When DS goes 'down' after it was already 'up', the new status is not shown. This is probably feature of // Eureka client which caches the status of services. When DS is down the cache is not refreshed. - var discoveryUp = !this.discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId()).isEmpty(); - var zaasUp = !this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).isEmpty(); + var discoveryUp = isThisDeploymentServiceUp(CoreService.DISCOVERY.getServiceId()); + var zaasUp = isThisDeploymentServiceUp(CoreService.ZAAS.getServiceId()); var gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size(); var zaasCount = this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).size(); @@ -81,6 +84,11 @@ protected void doHealthCheck(Health.Builder builder) { } } + private boolean isThisDeploymentServiceUp(String serviceId) { + var instances = this.discoveryClient.getInstances(serviceId); + return !instances.isEmpty() && instances.stream().anyMatch(instance -> instance.getInstanceId().contains(hostname)); + } + @EventListener(ApplicationReadyEvent.class) public void onApplicationEvent(ApplicationReadyEvent event) { applicationReady.set(true); @@ -99,4 +107,5 @@ boolean isStartedInformationPublished() { private Status toStatus(boolean up) { return up ? UP : DOWN; } + } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java index 3afddfd092..73eba39b28 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java @@ -10,13 +10,18 @@ package org.zowe.apiml.gateway.config; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.test.util.ReflectionTestUtils; import org.zowe.apiml.product.constants.CoreService; import java.util.Collections; @@ -27,8 +32,20 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class GatewayHealthIndicatorTest { + @Mock + private DiscoveryClient discoveryClient; + + private GatewayHealthIndicator healthIndicator; + + @BeforeEach + void setUp() { + this.healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId()); + ReflectionTestUtils.setField(healthIndicator, "hostname", "host"); + } + private DefaultServiceInstance getDefaultServiceInstance(String serviceId, String hostname, int port) { return new DefaultServiceInstance( hostname + ":" + serviceId + ":" + port, @@ -40,13 +57,13 @@ private DefaultServiceInstance getDefaultServiceInstance(String serviceId, Strin class WhenCatalogAndDiscoveryAreAvailable { @Test void testStatusIsUp() { - DiscoveryClient discoveryClient = mock(DiscoveryClient.class); when(discoveryClient.getInstances(CoreService.API_CATALOG.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.API_CATALOG.getServiceId(), "host", 10014))); when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.DISCOVERY.getServiceId(), "host", 10011))); + when(discoveryClient.getInstances(CoreService.ZAAS.getServiceId())).thenReturn( + Collections.singletonList(getDefaultServiceInstance(CoreService.ZAAS.getServiceId(), "host", 10011))); - GatewayHealthIndicator healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId()); Health.Builder builder = new Health.Builder(); healthIndicator.doHealthCheck(builder); assertEquals(Status.UP, builder.build().getStatus()); @@ -57,12 +74,10 @@ void testStatusIsUp() { class WhenDiscoveryIsNotAreAvailable { @Test void testStatusIsDown() { - DiscoveryClient discoveryClient = mock(DiscoveryClient.class); when(discoveryClient.getInstances(CoreService.API_CATALOG.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.API_CATALOG.getServiceId(), "host", 10014))); when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn(Collections.emptyList()); - GatewayHealthIndicator healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId()); Health.Builder builder = new Health.Builder(); healthIndicator.doHealthCheck(builder); assertEquals(Status.DOWN, builder.build().getStatus()); @@ -71,23 +86,26 @@ void testStatusIsDown() { @Nested class GivenCustomCatalogProvider { + @Test void whenHealthIsRequested_thenStatusIsUp() { String customCatalogServiceId = "customCatalog"; - DiscoveryClient discoveryClient = mock(DiscoveryClient.class); when(discoveryClient.getInstances(customCatalogServiceId)).thenReturn( Collections.singletonList(getDefaultServiceInstance(customCatalogServiceId, "host", 10014))); when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.DISCOVERY.getServiceId(), "host", 10011))); - GatewayHealthIndicator healthIndicator = new GatewayHealthIndicator(discoveryClient, customCatalogServiceId); + var healthIndicator = new GatewayHealthIndicator(discoveryClient, customCatalogServiceId); + ReflectionTestUtils.setField(healthIndicator, "hostname", "host"); + Health.Builder builder = new Health.Builder(); healthIndicator.doHealthCheck(builder); String code = (String) builder.build().getDetails().get(CoreService.API_CATALOG.getServiceId()); assertThat(code, is("UP")); } + } @Nested @@ -95,8 +113,6 @@ class GivenEverythingIsHealthy { @Test void whenHealthRequested_onceLogMessageAboutStartup() { - - DiscoveryClient discoveryClient = mock(DiscoveryClient.class); when(discoveryClient.getInstances(CoreService.API_CATALOG.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.API_CATALOG.getServiceId(), "host", 10014))); when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn( @@ -104,7 +120,6 @@ void whenHealthRequested_onceLogMessageAboutStartup() { when(discoveryClient.getInstances(CoreService.ZAAS.getServiceId())).thenReturn( Collections.singletonList(getDefaultServiceInstance(CoreService.ZAAS.getServiceId(), "host", 10023))); - GatewayHealthIndicator healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId()); Health.Builder builder = new Health.Builder(); healthIndicator.onApplicationEvent(mock(ApplicationReadyEvent.class)); From 962d19c804b014cd3ac0d020f8925aca3036a65e Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Mon, 18 May 2026 15:47:50 +0200 Subject: [PATCH 2/8] add log to troubleshoot GA Signed-off-by: Pablo Carle --- .../org/zowe/apiml/gateway/config/GatewayHealthIndicator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index 018a79a822..9dfa035451 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -10,6 +10,7 @@ package org.zowe.apiml.gateway.config; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.AbstractHealthIndicator; @@ -37,6 +38,7 @@ */ @Component @ConditionalOnMissingBean(name = "modulithConfig") +@Slf4j public class GatewayHealthIndicator extends AbstractHealthIndicator { @Value("${apiml.service.hostname") @@ -86,6 +88,7 @@ protected void doHealthCheck(Health.Builder builder) { private boolean isThisDeploymentServiceUp(String serviceId) { var instances = this.discoveryClient.getInstances(serviceId); + log.error("instances: {}, hostname: {}", instances, hostname); return !instances.isEmpty() && instances.stream().anyMatch(instance -> instance.getInstanceId().contains(hostname)); } From 893979658ed7f7b5bf21e03f8a2ebf3269102d4e Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Mon, 18 May 2026 16:48:47 +0200 Subject: [PATCH 3/8] fix spel Signed-off-by: Pablo Carle --- .../org/zowe/apiml/gateway/config/GatewayHealthIndicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index 9dfa035451..ec06485eda 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -41,7 +41,7 @@ @Slf4j public class GatewayHealthIndicator extends AbstractHealthIndicator { - @Value("${apiml.service.hostname") + @Value("${apiml.service.hostname}") private String hostname; protected final DiscoveryClient discoveryClient; From 320328a2c6e93f11daac91c527b398715d40da2e Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Mon, 18 May 2026 17:14:15 +0200 Subject: [PATCH 4/8] remove log Signed-off-by: Pablo Carle --- .../org/zowe/apiml/gateway/config/GatewayHealthIndicator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index ec06485eda..978e5d1bf6 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -88,7 +88,6 @@ protected void doHealthCheck(Health.Builder builder) { private boolean isThisDeploymentServiceUp(String serviceId) { var instances = this.discoveryClient.getInstances(serviceId); - log.error("instances: {}, hostname: {}", instances, hostname); return !instances.isEmpty() && instances.stream().anyMatch(instance -> instance.getInstanceId().contains(hostname)); } From 2298c707465ed229d9d99bdeb183cc7214234d84 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Mon, 18 May 2026 17:41:49 +0200 Subject: [PATCH 5/8] check count Signed-off-by: Pablo Carle --- .../gateway/config/GatewayHealthIndicator.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index 978e5d1bf6..2721b9c645 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; @@ -65,10 +66,11 @@ protected void doHealthCheck(Health.Builder builder) { // When DS goes 'down' after it was already 'up', the new status is not shown. This is probably feature of // Eureka client which caches the status of services. When DS is down the cache is not refreshed. - var discoveryUp = isThisDeploymentServiceUp(CoreService.DISCOVERY.getServiceId()); - var zaasUp = isThisDeploymentServiceUp(CoreService.ZAAS.getServiceId()); + var discoveryUp = !this.discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId()).isEmpty(); + var zaasUp = !this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).isEmpty(); var gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size(); + var discoveryCount = this.discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId()).size(); var zaasCount = this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).size(); builder.status(toStatus(discoveryUp)) @@ -82,15 +84,16 @@ protected void doHealthCheck(Health.Builder builder) { } if (discoveryUp && apiCatalogUp && zaasUp && applicationReady.get()) { + var instancesCount = NumberUtils.max(gatewayCount, zaasCount, discoveryCount); + if (instancesCount > 1 && (gatewayCount != discoveryCount || gatewayCount != zaasCount)) { + log.debug("instancesCount: {}, gatewayCount: {}, zaasCount: {}, discoveryCount: {}", instancesCount, gatewayCount, zaasCount, discoveryCount); + return; + } + onFullyUp(); } } - private boolean isThisDeploymentServiceUp(String serviceId) { - var instances = this.discoveryClient.getInstances(serviceId); - return !instances.isEmpty() && instances.stream().anyMatch(instance -> instance.getInstanceId().contains(hostname)); - } - @EventListener(ApplicationReadyEvent.class) public void onApplicationEvent(ApplicationReadyEvent event) { applicationReady.set(true); From 6c0601d725b973d1ff8734be85e17e234aada8dc Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 19 May 2026 10:25:47 +0200 Subject: [PATCH 6/8] remove unused hostname Signed-off-by: Pablo Carle --- .../org/zowe/apiml/gateway/config/GatewayHealthIndicator.java | 3 --- .../zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java | 3 --- 2 files changed, 6 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java index 2721b9c645..202207c3c9 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/GatewayHealthIndicator.java @@ -42,9 +42,6 @@ @Slf4j public class GatewayHealthIndicator extends AbstractHealthIndicator { - @Value("${apiml.service.hostname}") - private String hostname; - protected final DiscoveryClient discoveryClient; private final String apiCatalogServiceId; @InjectApimlLogger diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java index 73eba39b28..17df7c5bd2 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java @@ -21,7 +21,6 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.test.util.ReflectionTestUtils; import org.zowe.apiml.product.constants.CoreService; import java.util.Collections; @@ -43,7 +42,6 @@ class GatewayHealthIndicatorTest { @BeforeEach void setUp() { this.healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId()); - ReflectionTestUtils.setField(healthIndicator, "hostname", "host"); } private DefaultServiceInstance getDefaultServiceInstance(String serviceId, String hostname, int port) { @@ -97,7 +95,6 @@ void whenHealthIsRequested_thenStatusIsUp() { Collections.singletonList(getDefaultServiceInstance(CoreService.DISCOVERY.getServiceId(), "host", 10011))); var healthIndicator = new GatewayHealthIndicator(discoveryClient, customCatalogServiceId); - ReflectionTestUtils.setField(healthIndicator, "hostname", "host"); Health.Builder builder = new Health.Builder(); healthIndicator.doHealthCheck(builder); From b9bc8f34fbe7737d19bb180ffcf71b734efcbc34 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 19 May 2026 13:02:29 +0200 Subject: [PATCH 7/8] add unit test for coverage Signed-off-by: Pablo Carle --- .../config/GatewayHealthIndicatorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java index 17df7c5bd2..50cc945b10 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java @@ -20,15 +20,20 @@ import org.springframework.boot.actuate.health.Status; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.test.util.ReflectionTestUtils; +import org.zowe.apiml.message.log.ApimlLogger; import org.zowe.apiml.product.constants.CoreService; import java.util.Collections; +import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -127,4 +132,32 @@ void whenHealthRequested_onceLogMessageAboutStartup() { } + @Nested + class WhenHAIsNotComplete { + + @Mock + private ApimlLogger apimlLogger; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(healthIndicator, "apimlLog", apimlLogger); + } + + @Test + void whenHealthRequested_skipLog() { + // var gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size(); + when(discoveryClient.getInstances(CoreService.GATEWAY.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class), mock(ServiceInstance.class))); + when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class))); + when(discoveryClient.getInstances(CoreService.ZAAS.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class))); + when(discoveryClient.getInstances(CoreService.API_CATALOG.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class))); + healthIndicator.onApplicationEvent(mock(ApplicationReadyEvent.class)); + + var builder = new Health.Builder(); + healthIndicator.doHealthCheck(builder); + + verifyNoInteractions(apimlLogger); + } + + } + } From 59f7e91f11f1da7bf79eafd830553b0608ec672a Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 19 May 2026 14:11:56 +0200 Subject: [PATCH 8/8] remove comment Signed-off-by: Pablo Carle --- .../zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java index 50cc945b10..b71be4d2ee 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/GatewayHealthIndicatorTest.java @@ -145,7 +145,6 @@ void setUp() { @Test void whenHealthRequested_skipLog() { - // var gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size(); when(discoveryClient.getInstances(CoreService.GATEWAY.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class), mock(ServiceInstance.class))); when(discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class))); when(discoveryClient.getInstances(CoreService.ZAAS.getServiceId())).thenReturn(List.of(mock(ServiceInstance.class)));