diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/ConfigControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/ConfigControllerTest.kt index 7fc0121..749cbdd 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/ConfigControllerTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/ConfigControllerTest.kt @@ -21,7 +21,7 @@ package dev.dnpm.etl.processor.web import dev.dnpm.etl.processor.config.AppConfiguration import dev.dnpm.etl.processor.config.AppSecurityConfiguration -import dev.dnpm.etl.processor.monitoring.ConnectionCheckService +import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.pseudonym.Generator @@ -69,10 +69,10 @@ abstract class MockSink : Sinks.Many @MockBean( Generator::class, MtbFileSender::class, - ConnectionCheckService::class, RequestProcessor::class, TransformationService::class, TokenRepository::class, + GPasConnectionCheckService::class, RestConnectionCheckService::class ) class ConfigControllerTest { diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt index 81ad922..1afaa32 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt @@ -26,22 +26,18 @@ import jakarta.annotation.PostConstruct import org.apache.kafka.clients.consumer.Consumer import org.apache.kafka.common.errors.TimeoutException import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.http.RequestEntity +import org.springframework.http.* import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.client.RestTemplate import org.springframework.web.util.UriComponentsBuilder import reactor.core.publisher.Sinks +import java.time.Instant import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration interface ConnectionCheckService { - fun connectionAvailable(): Boolean + fun connectionAvailable(): ConnectionCheckResult } @@ -51,9 +47,27 @@ sealed class ConnectionCheckResult { abstract val available: Boolean - data class KafkaConnectionCheckResult(override val available: Boolean) : ConnectionCheckResult() - data class RestConnectionCheckResult(override val available: Boolean) : ConnectionCheckResult() - data class GPasConnectionCheckResult(override val available: Boolean) : ConnectionCheckResult() + abstract val timestamp: Instant + + abstract val lastChange: Instant + + data class KafkaConnectionCheckResult( + override val available: Boolean, + override val timestamp: Instant, + override val lastChange: Instant + ) : ConnectionCheckResult() + + data class RestConnectionCheckResult( + override val available: Boolean, + override val timestamp: Instant, + override val lastChange: Instant + ) : ConnectionCheckResult() + + data class GPasConnectionCheckResult( + override val available: Boolean, + override val timestamp: Instant, + override val lastChange: Instant + ) : ConnectionCheckResult() } class KafkaConnectionCheckService( @@ -62,25 +76,33 @@ class KafkaConnectionCheckService( private val connectionCheckUpdateProducer: Sinks.Many ) : OutputConnectionCheckService { - private var connectionAvailable: Boolean = false - + private var result = ConnectionCheckResult.KafkaConnectionCheckResult(false, Instant.now(), Instant.now()) @PostConstruct @Scheduled(cron = "0 * * * * *") fun check() { - connectionAvailable = try { - null != consumer.listTopics(5.seconds.toJavaDuration()) + result = try { + val available = null != consumer.listTopics(5.seconds.toJavaDuration()) + ConnectionCheckResult.KafkaConnectionCheckResult( + available, + Instant.now(), + if (result.available == available) { result.lastChange } else { Instant.now() } + ) } catch (e: TimeoutException) { - false + ConnectionCheckResult.KafkaConnectionCheckResult( + false, + Instant.now(), + if (!result.available) { result.lastChange } else { Instant.now() } + ) } connectionCheckUpdateProducer.emitNext( - ConnectionCheckResult.KafkaConnectionCheckResult(connectionAvailable), + result, Sinks.EmitFailureHandler.FAIL_FAST ) } - override fun connectionAvailable(): Boolean { - return this.connectionAvailable + override fun connectionAvailable(): ConnectionCheckResult.KafkaConnectionCheckResult { + return this.result } } @@ -92,27 +114,37 @@ class RestConnectionCheckService( private val connectionCheckUpdateProducer: Sinks.Many ) : OutputConnectionCheckService { - private var connectionAvailable: Boolean = false + private var result = ConnectionCheckResult.RestConnectionCheckResult(false, Instant.now(), Instant.now()) @PostConstruct @Scheduled(cron = "0 * * * * *") fun check() { - connectionAvailable = try { - restTemplate.getForEntity( + result = try { + val available = restTemplate.getForEntity( restTargetProperties.uri?.replace("/etl/api", "").toString(), String::class.java ).statusCode == HttpStatus.OK + + ConnectionCheckResult.RestConnectionCheckResult( + available, + Instant.now(), + if (result.available == available) { result.lastChange } else { Instant.now() } + ) } catch (e: Exception) { - false + ConnectionCheckResult.RestConnectionCheckResult( + false, + Instant.now(), + if (!result.available) { result.lastChange } else { Instant.now() } + ) } connectionCheckUpdateProducer.emitNext( - ConnectionCheckResult.RestConnectionCheckResult(connectionAvailable), + result, Sinks.EmitFailureHandler.FAIL_FAST ) } - override fun connectionAvailable(): Boolean { - return this.connectionAvailable + override fun connectionAvailable(): ConnectionCheckResult.RestConnectionCheckResult { + return this.result } } @@ -123,12 +155,12 @@ class GPasConnectionCheckService( private val connectionCheckUpdateProducer: Sinks.Many ) : ConnectionCheckService { - private var connectionAvailable: Boolean = false + private var result = ConnectionCheckResult.GPasConnectionCheckResult(false, Instant.now(), Instant.now()) @PostConstruct @Scheduled(cron = "0 * * * * *") fun check() { - connectionAvailable = try { + result = try { val uri = UriComponentsBuilder.fromUriString( gPasConfigProperties.uri?.replace("/\$pseudonymizeAllowCreate", "/\$pseudonymize").toString() ) @@ -141,22 +173,33 @@ class GPasConnectionCheckService( if (!gPasConfigProperties.username.isNullOrBlank() && !gPasConfigProperties.password.isNullOrBlank()) { headers.setBasicAuth(gPasConfigProperties.username, gPasConfigProperties.password) } - restTemplate.exchange( + + val available = restTemplate.exchange( uri, HttpMethod.GET, HttpEntity(headers), Void::class.java ).statusCode == HttpStatus.OK + + ConnectionCheckResult.GPasConnectionCheckResult( + available, + Instant.now(), + if (result.available == available) { result.lastChange } else { Instant.now() } + ) } catch (e: Exception) { - false + ConnectionCheckResult.GPasConnectionCheckResult( + false, + Instant.now(), + if (!result.available) { result.lastChange } else { Instant.now() } + ) } connectionCheckUpdateProducer.emitNext( - ConnectionCheckResult.GPasConnectionCheckResult(connectionAvailable), + result, Sinks.EmitFailureHandler.FAIL_FAST ) } - override fun connectionAvailable(): Boolean { - return this.connectionAvailable + override fun connectionAvailable(): ConnectionCheckResult.GPasConnectionCheckResult { + return this.result } } \ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt index eb9d541..36589c8 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt @@ -56,7 +56,7 @@ class ConfigController( @GetMapping fun index(model: Model): String { val outputConnectionAvailable = - connectionCheckServices.filterIsInstance().first().connectionAvailable() + connectionCheckServices.filterIsInstance().firstOrNull()?.connectionAvailable() val gPasConnectionAvailable = connectionCheckServices.filterIsInstance().firstOrNull()?.connectionAvailable() diff --git a/src/main/resources/templates/configs/gPasConnectionAvailable.html b/src/main/resources/templates/configs/gPasConnectionAvailable.html index 6dccc60..a9a8517 100644 --- a/src/main/resources/templates/configs/gPasConnectionAvailable.html +++ b/src/main/resources/templates/configs/gPasConnectionAvailable.html @@ -2,15 +2,20 @@

🟦 gPAS nicht konfiguriert - Patienten-IDs werden intern anonymisiert

-

Verbindung zu gPAS

+

Verbindung zu gPAS

- Die Verbindung ist aktuell - verfügbar. - nicht verfügbar. + Stand: +  |  + Letzte Änderung: +
+
+ Die Verbindung ist aktuell + verfügbar. + nicht verfügbar.
ETL-Processor - + gPAS ETL-Processor diff --git a/src/main/resources/templates/configs/outputConnectionAvailable.html b/src/main/resources/templates/configs/outputConnectionAvailable.html index 2b18b75..4b7f8d1 100644 --- a/src/main/resources/templates/configs/outputConnectionAvailable.html +++ b/src/main/resources/templates/configs/outputConnectionAvailable.html @@ -1,16 +1,26 @@ -

MTB-File Verbindung

-
- Verbindung über [[ ${mtbFileSender} ]]. Die Verbindung ist aktuell - verfügbar. - nicht verfügbar. -
-
- ETL-Processor - - bwHC-Backend - Kafka-Broker - ETL-Processor - - bwHC-Backend - Kafka-Broker -
\ No newline at end of file + +

🟦 Keine Ausgabenkonfiguration

+
+ +

MTB-File Verbindung

+
+ Stand: +  |  + Letzte Änderung: +
+
+ Verbindung über [[ ${mtbFileSender} ]]. Die Verbindung ist aktuell + verfügbar. + nicht verfügbar. +
+
+ ETL-Processor + + bwHC-Backend + Kafka-Broker + ETL-Processor + + bwHC-Backend + Kafka-Broker +
+
\ No newline at end of file