From 45b1327345c33a1f15f625cb297b59930698577f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brage=20Skj=C3=B8nborg?= Date: Sun, 4 Jan 2026 21:51:35 +0100 Subject: [PATCH] Health --- apps/converter/build.gradle.kts | 6 ++-- .../controller/ReadinessController.kt | 28 +++++++++++++++ .../src/main/resources/application.yml | 13 ++++--- .../src/test/resources/application.yml | 7 +++- apps/coordinator/build.gradle.kts | 7 +--- .../mediaprocessing/coordinator/Preference.kt | 34 ++++++++++++++----- .../controller/ReadinessController.kt | 28 +++++++++++++++ .../src/main/resources/application.properties | 8 ----- .../src/main/resources/application.yml | 25 ++++++++++++++ .../src/test/resources/application.yml | 7 +++- apps/processer/build.gradle.kts | 3 +- .../controller/ReadinessController.kt | 28 +++++++++++++++ .../src/main/resources/application.properties | 5 --- .../src/main/resources/application.yml | 24 +++++++++++++ .../src/test/resources/application.yml | 33 ++++++++++++++++++ shared/common/build.gradle.kts | 7 ++-- .../database/DatabaseHealthIndicator.kt | 27 +++++++++++++++ .../database/queries/FilesTableQueries.kt | 22 ++++++++++++ .../common/database/tables/FilesTable.kt | 12 +++++++ .../shared/common/dto/FileTableItem.kt | 10 ++++++ 20 files changed, 294 insertions(+), 40 deletions(-) create mode 100644 apps/converter/src/main/kotlin/no/iktdev/mediaprocessing/converter/controller/ReadinessController.kt create mode 100644 apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/controller/ReadinessController.kt delete mode 100644 apps/coordinator/src/main/resources/application.properties create mode 100644 apps/coordinator/src/main/resources/application.yml create mode 100644 apps/processer/src/main/kotlin/no/iktdev/mediaprocessing/processer/controller/ReadinessController.kt delete mode 100644 apps/processer/src/main/resources/application.properties create mode 100644 apps/processer/src/main/resources/application.yml create mode 100644 apps/processer/src/test/resources/application.yml create mode 100644 shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/DatabaseHealthIndicator.kt create mode 100644 shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/queries/FilesTableQueries.kt create mode 100644 shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/tables/FilesTable.kt create mode 100644 shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/dto/FileTableItem.kt diff --git a/apps/converter/build.gradle.kts b/apps/converter/build.gradle.kts index 93c54eab..93008c16 100644 --- a/apps/converter/build.gradle.kts +++ b/apps/converter/build.gradle.kts @@ -29,8 +29,10 @@ repositories { dependencies { /*Spring boot*/ implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter:2.7.0") - implementation("org.springframework.boot:spring-boot-starter-websocket:2.6.3") + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-websocket") + implementation("org.springframework:spring-tx") + implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") diff --git a/apps/converter/src/main/kotlin/no/iktdev/mediaprocessing/converter/controller/ReadinessController.kt b/apps/converter/src/main/kotlin/no/iktdev/mediaprocessing/converter/controller/ReadinessController.kt new file mode 100644 index 00000000..9626f575 --- /dev/null +++ b/apps/converter/src/main/kotlin/no/iktdev/mediaprocessing/converter/controller/ReadinessController.kt @@ -0,0 +1,28 @@ +package no.iktdev.mediaprocessing.converter.controller + +import org.springframework.boot.actuate.health.HealthEndpoint +import org.springframework.boot.actuate.health.Status +import org.springframework.http.HttpStatus +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 + +@RestController +@RequestMapping("/system") +class ReadinessController( + private val healthEndpoint: HealthEndpoint +) { + + @GetMapping("/ready") + fun ready(): ResponseEntity { + val health = healthEndpoint.health() + + return if (health.status == Status.UP) { + ResponseEntity.ok("READY") + } else { + ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body("NOT_READY: ${health.status}") + } + } +} diff --git a/apps/converter/src/main/resources/application.yml b/apps/converter/src/main/resources/application.yml index fc3d5ccc..75920619 100644 --- a/apps/converter/src/main/resources/application.yml +++ b/apps/converter/src/main/resources/application.yml @@ -6,12 +6,15 @@ spring: enabled: true locations: classpath:flyway baseline-on-migrate: true - datasource: - url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 - driver-class-name: org.h2.Driver - username: sa - password: +management: + endpoints: + web: + exposure: + include: health + endpoint: + health: + show-details: always logging: level: diff --git a/apps/converter/src/test/resources/application.yml b/apps/converter/src/test/resources/application.yml index 3df1331c..0edf3706 100644 --- a/apps/converter/src/test/resources/application.yml +++ b/apps/converter/src/test/resources/application.yml @@ -25,4 +25,9 @@ management: endpoints: web: exposure: - include: mappings + include: + - mappings + - health + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/apps/coordinator/build.gradle.kts b/apps/coordinator/build.gradle.kts index 05fe3032..8bc91a7a 100644 --- a/apps/coordinator/build.gradle.kts +++ b/apps/coordinator/build.gradle.kts @@ -22,12 +22,12 @@ repositories { } -val exposedVersion = "0.61.0" dependencies { /*Spring boot*/ implementation("org.springframework.boot:spring-boot-starter") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework:spring-tx") @@ -49,11 +49,6 @@ dependencies { implementation(project(mapOf("path" to ":shared:ffmpeg"))) implementation(project(mapOf("path" to ":shared:common"))) - implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion") - implementation ("mysql:mysql-connector-java:8.0.29") implementation("org.jetbrains.kotlin:kotlin-stdlib") diff --git a/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/Preference.kt b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/Preference.kt index 1a82b636..027e4010 100644 --- a/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/Preference.kt +++ b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/Preference.kt @@ -1,16 +1,28 @@ package no.iktdev.mediaprocessing.coordinator import com.google.gson.Gson -import com.google.gson.GsonBuilder import no.iktdev.mediaprocessing.ffmpeg.dsl.AudioCodec import no.iktdev.mediaprocessing.ffmpeg.dsl.VideoCodec -import no.iktdev.mediaprocessing.shared.common.silentTry import java.io.File + +data class PeferenceConfig( + val processer: ProcesserPreference +) + data class ProcesserPreference( val videoPreference: VideoPreference? = null, val audioPreference: AudioPreference? = null -) +) { + companion object { + fun default(): ProcesserPreference { + return ProcesserPreference( + videoPreference = VideoPreference(VideoCodec.Hevc(), false), + audioPreference = AudioPreference("jpn", AudioCodec.Aac()) + ) + } + } +} data class VideoPreference( val codec: VideoCodec, @@ -25,22 +37,26 @@ data class AudioPreference( object Preference { fun getProcesserPreference(): ProcesserPreference { - var preference: ProcesserPreference = ProcesserPreference() - CoordinatorEnv.preference.ifExists { + var preference: ProcesserPreference = ProcesserPreference.default() + CoordinatorEnv.preference.ifExists({ val text = readText() try { - val result = Gson().fromJson(text, ProcesserPreference::class.java) - preference = result + val result = Gson().fromJson(text, PeferenceConfig::class.java) + preference = result.processer } catch (e: Exception) { e.printStackTrace() } - } + }, orElse = { + CoordinatorEnv.preference.writeText(Gson().toJson(PeferenceConfig(preference))) + }) return preference } } -private fun File.ifExists(block: File.() -> Unit) { +private fun File.ifExists(block: File.() -> Unit, orElse: () -> Unit = {}) { if (this.exists()) { block() + } else { + orElse() } } diff --git a/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/controller/ReadinessController.kt b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/controller/ReadinessController.kt new file mode 100644 index 00000000..596aaa1a --- /dev/null +++ b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/controller/ReadinessController.kt @@ -0,0 +1,28 @@ +package no.iktdev.mediaprocessing.coordinator.controller + +import org.springframework.boot.actuate.health.HealthEndpoint +import org.springframework.boot.actuate.health.Status +import org.springframework.http.HttpStatus +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 + +@RestController +@RequestMapping("/system") +class ReadinessController( + private val healthEndpoint: HealthEndpoint +) { + + @GetMapping("/ready") + fun ready(): ResponseEntity { + val health = healthEndpoint.health() + + return if (health.status == Status.UP) { + ResponseEntity.ok("READY") + } else { + ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body("NOT_READY: ${health.status}") + } + } +} diff --git a/apps/coordinator/src/main/resources/application.properties b/apps/coordinator/src/main/resources/application.properties deleted file mode 100644 index a7fb1339..00000000 --- a/apps/coordinator/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ -spring.output.ansi.enabled=always -logging.level.org.apache.kafka=INFO -logging.level.root=INFO -logging.level.Exposed=OFF -logging.level.org.springframework.web.socket.config.WebSocketMessageBrokerStats = WARN -management.endpoints.web.exposure.include=* -management.endpoint.health.show-details=always -management.endpoints.web.base-path=/actuator diff --git a/apps/coordinator/src/main/resources/application.yml b/apps/coordinator/src/main/resources/application.yml new file mode 100644 index 00000000..406c1f65 --- /dev/null +++ b/apps/coordinator/src/main/resources/application.yml @@ -0,0 +1,25 @@ +spring: + output: + ansi: + enabled: always + flyway: + enabled: true + locations: classpath:flyway + baseline-on-migrate: true + +management: + endpoints: + web: + exposure: + include: health + endpoint: + health: + show-details: always + + +logging: + level: + root: INFO + org.apache.kafka: INFO + Exposed: OFF + org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN diff --git a/apps/coordinator/src/test/resources/application.yml b/apps/coordinator/src/test/resources/application.yml index 3df1331c..0edf3706 100644 --- a/apps/coordinator/src/test/resources/application.yml +++ b/apps/coordinator/src/test/resources/application.yml @@ -25,4 +25,9 @@ management: endpoints: web: exposure: - include: mappings + include: + - mappings + - health + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/apps/processer/build.gradle.kts b/apps/processer/build.gradle.kts index d5032369..27f2adcc 100644 --- a/apps/processer/build.gradle.kts +++ b/apps/processer/build.gradle.kts @@ -24,9 +24,10 @@ repositories { dependencies { /*Spring boot*/ - implementation("org.springframework.boot:spring-boot-starter") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.springframework:spring-tx") // implementation("org.springframework.kafka:spring-kafka:3.0.1") diff --git a/apps/processer/src/main/kotlin/no/iktdev/mediaprocessing/processer/controller/ReadinessController.kt b/apps/processer/src/main/kotlin/no/iktdev/mediaprocessing/processer/controller/ReadinessController.kt new file mode 100644 index 00000000..acace8bf --- /dev/null +++ b/apps/processer/src/main/kotlin/no/iktdev/mediaprocessing/processer/controller/ReadinessController.kt @@ -0,0 +1,28 @@ +package no.iktdev.mediaprocessing.processer.controller + +import org.springframework.boot.actuate.health.HealthEndpoint +import org.springframework.boot.actuate.health.Status +import org.springframework.http.HttpStatus +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 + +@RestController +@RequestMapping("/system") +class ReadinessController( + private val healthEndpoint: HealthEndpoint +) { + + @GetMapping("/ready") + fun ready(): ResponseEntity { + val health = healthEndpoint.health() + + return if (health.status == Status.UP) { + ResponseEntity.ok("READY") + } else { + ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body("NOT_READY: ${health.status}") + } + } +} diff --git a/apps/processer/src/main/resources/application.properties b/apps/processer/src/main/resources/application.properties deleted file mode 100644 index 775a2d6c..00000000 --- a/apps/processer/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ -spring.output.ansi.enabled=always -logging.level.org.apache.kafka=INFO -logging.level.root=INFO -logging.level.Exposed=OFF -logging.level.org.springframework.web.socket.config.WebSocketMessageBrokerStats = WARN \ No newline at end of file diff --git a/apps/processer/src/main/resources/application.yml b/apps/processer/src/main/resources/application.yml new file mode 100644 index 00000000..75920619 --- /dev/null +++ b/apps/processer/src/main/resources/application.yml @@ -0,0 +1,24 @@ +spring: + output: + ansi: + enabled: always + flyway: + enabled: true + locations: classpath:flyway + baseline-on-migrate: true + +management: + endpoints: + web: + exposure: + include: health + endpoint: + health: + show-details: always + +logging: + level: + root: INFO + org.apache.kafka: INFO + Exposed: OFF + org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN diff --git a/apps/processer/src/test/resources/application.yml b/apps/processer/src/test/resources/application.yml new file mode 100644 index 00000000..0edf3706 --- /dev/null +++ b/apps/processer/src/test/resources/application.yml @@ -0,0 +1,33 @@ +spring: + main: + allow-bean-definition-overriding: true + flyway: + enabled: false + locations: classpath:flyway + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + + output: + ansi: + enabled: always + +springdoc: + swagger-ui: + path: /open/swagger-ui + +logging: + level: + org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN + org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping: DEBUG + +management: + endpoints: + web: + exposure: + include: + - mappings + - health + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/shared/common/build.gradle.kts b/shared/common/build.gradle.kts index 6ea02e87..fa032781 100644 --- a/shared/common/build.gradle.kts +++ b/shared/common/build.gradle.kts @@ -27,6 +27,11 @@ val exposedVersion = "0.61.0" dependencies { + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-websocket") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + implementation("com.github.pgreze:kotlin-process:1.3.1") implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") implementation(libs.exfl) @@ -34,9 +39,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") implementation("com.google.code.gson:gson:2.8.9") implementation("org.json:json:20231013") - implementation("org.springframework.boot:spring-boot-starter-websocket:2.6.3") - implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-mysql") diff --git a/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/DatabaseHealthIndicator.kt b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/DatabaseHealthIndicator.kt new file mode 100644 index 00000000..2d7fd614 --- /dev/null +++ b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/DatabaseHealthIndicator.kt @@ -0,0 +1,27 @@ +package no.iktdev.mediaprocessing.shared.common.database + +import org.jetbrains.exposed.sql.transactions.transaction +import org.springframework.boot.actuate.health.Health +import org.springframework.boot.actuate.health.HealthIndicator +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.stereotype.Component +import javax.sql.DataSource + +@Component +@ConditionalOnBean(DataSource::class) +class ExposedHealthIndicator : HealthIndicator { + + override fun health(): Health { + return try { + transaction { + exec("SELECT 1") { rs -> + if (rs.next()) rs.getInt(1) else null + } + } + Health.up().build() + } catch (e: Exception) { + Health.down(e).build() + } + } +} + diff --git a/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/queries/FilesTableQueries.kt b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/queries/FilesTableQueries.kt new file mode 100644 index 00000000..50cc8503 --- /dev/null +++ b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/queries/FilesTableQueries.kt @@ -0,0 +1,22 @@ +package no.iktdev.mediaprocessing.shared.common.database.queries + +import no.iktdev.mediaprocessing.shared.common.database.tables.FilesTable +import no.iktdev.mediaprocessing.shared.common.database.withTransaction +import no.iktdev.mediaprocessing.shared.common.dto.FileTableItem +import org.jetbrains.exposed.sql.selectAll + +class FilesTableQueries { + fun getFiles(): List { + return withTransaction { + FilesTable.selectAll() + .mapNotNull { + FileTableItem( + name = it[FilesTable.name], + uri = it[FilesTable.uri], + checksum = it[FilesTable.checksum], + identifiedAt = it[FilesTable.identifiedAt], + ) + } + }.getOrElse { emptyList() } + } +} \ No newline at end of file diff --git a/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/tables/FilesTable.kt b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/tables/FilesTable.kt new file mode 100644 index 00000000..6713a55b --- /dev/null +++ b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/database/tables/FilesTable.kt @@ -0,0 +1,12 @@ +package no.iktdev.mediaprocessing.shared.common.database.tables + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.javatime.datetime + +object FilesTable: IntIdTable("FILES") { + val name: Column = varchar("NAME", 255) + val uri: Column = text("URI") + val checksum: Column = char("CHECKSUM", 64) + val identifiedAt: Column = datetime("IDENTIFIED_AT") +} \ No newline at end of file diff --git a/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/dto/FileTableItem.kt b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/dto/FileTableItem.kt new file mode 100644 index 00000000..a8300d4b --- /dev/null +++ b/shared/common/src/main/kotlin/no/iktdev/mediaprocessing/shared/common/dto/FileTableItem.kt @@ -0,0 +1,10 @@ +package no.iktdev.mediaprocessing.shared.common.dto + +import java.time.LocalDateTime + +data class FileTableItem( + val name: String, + val uri: String, + val checksum: String, + val identifiedAt: LocalDateTime, +) \ No newline at end of file