Skip to content

Commit

Permalink
Merge pull request #646 from FgForrest/645-extends-status-endpoint-to…
Browse files Browse the repository at this point in the history
…-provide-useful-info-about-apis

feat(#625): extending server status for the sake of evitaLab usage
  • Loading branch information
novoj authored Aug 13, 2024
2 parents 20e31e2 + 0801279 commit 6968bdd
Show file tree
Hide file tree
Showing 336 changed files with 7,658 additions and 3,006 deletions.
85 changes: 78 additions & 7 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import static java.util.Optional.ofNullable;
Expand Down Expand Up @@ -173,6 +174,22 @@ public boolean atLeastOnEndpointRequiresMtls() {
});
}

/**
* Returns the enabled API endpoints.
*
* @return array of codes of the enabled API endpoints
*/
@Nonnull
public String[] getEnabledApiEndpoints() {
return endpoints()
.entrySet()
.stream()
.filter(entry -> entry.getValue() != null)
.filter(entry -> entry.getValue().isEnabled())
.map(Entry::getKey)
.toArray(String[]::new);
}

/**
* Standard builder pattern implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;

/**
* Descriptor of single external API provider. External API provider is system that is responsible for serving
Expand Down Expand Up @@ -58,6 +60,16 @@ public interface ExternalApiProvider<T extends AbstractApiConfiguration> {
@Nonnull
HttpServiceDefinition[] getHttpServiceDefinitions();

/**
* Returns map of key endpoints and their absolute URLs that could be published to the user via.
* console or administration GUI
* @return index of symbolic name of the endpoint and the absolute URL as a value
*/
@Nonnull
default Map<String, String[]> getKeyEndPoints() {
return Collections.emptyMap();
}

