Skip to content

Commit

Permalink
task: monitoring endpoint and self-check refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominick Leppich committed Oct 25, 2024
1 parent bc11fa6 commit e979ddc
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 35 deletions.
44 changes: 44 additions & 0 deletions module-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- Manifest information -->
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
<buildDate>${maven.build.timestamp}</buildDate>

<!-- Library versions -->
<poi.version>5.2.5</poi.version>
</properties>
<scm>
<url>scm:git:https://github.com/intranda/goobi-vocabulary-server</url>
<connection>scm:git:https://github.com/intranda/goobi-vocabulary-server</connection>
</scm>
<dependencies>
<dependency>
<groupId>io.goobi.vocabulary</groupId>
Expand Down Expand Up @@ -162,6 +171,41 @@
</configuration>
</plugin>

<!-- MANIFEST -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<shortRevisionLength>7</shortRevisionLength>
<doCheck>false</doCheck>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Application-Name>${project.name}</Application-Name>
<Build-Date>${buildDate}</Build-Date>
<Version>${project.version}</Version>
<Revision>${buildNumber}</Revision>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- MANIFEST -->

<!-- COMPILATION -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.goobi.vocabulary.api;

import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;
import io.goobi.vocabulary.service.manager.MaintenanceManager;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -16,14 +16,7 @@ public MaintenanceController(MaintenanceManager maintenanceManager) {
}

