Skip to content

Commit 354e578

Browse files
authored
Merge pull request #130 from modelix/issue/MODELIX-411
MODELIX-411 /health endpoint
2 parents 4eec68a + 21f8138 commit 354e578

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

model-server-lib/src/main/kotlin/org/modelix/model/server/light/LightModelServer.kt

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import io.ktor.server.application.*
2121
import io.ktor.server.engine.*
2222
import io.ktor.server.netty.*
2323
import io.ktor.server.plugins.cors.routing.*
24+
import io.ktor.server.response.*
2425
import io.ktor.server.routing.*
2526
import io.ktor.server.websocket.*
27+
import io.ktor.util.*
2628
import io.ktor.websocket.*
2729
import kotlinx.coroutines.*
2830
import org.modelix.model.api.ConceptReference
@@ -54,14 +56,25 @@ import java.time.Duration
5456
import java.util.*
5557
import kotlin.time.Duration.Companion.seconds
5658

57-
class LightModelServer(val port: Int, val rootNode: INode, val ignoredRoles: Set<IRole> = emptySet()) {
59+
class LightModelServer @JvmOverloads constructor (val port: Int, val rootNode: INode, val ignoredRoles: Set<IRole> = emptySet(), additionalHealthChecks: List<IHealthCheck> = emptyList()) {
60+
5861
companion object {
5962
private val LOG = mu.KotlinLogging.logger { }
6063
}
6164

6265
private var server: NettyApplicationEngine? = null
6366
private val sessions: MutableSet<SessionData> = Collections.synchronizedSet(HashSet())
6467
private val ignoredRolesCache: MutableMap<IConceptReference, IgnoredRoles> = HashMap()
68+
private val healthChecks: List<IHealthCheck> = listOf(object : IHealthCheck {
69+
override val id: String = "readRootNode"
70+
override val enabledByDefault: Boolean = true
71+
72+
override fun run(output: StringBuilder): Boolean {
73+
val count = getArea().executeRead { rootNode.allChildren.count() }
74+
output.appendLine("root node has $count children")
75+
return true
76+
}
77+
}) + additionalHealthChecks
6578

6679
fun start() {
6780
LOG.trace { "server starting on port $port ..." }
@@ -123,6 +136,45 @@ class LightModelServer(val port: Int, val rootNode: INode, val ignoredRoles: Set
123136
sessions.remove(session)
124137
}
125138
}
139+
get("/health") {
140+
val output = StringBuilder()
141+
try {
142+
val allChecks = healthChecks.associateBy { it.id }.toMap()
143+
val enabledChecks = allChecks.filter { it.value.enabledByDefault }.keys.toMutableSet()
144+
145+
call.request.queryParameters.entries().forEach { entry ->
146+
entry.value.forEach { value ->
147+
if (!allChecks.containsKey(entry.key)) throw IllegalArgumentException("Unknown check: ${entry.key}")
148+
if (value.toBooleanStrict()) {
149+
enabledChecks.add(entry.key)
150+
} else {
151+
enabledChecks.remove(entry.key)
152+
}
153+
}
154+
}
155+
var isHealthy = true
156+
for (healthCheck in allChecks.values) {
157+
if (enabledChecks.contains(healthCheck.id)) {
158+
output.appendLine("--- running check '${healthCheck.id}' ---")
159+
val result = healthCheck.run(output)
160+
output.appendLine()
161+
output.appendLine("-> " + if (result) "successful" else "failed")
162+
isHealthy = isHealthy && result
163+
} else {
164+
output.appendLine("--- check '${healthCheck.id}' is disabled. Use '/health?${healthCheck.id}=true' to enable it.")
165+
}
166+
}
167+
if (isHealthy) {
168+
call.respond(HttpStatusCode.OK, "healthy\n\n$output")
169+
} else {
170+
call.respond(HttpStatusCode.InternalServerError, "unhealthy\n\n$output")
171+
}
172+
} catch (ex: Exception) {
173+
output.appendLine()
174+
output.appendLine(ex.stackTraceToString())
175+
call.respond(HttpStatusCode.InternalServerError, "unhealthy\n\n$output")
176+
}
177+
}
126178
}
127179
install(CORS) {
128180
anyHost()
@@ -314,6 +366,12 @@ class LightModelServer(val port: Int, val rootNode: INode, val ignoredRoles: Set
314366
children = childrenMap,
315367
)
316368
}
369+
370+
interface IHealthCheck {
371+
val id: String
372+
val enabledByDefault: Boolean
373+
fun run(output: StringBuilder): Boolean
374+
}
317375
}
318376

319377
private class IgnoredRoles(val children: Set<String>, val properties: Set<String>, val references: Set<String>) {

0 commit comments

Comments
 (0)