Skip to content
Merged
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 @@ -14,7 +14,9 @@
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

Expand All @@ -29,6 +31,8 @@
@RequiredArgsConstructor
public class ApimlLogInjector implements BeanPostProcessor {

private final ApplicationContext applicationContext;

@Override
public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) {
return bean;
Expand All @@ -41,7 +45,13 @@ public Object postProcessBeforeInitialization(final Object bean, @Nonnull String
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
Class<?> clazz = getClass(bean);
ApimlLogger log = ApimlLogger.of(clazz, YamlMessageServiceInstance.getInstance());

ApimlLogger log;
try {
log = applicationContext.getBean(ApimlLogger.class);
} catch (BeansException e) {
log = ApimlLogger.of(clazz, YamlMessageServiceInstance.getInstance());
}
field.set(bean, log);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
Expand Down Expand Up @@ -47,13 +48,16 @@ private static class TestComponent {
@TestConfiguration
static class TestConfig {

@Autowired
private ApplicationContext applicationContext;

@Bean
public ApimlLogInjector apimlLogInjector() {
return new ApimlLogInjector();
ApimlLogInjector apimlLogInjector() {
return new ApimlLogInjector(applicationContext);
}

@Bean
public TestComponent testComponent() {
TestComponent testComponent() {
return new ApimlLogInjectorTest.TestComponent();
}

Expand Down
51 changes: 41 additions & 10 deletions apiml/src/main/java/org/zowe/apiml/GatewayHealthIndicator.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@
package org.zowe.apiml;

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.Status;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
import org.zowe.apiml.product.compatibility.ApimlHealthCheckHandler;
import org.zowe.apiml.product.constants.CoreService;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import org.zowe.apiml.zaas.ZaasServiceAvailableEvent;

import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -39,25 +42,27 @@
@RequiredArgsConstructor
public class GatewayHealthIndicator extends AbstractHealthIndicator {

private static final ApimlLogger apimlLog = ApimlLogger.of(GatewayHealthIndicator.class, YamlMessageServiceInstance.getInstance());
private final DiscoveryClient discoveryClient;

@InjectApimlLogger
private final ApimlLogger apimlLog = ApimlLogger.empty();

@Value("${apiml.catalog.serviceId:}")
private String apiCatalogServiceId;

private AtomicBoolean discoveryAvailable = new AtomicBoolean(false);
private AtomicBoolean zaasAvailable = new AtomicBoolean(false);

private AtomicBoolean catalogAvailable = new AtomicBoolean(false);
private AtomicBoolean startedInformationPublished = new AtomicBoolean(false);

@Override
protected void doHealthCheck(Builder builder) throws Exception {
var anyCatalogIsAvailable = apiCatalogServiceId != null && !apiCatalogServiceId.isEmpty();
var apiCatalogUp = !this.discoveryClient.getInstances(apiCatalogServiceId).isEmpty();
var anyCatalogIsAvailable = StringUtils.isNotBlank(apiCatalogServiceId);
catalogAvailable.set(anyCatalogIsAvailable && !this.discoveryClient.getInstances(apiCatalogServiceId).isEmpty());

// Keeping for backwards compatibility, in modulith the amount of gateways is the amount of authentication services available
int gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size();
int zaasCount = gatewayCount;
var gatewayCount = this.discoveryClient.getInstances(CoreService.GATEWAY.getServiceId()).size();
var zaasCount = gatewayCount;

builder.status(toStatus(discoveryAvailable.get() && zaasAvailable.get()))
.withDetail(CoreService.DISCOVERY.getServiceId(), toStatus(discoveryAvailable.get()).getCode())
Expand All @@ -66,23 +71,49 @@ protected void doHealthCheck(Builder builder) throws Exception {
.withDetail("zaasCount", zaasCount);

if (anyCatalogIsAvailable) {
builder.withDetail(CoreService.API_CATALOG.getServiceId(), toStatus(apiCatalogUp).getCode());
builder.withDetail(CoreService.API_CATALOG.getServiceId(), toStatus(catalogAvailable.get()).getCode());
}

if (isFullyUp()) {
onFullyUp();
}
}

private boolean isFullyUp() {
return !startedInformationPublished.get() && discoveryAvailable.get() && catalogAvailable.get() && zaasAvailable.get();
}

if (!startedInformationPublished.get() && discoveryAvailable.get() && apiCatalogUp && zaasAvailable.get()) {
private void onFullyUp() {
if (startedInformationPublished.compareAndSet(false, true)) {
apimlLog.log("org.zowe.apiml.common.mediationLayerStarted");
startedInformationPublished.set(true);
}
}

@EventListener
public void onApplicationEvent(ZaasServiceAvailableEvent event) {
zaasAvailable.set(true);
if (isFullyUp()) {
onFullyUp();
}
}

@EventListener
public void onApplicationEvent(EurekaRegistryAvailableEvent event) {
discoveryAvailable.set(true);
if (isFullyUp()) {
onFullyUp();
}
}

@EventListener
public void onApplicationEvent(EurekaInstanceRegisteredEvent event) {
var instanceInfo = event.getInstanceInfo();
if (String.valueOf(instanceInfo.getAppName()).equalsIgnoreCase(apiCatalogServiceId)) {
catalogAvailable.set(true);
}
if (isFullyUp()) {
onFullyUp();
}
}

boolean isStartedInformationPublished() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public class AcceptanceTestWithMockServices extends AcceptanceTestWithBasePath {

@Autowired
private ApplicationEventPublisher applicationEventPublisher;
protected ApplicationEventPublisher applicationEventPublisher;

@Autowired
@Qualifier("applicationRegistry")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.acceptance;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.eureka.EurekaServerConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.zaas.ZaasServiceAvailableEvent;

import java.util.concurrent.TimeUnit;

import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;

@AcceptanceTest
@ExtendWith(MockitoExtension.class)
public class StartupMessageAcceptanceTest extends AcceptanceTestWithMockServices {

@MockitoBean private ApimlLogger logger;

@Mock private InstanceInfo instanceInfo;

@BeforeEach
void setUp() {
lenient().when(instanceInfo.getInstanceId()).thenReturn("apicatalog:localhost:1000");
lenient().when(instanceInfo.getAppName()).thenReturn("APICATALOG");

applicationEventPublisher.publishEvent(new ZaasServiceAvailableEvent("dummy"));
applicationEventPublisher.publishEvent(new EurekaRegistryAvailableEvent(mock(EurekaServerConfig.class)));
applicationEventPublisher.publishEvent(new EurekaInstanceRegisteredEvent(new Object(), instanceInfo, DISCOVERY_PORT, false));
}

@Test
@Timeout(unit = TimeUnit.SECONDS, value = 30)
void whenFullyStartedUp_thenEmitMessage() {

verify(logger, timeout(30_000)).log("org.zowe.apiml.common.mediationLayerStarted");

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.zowe.apiml.gateway.config;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
Expand All @@ -18,9 +19,11 @@
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
import org.zowe.apiml.product.compatibility.ApimlHealthCheckHandler;
import org.zowe.apiml.product.constants.CoreService;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.springframework.boot.actuate.health.Status.DOWN;
import static org.springframework.boot.actuate.health.Status.UP;
Expand All @@ -35,11 +38,11 @@
public class GatewayHealthIndicator extends AbstractHealthIndicator {

protected final DiscoveryClient discoveryClient;
private String apiCatalogServiceId;
private final String apiCatalogServiceId;
@InjectApimlLogger
private final ApimlLogger apimlLog = ApimlLogger.empty();

private final ApimlLogger apimlLog = ApimlLogger.of(GatewayHealthIndicator.class,
YamlMessageServiceInstance.getInstance());
boolean startedInformationPublished = false;
private AtomicBoolean startedInformationPublished = new AtomicBoolean(false);

public GatewayHealthIndicator(DiscoveryClient discoveryClient,
@Value("${apiml.catalog.serviceId:}") String apiCatalogServiceId) {
Expand All @@ -49,16 +52,16 @@ public GatewayHealthIndicator(DiscoveryClient discoveryClient,

@Override
protected void doHealthCheck(Health.Builder builder) {
boolean anyCatalogIsAvailable = apiCatalogServiceId != null && !apiCatalogServiceId.isEmpty();
boolean apiCatalogUp = !this.discoveryClient.getInstances(apiCatalogServiceId).isEmpty();
var anyCatalogIsAvailable = StringUtils.isNotBlank(apiCatalogServiceId);
var apiCatalogUp = anyCatalogIsAvailable && !this.discoveryClient.getInstances(apiCatalogServiceId).isEmpty();

// 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.
boolean discoveryUp = !this.discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId()).isEmpty();
boolean zaasUp = !this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).isEmpty();
var discoveryUp = !this.discoveryClient.getInstances(CoreService.DISCOVERY.getServiceId()).isEmpty();
var zaasUp = !this.discoveryClient.getInstances(CoreService.ZAAS.getServiceId()).isEmpty();

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

builder.status(toStatus(discoveryUp))
.withDetail(CoreService.DISCOVERY.getServiceId(), toStatus(discoveryUp).getCode())
Expand All @@ -76,12 +79,15 @@ protected void doHealthCheck(Health.Builder builder) {
}

private void onFullyUp() {
if (!startedInformationPublished) {
if (startedInformationPublished.compareAndSet(false, true)) {
apimlLog.log("org.zowe.apiml.common.mediationLayerStarted");
startedInformationPublished = true;
}
}

boolean isStartedInformationPublished() {
return startedInformationPublished.get();
}

private Status toStatus(boolean up) {
return up ? UP : DOWN;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ void whenHealthRequested_onceLogMessageAboutStartup() {
Health.Builder builder = new Health.Builder();
healthIndicator.doHealthCheck(builder);

assertThat(healthIndicator.startedInformationPublished, is(true));
assertThat(healthIndicator.isStartedInformationPublished(), is(true));
}
}
}
Loading