mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
Adding GET /system/connectors/stored_procedure_vDec2019/health
This commit is contained in:
parent
2731a4954b
commit
dc53c9367b
@ -412,6 +412,9 @@ object ApiRole extends MdcLoggable{
|
||||
case class CanGetDatabasePoolInfo(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetDatabasePoolInfo = CanGetDatabasePoolInfo()
|
||||
|
||||
case class CanGetConnectorHealth(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetConnectorHealth = CanGetConnectorHealth()
|
||||
|
||||
|
||||
case class CanGetCacheNamespaces(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetCacheNamespaces = CanGetCacheNamespaces()
|
||||
|
||||
@ -113,6 +113,7 @@ object ApiTag {
|
||||
val apiTagJsonSchemaValidation = ResourceDocTag("JSON-Schema-Validation")
|
||||
val apiTagAuthenticationTypeValidation = ResourceDocTag("Authentication-Type-Validation")
|
||||
val apiTagConnectorMethod = ResourceDocTag("Connector-Method")
|
||||
val apiTagConnector = ResourceDocTag("Connector")
|
||||
|
||||
// To mark the Berlin Group APIs suggested order of implementation
|
||||
val apiTagBerlinGroupM = ResourceDocTag("Berlin-Group-M")
|
||||
|
||||
@ -1854,7 +1854,7 @@ trait APIMethods310 {
|
||||
"GET",
|
||||
"/connector/loopback",
|
||||
"Get Connector Status (Loopback)",
|
||||
s"""This endpoint makes a call to the Connector to check the backend transport is reachable. (WIP)
|
||||
s"""This endpoint makes a call to the Connector to check the backend transport is reachable. (Deprecated)
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|
||||
@ -35,6 +35,7 @@ import code.api.v6_0_0.OBPAPI6_0_0
|
||||
import code.abacrule.{AbacRuleEngine, MappedAbacRuleProvider}
|
||||
import code.metrics.APIMetrics
|
||||
import code.bankconnectors.{Connector, LocalMappedConnectorInternal}
|
||||
import code.bankconnectors.storedprocedure.StoredProcedureUtils
|
||||
import code.bankconnectors.LocalMappedConnectorInternal._
|
||||
import code.entitlement.Entitlement
|
||||
import code.loginattempts.LoginAttempt
|
||||
@ -851,6 +852,71 @@ trait APIMethods600 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getStoredProcedureConnectorHealth,
|
||||
implementedInApiVersion,
|
||||
nameOf(getStoredProcedureConnectorHealth),
|
||||
"GET",
|
||||
"/system/connectors/stored_procedure_vDec2019/health",
|
||||
"Get Stored Procedure Connector Health",
|
||||
"""Returns health status of the stored procedure connector including:
|
||||
|
|
||||
|- Connection status (ok/error)
|
||||
|- Database server name: identifies which backend node handled the request (useful for load balancer diagnostics)
|
||||
|- Server IP address
|
||||
|- Database name
|
||||
|- Response time in milliseconds
|
||||
|- Error message (if any)
|
||||
|
|
||||
|Supports database-specific queries for: SQL Server, PostgreSQL, Oracle, and MySQL/MariaDB.
|
||||
|
|
||||
|This endpoint is useful for diagnosing connectivity issues, especially when the database is behind a load balancer
|
||||
|and you need to identify which node is responding or experiencing SSL certificate issues.
|
||||
|
|
||||
|Note: This endpoint may take a long time to respond if the database connection is slow or experiencing issues.
|
||||
|The response time depends on the connection pool timeout and JDBC driver settings.
|
||||
|
|
||||
|Authentication is Required
|
||||
|""",
|
||||
EmptyBody,
|
||||
StoredProcedureConnectorHealthJsonV600(
|
||||
status = "ok",
|
||||
server_name = Some("DBSERVER01"),
|
||||
server_ip = Some("10.0.1.50"),
|
||||
database_name = Some("obp_adapter"),
|
||||
response_time_ms = 45,
|
||||
error_message = None
|
||||
),
|
||||
List(
|
||||
AuthenticatedUserIsRequired,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagConnector, apiTagSystem, apiTagApi),
|
||||
Some(List(canGetConnectorHealth))
|
||||
)
|
||||
|
||||
lazy val getStoredProcedureConnectorHealth: OBPEndpoint = {
|
||||
case "system" :: "connectors" :: "stored_procedure_vDec2019" :: "health" :: Nil JsonGet _ => {
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", u.userId, canGetConnectorHealth, callContext)
|
||||
} yield {
|
||||
val health = StoredProcedureUtils.getHealth()
|
||||
val result = StoredProcedureConnectorHealthJsonV600(
|
||||
status = health.status,
|
||||
server_name = health.serverName,
|
||||
server_ip = health.serverIp,
|
||||
database_name = health.databaseName,
|
||||
response_time_ms = health.responseTimeMs,
|
||||
error_message = health.errorMessage
|
||||
)
|
||||
(result, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy val getCurrentConsumer: OBPEndpoint = {
|
||||
case "consumers" :: "current" :: Nil JsonGet _ => {
|
||||
cc => {
|
||||
|
||||
@ -325,6 +325,15 @@ case class DatabasePoolInfoJsonV600(
|
||||
keepalive_time_ms: Long
|
||||
)
|
||||
|
||||
case class StoredProcedureConnectorHealthJsonV600(
|
||||
status: String,
|
||||
server_name: Option[String],
|
||||
server_ip: Option[String],
|
||||
database_name: Option[String],
|
||||
response_time_ms: Long,
|
||||
error_message: Option[String]
|
||||
)
|
||||
|
||||
case class PostCustomerJsonV600(
|
||||
legal_name: String,
|
||||
customer_number: Option[String] = None,
|
||||
|
||||
@ -49,6 +49,112 @@ object StoredProcedureUtils extends MdcLoggable{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Health check case class for stored procedure connector
|
||||
*/
|
||||
case class StoredProcedureConnectorHealth(
|
||||
status: String,
|
||||
serverName: Option[String],
|
||||
serverIp: Option[String],
|
||||
databaseName: Option[String],
|
||||
responseTimeMs: Long,
|
||||
errorMessage: Option[String]
|
||||
)
|
||||
|
||||
/**
|
||||
* Perform a health check on the stored procedure connector.
|
||||
* Executes a database-specific query to verify connectivity and identify the backend node.
|
||||
* Supports: SQL Server, PostgreSQL, Oracle, and MySQL.
|
||||
*/
|
||||
def getHealth(): StoredProcedureConnectorHealth = {
|
||||
val startTime = System.currentTimeMillis()
|
||||
try {
|
||||
val (serverName, serverIp, databaseName) = scalikeDB readOnly { implicit session =>
|
||||
val driver = APIUtil.getPropsValue("stored_procedure_connector.driver", "")
|
||||
|
||||
if (driver.contains("sqlserver")) {
|
||||
// Microsoft SQL Server
|
||||
val result = sql"""
|
||||
SELECT
|
||||
@@SERVERNAME AS server_name,
|
||||
CAST(CONNECTIONPROPERTY('local_net_address') AS VARCHAR(50)) AS server_ip,
|
||||
DB_NAME() AS database_name
|
||||
""".map(rs => (
|
||||
Option(rs.string("server_name")),
|
||||
Option(rs.string("server_ip")),
|
||||
Option(rs.string("database_name"))
|
||||
)).single.apply()
|
||||
result.getOrElse((None, None, None))
|
||||
} else if (driver.contains("postgresql")) {
|
||||
// PostgreSQL
|
||||
val result = sql"""
|
||||
SELECT
|
||||
inet_server_addr()::text AS server_ip,
|
||||
current_database() AS database_name,
|
||||
(SELECT setting FROM pg_settings WHERE name = 'cluster_name') AS server_name
|
||||
""".map(rs => (
|
||||
rs.stringOpt("server_name"),
|
||||
rs.stringOpt("server_ip"),
|
||||
rs.stringOpt("database_name")
|
||||
)).single.apply()
|
||||
result.getOrElse((None, None, None))
|
||||
} else if (driver.contains("oracle")) {
|
||||
// Oracle
|
||||
val result = sql"""
|
||||
SELECT
|
||||
SYS_CONTEXT('USERENV', 'SERVER_HOST') AS server_name,
|
||||
SYS_CONTEXT('USERENV', 'IP_ADDRESS') AS server_ip,
|
||||
SYS_CONTEXT('USERENV', 'DB_NAME') AS database_name
|
||||
FROM DUAL
|
||||
""".map(rs => (
|
||||
Option(rs.string("server_name")),
|
||||
Option(rs.string("server_ip")),
|
||||
Option(rs.string("database_name"))
|
||||
)).single.apply()
|
||||
result.getOrElse((None, None, None))
|
||||
} else if (driver.contains("mysql") || driver.contains("mariadb")) {
|
||||
// MySQL / MariaDB
|
||||
val result = sql"""
|
||||
SELECT
|
||||
@@hostname AS server_name,
|
||||
@@bind_address AS server_ip,
|
||||
DATABASE() AS database_name
|
||||
""".map(rs => (
|
||||
Option(rs.string("server_name")),
|
||||
Option(rs.string("server_ip")),
|
||||
Option(rs.string("database_name"))
|
||||
)).single.apply()
|
||||
result.getOrElse((None, None, None))
|
||||
} else {
|
||||
// Generic fallback - just test connectivity
|
||||
sql"SELECT 1".map(_ => ()).single.apply()
|
||||
(None, None, None)
|
||||
}
|
||||
}
|
||||
val responseTime = System.currentTimeMillis() - startTime
|
||||
StoredProcedureConnectorHealth(
|
||||
status = "ok",
|
||||
serverName = serverName,
|
||||
serverIp = serverIp,
|
||||
databaseName = databaseName,
|
||||
responseTimeMs = responseTime,
|
||||
errorMessage = None
|
||||
)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val responseTime = System.currentTimeMillis() - startTime
|
||||
logger.error(s"Stored procedure connector health check failed: ${e.getMessage}", e)
|
||||
StoredProcedureConnectorHealth(
|
||||
status = "error",
|
||||
serverName = None,
|
||||
serverIp = None,
|
||||
databaseName = None,
|
||||
responseTimeMs = responseTime,
|
||||
errorMessage = Some(e.getMessage)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def callProcedure[T: Manifest](procedureName: String, outBound: TopicTrait): Box[T] = {
|
||||
val procedureParam: String = write(outBound) // convert OutBound to json string
|
||||
logger.debug(s"${StoredProcedureConnector_vDec2019.toString} outBoundJson: $procedureName = $procedureParam" )
|
||||
|
||||
Loading…
Reference in New Issue
Block a user