Skip to content

Commit

Permalink
feat(#406): System / gRPC API should provide information about evitaD…
Browse files Browse the repository at this point in the history
…B server version

We need to provide sever version in gRPC evitaLab API, so then the clients can visualize it and reason about it. We should also provide a method to detect the client version. Java client should log an error/warning if its minor version differs from the server version, as it might signal compatibility problems.

Implemented system endpoint.
  • Loading branch information
novoj committed Jan 18, 2024
1 parent 7fbf593 commit 0674fd9
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 1 deletion.
31 changes: 30 additions & 1 deletion evita_common/src/main/java/io/evitadb/utils/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.security.NoSuchAlgorithmException;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.time.Duration;
import java.util.Arrays;
import java.util.HexFormat;
import java.util.List;
Expand Down Expand Up @@ -527,10 +528,38 @@ public static String replaceEach(final String text, final String[] searchList, f
return replaceEach(text, searchList, replacementList, false, 0);
}

/**
* Formats duration to human readable format.
* @param duration duration to be formatted
* @return formatted duration
*/
@Nonnull
public static String formatDuration(@Nonnull Duration duration) {
long days = duration.toDaysPart();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
long seconds = duration.toSecondsPart();

StringBuilder sb = new StringBuilder(32);

if (days > 0) {
sb.append(days).append("d ");
}
if (hours > 0 || days > 0) {
sb.append(hours).append("h ");
}
if (minutes > 0 || hours > 0 || days > 0) {
sb.append(minutes).append("m ");
}
sb.append(seconds).append("s");

return sb.toString().trim();
}

/**
* <p>
* Replace all occurrences of Strings within another String.
* This is a private recursive helper method for {@link #replaceEachRepeatedly(String, String[], String[])} and
* This is a private recursive helper method for {@link #replaceEach(String, String[], String[])} and
* {@link #replaceEach(String, String[], String[])}
* </p>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

package io.evitadb.externalApi.system;

import io.evitadb.api.requestResponse.system.SystemStatus;
import io.evitadb.core.Evita;
import io.evitadb.exception.EvitaInternalError;
import io.evitadb.externalApi.configuration.ApiOptions;
Expand All @@ -33,6 +34,7 @@
import io.evitadb.externalApi.http.ExternalApiProviderRegistrar;
import io.evitadb.externalApi.system.configuration.SystemConfig;
import io.evitadb.utils.CertificateUtils;
import io.evitadb.utils.StringUtils;
import io.undertow.Handlers;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.server.handlers.PathHandler;
Expand All @@ -45,7 +47,9 @@
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
* Registers System API provider to provide system API to clients.
Expand All @@ -54,6 +58,7 @@
*/
public class SystemProviderRegistrar implements ExternalApiProviderRegistrar<SystemConfig> {
private static final String ENDPOINT_SERVER_NAME = "server-name";
private static final String ENDPOINT_SYSTEM_STATUS = "status";

Check warning on line 61 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L61

Added line #L61 was not covered by tests

@Nonnull
@Override
Expand Down Expand Up @@ -99,6 +104,21 @@ public ExternalApiProvider<SystemConfig> register(@Nonnull Evita evita, @Nonnull
}
);

router.addExactPath(

Check warning on line 107 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L107

Added line #L107 was not covered by tests
"/" + ENDPOINT_SYSTEM_STATUS,
exchange -> {

Check warning on line 109 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L109

Added line #L109 was not covered by tests
exchange.setStatusCode(StatusCodes.OK);
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");

Check warning on line 111 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L111

Added line #L111 was not covered by tests
exchange.getResponseSender().send(
renderStatus(

Check warning on line 113 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L113

Added line #L113 was not covered by tests
evita.getConfiguration().name(),
evita.getSystemStatus(),

Check warning on line 115 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L115

Added line #L115 was not covered by tests
apiOptions
)
);
}

Check warning on line 119 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L119

Added line #L119 was not covered by tests
);

Check warning on line 121 in evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java

View check run for this annotation

Codecov / codecov/patch

evita_external_api/evita_external_api_system/src/main/java/io/evitadb/externalApi/system/SystemProviderRegistrar.java#L121

Added line #L121 was not covered by tests
final ResourceHandler fileSystemHandler;
try (ResourceManager resourceManager = new FileResourceManager(file, 100)) {
fileSystemHandler = new ResourceHandler(
Expand Down Expand Up @@ -152,4 +172,47 @@ public ExternalApiProvider<SystemConfig> register(@Nonnull Evita evita, @Nonnull
throw new EvitaInternalError(e.getMessage(), e);
}
}

/**
* Renders the status of the evitaDB server as a JSON string.
*
* @param instanceId the unique identifier of the server instance
* @param systemStatus the SystemStatus object containing information about the server
* @param apiOptions the common settings shared among all the API endpoints
* @return the JSON string representing the server status
*/
@Nonnull
private static String renderStatus(@Nonnull String instanceId, @Nonnull SystemStatus systemStatus, ApiOptions apiOptions) {
return String.format("""
{
"serverName": "%s",
"version": "%s",
"startedAt": "%s",
"uptime": %d,
"uptimeForHuman": "%s",
"catalogsCorrupted": %d,
"catalogsOk": %d,
"apis": [
%s
]
}""",
instanceId,
systemStatus.version(),
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(systemStatus.startedAt()),
systemStatus.uptime().toSeconds(),
StringUtils.formatDuration(systemStatus.uptime()),
systemStatus.catalogsCorrupted(),
systemStatus.catalogsOk(),
apiOptions.endpoints().entrySet().stream()
.map(
entry -> " {\n \"" + entry.getKey() + "\": [\n" +
Arrays.stream(entry.getValue().getBaseUrls(apiOptions.exposedOn()))
.map(it -> " \"" + it + "\"")
.collect(Collectors.joining(",\n")) +
"\n ]\n }"
)
.collect(Collectors.joining(",\n"))
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

import org.junit.jupiter.api.Test;

import java.time.Duration;

import static io.evitadb.utils.StringUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

Expand Down Expand Up @@ -208,4 +210,40 @@ void shouldAddRightPadding() {
assertEquals("a ", StringUtils.rightPad("a", " ", 10));
assertEquals("dsfadfsadfsadfd", StringUtils.rightPad("dsfadfsadfsadfd", " ", 10));
}

@Test
void shouldFormatDurationLessThanADay() {
Duration duration = Duration.ofHours(5).plusMinutes(30).plusSeconds(15);
String formattedDuration = StringUtils.formatDuration(duration);
assertEquals("5h 30m 15s", formattedDuration);
}

@Test
void shouldFormatDurationExactlyOneDay() {
Duration duration = Duration.ofDays(1);
String formattedDuration = StringUtils.formatDuration(duration);
assertEquals("1d 0h 0m 0s", formattedDuration);
}

@Test
void shouldFormatDurationMoreThanOneDay() {
Duration duration = Duration.ofDays(2).plusHours(5).plusMinutes(30).plusSeconds(15);
String formattedDuration = StringUtils.formatDuration(duration);
assertEquals("2d 5h 30m 15s", formattedDuration);
}

@Test
void shouldFormatDurationMinutesOnly() {
Duration duration = Duration.ofMinutes(30).plusSeconds(15);
String formattedDuration = StringUtils.formatDuration(duration);
assertEquals("30m 15s", formattedDuration);
}

@Test
void shouldFormatZeroDuration() {
Duration duration = Duration.ZERO;
String formattedDuration = StringUtils.formatDuration(duration);
assertEquals("0s", formattedDuration);
}

}

0 comments on commit 0674fd9

Please sign in to comment.