@GetMapping("/selfcheck")
public ResponseEntity<String> selfCheck() {
String status = maintenanceManager.testAllData();
if (status.contains("FAIL")) {
return ResponseEntity.internalServerError()
.body(status);
} else {
return ResponseEntity.ok()
.body(status);
}
public SelfCheckResult selfCheck() {
return maintenanceManager.performFullSelfCheck();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.goobi.vocabulary.api;

import io.goobi.vocabulary.maintenance.MonitoringResult;
import io.goobi.vocabulary.service.manager.MaintenanceManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/api/v1")
public class MonitoringController {
private final MaintenanceManager maintenanceManager;

public MonitoringController(MaintenanceManager maintenanceManager) {
this.maintenanceManager = maintenanceManager;
}

@GetMapping("/monitoring")
public MonitoringResult monitoring() {
return maintenanceManager.getMonitoringResult();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.goobi.vocabulary.maintenance;

import java.util.Date;

public record FlywayInformation(String schemaVersion, String description, Date date, int executionTime, boolean success) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.goobi.vocabulary.maintenance;

import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

@Getter
@Service
public class ManifestReader {
private final Manifest manifest;

private final String revision;
private final String version;
private final String buildDate;

public ManifestReader() throws IllegalArgumentException, IOException {
manifest = new Manifest(getClass().getResourceAsStream("/META-INF/MANIFEST.MF"));
Attributes mainAttributes = manifest.getMainAttributes();
revision = getOptionalValue(mainAttributes, "Revision").orElse("unknown");
version = getOptionalValue(mainAttributes, "Version").orElse("unknown");
buildDate = getOptionalValue(mainAttributes, "Build-Date").orElse("unknown");
}

private Optional<String> getOptionalValue(Attributes attributes, String attributeName) throws IllegalArgumentException {
String result = attributes.getValue(attributeName);
if (StringUtils.isBlank(result)) {
result = null;
}
return Optional.ofNullable(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.goobi.vocabulary.maintenance;

import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;

public record MonitoringResult(MonitoringState monitoring, VersionsCollection versions, FlywayInformation flyway, SelfCheckResult selfCheck) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.goobi.vocabulary.maintenance;

public record MonitoringState(String database, String selfCheck) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.goobi.vocabulary.maintenance;

public record VersionInformation(String version, String hash) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.goobi.vocabulary.maintenance;

public record VersionsCollection(VersionInformation core) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.goobi.vocabulary.maintenance.selfcheck;

import java.util.Date;

public record SelfCheckResult(Date date, ValidationResult types, ValidationResult vocabularies, ValidationResult schemas, ValidationResult records) {
public boolean success() {
return types.errors().isEmpty() && vocabularies.errors().isEmpty() && schemas.errors().isEmpty() && records.errors().isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.goobi.vocabulary.maintenance.selfcheck;

import java.util.List;

public record ValidationResult(List<String> errors) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package io.goobi.vocabulary.service.manager;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.goobi.vocabulary.exception.VocabularyException;
import io.goobi.vocabulary.maintenance.FlywayInformation;
import io.goobi.vocabulary.maintenance.ManifestReader;
import io.goobi.vocabulary.maintenance.MonitoringResult;
import io.goobi.vocabulary.maintenance.MonitoringState;
import io.goobi.vocabulary.maintenance.VersionInformation;
import io.goobi.vocabulary.maintenance.VersionsCollection;
import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;
import io.goobi.vocabulary.maintenance.selfcheck.ValidationResult;
import io.goobi.vocabulary.model.jpa.FieldTypeEntity;
import io.goobi.vocabulary.model.jpa.VocabularyEntity;
import io.goobi.vocabulary.model.jpa.VocabularyRecordEntity;
Expand All @@ -10,14 +19,23 @@
import io.goobi.vocabulary.repositories.VocabularyRepository;
import io.goobi.vocabulary.repositories.VocabularySchemaRepository;
import io.goobi.vocabulary.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
public class MaintenanceManager {
public static final File SELF_CHECK_CACHE_FILE = new File("/tmp/vocabulary-server-self-check.json");

private final FieldTypeRepository fieldTypeRepository;
private final VocabularyRepository vocabularyRepository;
private final VocabularySchemaRepository vocabularySchemaRepository;
Expand All @@ -28,7 +46,12 @@ public class MaintenanceManager {
private final Validator<VocabularySchemaEntity> vocabularySchemaValidator;
private final Validator<VocabularyRecordEntity> vocabularyRecordValidator;

public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRepository vocabularyRepository, VocabularySchemaRepository vocabularySchemaRepository, VocabularyRecordRepository vocabularyRecordRepository, Validator<FieldTypeEntity> fieldTypeValidator, Validator<VocabularyEntity> vocabularyValidator, Validator<VocabularySchemaEntity> vocabularySchemaValidator, Validator<VocabularyRecordEntity> vocabularyRecordValidator) {
private final ManifestReader manifestReader;
private final Flyway flyway;

private final ObjectMapper objectMapper;

public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRepository vocabularyRepository, VocabularySchemaRepository vocabularySchemaRepository, VocabularyRecordRepository vocabularyRecordRepository, Validator<FieldTypeEntity> fieldTypeValidator, Validator<VocabularyEntity> vocabularyValidator, Validator<VocabularySchemaEntity> vocabularySchemaValidator, Validator<VocabularyRecordEntity> vocabularyRecordValidator, ManifestReader manifestReader, Flyway flyway, ObjectMapper objectMapper) {
this.fieldTypeRepository = fieldTypeRepository;
this.vocabularyRepository = vocabularyRepository;
this.vocabularyValidator = vocabularyValidator;
Expand All @@ -37,47 +60,90 @@ public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRep
this.fieldTypeValidator = fieldTypeValidator;
this.vocabularySchemaValidator = vocabularySchemaValidator;
this.vocabularyRecordValidator = vocabularyRecordValidator;
this.manifestReader = manifestReader;
this.flyway = flyway;
this.objectMapper = objectMapper;
}

public String testAllData() {
List<String> selfCheckResults = new LinkedList<>();
public SelfCheckResult performFullSelfCheck() {
ValidationResult types = new ValidationResult(selfTest(fieldTypeRepository, fieldTypeValidator));
ValidationResult vocabularies = new ValidationResult(selfTest(vocabularyRepository, vocabularyValidator));
ValidationResult vocabularySchemas = new ValidationResult(selfTest(vocabularySchemaRepository, vocabularySchemaValidator));
ValidationResult vocabularyRecords = new ValidationResult(selfTest(vocabularyRecordRepository, vocabularyRecordValidator));

selfCheckResults.add(selfTest("Types", fieldTypeRepository, fieldTypeValidator));
selfCheckResults.add(selfTest("Vocabularies", vocabularyRepository, vocabularyValidator));
selfCheckResults.add(selfTest("Vocabulary Schemas", vocabularySchemaRepository, vocabularySchemaValidator));
selfCheckResults.add(selfTest("Vocabulary Records", vocabularyRecordRepository, vocabularyRecordValidator));

return String.join("\n", selfCheckResults);
SelfCheckResult result = new SelfCheckResult(new Date(), types, vocabularies, vocabularySchemas, vocabularyRecords);
saveSelfCheckResult(result);
return result;
}


private <Entity> String selfTest(String entityName, ListCrudRepository<Entity, Long> repo, Validator<Entity> validator) {
private <Entity> List<String> selfTest(ListCrudRepository<Entity, Long> repo, Validator<Entity> validator) {
List<String> errors = new LinkedList<>();
for (Entity entity : repo.findAll()) {
try {
validator.validate(entity);
} catch (VocabularyException e) {
errors.add(dumpException(e, 1));
StringBuilder s = new StringBuilder();
dumpException(s, e);
errors.add(s.toString());
}
}
if (errors.isEmpty()) {
return entityName + ": OK";
} else {
return entityName + ": FAIL"
+ String.join("", errors);
}
return errors;
}

private String dumpException(VocabularyException ex, int level) {
StringBuilder s = new StringBuilder();
s.append('\n')
.append("\t".repeat(level))
.append(ex.getMessage());
private void dumpException(StringBuilder s, VocabularyException ex) {
s.append(ex.getMessage());
if (ex.getCauses() != null) {
for (VocabularyException c : ex.getCauses()) {
s.append(dumpException(c, level + 1));
s.append("; ");
dumpException(s, c);
}
}
return s.toString();
}

public MonitoringResult getMonitoringResult() {
VersionInformation coreVersion = new VersionInformation(manifestReader.getVersion(), manifestReader.getRevision());
VersionsCollection versions = new VersionsCollection(coreVersion);
FlywayInformation flywayInformation = getFlywayInformation();
Optional<SelfCheckResult> selfCheckResult = loadSelfCheckResult();
MonitoringState monitoringState = getMonitoringState(flywayInformation, selfCheckResult);
return new MonitoringResult(monitoringState, versions, flywayInformation, selfCheckResult.orElse(null));
}

private MonitoringState getMonitoringState(FlywayInformation flywayInformation, Optional<SelfCheckResult> selfCheckStatus) {
String database = flywayInformation.success() ? "ok" : "error";
String selfCheck = "unknown";
if (selfCheckStatus.isPresent()) {
selfCheck = selfCheckStatus.get().success() ? "ok" : "error";
}
return new MonitoringState(database, selfCheck);
}

private FlywayInformation getFlywayInformation() {
String version = flyway.info().current().getVersion().getVersion();
String description = flyway.info().current().getDescription();
Date date = flyway.info().current().getInstalledOn();
int executionTime = flyway.info().current().getExecutionTime();
boolean success = flyway.info().current().getState().isApplied();
return new FlywayInformation(version, description, date, executionTime, success);
}

private void saveSelfCheckResult(SelfCheckResult result) {
try {
objectMapper.writeValue(SELF_CHECK_CACHE_FILE, result);
} catch (IOException e) {
log.error("Error saving self-check result", e);
}
}

private Optional<SelfCheckResult> loadSelfCheckResult() {
if (!MaintenanceManager.SELF_CHECK_CACHE_FILE.exists()) {
return Optional.empty();
}
try {
return Optional.of(objectMapper.readValue(MaintenanceManager.SELF_CHECK_CACHE_FILE, SelfCheckResult.class));
} catch (IOException e) {
log.error("Error reading cached self-check results", e);
return Optional.empty();
}
}
}

0 comments on commit e979ddc

Please sign in to comment.