diff --git a/gateway-service/build.gradle b/gateway-service/build.gradle index a8251e1e44..84f204d8b5 100644 --- a/gateway-service/build.gradle +++ b/gateway-service/build.gradle @@ -157,6 +157,11 @@ bootRun { systemProperties = System.properties } +test { + useJUnitPlatform() + jvmArgs '--add-opens=jdk.httpserver/sun.net.httpserver=ALL-UNNAMED' +} + publishing { publications { mavenJavaFat(MavenPublication) { diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RetryPerServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RetryPerServiceTest.java index e6bfc64cd3..72f75c2cff 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RetryPerServiceTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/RetryPerServiceTest.java @@ -14,50 +14,50 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.test.context.TestPropertySource; import org.zowe.apiml.gateway.MockService; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTestWithMockServices; import org.zowe.apiml.gateway.acceptance.common.MicroservicesAcceptanceTest; import static io.restassured.RestAssured.given; -import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; -import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.apache.http.HttpStatus.*; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; -@MicroservicesAcceptanceTest -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@TestPropertySource(properties = { - "apiml.gateway.servicesToDisableRetry=no-retry-service,no-RETRY-Service-2" -}) -class RetryPerServiceTest extends AcceptanceTestWithMockServices { +class RetryPerServiceTest { private static final String HEADER_X_FORWARD_TO = "X-Forward-To"; - private MockService mockService; - private MockService mockNoRetryService; - private MockService mockNoRetryService2; - - @BeforeAll - void startMockService() { - mockService = mockService("serviceid1").scope(MockService.Scope.CLASS) + @Nested + @MicroservicesAcceptanceTest + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @TestPropertySource(properties = { + "apiml.gateway.servicesToDisableRetry=no-retry-service,no-RETRY-Service-2" + }) + class GivenRetryOnAllOperationsIsDisabled extends AcceptanceTestWithMockServices { + + private MockService mockService; + private MockService mockNoRetryService; + private MockService mockNoRetryService2; + + @BeforeAll + void startMockService() { + mockService = mockService("serviceid1").scope(MockService.Scope.CLASS) .addEndpoint("/503").responseCode(503) - .and() + .and() .addEndpoint("/401").responseCode(401) - .and().start(); + .and().start(); - mockNoRetryService = mockService("no-retry-service").scope(MockService.Scope.CLASS) - .addEndpoint("/503").responseCode(503) - .and().start(); - - mockNoRetryService2 = mockService("No-Retry-Service-2").scope(MockService.Scope.CLASS) - .addEndpoint("/503").responseCode(503) - .and().start(); - } + mockNoRetryService = mockService("no-retry-service").scope(MockService.Scope.CLASS) + .addEndpoint("/503").responseCode(503) + .and().start(); - @Nested - class GivenRetryOnAllOperationsIsDisabled { - //Only default GET method remains active + mockNoRetryService2 = mockService("No-Retry-Service-2").scope(MockService.Scope.CLASS) + .addEndpoint("/503").responseCode(503) + .and().start(); + } @Test void whenGetReturnsUnavailable_thenRetry() { @@ -119,4 +119,54 @@ void whenRetryForServiceIsDisabled_andGetReturnsUnavailable_onMixedCaseServiceId } + @Nested + @MicroservicesAcceptanceTest + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class ConnectionReset extends AcceptanceTestWithMockServices { + + private MockService mockService; + + @BeforeAll + void startMockService() { + mockService = mockService("serviceid1").scope(MockService.Scope.CLASS) + .addEndpoint("/200").responseCode(200) + .and().start(); + } + + @ParameterizedTest(name = "givenConnectionInPool_whenServerWasRestarted_thenRetry({0}, {1})") + @CsvSource({ + "GET,CLOSE,200", + "GET,CLOSE_CHANNEL,200", + "GET,KILL_CHANNEL,200", + "GET,MARK_CHANNEL_AS_CLOSED,200", + "POST,CLOSE,200", + "POST,CLOSE_CHANNEL,200", + "POST,KILL_CHANNEL,500", + "POST,MARK_CHANNEL_AS_CLOSED,500" + }) + void givenConnectionInPool_whenServerWasRestarted_thenRetry(String method, MockService.ConnectionCleanupType cleanupType, int responseStatus) { + var port = mockService.getPort(); + + given() + .header(HEADER_X_FORWARD_TO, "serviceid1") + .when() + .get(basePath + "/200") + .then() + .statusCode(is(SC_OK)); + + mockService.cleanConnections(cleanupType); + + var port2 = mockService.getPort(); + assertEquals(port, port2); + + given() + .header(HEADER_X_FORWARD_TO, "serviceid1") + .when() + .request(method, basePath + "/200") + .then() + .statusCode(is(responseStatus)); + } + + } + } diff --git a/gateway-service/src/testFixtures/java/org/zowe/apiml/gateway/MockService.java b/gateway-service/src/testFixtures/java/org/zowe/apiml/gateway/MockService.java index 0c5ca60f21..782b33a735 100644 --- a/gateway-service/src/testFixtures/java/org/zowe/apiml/gateway/MockService.java +++ b/gateway-service/src/testFixtures/java/org/zowe/apiml/gateway/MockService.java @@ -24,16 +24,14 @@ import org.assertj.core.error.MultipleAssertionsError; import org.springframework.cloud.netflix.eureka.EurekaServiceInstance; import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; import org.zowe.apiml.auth.AuthenticationScheme; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -223,6 +221,38 @@ public void stop() { setStatus(Status.STOPPED); } + + /** + * To close and clean all open connection on the server + * @throws IOException in case of error during closing a connection + */ + public void cleanConnections(ConnectionCleanupType cleanupType) { + Object serverImpl = ReflectionTestUtils.getField(server, "server"); + Set allConnections = (Set) ReflectionTestUtils.getField(serverImpl, "allConnections"); + Set idleConnections = (Set) ReflectionTestUtils.getField(serverImpl, "idleConnections"); + synchronized (allConnections) { + for (Object connection : allConnections) { + var channel = ReflectionTestUtils.invokeMethod(connection, "getChannel"); + switch (cleanupType) { + case CLOSE: + ReflectionTestUtils.invokeMethod(connection, "close"); + break; + case CLOSE_CHANNEL: + ReflectionTestUtils.invokeMethod(channel, "close"); + break; + case KILL_CHANNEL: + ReflectionTestUtils.invokeMethod(channel, "kill"); + break; + case MARK_CHANNEL_AS_CLOSED: + ReflectionTestUtils.setField(channel, "closed", false); + break; + } + } + } + allConnections.clear(); + idleConnections.clear(); + } + /** * To stop service without any notification (to be still in the registry). In the case service is down, just notify * to be in the registry. @@ -541,4 +571,13 @@ public boolean isUp() { } + public enum ConnectionCleanupType { + + CLOSE, + CLOSE_CHANNEL, + KILL_CHANNEL, + MARK_CHANNEL_AS_CLOSED + + } + }