diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java index e2193407eb..e375eca962 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java @@ -11,6 +11,7 @@ package org.zowe.apiml.gateway.config; import io.netty.channel.ChannelOption; +import io.netty.channel.ConnectTimeoutException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; @@ -25,6 +26,7 @@ import reactor.netty.http.client.HttpClient; import java.net.ConnectException; +import java.net.NoRouteToHostException; import java.time.Duration; import java.util.List; import java.util.Optional; @@ -84,14 +86,26 @@ protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return super.filter(exchange, chain).onErrorResume(e -> { - if (e.getCause() instanceof ConnectException) { + if (isServiceUnavailable(e)) { log.debug("Connection to {} was not established: {}", exchange.getRequest().getURI(), e.getMessage()); var uri = exchange.getRequest().getURI(); return Mono.error(new ServiceNotAccessibleException(String.format("Service is not available at %s://%s:%d", uri.getScheme(), uri.getHost(), uri.getPort()), e)); } return Mono.error(e); }); + } + static boolean isServiceUnavailable(Throwable e) { + Throwable cause = e; + while (cause != null) { + if (cause instanceof ConnectException + || cause instanceof ConnectTimeoutException + || cause instanceof NoRouteToHostException) { + return true; + } + cause = cause.getCause(); + } + return false; } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java index d2c8927346..797213f41e 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.netty.channel.ChannelOption; +import io.netty.channel.ConnectTimeoutException; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import org.hamcrest.Matchers; @@ -28,6 +29,8 @@ import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLException; +import java.net.ConnectException; +import java.nio.channels.ClosedChannelException; import java.time.Duration; import static io.restassured.RestAssured.given; @@ -158,6 +161,39 @@ void givenTimeoutAndRequirementsForClientCert_whenGetHttpClient_thenCallWithoutC } + @Nested + class IsServiceUnavailable { + + @Test + void givenConnectTimeoutException_whenIsServiceUnavailable_thenReturnsTrue() { + assertTrue(NettyRoutingFilterApiml.isServiceUnavailable(new ConnectTimeoutException("connect timed out"))); + } + + @Test + void givenConnectException_whenIsServiceUnavailable_thenReturnsTrue() { + assertTrue(NettyRoutingFilterApiml.isServiceUnavailable(new ConnectException("connection refused"))); + } + + @Test + void givenConnectExceptionNestedInClosedChannelException_whenIsServiceUnavailable_thenReturnsTrue() { + ClosedChannelException outer = new ClosedChannelException(); + ConnectException inner = new ConnectException("connection refused"); + outer.initCause(inner); + assertTrue(NettyRoutingFilterApiml.isServiceUnavailable(outer)); + } + + @Test + void givenUnrelatedException_whenIsServiceUnavailable_thenReturnsFalse() { + assertFalse(NettyRoutingFilterApiml.isServiceUnavailable(new RuntimeException("some other error"))); + } + + @Test + void givenSslException_whenIsServiceUnavailable_thenReturnsFalse() { + assertFalse(NettyRoutingFilterApiml.isServiceUnavailable(new javax.net.ssl.SSLException("cert error"))); + } + + } + @Nested class UnavailableService extends AcceptanceTestWithMockServices {