From 1dd9e7f5e8cd09e3051dbeb6580ed3ba4f871413 Mon Sep 17 00:00:00 2001 From: Florian Tack Date: Thu, 29 Jan 2026 14:22:46 +0100 Subject: [PATCH 1/2] feat: make DB Statement for health check configurable --- .../identity/uaa/health/HealthzEndpoint.java | 8 ++++-- .../uaa/health/HealthzEndpointTests.java | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java index e9bfd4973da..7b7d920fd0f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java @@ -24,11 +24,14 @@ public class HealthzEndpoint { private volatile boolean stopping; private volatile Boolean wasLastConnectionSuccessful = null; private DataSource dataSource; + private String healthCheckStatement; + public HealthzEndpoint( @Value("${uaa.shutdown.sleep:10000}") final long sleepTime, final Runtime runtime, - final DataSource dataSource) { + final DataSource dataSource, + @Value("${uaa.health.db.statement:SELECT 1 from identity_zone;}") final String healthCheckStatement) { Thread shutdownHook = new Thread(() -> { stopping = true; logger.warn("Shutdown hook received, future requests to this endpoint will return 503"); @@ -43,6 +46,7 @@ public HealthzEndpoint( }); runtime.addShutdownHook(shutdownHook); this.dataSource = dataSource; + this.healthCheckStatement = healthCheckStatement; } @GetMapping({"/healthz", "/healthz/**"}) @@ -72,7 +76,7 @@ public String getHealthz(HttpServletResponse response) { @Scheduled(fixedRateString = "${uaa.health.db.rate:10000}") void isDataSourceConnectionAvailable() { try (Connection c = dataSource.getConnection(); Statement statement = c.createStatement()) { - statement.execute("SELECT 1 from identity_zone;"); //"SELECT 1;" Not supported by HSQLDB + statement.execute(healthCheckStatement); wasLastConnectionSuccessful = true; return; } catch (Exception ex) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java index 1183c3769bf..ee7833a17da 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java @@ -36,7 +36,8 @@ void setUp() throws SQLException { statement = mock(Statement.class); when(dataSource.getConnection()).thenReturn(connection); when(connection.createStatement()).thenReturn(statement); - endpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, dataSource); + String healthCheckStatement = "SELECT 1 FROM identity_zone;"; + endpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, dataSource, healthCheckStatement); response = new MockHttpServletResponse(); ArgumentCaptor threadArgumentCaptor = ArgumentCaptor.forClass(Thread.class); @@ -51,9 +52,10 @@ void getHealthz() throws SQLException { } @Test - void getHealthz_connectionSuccess() { + void getHealthz_connectionSuccess() throws SQLException { endpoint.isDataSourceConnectionAvailable(); assertThat(endpoint.getHealthz(response)).isEqualTo("ok\n"); + verify(statement).execute("SELECT 1 FROM identity_zone;"); } @Test @@ -64,6 +66,24 @@ void getHealthz_connectionFailed() throws SQLException { assertThat(response.getStatus()).isEqualTo(503); } + @Test + void getHealthz_withChangedStatement() throws SQLException { + Runtime mockRuntime = mock(Runtime.class); + DataSource changedDataSource = mock(DataSource.class); + Connection changedConnection = mock(Connection.class); + Statement changeStatement = mock(Statement.class); + when(changedDataSource.getConnection()).thenReturn(changedConnection); + when(changedConnection.createStatement()).thenReturn(changeStatement); + + String changedHealthCheckStatement = "SELECT 1;"; + HealthzEndpoint changedEndpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, changedDataSource, changedHealthCheckStatement); + MockHttpServletResponse changedResponse = new MockHttpServletResponse(); + + changedEndpoint.isDataSourceConnectionAvailable(); + assertThat(changedEndpoint.getHealthz(changedResponse)).isEqualTo("ok\n"); + verify(changeStatement).execute(changedHealthCheckStatement); + } + @Test void shutdownSendsStopping() throws InterruptedException { long now = System.currentTimeMillis(); @@ -81,7 +101,8 @@ class WithoutSleeping { void setUp() { Runtime mockRuntime = mock(Runtime.class); DataSource dataSource = mock(DataSource.class); - endpoint = new HealthzEndpoint(-1, mockRuntime, dataSource); + String healthCheckStatement = "SELECT 1 FROM identity_zone;"; + endpoint = new HealthzEndpoint(-1, mockRuntime, dataSource, healthCheckStatement); response = new MockHttpServletResponse(); ArgumentCaptor threadArgumentCaptor = ArgumentCaptor.forClass(Thread.class); From 9f6f33d43e78d56655ea8d6779f5cb4feb83129d Mon Sep 17 00:00:00 2001 From: Florian Tack Date: Mon, 13 Apr 2026 13:13:49 +0200 Subject: [PATCH 2/2] rely on validationQuery from DataSource --- .../identity/uaa/health/HealthzEndpoint.java | 13 ++++---- .../uaa/health/HealthzEndpointTests.java | 33 +++---------------- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java index 7b7d920fd0f..aaf2c05558f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java @@ -11,12 +11,15 @@ import javax.sql.DataSource; import java.sql.Connection; -import java.sql.Statement; /** * Simple controller that just returns "ok" in a request body for the purposes * of monitoring health of the application. It also registers a shutdown hook * and returns "stopping" and a 503 when the process is shutting down. + * The background database check only establishes a connection from the pool + * without executing an additional query. The configured DataSource already + * runs a validation query on every connection borrow ({@code testOnBorrow=true}), + * so obtaining a connection is sufficient to verify database connectivity. */ @Controller public class HealthzEndpoint { @@ -24,14 +27,12 @@ public class HealthzEndpoint { private volatile boolean stopping; private volatile Boolean wasLastConnectionSuccessful = null; private DataSource dataSource; - private String healthCheckStatement; public HealthzEndpoint( @Value("${uaa.shutdown.sleep:10000}") final long sleepTime, final Runtime runtime, - final DataSource dataSource, - @Value("${uaa.health.db.statement:SELECT 1 from identity_zone;}") final String healthCheckStatement) { + final DataSource dataSource) { Thread shutdownHook = new Thread(() -> { stopping = true; logger.warn("Shutdown hook received, future requests to this endpoint will return 503"); @@ -46,7 +47,6 @@ public HealthzEndpoint( }); runtime.addShutdownHook(shutdownHook); this.dataSource = dataSource; - this.healthCheckStatement = healthCheckStatement; } @GetMapping({"/healthz", "/healthz/**"}) @@ -75,8 +75,7 @@ public String getHealthz(HttpServletResponse response) { @Scheduled(fixedRateString = "${uaa.health.db.rate:10000}") void isDataSourceConnectionAvailable() { - try (Connection c = dataSource.getConnection(); Statement statement = c.createStatement()) { - statement.execute(healthCheckStatement); + try (Connection c = dataSource.getConnection()) { wasLastConnectionSuccessful = true; return; } catch (Exception ex) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java index ee7833a17da..1f973baf133 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/health/HealthzEndpointTests.java @@ -9,10 +9,8 @@ import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -26,18 +24,14 @@ class HealthzEndpointTests { private Thread shutdownHook; private DataSource dataSource; private Connection connection; - private Statement statement; @BeforeEach void setUp() throws SQLException { Runtime mockRuntime = mock(Runtime.class); dataSource = mock(DataSource.class); connection = mock(Connection.class); - statement = mock(Statement.class); when(dataSource.getConnection()).thenReturn(connection); - when(connection.createStatement()).thenReturn(statement); - String healthCheckStatement = "SELECT 1 FROM identity_zone;"; - endpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, dataSource, healthCheckStatement); + endpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, dataSource); response = new MockHttpServletResponse(); ArgumentCaptor threadArgumentCaptor = ArgumentCaptor.forClass(Thread.class); @@ -55,35 +49,17 @@ void getHealthz() throws SQLException { void getHealthz_connectionSuccess() throws SQLException { endpoint.isDataSourceConnectionAvailable(); assertThat(endpoint.getHealthz(response)).isEqualTo("ok\n"); - verify(statement).execute("SELECT 1 FROM identity_zone;"); + verify(dataSource).getConnection(); } @Test void getHealthz_connectionFailed() throws SQLException { - when(statement.execute(anyString())).thenThrow(new SQLException()); + when(dataSource.getConnection()).thenThrow(new SQLException()); endpoint.isDataSourceConnectionAvailable(); assertThat(endpoint.getHealthz(response)).isEqualTo("Database Connection failed.\n"); assertThat(response.getStatus()).isEqualTo(503); } - @Test - void getHealthz_withChangedStatement() throws SQLException { - Runtime mockRuntime = mock(Runtime.class); - DataSource changedDataSource = mock(DataSource.class); - Connection changedConnection = mock(Connection.class); - Statement changeStatement = mock(Statement.class); - when(changedDataSource.getConnection()).thenReturn(changedConnection); - when(changedConnection.createStatement()).thenReturn(changeStatement); - - String changedHealthCheckStatement = "SELECT 1;"; - HealthzEndpoint changedEndpoint = new HealthzEndpoint(SLEEP_UPON_SHUTDOWN, mockRuntime, changedDataSource, changedHealthCheckStatement); - MockHttpServletResponse changedResponse = new MockHttpServletResponse(); - - changedEndpoint.isDataSourceConnectionAvailable(); - assertThat(changedEndpoint.getHealthz(changedResponse)).isEqualTo("ok\n"); - verify(changeStatement).execute(changedHealthCheckStatement); - } - @Test void shutdownSendsStopping() throws InterruptedException { long now = System.currentTimeMillis(); @@ -101,8 +77,7 @@ class WithoutSleeping { void setUp() { Runtime mockRuntime = mock(Runtime.class); DataSource dataSource = mock(DataSource.class); - String healthCheckStatement = "SELECT 1 FROM identity_zone;"; - endpoint = new HealthzEndpoint(-1, mockRuntime, dataSource, healthCheckStatement); + endpoint = new HealthzEndpoint(-1, mockRuntime, dataSource); response = new MockHttpServletResponse(); ArgumentCaptor threadArgumentCaptor = ArgumentCaptor.forClass(Thread.class);