libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 2daf4029d66d72e497d2fd70285ef64b5d52337a
parent 80c0ec6b40af59e8c136f5f6393405df28c86247
Author: Antoine A <>
Date:   Tue, 14 Oct 2025 18:58:40 +0100

bank: add observability api

Diffstat:
Mbank/build.gradle | 5+++++
Mbank/src/main/kotlin/tech/libeufin/bank/Constants.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 15++++++++-------
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 7+++++--
Abank/src/main/kotlin/tech/libeufin/bank/api/ObservabilityApi.kt | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt | 1+
Abank/src/test/kotlin/ObservabilityTest.kt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbuild.gradle | 1+
Mdatabase-versioning/libeufin-bank-0010.sql | 2+-
Mdatabase-versioning/libeufin-bank-0014.sql | 3+++
Mnexus/build.gradle | 7+++----
Mnexus/src/test/kotlin/ObservabilityTest.kt | 2+-
12 files changed, 153 insertions(+), 16 deletions(-)

diff --git a/bank/build.gradle b/bank/build.gradle @@ -27,6 +27,11 @@ dependencies { implementation("com.github.ajalt.clikt:clikt:$clikt_version") implementation("com.github.ajalt.mordant:mordant:3.0.2") + // Metrics + implementation("io.prometheus:prometheus-metrics-core:$prometheus_version") + implementation("io.prometheus:prometheus-metrics-instrumentation-jvm:$prometheus_version") + implementation("io.prometheus:prometheus-metrics-exposition-formats:$prometheus_version") + implementation("io.ktor:ktor-server-core:$ktor_version") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt @@ -39,6 +39,6 @@ const val MAX_TOKEN_CREATION_ATTEMPTS: Int = 5 const val MAX_ACTIVE_CHALLENGES: Int = 5 // API version -const val COREBANK_API_VERSION: String = "10:0:0" +const val COREBANK_API_VERSION: String = "11:0:1" const val CONVERSION_API_VERSION: String = "2:0:1" const val INTEGRATION_API_VERSION: String = "5:0:5" diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -34,13 +34,14 @@ import com.github.ajalt.clikt.core.main val logger: Logger = LoggerFactory.getLogger("libeufin-bank") /** Set up web server handlers for the Taler corebank API */ -fun Application.corebankWebApp(db: Database, ctx: BankConfig) = talerApi(LoggerFactory.getLogger("libeufin-bank-api")) { - coreBankApi(db, ctx) - conversionApi(db, ctx) - bankIntegrationApi(db, ctx) - wireGatewayApi(db, ctx) - revenueApi(db, ctx) - ctx.spaPath?.let { +fun Application.corebankWebApp(db: Database, cfg: BankConfig) = talerApi(LoggerFactory.getLogger("libeufin-bank-api")) { + coreBankApi(db, cfg) + conversionApi(db, cfg) + bankIntegrationApi(db, cfg) + wireGatewayApi(db, cfg) + revenueApi(db, cfg) + observabilityApi(db, cfg) + cfg.spaPath?.let { get("/") { call.respondRedirect("/webui/") } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -353,7 +353,8 @@ enum class TokenScope { readonly, readwrite, revenue, - wiregateway; + wiregateway, + observability; fun logical(): TokenLogicalScope = when (this) { @@ -361,6 +362,7 @@ enum class TokenScope { readwrite -> TokenLogicalScope.readwrite revenue -> TokenLogicalScope.revenue wiregateway -> TokenLogicalScope.readwrite_wiregateway + observability -> TokenLogicalScope.observability } } @@ -370,7 +372,8 @@ enum class TokenLogicalScope { revenue, refreshable, readonly_wiregateway, - readwrite_wiregateway + readwrite_wiregateway, + observability } data class BearerToken( diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/ObservabilityApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/ObservabilityApi.kt @@ -0,0 +1,68 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2025 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin.bank.api + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.pipeline.* +import io.prometheus.metrics.core.metrics.* +import io.prometheus.metrics.model.registry.PrometheusRegistry +import io.prometheus.metrics.model.snapshots.Unit +import io.prometheus.metrics.instrumentation.jvm.JvmMetrics; +import io.prometheus.metrics.expositionformats.ExpositionFormats +import tech.libeufin.common.* +import tech.libeufin.common.db.* +import tech.libeufin.bank.* +import tech.libeufin.bank.db.* +import tech.libeufin.bank.auth.* +import java.time.Instant +import java.io.ByteArrayOutputStream + +object Metrics { + @Volatile + private var tanChannelCounter = Counter.builder() + .name("libeufin_bank_tan_channel") + .help("TAN script calls") + .labelNames("channel", "exit") + + init { + // Register JVM metrics + JvmMetrics.builder().register() + } + + // TODO add database table counter info ? +} + +fun Routing.observabilityApi(db: Database, cfg: BankConfig) { + get("/taler-observability/config") { + call.respond(TalerObservabilityConfig()) + } + authAdmin(db, cfg.pwCrypto, TokenLogicalScope.observability, cfg.basicAuthCompat) { + get("/taler-observability/metrics") { + val snapshot = PrometheusRegistry.defaultRegistry.scrape() + val outputStream = ByteArrayOutputStream() + ExpositionFormats.init().getPrometheusTextFormatWriter().write(outputStream, snapshot) + call.respondText(outputStream.toString(Charsets.UTF_8), ContentType.parse("text/plain; version=0.0.4; charset=utf-8")) + } + } +} +\ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt b/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt @@ -230,6 +230,7 @@ fun validScope(required: TokenLogicalScope, scope: TokenScope): Boolean = when ( TokenLogicalScope.revenue -> scope in setOf(TokenScope.readonly, TokenScope.readwrite, TokenScope.revenue) TokenLogicalScope.readonly_wiregateway -> scope in setOf(TokenScope.wiregateway, TokenScope.readonly, TokenScope.readwrite) TokenLogicalScope.readwrite_wiregateway -> scope in setOf(TokenScope.wiregateway, TokenScope.readwrite) + TokenLogicalScope.observability -> scope in setOf(TokenScope.readonly, TokenScope.readwrite, TokenScope.observability) TokenLogicalScope.refreshable -> true } diff --git a/bank/src/test/kotlin/ObservabilityTest.kt b/bank/src/test/kotlin/ObservabilityTest.kt @@ -0,0 +1,54 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2025 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +import io.ktor.http.* +import io.ktor.client.request.* +import org.junit.Test +import tech.libeufin.bank.* +import tech.libeufin.common.* +import tech.libeufin.common.test.* + +class ObservabilityApiTest { + // GET /taler-observability/config + @Test + fun config() = bankSetup { + client.get("/taler-observability/config").assertOkJson<TalerObservabilityConfig>() + } + + // GET /taler-observability/metrics + @Test + fun metrics() = bankSetup { db -> + authRoutine(HttpMethod.Get, "/taler-observability/metrics", requireAdmin = true) + client.getAdmin("/taler-observability/metrics").assertOk() + + // Check observability token + val response = client.post("/accounts/admin/token") { + pwAuth() + json { + "scope" to "observability" + "duration" to obj { + "d_us" to "forever" + } + } + }.assertOkJson<TokenSuccessResponse>() + client.get("/taler-observability/metrics") { + headers[HttpHeaders.Authorization] = "Bearer ${response.access_token}" + }.assertOk() + } +} +\ No newline at end of file diff --git a/build.gradle b/build.gradle @@ -25,6 +25,7 @@ allprojects { set("postgres_version", "42.7.7") set("junixsocket_version", "2.10.1") set("shadow_version", "9.1.0") + set("prometheus_version", "1.4.1") } repositories { diff --git a/database-versioning/libeufin-bank-0010.sql b/database-versioning/libeufin-bank-0010.sql @@ -18,7 +18,7 @@ BEGIN; SELECT _v.register_patch('libeufin-bank-0010', NULL, NULL); SET search_path TO libeufin_bank; --- Add new token scope 'revenue' +-- Add new token scope 'wiregateway' ALTER TYPE token_scope_enum ADD VALUE 'wiregateway'; COMMIT; diff --git a/database-versioning/libeufin-bank-0014.sql b/database-versioning/libeufin-bank-0014.sql @@ -48,4 +48,7 @@ COMMENT ON COLUMN tan_challenges.salt CREATE INDEX tan_challenges_uuid_index ON tan_challenges (uuid); +-- Add new token scope 'observability' +ALTER TYPE token_scope_enum ADD VALUE 'observability'; + COMMIT; diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -24,10 +24,9 @@ dependencies { implementation(project(":common")) // Metrics - implementation('io.prometheus:prometheus-metrics-core:1.4.1') - implementation('io.prometheus:prometheus-metrics-instrumentation-jvm:1.4.1') - implementation("io.prometheus:prometheus-metrics-exporter-httpserver:1.4.1") - implementation("io.prometheus:prometheus-metrics-exposition-formats:1.4.1") + implementation("io.prometheus:prometheus-metrics-core:$prometheus_version") + implementation("io.prometheus:prometheus-metrics-instrumentation-jvm:$prometheus_version") + implementation("io.prometheus:prometheus-metrics-exposition-formats:$prometheus_version") // Command line parsing implementation("com.github.ajalt.clikt:clikt:$clikt_version") diff --git a/nexus/src/test/kotlin/ObservabilityTest.kt b/nexus/src/test/kotlin/ObservabilityTest.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024-2025 Taler Systems S.A. + * Copyright (C) 2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as