Skip to content
This repository has been archived by the owner on Sep 29, 2022. It is now read-only.

Feature/aos 2839 helse endepunkt formater #62

Merged
merged 3 commits into from
Oct 1, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,13 @@ enum class HealthStatus { UP, OBSERVE, COMMENT, UNKNOWN, OUT_OF_SERVICE, DOWN }

data class HealthResponse(
val status: HealthStatus,
val parts: MutableMap<String, HealthPart> = mutableMapOf()
) {
@JsonAnySetter(enabled = true)
private fun setAny(name: String, value: HealthPart) {
parts[name] = value
}
}
val parts: Map<String, HealthPart> = emptyMap()
)

data class HealthPart(val status: HealthStatus, val details: MutableMap<String, JsonNode> = mutableMapOf()) {
@JsonAnySetter(enabled = true)
private fun setAny(name: String, value: JsonNode) {
details[name] = value
}
}
data class HealthPart(
val status: HealthStatus=HealthStatus.UP,
val details: Map<String, JsonNode> = emptyMap()
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class InfoResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import no.skatteetaten.aurora.mokey.extensions.asMap
import no.skatteetaten.aurora.mokey.extensions.extract
import no.skatteetaten.aurora.mokey.model.Endpoint
import no.skatteetaten.aurora.mokey.model.Endpoint.ENV
import no.skatteetaten.aurora.mokey.model.Endpoint.HEALTH
import no.skatteetaten.aurora.mokey.model.Endpoint.INFO
import no.skatteetaten.aurora.mokey.model.HealthPart
import no.skatteetaten.aurora.mokey.model.HealthResponse
import no.skatteetaten.aurora.mokey.model.HealthStatus
import no.skatteetaten.aurora.mokey.model.HttpResponse
import no.skatteetaten.aurora.mokey.model.InfoResponse
import no.skatteetaten.aurora.mokey.model.ManagementLinks
Expand Down Expand Up @@ -40,7 +44,11 @@ class ManagementEndpoint internal constructor(
) {

@Throws(ManagementEndpointException::class)
fun getHealthEndpointResponse(): HttpResponse<HealthResponse> = findJsonResource(HEALTH, HealthResponse::class)
fun getHealthEndpointResponse(): HttpResponse<HealthResponse> {
val response = findJsonResource(HEALTH, JsonNode::class)
val healthResponse = HealthResponseParser.parse(response.deserialized)
return HttpResponse(healthResponse, response.textResponse, response.createdAt)
}

@Throws(ManagementEndpointException::class)
fun getInfoEndpointResponse(): HttpResponse<InfoResponse> = findJsonResource(INFO, InfoResponse::class)
Expand Down Expand Up @@ -101,4 +109,60 @@ class ManagementEndpoint internal constructor(
return HttpResponse(deserialized, jsonString)
}
}
}

