Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

package org.zowe.apiml.gateway.config;

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;
Expand All @@ -37,6 +39,7 @@
*/
@Component
@ConditionalOnMissingBean(name = "modulithConfig")
@Slf4j
public class GatewayHealthIndicator extends AbstractHealthIndicator {

protected final DiscoveryClient discoveryClient;
Expand Down Expand Up @@ -64,6 +67,7 @@ protected void doHealthCheck(Health.Builder builder) {
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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about rather to get how many instances are in the configuration? The check itself has no exact amount in the validation.

Also, if there is no validation of instance, how do we know that only local instances are up or all in the HA setup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes it makes sense. This is a partial fix for a specific scenario in which the second instance would print the API Mediation Layer ready before Discovery and/or ZAAS are available.
I guess the question is more generic. It's true this does not cover scenarios with 3 or more instances. It's also true that the message in the second instance is correct even if there's only one Discovery and/or ZAAS.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will try to refactor it

var zaasCount = this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).size();

builder.status(toStatus(discoveryUp))
Expand All @@ -77,6 +81,12 @@ 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();
}
}
Expand All @@ -99,4 +109,5 @@ boolean isStartedInformationPublished() {
private Status toStatus(boolean up) {
return up ? UP : DOWN;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,45 @@

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.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)
class GatewayHealthIndicatorTest {

@Mock
private DiscoveryClient discoveryClient;

private GatewayHealthIndicator healthIndicator;

@BeforeEach
void setUp() {
this.healthIndicator = new GatewayHealthIndicator(discoveryClient, CoreService.API_CATALOG.getServiceId());
}

private DefaultServiceInstance getDefaultServiceInstance(String serviceId, String hostname, int port) {
return new DefaultServiceInstance(
hostname + ":" + serviceId + ":" + port,
Expand All @@ -40,13 +60,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());
Expand All @@ -57,12 +77,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());
Expand All @@ -71,40 +89,39 @@ 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);

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
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(
Collections.singletonList(getDefaultServiceInstance(CoreService.DISCOVERY.getServiceId(), "host", 10011)));
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));

Expand All @@ -115,4 +132,31 @@ void whenHealthRequested_onceLogMessageAboutStartup() {

}

@Nested
class WhenHAIsNotComplete {

@Mock
private ApimlLogger apimlLogger;

@BeforeEach
void setUp() {
ReflectionTestUtils.setField(healthIndicator, "apimlLog", apimlLogger);
}

@Test
void whenHealthRequested_skipLog() {
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);
}

}

}
Loading