Skip to content

Commit

Permalink
MCPODS-6503 gradle, vert.x and retry
Browse files Browse the repository at this point in the history
  • Loading branch information
Amaneusz committed Nov 21, 2023
1 parent 3fa215e commit 9bebc6b
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 39 deletions.
11 changes: 6 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ jobs:
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle, execute tests & verify overall coverage
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: test jacocoTestReport jacocoTestCoverageVerification
gradle-version: 8.2
- name: Execute tests & verify overall coverage
run: gradle test jacocoTestReport jacocoTestCoverageVerification
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
Expand All @@ -62,6 +63,6 @@ jobs:
title: Code Coverage
- name: Fail when coverage of changed files is too low
run: |
CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
[[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
[[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
[[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
[[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
18 changes: 16 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
@file:Suppress("PropertyName")

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.net.URI
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import org.gradle.kotlin.dsl.KotlinClosure2

repositories {
maven {
Expand Down Expand Up @@ -30,7 +32,7 @@ version = rootProject.properties["version"] as String

val jetbrains_annotations = "org.jetbrains:annotations:24.0.1"

val vertx_version = "4.4.5"
val vertx_version = "4.5.0"
val vertx_core = "io.vertx:vertx-core:$vertx_version"
val vertx_config = "io.vertx:vertx-config:$vertx_version"
val vertx_auth_jwt = "io.vertx:vertx-auth-jwt:$vertx_version"
Expand Down Expand Up @@ -105,6 +107,7 @@ val mockito = "org.mockito:mockito-core:3.12.4"

val flipkart_zjsonpatch = "com.flipkart.zjsonpatch:zjsonpatch:0.4.13"
val json_assert = "org.skyscreamer:jsonassert:1.5.1"
val resillience4j_retry = "io.github.resilience4j:resilience4j-retry:2.0.0"

val mavenUrl = rootProject.properties["mavenUrl"] as String
val mavenUser = rootProject.properties["mavenUser"] as String
Expand Down Expand Up @@ -185,7 +188,17 @@ subprojects {
test {
maxHeapSize = "4g"
useJUnitPlatform()
testLogging.showStandardStreams = true
testLogging {
showStandardStreams = true
exceptionFormat = TestExceptionFormat.FULL
events("standardOut", "started", "passed", "skipped", "failed")
}
afterTest(KotlinClosure2(
{ descriptor: TestDescriptor, result: TestResult ->
val totalTime = result.endTime - result.startTime
println("Total time of $descriptor.name was $totalTime")
}
))
}

compileJava {
Expand Down Expand Up @@ -451,6 +464,7 @@ project(":here-naksha-lib-handlers") {
implementation(vertx_web_openapi)

testImplementation(json_assert)
testImplementation(resillience4j_retry)
}
setOverallCoverage(0.25) // only increasing allowed!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ public final class NakshaApp extends Thread {
* @param args The console arguments given.
*/
public static void main(@NotNull String... args) {
System.out.println("args: " + args);
if (args.length < 1) {
printUsage();
System.exit(1);
Expand Down Expand Up @@ -121,8 +120,6 @@ private static void printUsage() {

final String cfgId;
final String url;

System.out.println("args: " + args);
switch (args.length) {
case 1 -> {
cfgId = args[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@

import com.here.naksha.app.service.AbstractNakshaHubVerticle;
import com.here.naksha.app.service.NakshaApp;
import com.here.naksha.app.service.http.apis.*;
import com.here.naksha.app.service.http.apis.Api;
import com.here.naksha.app.service.http.apis.EventHandlerApi;
import com.here.naksha.app.service.http.apis.HealthApi;
import com.here.naksha.app.service.http.apis.ReadFeatureApi;
import com.here.naksha.app.service.http.apis.SpaceApi;
import com.here.naksha.app.service.http.apis.StorageApi;
import com.here.naksha.app.service.http.apis.WriteFeatureApi;
import com.here.naksha.app.service.http.auth.NakshaJwtAuthHandler;
import com.here.naksha.lib.core.AbstractTask;
import com.here.naksha.lib.core.INaksha;
Expand All @@ -60,9 +66,11 @@
import com.here.naksha.lib.core.util.MIMEType;
import com.here.naksha.lib.hub.NakshaHubConfig;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpConnection;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerResponse;
Expand Down Expand Up @@ -216,21 +224,24 @@ public void start(final @NotNull Promise<Void> startPromise) {
// round-robin strategy.
//
// https://vertx.io/docs/vertx-core/java/#_server_sharing
vertx.createHttpServer(SERVER_OPTIONS).requestHandler(router).listen(hubConfig.httpPort, result -> {
if (result.succeeded()) {
log.atInfo()
.setMessage("HTTP Server started on port {}")
.addArgument(hubConfig.httpPort)
.log();
startPromise.complete();
} else {
log.atError()
.setMessage("An error occurred, during the initialization of the server.")
.setCause(result.cause())
.log();
startPromise.fail(result.cause());
}
});
vertx.createHttpServer(SERVER_OPTIONS)
.requestHandler(router)
.connectionHandler(loggingConnectionHandler())
.listen(hubConfig.httpPort, result -> {
if (result.succeeded()) {
log.atInfo()
.setMessage("HTTP Server started on port {}")
.addArgument(hubConfig.httpPort)
.log();
startPromise.complete();
} else {
log.atError()
.setMessage("An error occurred, during the initialization of the server.")
.setCause(result.cause())
.log();
startPromise.fail(result.cause());
}
});
} catch (Throwable t) {
log.atError()
.setMessage(
Expand Down Expand Up @@ -327,7 +338,8 @@ private void notFoundHandler(final @NotNull RoutingContext routingContext) {
*
* @param routingContext The routing context.
*/
private void onHeadersEnd(final @NotNull RoutingContext routingContext) {}
private void onHeadersEnd(final @NotNull RoutingContext routingContext) {
}

/**
* An end handler for the response. This will be called when the response is disposed to allow consistent cleanup of the response.
Expand Down Expand Up @@ -396,6 +408,15 @@ private void onNewRequest(final @NotNull RoutingContext routingContext) {
routingContext.next();
}

/**
* @return simple http connection handler that logs closing and failing connections
*/
private Handler<HttpConnection> loggingConnectionHandler() {
return httpConnection -> httpConnection
.closeHandler(v -> log.info("Closing connection"))
.exceptionHandler(t -> log.info("Connection exception", t));
}

private static final Pattern FATAL_ERROR_MSG_PATTERN = Pattern.compile("^[0-9a-zA-Z.-_\\-]+$");

/**
Expand Down Expand Up @@ -553,7 +574,7 @@ public void sendErrorResponse(@NotNull RoutingContext routingContext, @NotNull T
/**
* Prepare XyzFeatureCollection response by extracting feature results from ModifyFeaturesResp object
*
* @param modifyResponse The object to extract feature results from
* @param modifyResponse The object to extract feature results from
* @return The XyzFeatureCollection response containing list of inserted/updated/delete features
*/
public XyzFeatureCollection transformModifyResponse(@NotNull ModifyFeaturesResp modifyResponse) {
Expand Down Expand Up @@ -588,6 +609,7 @@ public XyzResponse sendXyzResponse(
@NotNull RoutingContext routingContext,
@Nullable HttpResponseType responseType,
@NotNull XyzResponse response) {
log.info("SendXyzResponse start on path {}", routingContext.request().path()); // TODO(Kuba): remove this line
try {
final String etag = response.getEtag();
if (etag != null) {
Expand Down Expand Up @@ -680,7 +702,11 @@ public void sendRawResponse(
final HttpServerResponse httpResponse = routingContext.response();
httpResponse.setStatusCode(status.code()).setStatusMessage(status.reasonPhrase());
httpResponse.putHeader(STREAM_ID, streamId(routingContext));
log.info("Sending raw response on path {}", routingContext.request().path()); // TODO(Kuba): remove this line
if (content == null || content.length() == 0) {
log.info(
"Empty content served from path {}",
routingContext.request().path()); // TODO(Kuba): remove this line
httpResponse.end();
} else {
if (contentType != null) {
Expand All @@ -700,4 +726,16 @@ public void sendRawResponse(
// ctx.setAuthor();
return ctx;
}

@Override
public void stop() throws Exception {
log.info("Stop (no params) called on {}", this.getClass().getSimpleName()); // TODO(Kuba): remove this line
super.stop();
}

@Override
public void stop(Promise<Void> stopPromise) throws Exception {
log.info("Stop (with promise) called on {}", this.getClass().getSimpleName()); // TODO(Kuba): remove this line
super.stop(stopPromise);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID;
import static java.net.http.HttpClient.Version.HTTP_1_1;

import io.github.resilience4j.core.functions.CheckedFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
Expand All @@ -29,21 +33,28 @@
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NakshaTestWebClient {

private static final Logger logger = LoggerFactory.getLogger(NakshaTestWebClient.class);
private static final String NAKSHA_HTTP_URI = "http://localhost:8080/";
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10);
private static final Duration SOCKET_TIMEOUT = Duration.ofSeconds(2);

private final HttpClient httpClient;

private final RetryRegistry retryRegistry;

public NakshaTestWebClient() {
httpClient = HttpClient.newBuilder()
.version(HTTP_1_1)
.connectTimeout(CONNECT_TIMEOUT)
.build();
retryRegistry = configureRetryRegistry();
}

public HttpResponse<String> get(String subPath, String streamId) throws URISyntaxException {
Expand Down Expand Up @@ -76,13 +87,20 @@ public HttpResponse<String> put(String subPath, String jsonBody, String streamId
}

private HttpResponse<String> send(HttpRequest request) {
String retryId = retryIdForRequest(request);
CheckedFunction<HttpRequest, HttpResponse<String>> responseSupplier =
Retry.decorateCheckedFunction(retry(retryId), this::sendOnce);
try {
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RequestException(request, e);
return responseSupplier.apply(request);
} catch (Throwable e) {
throw new RuntimeException("Applying retry (%s) failed".formatted(retryId), e);
}
}

private HttpResponse<String> sendOnce(HttpRequest request) throws IOException, InterruptedException {
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}

private HttpRequest.Builder requestBuilder() {
return HttpRequest.newBuilder().version(Version.HTTP_1_1).timeout(SOCKET_TIMEOUT);
}
Expand All @@ -91,15 +109,21 @@ private URI nakshaPath(String subPath) throws URISyntaxException {
return new URI(NAKSHA_HTTP_URI + subPath);
}

static class RequestException extends RuntimeException {
private Retry retry(String name) {
Retry retry = retryRegistry.retry(name);
retry.getEventPublisher().onRetry(ev -> logger.info("Retry triggereed: {}", name));
return retry;
}

public RequestException(HttpRequest request, Throwable cause) {
super(msg(request), cause);
}
private static String retryIdForRequest(HttpRequest request) {
return "%s_%s_retry".formatted(request.method(), request.uri().toString());
}

private static String msg(HttpRequest request) {
return "Request to Naksha failed, method: %s, uri: %s"
.formatted(request.method(), request.uri().toString());
}
private static RetryRegistry configureRetryRegistry() {
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.retryExceptions(HttpTimeoutException.class)
.build();
return RetryRegistry.of(config);
}
}

0 comments on commit 9bebc6b

Please sign in to comment.