/**
* Called automatically when entire server is done initializing but not started yet.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import io.evitadb.externalApi.configuration.HostDefinition;
import io.evitadb.externalApi.configuration.TlsMode;
import io.evitadb.externalApi.grpc.configuration.GrpcConfig;
import io.evitadb.externalApi.grpc.generated.EvitaManagementServiceGrpc.EvitaManagementServiceBlockingStub;
import io.evitadb.externalApi.grpc.generated.EvitaServiceGrpc.EvitaServiceBlockingStub;
import io.evitadb.externalApi.http.ExternalApiProvider;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -124,12 +124,11 @@ public boolean isReady() {
*/
public boolean checkReachable(@Nonnull String uri) {
try {
final EvitaManagementServiceBlockingStub evitaService = GrpcClients.builder(uri)
final EvitaServiceBlockingStub evitaService = GrpcClients.builder(uri)
.factory(clientFactoryBuilder.build())
.responseTimeoutMillis(100)
.build(EvitaManagementServiceBlockingStub.class);
final long uptime = evitaService.serverStatus(Empty.newBuilder().build()).getUptime();
if (uptime > 0) {
.build(EvitaServiceBlockingStub.class);
if (evitaService.isReady(Empty.newBuilder().build()).getReady()) {
reachableUrl = uri;
return true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public Class<GrpcConfig> getConfigurationClass() {
public ExternalApiProvider<GrpcConfig> register(@Nonnull Evita evita, @Nonnull ExternalApiServer externalApiServer, @Nonnull ApiOptions apiOptions, @Nonnull GrpcConfig grpcAPIConfig) {
final GrpcServiceBuilder grpcServiceBuilder = GrpcService.builder()
.addService(new EvitaService(evita))
.addService(new EvitaManagementService(evita))
.addService(new EvitaManagementService(evita, externalApiServer))
.addService(new EvitaSessionService(evita))
.addService(ProtoReflectionService.newInstance())
.intercept(new ServerSessionInterceptor(evita, grpcAPIConfig.getTlsMode()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@
import io.evitadb.core.file.ExportFileService;
import io.evitadb.dataType.PaginatedList;
import io.evitadb.exception.UnexpectedIOException;
import io.evitadb.externalApi.api.system.ProbesProvider;
import io.evitadb.externalApi.api.system.ProbesProvider.ApiState;
import io.evitadb.externalApi.api.system.ProbesProvider.Readiness;
import io.evitadb.externalApi.api.system.ProbesProvider.ReadinessState;
import io.evitadb.externalApi.configuration.AbstractApiConfiguration;
import io.evitadb.externalApi.grpc.constants.GrpcHeaders;
import io.evitadb.externalApi.grpc.dataType.EvitaDataTypesConverter;
import io.evitadb.externalApi.grpc.generated.*;
import io.evitadb.externalApi.grpc.generated.GrpcTaskStatusesResponse.Builder;
import io.evitadb.externalApi.grpc.services.interceptors.GlobalExceptionHandlerInterceptor;
import io.evitadb.externalApi.grpc.services.interceptors.ServerSessionInterceptor;
import io.evitadb.externalApi.http.ExternalApiProvider;
import io.evitadb.externalApi.http.ExternalApiServer;
import io.evitadb.externalApi.trace.ExternalApiTracingContextProvider;
import io.evitadb.utils.Assert;
import io.grpc.Metadata;
Expand All @@ -58,14 +65,21 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;

import static io.evitadb.externalApi.grpc.dataType.EvitaDataTypesConverter.toGrpcOffsetDateTime;
import static io.evitadb.externalApi.grpc.dataType.EvitaDataTypesConverter.toGrpcTaskStatus;
import static io.evitadb.externalApi.grpc.dataType.EvitaDataTypesConverter.toUuid;
import static io.evitadb.externalApi.grpc.requestResponse.EvitaEnumConverter.toGrpcHealthProblem;
import static io.evitadb.externalApi.grpc.requestResponse.EvitaEnumConverter.toGrpcReadinessState;
import static java.util.Optional.ofNullable;

/**
* This service contains methods that could be called by gRPC clients on {@link EvitaManagementContract}.
Expand All @@ -78,6 +92,10 @@ public class EvitaManagementService extends EvitaManagementServiceGrpc.EvitaMana
* Instance of Evita upon which will be executed service calls
*/
@Nonnull private final Evita evita;
/**
* Instance of {@link ExternalApiServer} that is used to handle HTTP requests - for the sake of checking the status.
*/
@Nonnull private final ExternalApiServer externalApiServer;
/**
* Direct reference to {@link EvitaManagement} instance.
*/
Expand Down Expand Up @@ -127,8 +145,9 @@ private static void deleteFileIfExists(@Nullable Path backupFilePath, @Nonnull S
}
}

public EvitaManagementService(@Nonnull Evita evita) {
public EvitaManagementService(@Nonnull Evita evita, @Nonnull ExternalApiServer externalApiServer) {
this.evita = evita;
this.externalApiServer = externalApiServer;
this.management = evita.management();
}

Expand All @@ -143,17 +162,72 @@ public void serverStatus(Empty request, StreamObserver<GrpcEvitaServerStatusResp
executeWithClientContext(
() -> {
final SystemStatus systemStatus = management.getSystemStatus();
responseObserver.onNext(
GrpcEvitaServerStatusResponse
.newBuilder()
.setVersion(systemStatus.version())
.setStartedAt(toGrpcOffsetDateTime(systemStatus.startedAt()))
.setUptime(systemStatus.uptime().toSeconds())
.setInstanceId(systemStatus.instanceId())
.setCatalogsCorrupted(systemStatus.catalogsCorrupted())
.setCatalogsOk(systemStatus.catalogsOk())
.build()
);
final List<ProbesProvider> probes = ServiceLoader.load(ProbesProvider.class)
.stream()
.map(Provider::get)
.toList();

final String[] enabledApiEndpoints = externalApiServer.getApiOptions().getEnabledApiEndpoints();
final Optional<Readiness> readiness = probes.stream()
.findFirst()
.map(it -> it.getReadiness(evita, externalApiServer, enabledApiEndpoints));

final GrpcEvitaServerStatusResponse.Builder responseBuilder = GrpcEvitaServerStatusResponse
.newBuilder()
.setVersion(systemStatus.version())
.setStartedAt(toGrpcOffsetDateTime(systemStatus.startedAt()))
.setUptime(systemStatus.uptime().toSeconds())
.setInstanceId(systemStatus.instanceId())
.setCatalogsCorrupted(systemStatus.catalogsCorrupted())
.setCatalogsOk(systemStatus.catalogsOk())
.setReadiness(toGrpcReadinessState(readiness.map(Readiness::state).orElse(ReadinessState.UNKNOWN)));

probes.stream()
.flatMap(probe -> probe.getHealthProblems(evita, externalApiServer, enabledApiEndpoints).stream())
.distinct()
.forEach(problem -> responseBuilder.addHealthProblems(toGrpcHealthProblem(problem)));

final Set<String> enabledApiEndpointsSet = Set.of(enabledApiEndpoints);
ExternalApiServer.gatherExternalApiProviders()
.forEach(apiRegistrar -> {
final GrpcApiStatus.Builder apiBuilder = GrpcApiStatus.newBuilder()
.setEnabled(enabledApiEndpointsSet.contains(apiRegistrar.getExternalApiCode()))
.setReady(
readiness.map(it -> Arrays.stream(it.apiStates())
.filter(apiState -> apiState.apiCode().equals(apiRegistrar.getExternalApiCode()))
.anyMatch(ApiState::isReady)
).orElse(false)
);

final Optional<ExternalApiProvider<?>> externalApiProviderByCode = ofNullable(externalApiServer.getExternalApiProviderByCode(apiRegistrar.getExternalApiCode()));
externalApiProviderByCode
.ifPresent(provider -> {
final AbstractApiConfiguration configuration = provider.getConfiguration();
Arrays.stream(configuration.getBaseUrls(configuration.getExposedHost()))
.forEach(apiBuilder::addBaseUrl);

provider.getKeyEndPoints()
.forEach(
(key, value) -> {
final GrpcEndpoint.Builder endpointBuilder = GrpcEndpoint.newBuilder()
.setName(key);
for (String url : value) {
endpointBuilder.addUrl(url);
}
apiBuilder.addEndpoints(
endpointBuilder.build()
);
}
);
});

responseBuilder.putApi(
apiRegistrar.getExternalApiCode(),
apiBuilder.build()
);
});

responseObserver.onNext(responseBuilder.build());
responseObserver.onCompleted();
},
evita.getRequestExecutor(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ public EvitaService(@Nonnull Evita evita) {
this.evita = evita;
}

/**
* Method is used to check readiness of the gRPC API.
*
* @param request empty message
* @param responseObserver observer on which errors might be thrown and result returned
*/
@Override
public void isReady(Empty request, StreamObserver<GrpcReadyResponse> responseObserver) {
responseObserver.onNext(GrpcReadyResponse.newBuilder().setReady(true).build());
responseObserver.onCompleted();
}

/**
* Method is used to create read only session by calling {@link Evita#createSession(SessionTraits)}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
public class ServerSessionInterceptor implements ServerInterceptor {
private static final Set<String> ENDPOINTS_NOT_REQUIRING_SESSION = CollectionUtils.createHashSet(32);
static {
ENDPOINTS_NOT_REQUIRING_SESSION.add("io.evitadb.externalApi.grpc.generated.EvitaService/IsReady");
ENDPOINTS_NOT_REQUIRING_SESSION.add("io.evitadb.externalApi.grpc.generated.EvitaService/CreateReadOnlySession");
ENDPOINTS_NOT_REQUIRING_SESSION.add("io.evitadb.externalApi.grpc.generated.EvitaService/CreateReadWriteSession");
ENDPOINTS_NOT_REQUIRING_SESSION.add("io.evitadb.externalApi.grpc.generated.EvitaService/CreateBinaryReadOnlySession");
Expand Down
Loading

0 comments on commit 6968bdd

Please sign in to comment.