object HealthResponseParser {

enum class HealthResponseFormat { SPRING_BOOT_1X, SPRING_BOOT_2X }

private const val STATUS_PROPERTY = "status"
private const val DETAILS_PROPERTY = "details"

fun parse(json: JsonNode): HealthResponse = when (json.format) {
HealthResponseFormat.SPRING_BOOT_2X -> handleSpringBoot2Format(json)
else -> handleSpringBoot1Format(json)
}

private fun handleSpringBoot2Format(json: JsonNode): HealthResponse =
handleSpringBootFormat(json) { it.details }

private fun handleSpringBoot1Format(json: JsonNode): HealthResponse =
handleSpringBootFormat(json) { it.allNodesExceptStatus }

private fun handleSpringBootFormat(
json: JsonNode,
detailsExtractor: (JsonNode) -> Map<String, JsonNode>
): HealthResponse {
val healthStatus = json.status
val allDetails = detailsExtractor(json)
val parts = allDetails.mapValues {
val status = it.value.status
val partDetails = detailsExtractor(it.value)
HealthPart(status, partDetails)
}
return HealthResponse(healthStatus, parts)
}

private val JsonNode.format
get(): HealthResponseFormat {
return if (this.has(DETAILS_PROPERTY) && this.has(STATUS_PROPERTY) && this.size() == 2) {
HealthResponseFormat.SPRING_BOOT_2X
} else {
HealthResponseFormat.SPRING_BOOT_1X
}
}

private val JsonNode.details get() = this.extract("/$DETAILS_PROPERTY").asMap()

private val JsonNode.allNodesExceptStatus
get() = this.asMap().toMutableMap().also { it.remove(STATUS_PROPERTY) }.toMap()

private val JsonNode.status
get(): HealthStatus {
return try {
this.extract("/$STATUS_PROPERTY")?.textValue()?.let { HealthStatus.valueOf(it) }
} catch (e: Throwable) {
null
} ?: throw IllegalArgumentException("Element did not contain valid $STATUS_PROPERTY property")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package no.skatteetaten.aurora.mokey.service

import assertk.assertions.isEqualTo
import com.fasterxml.jackson.databind.node.IntNode
import com.fasterxml.jackson.databind.node.LongNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import no.skatteetaten.aurora.mokey.model.HealthPart
import no.skatteetaten.aurora.mokey.model.HealthResponse
import no.skatteetaten.aurora.mokey.model.HealthStatus
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Test

class HealthResponseParserTest {

@Test
fun `parse flat spring-boot 1-X health endpoint response`() {
@Language("JSON")
val json = """{
"status": "UP",
"atsServiceHelse": {
"status": "UP"
},
"diskSpace": {
"status": "UP",
"total": 10718543872,
"free": 10508611584,
"threshold": 10485760
},
"db": {
"status": "UP",
"database": "Oracle",
"hello": "Hello"
}
}"""

val response = parse(json)
assertk.assert(response).isEqualTo(
HealthResponse(
HealthStatus.UP,
mutableMapOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Her syns jeg det hadde vært ryddig å enten dele opp koden eller ha med named arguments. For min del begynner dette å bli såpass mye kode på en gang at jeg sliter litt med å lese det.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare noen småting, du kan velge om du vil ta det med eller ikke :)

"atsServiceHelse" to HealthPart(HealthStatus.UP, mutableMapOf()),
"diskSpace" to HealthPart(
HealthStatus.UP, mutableMapOf(
"total" to LongNode.valueOf(10718543872),
"threshold" to IntNode.valueOf(10485760),
"free" to LongNode.valueOf(10508611584)
)
),
"db" to HealthPart(
HealthStatus.UP, mutableMapOf(
"hello" to TextNode.valueOf("Hello"),
"database" to TextNode.valueOf("Oracle")
)
)
)
)
)
}

@Test
fun `parse nested spring-boot 2-X health endpoint response`() {
@Language("JSON")
val json = """{
"status": "UP",
"details": {
"diskSpace": {
"status": "UP",
"details": {
"total": 10718543872,
"free": 10502053888,
"threshold": 10485760
}
},
"db": {
"status": "UP",
"details": {
"database": "Oracle",
"hello": "Hello"
}
}
}
}"""

val response = parse(json)
assertk.assert(response).isEqualTo(
HealthResponse(
HealthStatus.UP,
mutableMapOf(
"diskSpace" to HealthPart(
HealthStatus.UP, mutableMapOf(
"total" to LongNode.valueOf(10718543872),
"threshold" to IntNode.valueOf(10485760),
"free" to LongNode.valueOf(10502053888)
)
),
"db" to HealthPart(
HealthStatus.UP, mutableMapOf(
"hello" to TextNode.valueOf("Hello"),
"database" to TextNode.valueOf("Oracle")
)
)
)
)
)
}

private fun parse(json: String) = HealthResponseParser.parse(jacksonObjectMapper().readTree(json))
}