Skip to content
5 changes: 5 additions & 0 deletions gateway-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ bootRun {
systemProperties = System.properties
}

test {
useJUnitPlatform()
jvmArgs '--add-opens=jdk.httpserver/sun.net.httpserver=ALL-UNNAMED'
}

publishing {
publications {
mavenJavaFat(MavenPublication) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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));
}
Comment on lines +147 to +168
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand what the purpose of this test is, what happens if you try to send the request before cleaning up? Would that make the purpose more clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. The first request is to warn up. It means the pool on both services side is established. The clean-up close connection on one side (the type simulate a specific operation) and the second call verify the handling of closed connection. We can make as many request we want before. It cannot change the behaviour after clean up. And the issue is related to the first call after clean-up.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just wondering what does it take to break this test, that's the part that's not very clear for me. Or which part of the API ML does not work otherwise, it feels to me that it's testing the mock service rather, but if it's clear to you it's fine with me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is testing the retry logic and handling connection in the outbound calls (see HTTP client in the gateway), If there is a change in handling I/O exceptions of prepared connection or retry logic it could break the test.


}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Object> allConnections = (Set<Object>) ReflectionTestUtils.getField(serverImpl, "allConnections");
Set<Object> idleConnections = (Set<Object>) 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.
Expand Down Expand Up @@ -541,4 +571,13 @@ public boolean isUp() {

}

public enum ConnectionCleanupType {

CLOSE,
CLOSE_CHANNEL,
KILL_CHANNEL,
MARK_CHANNEL_AS_CLOSED

}

}
Loading