From: Jacek Date: Thu, 10 Feb 2022 11:09:48 +0000 (+0100) Subject: SONAR-17200 Move to HikariCP from Apache DBCP X-Git-Tag: 9.7.0.61563~316 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=8961d0c0b80dd23b7ed01ea0f249282956374795;p=sonarqube.git SONAR-17200 Move to HikariCP from Apache DBCP --- diff --git a/build.gradle b/build.gradle index 5d6e47a4502..2359031dadd 100644 --- a/build.gradle +++ b/build.gradle @@ -261,7 +261,7 @@ subprojects { dependency 'org.awaitility:awaitility:4.2.0' dependency 'org.apache.commons:commons-csv:1.9.0' dependency 'org.apache.commons:commons-email:1.5' - dependency 'org.apache.commons:commons-dbcp2:2.9.0' + dependency 'com.zaxxer:HikariCP:5.0.1' dependency('org.apache.httpcomponents:httpclient:4.5.13'){ exclude 'commons-logging:commons-logging' } @@ -304,6 +304,7 @@ subprojects { dependency('org.mockito:mockito-core:4.7.0') { exclude 'org.hamcrest:hamcrest-core' } + dependency('org.mockito:mockito-inline:4.7.0') dependency 'org.mybatis:mybatis:3.5.10' dependency 'org.nanohttpd:nanohttpd:2.3.1' dependencySet(group: 'org.slf4j', version: '1.7.30') { diff --git a/server/sonar-ce/build.gradle b/server/sonar-ce/build.gradle index 054ac8d7c51..f66a080cfa7 100644 --- a/server/sonar-ce/build.gradle +++ b/server/sonar-ce/build.gradle @@ -13,7 +13,7 @@ dependencies { compile 'com.hazelcast:hazelcast' compile 'com.hazelcast:hazelcast-kubernetes' compile 'commons-io:commons-io' - compile 'org.apache.commons:commons-dbcp2' + compile 'com.zaxxer:HikariCP' compile 'org.sonarsource.api.plugin:sonar-plugin-api' compile project(':server:sonar-ce-common') compile project(':server:sonar-ce-task') diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBean.java b/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBean.java index fa8d6f7ad3e..a2e69a63094 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBean.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBean.java @@ -21,23 +21,18 @@ package org.sonar.ce.monitoring; public interface CeDatabaseMBean { - String OBJECT_NAME = "SonarQube:name=ComputeEngineDatabaseConnection"; - int getPoolActiveConnections(); - int getPoolMaxActiveConnections(); + int getPoolMaxConnections(); - int getPoolIdleConnections(); + int getPoolTotalConnections(); - int getPoolMaxIdleConnections(); + int getPoolIdleConnections(); int getPoolMinIdleConnections(); - int getPoolInitialSize(); + long getPoolMaxLifeTimeMillis(); long getPoolMaxWaitMillis(); - boolean getPoolRemoveAbandoned(); - - int getPoolRemoveAbandonedTimeoutSeconds(); } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBeanImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBeanImpl.java index c392abd1e05..2adb3b36c11 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBeanImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBeanImpl.java @@ -19,95 +19,34 @@ */ package org.sonar.ce.monitoring; -import org.apache.commons.dbcp2.BasicDataSource; -import org.sonar.api.Startable; +import org.sonar.db.DatabaseMBean; import org.sonar.db.DbClient; -import org.sonar.process.Jmx; -import org.sonar.process.systeminfo.SystemInfoSection; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -public class CeDatabaseMBeanImpl implements CeDatabaseMBean, Startable, SystemInfoSection { - private final DbClient dbClient; +public class CeDatabaseMBeanImpl extends DatabaseMBean implements CeDatabaseMBean { - public CeDatabaseMBeanImpl(DbClient dbClient) { - this.dbClient = dbClient; - } - - @Override - public void start() { - Jmx.register(OBJECT_NAME, this); - } - - /** - * Unregister, if needed - */ - @Override - public void stop() { - Jmx.unregister(OBJECT_NAME); - } - - @Override - public int getPoolActiveConnections() { - return commonsDbcp().getNumActive(); - } - - @Override - public int getPoolMaxActiveConnections() { - return commonsDbcp().getMaxTotal(); - } - - @Override - public int getPoolIdleConnections() { - return commonsDbcp().getNumIdle(); - } + private static final String OBJECT_NAME = "ComputeEngineDatabaseConnection"; - @Override - public int getPoolMaxIdleConnections() { - return commonsDbcp().getMaxIdle(); - } - - @Override - public int getPoolMinIdleConnections() { - return commonsDbcp().getMinIdle(); - } - - @Override - public int getPoolInitialSize() { - return commonsDbcp().getInitialSize(); - } - - @Override - public long getPoolMaxWaitMillis() { - return commonsDbcp().getMaxWaitMillis(); - } - - @Override - public boolean getPoolRemoveAbandoned() { - return commonsDbcp().getRemoveAbandonedOnBorrow(); + public CeDatabaseMBeanImpl(DbClient dbClient) { + super(dbClient); } @Override - public int getPoolRemoveAbandonedTimeoutSeconds() { - return commonsDbcp().getRemoveAbandonedTimeout(); - } - - private BasicDataSource commonsDbcp() { - return (BasicDataSource) dbClient.getDatabase().getDataSource(); + protected String name() { + return OBJECT_NAME; } @Override public ProtobufSystemInfo.Section toProtobuf() { ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder(); builder.setName("Compute Engine Database Connection"); - builder.addAttributesBuilder().setKey("Pool Initial Size").setLongValue(getPoolInitialSize()).build(); + builder.addAttributesBuilder().setKey("Pool Total Connections").setLongValue(getPoolTotalConnections()).build(); builder.addAttributesBuilder().setKey("Pool Active Connections").setLongValue(getPoolActiveConnections()).build(); builder.addAttributesBuilder().setKey("Pool Idle Connections").setLongValue(getPoolIdleConnections()).build(); - builder.addAttributesBuilder().setKey("Pool Max Active Connections").setLongValue(getPoolMaxActiveConnections()).build(); - builder.addAttributesBuilder().setKey("Pool Max Idle Connections").setLongValue(getPoolMaxIdleConnections()).build(); + builder.addAttributesBuilder().setKey("Pool Max Connections").setLongValue(getPoolMaxConnections()).build(); builder.addAttributesBuilder().setKey("Pool Min Idle Connections").setLongValue(getPoolMinIdleConnections()).build(); builder.addAttributesBuilder().setKey("Pool Max Wait (ms)").setLongValue(getPoolMaxWaitMillis()).build(); - builder.addAttributesBuilder().setKey("Pool Remove Abandoned").setBooleanValue(getPoolRemoveAbandoned()).build(); - builder.addAttributesBuilder().setKey("Pool Remove Abandoned Timeout (sec)").setLongValue(getPoolRemoveAbandonedTimeoutSeconds()).build(); + builder.addAttributesBuilder().setKey("Pool Max Lifetime (ms)").setLongValue(getPoolMaxLifeTimeMillis()).build(); return builder.build(); } } diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java index 35894dd9fdb..cd8e0de1eaa 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java @@ -33,7 +33,6 @@ import org.sonar.process.Jmx; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.ce.monitoring.CeDatabaseMBean.OBJECT_NAME; public class CeDatabaseMBeanImplTest { @@ -45,7 +44,7 @@ public class CeDatabaseMBeanImplTest { @BeforeClass public static void beforeClass() { // if any other class starts a container where CeDatabaseMBeanImpl is added, it will have been registered - Jmx.unregister(OBJECT_NAME); + Jmx.unregister("SonarQube:name=ComputeEngineDatabaseConnection"); } @Test @@ -63,15 +62,15 @@ public class CeDatabaseMBeanImplTest { public void export_system_info() { ProtobufSystemInfo.Section section = underTest.toProtobuf(); assertThat(section.getName()).isEqualTo("Compute Engine Database Connection"); - assertThat(section.getAttributesCount()).isEqualTo(9); - assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Initial Size"); - assertThat(section.getAttributes(0).getLongValue()).isGreaterThanOrEqualTo(0); + assertThat(section.getAttributesCount()).isEqualTo(7); + assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Total Connections"); + assertThat(section.getAttributes(0).getLongValue()).isPositive(); } @CheckForNull private ObjectInstance getMBean() throws Exception { try { - return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(OBJECT_NAME)); + return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName("SonarQube:name=ComputeEngineDatabaseConnection")); } catch (InstanceNotFoundException e) { return null; } diff --git a/server/sonar-db-core/build.gradle b/server/sonar-db-core/build.gradle index 83bd1e9c8c2..c37d4764667 100644 --- a/server/sonar-db-core/build.gradle +++ b/server/sonar-db-core/build.gradle @@ -12,7 +12,7 @@ dependencies { compile 'com.google.guava:guava' compile 'commons-io:commons-io' compile 'commons-lang:commons-lang' - compile 'org.apache.commons:commons-dbcp2' + compile 'com.zaxxer:HikariCP' compile 'org.mybatis:mybatis' compile 'org.slf4j:slf4j-api' compile 'org.sonarsource.api.plugin:sonar-plugin-api' @@ -27,7 +27,9 @@ dependencies { testCompile 'com.oracle.database.jdbc:ojdbc8' testCompile 'com.tngtech.java:junit-dataprovider' testCompile 'org.mockito:mockito-core' + testCompile 'org.mockito:mockito-inline' testCompile 'org.postgresql:postgresql' + testCompile project(':sonar-testing-harness') testRuntime 'com.h2database:h2' diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java b/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java index be9cd2b872c..837b941f445 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java @@ -21,15 +21,15 @@ package org.sonar.db; import ch.qos.logback.classic.Level; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import javax.sql.DataSource; -import org.apache.commons.dbcp2.BasicDataSource; -import org.apache.commons.dbcp2.BasicDataSourceFactory; import org.apache.commons.lang.StringUtils; import org.sonar.api.config.internal.Settings; import org.sonar.api.utils.log.Logger; @@ -43,7 +43,12 @@ import org.sonar.process.logging.LogbackHelper; import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; +import static org.sonar.process.ProcessProperties.Property.JDBC_DRIVER_PATH; +import static org.sonar.process.ProcessProperties.Property.JDBC_EMBEDDED_PORT; +import static org.sonar.process.ProcessProperties.Property.JDBC_MIN_IDLE; +import static org.sonar.process.ProcessProperties.Property.JDBC_PASSWORD; import static org.sonar.process.ProcessProperties.Property.JDBC_URL; +import static org.sonar.process.ProcessProperties.Property.JDBC_USERNAME; /** * @since 2.12 @@ -57,12 +62,25 @@ public class DefaultDatabase implements Database { private static final String SONAR_JDBC_DIALECT = "sonar.jdbc.dialect"; private static final String SONAR_JDBC_DRIVER = "sonar.jdbc.driverClassName"; private static final String SONAR_JDBC_MAX_ACTIVE = "sonar.jdbc.maxActive"; - private static final String DBCP_JDBC_MAX_ACTIVE = "maxTotal"; private static final String SONAR_JDBC_MAX_WAIT = "sonar.jdbc.maxWait"; - private static final String DBCP_JDBC_MAX_WAIT = "maxWaitMillis"; - private static final Map SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS = ImmutableMap.of( - SONAR_JDBC_MAX_ACTIVE, DBCP_JDBC_MAX_ACTIVE, - SONAR_JDBC_MAX_WAIT, DBCP_JDBC_MAX_WAIT); + private static final Set DEPRECATED_SONAR_PROPERTIES = Set.of( + "sonar.jdbc.maxIdle", + "sonar.jdbc.minEvictableIdleTimeMillis", + "sonar.jdbc.timeBetweenEvictionRunsMillis"); + + private static final Set IGNORED_SONAR_PROPERTIES = Set.of(SONAR_JDBC_DIALECT, JDBC_DRIVER_PATH.getKey(), + "sonar.jdbc.maxIdle", + "sonar.jdbc.minEvictableIdleTimeMillis", + "sonar.jdbc.timeBetweenEvictionRunsMillis"); + + private static final Map SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS = Map.of( + JDBC_USERNAME.getKey(), "dataSource.user", + JDBC_PASSWORD.getKey(), "dataSource.password", + JDBC_EMBEDDED_PORT.getKey(), "dataSource.portNumber", + JDBC_URL.getKey(), "jdbcUrl", + SONAR_JDBC_MAX_WAIT, "connectionTimeout", + SONAR_JDBC_MAX_ACTIVE, "maximumPoolSize", + JDBC_MIN_IDLE.getKey(), "minimumIdle"); private final LogbackHelper logbackHelper; private final Settings settings; @@ -99,16 +117,22 @@ public class DefaultDatabase implements Database { properties.setProperty(SONAR_JDBC_DRIVER, dialect.getDefaultDriverClassName()); } - private void initDataSource() throws Exception { - // but it's correctly caught by start() - LOG.info("Create JDBC data source for {}", properties.getProperty(JDBC_URL.getKey()), DEFAULT_URL); - BasicDataSource basicDataSource = BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties)); - datasource = new ProfiledDataSource(basicDataSource, NullConnectionInterceptor.INSTANCE); - datasource.setConnectionInitSqls(dialect.getConnectionInitStatements()); - datasource.setValidationQuery(dialect.getValidationQuery()); + private void initDataSource() { + LOG.info("Create JDBC data source for {}", properties.getProperty(JDBC_URL.getKey(), DEFAULT_URL)); + HikariDataSource ds = createHikariDataSource(); + datasource = new ProfiledDataSource(ds, NullConnectionInterceptor.INSTANCE); enableSqlLogging(datasource, logbackHelper.getLoggerLevel("sql") == Level.TRACE); } + private HikariDataSource createHikariDataSource() { + HikariConfig config = new HikariConfig(extractCommonsHikariProperties(properties)); + if (!dialect.getConnectionInitStatements().isEmpty()) { + config.setConnectionInitSql(dialect.getConnectionInitStatements().get(0)); + } + config.setConnectionTestQuery(dialect.getValidationQuery()); + return new HikariDataSource(config); + } + private void checkConnection() { Connection connection = null; try { @@ -124,11 +148,7 @@ public class DefaultDatabase implements Database { @Override public void stop() { if (datasource != null) { - try { - datasource.close(); - } catch (SQLException e) { - throw new IllegalStateException("Fail to stop JDBC connection pool", e); - } + datasource.close(); } } @@ -171,17 +191,22 @@ public class DefaultDatabase implements Database { } @VisibleForTesting - static Properties extractCommonsDbcpProperties(Properties properties) { + static Properties extractCommonsHikariProperties(Properties properties) { Properties result = new Properties(); - result.setProperty("accessToUnderlyingConnectionAllowed", "true"); for (Map.Entry entry : properties.entrySet()) { String key = (String) entry.getKey(); + if (IGNORED_SONAR_PROPERTIES.contains(key)) { + if (DEPRECATED_SONAR_PROPERTIES.contains(key)) { + LOG.warn("Property [{}] has no effect as pool connection implementation changed, check 9.7 upgrade notes.", key); + } + continue; + } if (StringUtils.startsWith(key, SONAR_JDBC)) { - String resolvedKey = toDbcpPropertyKey(key); + String resolvedKey = toHikariPropertyKey(key); String existingValue = (String) result.setProperty(resolvedKey, (String) entry.getValue()); checkState(existingValue == null || existingValue.equals(entry.getValue()), "Duplicate property declaration for resolved jdbc key '%s': conflicting values are '%s' and '%s'", resolvedKey, existingValue, entry.getValue()); - result.setProperty(toDbcpPropertyKey(key), (String) entry.getValue()); + result.setProperty(resolvedKey, (String) entry.getValue()); } } return result; @@ -193,9 +218,9 @@ public class DefaultDatabase implements Database { } } - private static String toDbcpPropertyKey(String key) { - if (SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS.containsKey(key)) { - return SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS.get(key); + private static String toHikariPropertyKey(String key) { + if (SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS.containsKey(key)) { + return SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS.get(key); } return StringUtils.removeStart(key, SONAR_JDBC); diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java index 1a856ca23ce..650405c19c5 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java @@ -21,12 +21,12 @@ package org.sonar.db.profiling; import java.sql.Connection; import java.sql.SQLException; -import org.apache.commons.dbcp2.BasicDataSource; +import javax.sql.DataSource; public interface ConnectionInterceptor { - Connection getConnection(BasicDataSource dataSource) throws SQLException; + Connection getConnection(DataSource dataSource) throws SQLException; - Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException; + Connection getConnection(DataSource dataSource, String login, String password) throws SQLException; } diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java index 9e069ccb408..c8d4d3b2915 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java @@ -21,18 +21,18 @@ package org.sonar.db.profiling; import java.sql.Connection; import java.sql.SQLException; -import org.apache.commons.dbcp2.BasicDataSource; +import javax.sql.DataSource; public enum NullConnectionInterceptor implements ConnectionInterceptor { INSTANCE; @Override - public Connection getConnection(BasicDataSource dataSource) throws SQLException { + public Connection getConnection(DataSource dataSource) throws SQLException { return dataSource.getConnection(); } @Override - public Connection getConnection(BasicDataSource dataSource, String user, String password) throws SQLException { + public Connection getConnection(DataSource dataSource, String user, String password) throws SQLException { return dataSource.getConnection(user, password); } } diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java index 43f79c54c68..b2c384f3572 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java @@ -22,18 +22,18 @@ package org.sonar.db.profiling; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; -import org.apache.commons.dbcp2.BasicDataSource; +import javax.sql.DataSource; public enum ProfiledConnectionInterceptor implements ConnectionInterceptor { INSTANCE; @Override - public Connection getConnection(BasicDataSource dataSource) throws SQLException { + public Connection getConnection(DataSource dataSource) throws SQLException { return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection())); } @Override - public Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException { + public Connection getConnection(DataSource dataSource, String login, String password) throws SQLException { return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection(login, password))); } diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java index 579a4dc2bea..410aecb0e7a 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java @@ -19,32 +19,37 @@ */ package org.sonar.db.profiling; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariConfigMXBean; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; +import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import java.io.PrintWriter; import java.sql.Connection; -import java.sql.Driver; +import java.sql.ConnectionBuilder; import java.sql.SQLException; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import javax.management.MBeanServer; -import javax.management.ObjectName; -import org.apache.commons.dbcp2.BasicDataSource; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.ShardingKeyBuilder; +import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import javax.sql.DataSource; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -public class ProfiledDataSource extends BasicDataSource { +public class ProfiledDataSource extends HikariDataSource { static final Logger SQL_LOGGER = Loggers.get("sql"); - private final BasicDataSource delegate; + private final HikariDataSource delegate; private ConnectionInterceptor connectionInterceptor; - public ProfiledDataSource(BasicDataSource delegate, ConnectionInterceptor connectionInterceptor) { + public ProfiledDataSource(HikariDataSource delegate, ConnectionInterceptor connectionInterceptor) { this.delegate = delegate; this.connectionInterceptor = connectionInterceptor; } - public BasicDataSource getDelegate() { + public HikariDataSource getDelegate() { return delegate; } @@ -53,33 +58,33 @@ public class ProfiledDataSource extends BasicDataSource { } @Override - public Boolean getDefaultAutoCommit() { - return delegate.getDefaultAutoCommit(); + public boolean isAutoCommit() { + return delegate.isAutoCommit(); } @Override - public Boolean getDefaultReadOnly() { - return delegate.getDefaultReadOnly(); + public boolean isReadOnly() { + return delegate.isReadOnly(); } @Override - public int getDefaultTransactionIsolation() { - return delegate.getDefaultTransactionIsolation(); + public String getTransactionIsolation() { + return delegate.getTransactionIsolation(); } @Override - public void setDefaultTransactionIsolation(int defaultTransactionIsolation) { - delegate.setDefaultTransactionIsolation(defaultTransactionIsolation); + public void setTransactionIsolation(String defaultTransactionIsolation) { + delegate.setTransactionIsolation(defaultTransactionIsolation); } @Override - public String getDefaultCatalog() { - return delegate.getDefaultCatalog(); + public String getCatalog() { + return delegate.getCatalog(); } @Override - public void setDefaultCatalog(String defaultCatalog) { - delegate.setDefaultCatalog(defaultCatalog); + public void setCatalog(String defaultCatalog) { + delegate.setCatalog(defaultCatalog); } @Override @@ -93,153 +98,158 @@ public class ProfiledDataSource extends BasicDataSource { } @Override - public synchronized ClassLoader getDriverClassLoader() { - return delegate.getDriverClassLoader(); + public int getMaximumPoolSize() { + return delegate.getMaximumPoolSize(); } @Override - public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) { - delegate.setDriverClassLoader(driverClassLoader); + public void setMaximumPoolSize(int maxActive) { + delegate.setMaximumPoolSize(maxActive); } @Override - public synchronized int getMaxTotal() { - return delegate.getMaxTotal(); + public Connection getConnection() throws SQLException { + return connectionInterceptor.getConnection(delegate); } @Override - public synchronized void setMaxTotal(int maxActive) { - delegate.setMaxTotal(maxActive); + public Connection getConnection(String login, String password) throws SQLException { + return connectionInterceptor.getConnection(this, login, password); } @Override - public synchronized int getMaxIdle() { - return delegate.getMaxIdle(); + public PrintWriter getLogWriter() throws SQLException { + return delegate.getLogWriter(); } @Override - public synchronized void setMaxIdle(int maxIdle) { - delegate.setMaxIdle(maxIdle); + public void setLogWriter(PrintWriter out) throws SQLException { + delegate.setLogWriter(out); } @Override - public synchronized int getMinIdle() { - return delegate.getMinIdle(); + public void setLoginTimeout(int seconds) throws SQLException { + delegate.setLoginTimeout(seconds); } @Override - public synchronized void setMinIdle(int minIdle) { - delegate.setMinIdle(minIdle); + public int getLoginTimeout() throws SQLException { + return delegate.getLoginTimeout(); } @Override - public synchronized int getInitialSize() { - return delegate.getInitialSize(); + public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { + return delegate.getParentLogger(); } @Override - public synchronized void setInitialSize(int initialSize) { - delegate.setInitialSize(initialSize); + public T unwrap(Class iface) throws SQLException { + return delegate.unwrap(iface); } @Override - public synchronized long getMaxWaitMillis() { - return delegate.getMaxWaitMillis(); + public boolean isWrapperFor(Class iface) throws SQLException { + return delegate.isWrapperFor(iface); } @Override - public synchronized void setMaxWaitMillis(long maxWait) { - delegate.setMaxWaitMillis(maxWait); + public void setMetricRegistry(Object metricRegistry) { + delegate.setMetricRegistry(metricRegistry); } @Override - public synchronized boolean isPoolPreparedStatements() { - return delegate.isPoolPreparedStatements(); + public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) { + delegate.setMetricsTrackerFactory(metricsTrackerFactory); } @Override - public synchronized void setPoolPreparedStatements(boolean poolingStatements) { - delegate.setPoolPreparedStatements(poolingStatements); + public void setHealthCheckRegistry(Object healthCheckRegistry) { + delegate.setHealthCheckRegistry(healthCheckRegistry); } @Override - public synchronized int getMaxOpenPreparedStatements() { - return delegate.getMaxOpenPreparedStatements(); + public boolean isRunning() { + return delegate.isRunning(); } @Override - public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) { - delegate.setMaxOpenPreparedStatements(maxOpenStatements); + public HikariPoolMXBean getHikariPoolMXBean() { + return delegate.getHikariPoolMXBean(); } @Override - public synchronized boolean getTestOnBorrow() { - return delegate.getTestOnBorrow(); + public HikariConfigMXBean getHikariConfigMXBean() { + return delegate.getHikariConfigMXBean(); } @Override - public synchronized void setTestOnBorrow(boolean testOnBorrow) { - delegate.setTestOnBorrow(testOnBorrow); + public void evictConnection(Connection connection) { + delegate.evictConnection(connection); } @Override - public synchronized boolean getTestOnReturn() { - return delegate.getTestOnReturn(); + public void close() { + delegate.close(); } @Override - public synchronized void setTestOnReturn(boolean testOnReturn) { - delegate.setTestOnReturn(testOnReturn); + public boolean isClosed() { + return delegate.isClosed(); } @Override - public synchronized long getTimeBetweenEvictionRunsMillis() { - return delegate.getTimeBetweenEvictionRunsMillis(); + public String toString() { + return delegate.toString(); } @Override - public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { - delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + public long getConnectionTimeout() { + return delegate.getConnectionTimeout(); } @Override - public synchronized int getNumTestsPerEvictionRun() { - return delegate.getNumTestsPerEvictionRun(); + public void setConnectionTimeout(long connectionTimeoutMs) { + delegate.setConnectionTimeout(connectionTimeoutMs); } @Override - public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { - delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + public long getIdleTimeout() { + return delegate.getIdleTimeout(); } @Override - public synchronized long getMinEvictableIdleTimeMillis() { - return delegate.getMinEvictableIdleTimeMillis(); + public void setIdleTimeout(long idleTimeoutMs) { + delegate.setIdleTimeout(idleTimeoutMs); } @Override - public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { - delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + public long getLeakDetectionThreshold() { + return delegate.getLeakDetectionThreshold(); } @Override - public synchronized boolean getTestWhileIdle() { - return delegate.getTestWhileIdle(); + public void setLeakDetectionThreshold(long leakDetectionThresholdMs) { + delegate.setLeakDetectionThreshold(leakDetectionThresholdMs); } @Override - public synchronized void setTestWhileIdle(boolean testWhileIdle) { - delegate.setTestWhileIdle(testWhileIdle); + public long getMaxLifetime() { + return delegate.getMaxLifetime(); } @Override - public synchronized int getNumActive() { - return delegate.getNumActive(); + public void setMaxLifetime(long maxLifetimeMs) { + delegate.setMaxLifetime(maxLifetimeMs); } @Override - public synchronized int getNumIdle() { - return delegate.getNumIdle(); + public int getMinimumIdle() { + return delegate.getMinimumIdle(); + } + + @Override + public void setMinimumIdle(int minIdle) { + delegate.setMinimumIdle(minIdle); } @Override @@ -252,16 +262,6 @@ public class ProfiledDataSource extends BasicDataSource { delegate.setPassword(password); } - @Override - public synchronized String getUrl() { - return delegate.getUrl(); - } - - @Override - public synchronized void setUrl(String url) { - delegate.setUrl(url); - } - @Override public String getUsername() { return delegate.getUsername(); @@ -273,368 +273,249 @@ public class ProfiledDataSource extends BasicDataSource { } @Override - public String getValidationQuery() { - return delegate.getValidationQuery(); - } - - @Override - public void setValidationQuery(String validationQuery) { - delegate.setValidationQuery(validationQuery); - } - - @Override - public int getValidationQueryTimeout() { - return delegate.getValidationQueryTimeout(); - } - - @Override - public void setValidationQueryTimeout(int timeout) { - delegate.setValidationQueryTimeout(timeout); - } - - @Override - public List getConnectionInitSqls() { - return delegate.getConnectionInitSqls(); - } - - @Override - public void setConnectionInitSqls(Collection connectionInitSqls) { - delegate.setConnectionInitSqls(connectionInitSqls); - } - - @Override - public synchronized boolean isAccessToUnderlyingConnectionAllowed() { - return delegate.isAccessToUnderlyingConnectionAllowed(); - } - - @Override - public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) { - delegate.setAccessToUnderlyingConnectionAllowed(allow); - } - - @Override - public Connection getConnection() throws SQLException { - return connectionInterceptor.getConnection(delegate); - } - - @Override - public Connection getConnection(String login, String password) throws SQLException { - return connectionInterceptor.getConnection(this, login, password); - } - - @Override - public int getLoginTimeout() throws SQLException { - return delegate.getLoginTimeout(); - } - - @Override - public java.util.logging.Logger getParentLogger() { - return java.util.logging.Logger.getLogger(getClass().getName()); - } - - @Override - public PrintWriter getLogWriter() throws SQLException { - return delegate.getLogWriter(); - } - - @Override - public void setLoginTimeout(int loginTimeout) throws SQLException { - delegate.setLoginTimeout(loginTimeout); - } - - @Override - public void setLogWriter(PrintWriter logWriter) throws SQLException { - delegate.setLogWriter(logWriter); - } - - @Override - public boolean getRemoveAbandonedOnBorrow() { - return delegate.getRemoveAbandonedOnBorrow(); - } - - @Override - public void setRemoveAbandonedOnBorrow(boolean removeAbandoned) { - delegate.setRemoveAbandonedOnBorrow(removeAbandoned); - } - - - @Override - public boolean getRemoveAbandonedOnMaintenance() { - return delegate.getRemoveAbandonedOnMaintenance(); + public long getValidationTimeout() { + return delegate.getValidationTimeout(); } @Override - public void setRemoveAbandonedOnMaintenance(boolean removeAbandoned) { - delegate.setRemoveAbandonedOnMaintenance(removeAbandoned); + public void setValidationTimeout(long validationTimeoutMs) { + delegate.setValidationTimeout(validationTimeoutMs); } @Override - public int getRemoveAbandonedTimeout() { - return delegate.getRemoveAbandonedTimeout(); + public String getConnectionTestQuery() { + return delegate.getConnectionTestQuery(); } @Override - public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) { - delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout); + public void setConnectionTestQuery(String connectionTestQuery) { + delegate.setConnectionTestQuery(connectionTestQuery); } @Override - public boolean getLogAbandoned() { - return delegate.getLogAbandoned(); + public String getConnectionInitSql() { + return delegate.getConnectionInitSql(); } @Override - public void setLogAbandoned(boolean logAbandoned) { - delegate.setLogAbandoned(logAbandoned); + public void setConnectionInitSql(String connectionInitSql) { + delegate.setConnectionInitSql(connectionInitSql); } @Override - public void addConnectionProperty(String name, String value) { - delegate.addConnectionProperty(name, value); + public DataSource getDataSource() { + return delegate.getDataSource(); } @Override - public void removeConnectionProperty(String name) { - delegate.removeConnectionProperty(name); + public void setDataSource(DataSource dataSource) { + delegate.setDataSource(dataSource); } @Override - public void setConnectionProperties(String connectionProperties) { - delegate.setConnectionProperties(connectionProperties); + public String getDataSourceClassName() { + return delegate.getDataSourceClassName(); } @Override - public synchronized void close() throws SQLException { - delegate.close(); - } - - @Override - public synchronized boolean isClosed() { - return delegate.isClosed(); + public void setDataSourceClassName(String className) { + delegate.setDataSourceClassName(className); } @Override - public boolean isWrapperFor(Class iface) throws SQLException { - return delegate.isWrapperFor(iface); + public void addDataSourceProperty(String propertyName, Object value) { + delegate.addDataSourceProperty(propertyName, value); } @Override - public T unwrap(Class iface) throws SQLException { - return delegate.unwrap(iface); + public String getDataSourceJNDI() { + return delegate.getDataSourceJNDI(); } @Override - public void setDefaultAutoCommit(Boolean defaultAutoCommit) { - delegate.setDefaultAutoCommit(defaultAutoCommit); + public void setDataSourceJNDI(String jndiDataSource) { + delegate.setDataSourceJNDI(jndiDataSource); } @Override - public void setDefaultReadOnly(Boolean defaultReadOnly) { - delegate.setDefaultReadOnly(defaultReadOnly); + public Properties getDataSourceProperties() { + return delegate.getDataSourceProperties(); } @Override - public Integer getDefaultQueryTimeout() { - return delegate.getDefaultQueryTimeout(); + public void setDataSourceProperties(Properties dsProperties) { + delegate.setDataSourceProperties(dsProperties); } @Override - public void setDefaultQueryTimeout(Integer defaultQueryTimeoutSeconds) { - delegate.setDefaultQueryTimeout(defaultQueryTimeoutSeconds); + public String getJdbcUrl() { + return delegate.getJdbcUrl(); } @Override - public String getDefaultSchema() { - return delegate.getDefaultSchema(); + public void setJdbcUrl(String jdbcUrl) { + delegate.setJdbcUrl(jdbcUrl); } @Override - public void setDefaultSchema(String defaultSchema) { - delegate.setDefaultSchema(defaultSchema); + public void setAutoCommit(boolean isAutoCommit) { + delegate.setAutoCommit(isAutoCommit); } @Override - public boolean getCacheState() { - return delegate.getCacheState(); + public boolean isAllowPoolSuspension() { + return delegate.isAllowPoolSuspension(); } @Override - public void setCacheState(boolean cacheState) { - delegate.setCacheState(cacheState); + public void setAllowPoolSuspension(boolean isAllowPoolSuspension) { + delegate.setAllowPoolSuspension(isAllowPoolSuspension); } @Override - public synchronized Driver getDriver() { - return delegate.getDriver(); + public long getInitializationFailTimeout() { + return delegate.getInitializationFailTimeout(); } @Override - public synchronized void setDriver(Driver driver) { - delegate.setDriver(driver); + public void setInitializationFailTimeout(long initializationFailTimeout) { + delegate.setInitializationFailTimeout(initializationFailTimeout); } @Override - public synchronized boolean getLifo() { - return delegate.getLifo(); + public boolean isIsolateInternalQueries() { + return delegate.isIsolateInternalQueries(); } @Override - public synchronized void setLifo(boolean lifo) { - delegate.setLifo(lifo); + public void setIsolateInternalQueries(boolean isolate) { + delegate.setIsolateInternalQueries(isolate); } @Override - public synchronized boolean getTestOnCreate() { - return delegate.getTestOnCreate(); + public MetricsTrackerFactory getMetricsTrackerFactory() { + return delegate.getMetricsTrackerFactory(); } @Override - public synchronized void setTestOnCreate(boolean testOnCreate) { - delegate.setTestOnCreate(testOnCreate); + public Object getMetricRegistry() { + return delegate.getMetricRegistry(); } @Override - public synchronized void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) { - delegate.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis); + public Object getHealthCheckRegistry() { + return delegate.getHealthCheckRegistry(); } @Override - public synchronized long getSoftMinEvictableIdleTimeMillis() { - return delegate.getSoftMinEvictableIdleTimeMillis(); + public Properties getHealthCheckProperties() { + return delegate.getHealthCheckProperties(); } @Override - public synchronized String getEvictionPolicyClassName() { - return delegate.getEvictionPolicyClassName(); + public void setHealthCheckProperties(Properties healthCheckProperties) { + delegate.setHealthCheckProperties(healthCheckProperties); } @Override - public synchronized void setEvictionPolicyClassName(String evictionPolicyClassName) { - delegate.setEvictionPolicyClassName(evictionPolicyClassName); + public void addHealthCheckProperty(String key, String value) { + delegate.addHealthCheckProperty(key, value); } @Override - public String[] getConnectionInitSqlsAsArray() { - return delegate.getConnectionInitSqlsAsArray(); + public long getKeepaliveTime() { + return delegate.getKeepaliveTime(); } @Override - public long getMaxConnLifetimeMillis() { - return delegate.getMaxConnLifetimeMillis(); + public void setKeepaliveTime(long keepaliveTimeMs) { + delegate.setKeepaliveTime(keepaliveTimeMs); } @Override - public boolean getLogExpiredConnections() { - return delegate.getLogExpiredConnections(); + public void setReadOnly(boolean readOnly) { + delegate.setReadOnly(readOnly); } @Override - public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { - delegate.setMaxConnLifetimeMillis(maxConnLifetimeMillis); + public boolean isRegisterMbeans() { + return delegate.isRegisterMbeans(); } @Override - public void setLogExpiredConnections(boolean logExpiredConnections) { - delegate.setLogExpiredConnections(logExpiredConnections); + public void setRegisterMbeans(boolean register) { + delegate.setRegisterMbeans(register); } @Override - public String getJmxName() { - return delegate.getJmxName(); + public String getPoolName() { + return delegate.getPoolName(); } @Override - public void setJmxName(String jmxName) { - delegate.setJmxName(jmxName); + public void setPoolName(String poolName) { + delegate.setPoolName(poolName); } @Override - public boolean getEnableAutoCommitOnReturn() { - return delegate.getEnableAutoCommitOnReturn(); + public ScheduledExecutorService getScheduledExecutor() { + return delegate.getScheduledExecutor(); } @Override - public void setEnableAutoCommitOnReturn(boolean enableAutoCommitOnReturn) { - delegate.setEnableAutoCommitOnReturn(enableAutoCommitOnReturn); + public void setScheduledExecutor(ScheduledExecutorService executor) { + delegate.setScheduledExecutor(executor); } @Override - public boolean getRollbackOnReturn() { - return delegate.getRollbackOnReturn(); + public String getSchema() { + return delegate.getSchema(); } @Override - public void setRollbackOnReturn(boolean rollbackOnReturn) { - delegate.setRollbackOnReturn(rollbackOnReturn); + public void setSchema(String schema) { + delegate.setSchema(schema); } @Override - public Set getDisconnectionSqlCodes() { - return delegate.getDisconnectionSqlCodes(); + public String getExceptionOverrideClassName() { + return delegate.getExceptionOverrideClassName(); } @Override - public String[] getDisconnectionSqlCodesAsArray() { - return delegate.getDisconnectionSqlCodesAsArray(); + public void setExceptionOverrideClassName(String exceptionOverrideClassName) { + delegate.setExceptionOverrideClassName(exceptionOverrideClassName); } @Override - public void setDisconnectionSqlCodes(Collection disconnectionSqlCodes) { - delegate.setDisconnectionSqlCodes(disconnectionSqlCodes); + public ThreadFactory getThreadFactory() { + return delegate.getThreadFactory(); } @Override - public boolean getFastFailValidation() { - return delegate.getFastFailValidation(); + public void setThreadFactory(ThreadFactory threadFactory) { + delegate.setThreadFactory(threadFactory); } @Override - public void setFastFailValidation(boolean fastFailValidation) { - delegate.setFastFailValidation(fastFailValidation); + public void copyStateTo(HikariConfig other) { + delegate.copyStateTo(other); } @Override - public PrintWriter getAbandonedLogWriter() { - return delegate.getAbandonedLogWriter(); + public void validate() { + delegate.validate(); } @Override - public void setAbandonedLogWriter(PrintWriter logWriter) { - delegate.setAbandonedLogWriter(logWriter); + public ConnectionBuilder createConnectionBuilder() throws SQLException { + return delegate.createConnectionBuilder(); } @Override - public boolean getAbandonedUsageTracking() { - return delegate.getAbandonedUsageTracking(); + public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException { + return delegate.createShardingKeyBuilder(); } - @Override - public void setAbandonedUsageTracking(boolean usageTracking) { - delegate.setAbandonedUsageTracking(usageTracking); - } - @Override - public void invalidateConnection(Connection connection) { - delegate.invalidateConnection(connection); - } - - @Override - public ObjectName preRegister(MBeanServer server, ObjectName objectName) { - return delegate.preRegister(server, objectName); - } - - @Override - public void postRegister(Boolean registrationDone) { - delegate.postRegister(registrationDone); - } - - @Override - public void preDeregister() throws Exception { - delegate.preDeregister(); - } - - @Override - public void postDeregister() { - delegate.postDeregister(); - } } diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java index 67d05f4e1e8..07488e08e90 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java @@ -22,11 +22,13 @@ package org.sonar.db; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import com.zaxxer.hikari.HikariDataSource; import java.util.Properties; -import org.apache.commons.dbcp2.BasicDataSource; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.log.LogTester; import org.sonar.db.dialect.PostgreSql; import org.sonar.process.logging.LogbackHelper; @@ -38,9 +40,11 @@ import static org.mockito.Mockito.mock; @RunWith(DataProviderRunner.class) public class DefaultDatabaseTest { - private LogbackHelper logbackHelper = mock(LogbackHelper.class); + private final LogbackHelper logbackHelper = mock(LogbackHelper.class); private static final String SONAR_JDBC = "sonar.jdbc."; + @Rule + public final LogTester logTester = new LogTester(); @Test public void shouldLoadDefaultValues() { @@ -54,19 +58,36 @@ public class DefaultDatabaseTest { } @Test - public void shouldExtractCommonsDbcpProperties() { + public void shouldExtractHikariProperties() { Properties props = new Properties(); props.setProperty("sonar.jdbc.driverClassName", "my.Driver"); props.setProperty("sonar.jdbc.username", "me"); - props.setProperty("sonar.jdbc.maxActive", "5"); - props.setProperty("sonar.jdbc.maxWait", "5000"); + props.setProperty("sonar.jdbc.maximumPoolSize", "5"); + props.setProperty("sonar.jdbc.connectionTimeout", "5000"); - Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props); + Properties hikariProps = DefaultDatabase.extractCommonsHikariProperties(props); - assertThat(commonsDbcpProps.getProperty("username")).isEqualTo("me"); - assertThat(commonsDbcpProps.getProperty("driverClassName")).isEqualTo("my.Driver"); - assertThat(commonsDbcpProps.getProperty("maxTotal")).isEqualTo("5"); - assertThat(commonsDbcpProps.getProperty("maxWaitMillis")).isEqualTo("5000"); + assertThat(hikariProps.getProperty("dataSource.user")).isEqualTo("me"); + assertThat(hikariProps.getProperty("driverClassName")).isEqualTo("my.Driver"); + assertThat(hikariProps.getProperty("maximumPoolSize")).isEqualTo("5"); + assertThat(hikariProps.getProperty("connectionTimeout")).isEqualTo("5000"); + } + + @Test + public void logWarningIfDeprecatedPropertyUsed() { + Properties props = new Properties(); + + props.setProperty("sonar.jdbc.maxIdle", "5"); + props.setProperty("sonar.jdbc.minEvictableIdleTimeMillis", "300000"); + props.setProperty("sonar.jdbc.timeBetweenEvictionRunsMillis", "1000"); + props.setProperty("sonar.jdbc.connectionTimeout", "8000"); + + DefaultDatabase.extractCommonsHikariProperties(props); + + assertThat(logTester.logs()) + .contains("Property [sonar.jdbc.maxIdle] has no effect as pool connection implementation changed, check 9.7 upgrade notes.") + .contains("Property [sonar.jdbc.minEvictableIdleTimeMillis] has no effect as pool connection implementation changed, check 9.7 upgrade notes.") + .contains("Property [sonar.jdbc.timeBetweenEvictionRunsMillis] has no effect as pool connection implementation changed, check 9.7 upgrade notes."); } @Test @@ -76,21 +97,21 @@ public class DefaultDatabaseTest { props.setProperty(jdbcProperty, "100"); props.setProperty(dbcpProperty, "100"); - Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props); + Properties commonsDbcpProps = DefaultDatabase.extractCommonsHikariProperties(props); assertThat(commonsDbcpProps.getProperty(removeStart(dbcpProperty, SONAR_JDBC))).isEqualTo("100"); } @Test @UseDataProvider("sonarJdbcAndDbcpProperties") - public void shouldThrowISEIfDuplicatedResolvedPropertiesWithDifferentValue(String jdbcProperty, String dbcpProperty) { + public void shouldThrowISEIfDuplicatedResolvedPropertiesWithDifferentValue(String jdbcProperty, String hikariProperty) { Properties props = new Properties(); props.setProperty(jdbcProperty, "100"); - props.setProperty(dbcpProperty, "200"); + props.setProperty(hikariProperty, "200"); - assertThatThrownBy(() -> DefaultDatabase.extractCommonsDbcpProperties(props)) + assertThatThrownBy(() -> DefaultDatabase.extractCommonsHikariProperties(props)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining(String.format("Duplicate property declaration for resolved jdbc key '%s': conflicting values are", removeStart(dbcpProperty, SONAR_JDBC))); + .hasMessageContaining(String.format("Duplicate property declaration for resolved jdbc key '%s': conflicting values are", removeStart(hikariProperty, SONAR_JDBC))); } @Test @@ -117,14 +138,14 @@ public class DefaultDatabaseTest { settings.setProperty("sonar.jdbc.driverClassName", "org.h2.Driver"); settings.setProperty("sonar.jdbc.username", "sonar"); settings.setProperty("sonar.jdbc.password", "sonar"); - settings.setProperty("sonar.jdbc.maxActive", "1"); + settings.setProperty("sonar.jdbc.maximumPoolSize", "1"); DefaultDatabase db = new DefaultDatabase(logbackHelper, settings); db.start(); db.stop(); assertThat(db.getDialect().getId()).isEqualTo("h2"); - assertThat(((BasicDataSource) db.getDataSource()).getMaxTotal()).isOne(); + assertThat(((HikariDataSource) db.getDataSource()).getMaximumPoolSize()).isOne(); } @Test @@ -152,8 +173,8 @@ public class DefaultDatabaseTest { @DataProvider public static Object[][] sonarJdbcAndDbcpProperties() { return new Object[][] { - {"sonar.jdbc.maxActive", "sonar.jdbc.maxTotal"}, - {"sonar.jdbc.maxWait", "sonar.jdbc.maxWaitMillis"} + {"sonar.jdbc.maxWait", "sonar.jdbc.connectionTimeout"}, + {"sonar.jdbc.maxActive", "sonar.jdbc.maximumPoolSize"} }; } } diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java index 80f897169a1..4d657ace44c 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.sonar.test.TestUtils; @@ -51,16 +52,6 @@ public class InvocationUtilsTest { Assert.assertThrows(SQLException.class, () -> InvocationUtils.invokeQuietly(target, prepareStatement, new Object[] {failSql})); } - @Test - public void should_wrap_undeclared_exception() throws Throwable { - Connection target = mock(Connection.class); - String failSql = "any sql"; - when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected")); - Method wait = Object.class.getMethod("wait"); - - Assert.assertThrows(IllegalStateException.class, () -> InvocationUtils.invokeQuietly(target, wait, new Object[0])); - } - @Test public void only_static_methods() { assertThat(TestUtils.hasOnlyPrivateConstructors(InvocationUtils.class)).isTrue(); diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java index cd1db27b42c..5a6a09a5d27 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java @@ -19,6 +19,8 @@ */ package org.sonar.db.profiling; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import java.io.ByteArrayInputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -27,7 +29,9 @@ import java.sql.Date; import java.sql.PreparedStatement; import java.sql.Statement; import java.sql.Timestamp; -import org.apache.commons.dbcp2.BasicDataSource; +import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.log.LogTester; @@ -36,6 +40,7 @@ import org.sonar.api.utils.log.LoggerLevel; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class ProfiledDataSourceTest { @@ -43,7 +48,7 @@ public class ProfiledDataSourceTest { @Rule public LogTester logTester = new LogTester(); - BasicDataSource originDataSource = mock(BasicDataSource.class); + HikariDataSource originDataSource = mock(HikariDataSource.class); @Test public void execute_and_log_statement() throws Exception { @@ -59,7 +64,7 @@ public class ProfiledDataSourceTest { ProfiledDataSource underTest = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); - assertThat(underTest.getUrl()).isNull(); + assertThat(underTest.getJdbcUrl()).isNull(); assertThat(underTest.getConnection().getClientInfo()).isNull(); final Statement statementProxy = underTest.getConnection().createStatement(); assertThat(statementProxy.getConnection()).isNull(); @@ -90,7 +95,7 @@ public class ProfiledDataSourceTest { ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); - assertThat(ds.getUrl()).isNull(); + assertThat(ds.getJdbcUrl()).isNull(); assertThat(ds.getConnection().getClientInfo()).isNull(); PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); preparedStatementProxy.setInt(1, param1); @@ -121,7 +126,7 @@ public class ProfiledDataSourceTest { ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); - assertThat(ds.getUrl()).isNull(); + assertThat(ds.getJdbcUrl()).isNull(); assertThat(ds.getConnection().getClientInfo()).isNull(); PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); assertThat(preparedStatementProxy.getConnection()).isNull(); @@ -144,7 +149,38 @@ public class ProfiledDataSourceTest { for (Method method : ProfiledDataSource.class.getDeclaredMethods()) { if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers())) { method.invoke(proxy); + } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(String.class) && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy, "test"); + } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Boolean.TYPE) && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy, true); + } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Long.TYPE) && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy, 1L); + } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Integer.TYPE) && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy, 1); } } + + proxy.addHealthCheckProperty("test", "test"); + verify(originDataSource).addHealthCheckProperty("test", "test"); + + var schedulerMock = mock(ScheduledExecutorService.class); + proxy.setScheduledExecutor(schedulerMock); + verify(originDataSource).setScheduledExecutor(schedulerMock); + + var hikariConfigMock = mock(HikariConfig.class); + proxy.copyStateTo(hikariConfigMock); + verify(originDataSource).copyStateTo(hikariConfigMock); + + var threadFactoryMock = mock(ThreadFactory.class); + proxy.setThreadFactory(threadFactoryMock); + verify(originDataSource).setThreadFactory(threadFactoryMock); + + var properties = new Properties(); + proxy.setHealthCheckProperties(properties); + verify(originDataSource).setHealthCheckProperties(properties); + + proxy.setDataSourceProperties(properties); + verify(originDataSource).setDataSourceProperties(properties); + } } diff --git a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/CoreH2Database.java b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/CoreH2Database.java index 3a46a6b56e2..926b4e34583 100644 --- a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/CoreH2Database.java +++ b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/CoreH2Database.java @@ -19,11 +19,11 @@ */ package org.sonar.db; +import com.zaxxer.hikari.HikariDataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; -import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.io.output.NullWriter; import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; @@ -38,7 +38,7 @@ import static java.lang.String.format; public class CoreH2Database implements Database { private static final String IGNORED_KEYWORDS_OPTION = ";NON_KEYWORDS=VALUE"; private final String name; - private BasicDataSource datasource; + private HikariDataSource datasource; /** * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest} @@ -54,11 +54,11 @@ public class CoreH2Database implements Database { private void startDatabase() { try { - datasource = new BasicDataSource(); + datasource = new HikariDataSource(); datasource.setDriverClassName("org.h2.Driver"); datasource.setUsername("sonar"); datasource.setPassword("sonar"); - datasource.setUrl("jdbc:h2:mem:" + name); + datasource.setJdbcUrl("jdbc:h2:mem:" + name); } catch (Exception e) { throw new IllegalStateException("Fail to start H2", e); } @@ -93,11 +93,7 @@ public class CoreH2Database implements Database { @Override public void stop() { - try { - datasource.close(); - } catch (SQLException e) { - // Ignore error - } + datasource.close(); } public DataSource getDataSource() { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java new file mode 100644 index 00000000000..77012a08823 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; +import java.util.function.Function; +import org.sonar.process.systeminfo.BaseSectionMBean; + +import static java.util.Optional.ofNullable; + +public abstract class DatabaseMBean extends BaseSectionMBean { + + private final DbClient dbClient; + + protected DatabaseMBean(DbClient dbClient) { + this.dbClient = dbClient; + } + + public int getPoolActiveConnections() { + return getIntPropertyOrZero(HikariPoolMXBean::getActiveConnections); + } + + public int getPoolTotalConnections() { + return getIntPropertyOrZero(HikariPoolMXBean::getTotalConnections); + } + + public int getPoolIdleConnections() { + return getIntPropertyOrZero(HikariPoolMXBean::getIdleConnections); + } + + public int getPoolMaxConnections() { + return hikariDatasource().getHikariConfigMXBean().getMaximumPoolSize(); + } + + public int getPoolMinIdleConnections() { + return hikariDatasource().getHikariConfigMXBean().getMinimumIdle(); + } + + public long getPoolMaxLifeTimeMillis() { + return hikariDatasource().getHikariConfigMXBean().getMaxLifetime(); + } + + public long getPoolMaxWaitMillis() { + return hikariDatasource().getHikariConfigMXBean().getConnectionTimeout(); + } + + private HikariDataSource hikariDatasource() { + return (HikariDataSource) dbClient.getDatabase().getDataSource(); + } + + private int getIntPropertyOrZero(Function function) { + return ofNullable(hikariDatasource().getHikariPoolMXBean()) + .map(function) + .orElse(0); + } + +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java new file mode 100644 index 00000000000..65cb41e760d --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DatabaseMBeanTest { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final FakeDatabaseMBean underTest = new FakeDatabaseMBean(dbTester.getDbClient()); + + @Test + public void verify_mBean() { + + assertThat(underTest.getPoolActiveConnections()).isNotNegative(); + assertThat(underTest.getPoolIdleConnections()).isNotNegative(); + assertThat(underTest.getPoolMaxConnections()).isNotNegative(); + assertThat(underTest.getPoolMaxLifeTimeMillis()).isNotNegative(); + assertThat(underTest.getPoolMinIdleConnections()).isNotNegative(); + assertThat(underTest.getPoolTotalConnections()).isNotNegative(); + assertThat(underTest.getPoolMaxWaitMillis()).isNotNegative(); + assertThat(underTest.name()).isEqualTo("FakeMBean"); + } + + private static class FakeDatabaseMBean extends DatabaseMBean { + + protected FakeDatabaseMBean(DbClient dbClient) { + super(dbClient); + } + + @Override + protected String name() { + return "FakeMBean"; + } + + @Override + public Section toProtobuf() { + return Section.newBuilder().build(); + } + } +} diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java index 1e2f4154dd8..8eb0c9f87b1 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java @@ -19,6 +19,7 @@ */ package org.sonar.db; +import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; @@ -27,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.apache.commons.dbcp2.BasicDataSource; import org.sonar.api.utils.System2; import org.sonar.core.util.SequenceUuidFactory; import org.sonar.core.util.UuidFactory; @@ -308,7 +308,7 @@ public class DbTester extends AbstractDbTester { } public String getUrl() { - return ((BasicDataSource) db.getDatabase().getDataSource()).getUrl(); + return ((HikariDataSource) db.getDatabase().getDataSource()).getJdbcUrl(); } private static class DbSessionConnectionSupplier implements ConnectionSupplier { diff --git a/server/sonar-docs/src/pages/instance-administration/monitoring.md b/server/sonar-docs/src/pages/instance-administration/monitoring.md index dc3793942dd..86454696cb7 100644 --- a/server/sonar-docs/src/pages/instance-administration/monitoring.md +++ b/server/sonar-docs/src/pages/instance-administration/monitoring.md @@ -72,14 +72,11 @@ All these MBeans are read-only. It's not possible to modify or reset their value | Attribute Name | Description | ---|--- | MigrationStatus | Possible values are: UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL (only available for WebServer). -| PoolActiveConnections | Number of active database connections +| PoolActiveConnections | Number of active database connections | PoolIdleConnections | Number of database connections waiting to be used -| PoolInitialSize | Initial size of the database connections pool. -| PoolMaxActiveConnections | Maximum number of active database connections -| PoolMaxIdleConnections | Maximum number of database connections waiting to be used -| PoolMaxWaitMillis | In milliseconds -| PoolRemoveAbandoned | Possible values : true, false -| PoolRemoveAbandonedTimeoutSeconds | In Seconds +| PoolMaxConnections | Maximum number of active database connections +| PoolTotalConnections | Total number of connections currently in the pool +| PoolMaxWaitMillis | Maximum number of milliseconds that a client will wait for a connection from the pool [[collapse]] | ## SonarQube MBean diff --git a/server/sonar-docs/src/pages/setup/upgrade-notes.md b/server/sonar-docs/src/pages/setup/upgrade-notes.md index 3499865db4d..5761e3f41f9 100644 --- a/server/sonar-docs/src/pages/setup/upgrade-notes.md +++ b/server/sonar-docs/src/pages/setup/upgrade-notes.md @@ -3,6 +3,11 @@ title: Release Upgrade Notes url: /setup/upgrade-notes/ --- +## Release 9.7 Upgrade notes + +**Database Connection Pool Changed** +Database connection pool has been changed, following properties will no longer have effect: sonar.jdbc.maxIdle, sonar.jdbc.minEvictableIdleTimeMillis, sonar.jdbc.timeBetweenEvictionRunsMillis. See documentation for changes in [monitoring database connection pool](/instance-administration/monitoring/). + ## Release 9.6 Upgrade notes **Microsoft SQL Server changes in configuration and Integrated Authentication** * If your Microsoft SQL Server doesn't support encryption, you will need to add `encrypt=false` to the JDBC URL connection string. ([SONAR-16249](https://jira.sonarsource.com/browse/SONAR-16249)). diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java index a23af65a163..82a0f51c235 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java @@ -55,11 +55,8 @@ public class ProcessProperties { JDBC_PASSWORD("sonar.jdbc.password", ""), JDBC_DRIVER_PATH("sonar.jdbc.driverPath"), JDBC_MAX_ACTIVE("sonar.jdbc.maxActive", "60"), - JDBC_MAX_IDLE("sonar.jdbc.maxIdle", "5"), - JDBC_MIN_IDLE("sonar.jdbc.minIdle", "2"), - JDBC_MAX_WAIT("sonar.jdbc.maxWait", "5000"), - JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS("sonar.jdbc.minEvictableIdleTimeMillis", "600000"), - JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS("sonar.jdbc.timeBetweenEvictionRunsMillis", "30000"), + JDBC_MIN_IDLE("sonar.jdbc.minIdle", "10"), + JDBC_MAX_WAIT("sonar.jdbc.maxWait", "8000"), JDBC_EMBEDDED_PORT("sonar.embeddedDatabase.port"), PATH_DATA("sonar.path.data", "data"), diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java new file mode 100644 index 00000000000..b88e6505f0b --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.systeminfo; + +import org.sonar.api.Startable; +import org.sonar.process.Jmx; + +/** + * Base implementation of a {@link SystemInfoSection} + * that is exported as a JMX bean + */ +public abstract class BaseSectionMBean implements SystemInfoSection, Startable { + + /** + * Auto-registers to MBean server + */ + @Override + public void start() { + Jmx.register(objectName(), this); + } + + /** + * Unregister, if needed + */ + @Override + public void stop() { + Jmx.unregister(objectName()); + } + + public String objectName() { + return "SonarQube:name=" + name(); + } + + /** + * Name of section in System Info page + */ + protected abstract String name(); +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java new file mode 100644 index 00000000000..f77f6ba3d19 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.systeminfo; + +import java.lang.management.ManagementFactory; +import javax.annotation.CheckForNull; +import javax.management.InstanceNotFoundException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import org.junit.Test; +import org.sonar.process.jmx.FakeMBean; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BaseSectionMBeanTest { + + private static final String EXPECTED_NAME = "SonarQube:name=FakeName"; + + private final FakeBaseSectionMBean underTest = new FakeBaseSectionMBean(); + + @Test + public void verify_mbean() throws Exception { + assertThat(lookupMBean()).isNull(); + + underTest.start(); + assertThat(lookupMBean()).isNotNull(); + + underTest.stop(); + assertThat(lookupMBean()).isNull(); + + assertThat(underTest.name()).isEqualTo("FakeName"); + assertThat(underTest.toProtobuf()).isNotNull(); + } + + @CheckForNull + private ObjectInstance lookupMBean() throws Exception { + try { + return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(EXPECTED_NAME)); + } catch (InstanceNotFoundException e) { + return null; + } + } + + private static class FakeBaseSectionMBean extends BaseSectionMBean implements FakeMBean { + + @Override + protected String name() { + return "FakeName"; + } + + @Override + public Section toProtobuf() { + return Section.newBuilder().build(); + } + + @Override + public void foo() { + // nothing to do + } + } +} diff --git a/server/sonar-webserver-core/build.gradle b/server/sonar-webserver-core/build.gradle index 622bbfb5a95..6443e5c4524 100644 --- a/server/sonar-webserver-core/build.gradle +++ b/server/sonar-webserver-core/build.gradle @@ -28,7 +28,7 @@ dependencies { compile 'org.apache.httpcomponents:httpclient' compile 'org.apache.logging.log4j:log4j-api' compile 'org.apache.tomcat.embed:tomcat-embed-core' - compile 'org.apache.commons:commons-dbcp2' + compile 'com.zaxxer:HikariCP' compile 'org.slf4j:jul-to-slf4j' compile 'org.slf4j:slf4j-api' compile 'org.sonarsource.api.plugin:sonar-plugin-api' diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java deleted file mode 100644 index b5afeaff0e2..00000000000 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.monitoring; - -import org.sonar.api.Startable; -import org.sonar.process.Jmx; -import org.sonar.process.systeminfo.SystemInfoSection; - -/** - * Base implementation of a {@link SystemInfoSection} - * that is exported as a JMX bean - */ -public abstract class BaseSectionMBean implements SystemInfoSection, Startable { - - /** - * Auto-registers to MBean server - */ - @Override - public void start() { - Jmx.register(objectName(), this); - } - - /** - * Unregister, if needed - */ - @Override - public void stop() { - Jmx.unregister(objectName()); - } - - String objectName() { - return "SonarQube:name=" + name(); - } - - /** - * Name of section in System Info page - */ - abstract String name(); -} diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSection.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSection.java index 8c59cbc0128..4d55ae46693 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSection.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSection.java @@ -19,9 +19,9 @@ */ package org.sonar.server.platform.monitoring; -import org.apache.commons.dbcp2.BasicDataSource; import org.sonar.api.SonarQubeSide; import org.sonar.api.SonarRuntime; +import org.sonar.db.DatabaseMBean; import org.sonar.db.DbClient; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section; import org.sonar.server.platform.db.migration.version.DatabaseVersion; @@ -31,15 +31,14 @@ import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; /** * Information about database connection pool */ -public class DbConnectionSection extends BaseSectionMBean implements DbConnectionSectionMBean { +public class DbConnectionSection extends DatabaseMBean implements DbConnectionSectionMBean { private final DatabaseVersion dbVersion; - private final DbClient dbClient; private final SonarRuntime runtime; public DbConnectionSection(DatabaseVersion dbVersion, DbClient dbClient, SonarRuntime runtime) { + super(dbClient); this.dbVersion = dbVersion; - this.dbClient = dbClient; this.runtime = runtime; } @@ -53,51 +52,6 @@ public class DbConnectionSection extends BaseSectionMBean implements DbConnectio return dbVersion.getStatus().name(); } - @Override - public int getPoolActiveConnections() { - return commonsDbcp().getNumActive(); - } - - @Override - public int getPoolMaxActiveConnections() { - return commonsDbcp().getMaxTotal(); - } - - @Override - public int getPoolIdleConnections() { - return commonsDbcp().getNumIdle(); - } - - @Override - public int getPoolMaxIdleConnections() { - return commonsDbcp().getMaxIdle(); - } - - @Override - public int getPoolMinIdleConnections() { - return commonsDbcp().getMinIdle(); - } - - @Override - public int getPoolInitialSize() { - return commonsDbcp().getInitialSize(); - } - - @Override - public long getPoolMaxWaitMillis() { - return commonsDbcp().getMaxWaitMillis(); - } - - @Override - public boolean getPoolRemoveAbandoned() { - return commonsDbcp().getRemoveAbandonedOnBorrow(); - } - - @Override - public int getPoolRemoveAbandonedTimeoutSeconds() { - return commonsDbcp().getRemoveAbandonedTimeout(); - } - @Override public Section toProtobuf() { Section.Builder protobuf = Section.newBuilder(); @@ -108,18 +62,13 @@ public class DbConnectionSection extends BaseSectionMBean implements DbConnectio } private void completePoolAttributes(Section.Builder protobuf) { + setAttribute(protobuf, "Pool Total Connections", getPoolTotalConnections()); setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections()); - setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections()); - setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize()); setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections()); + setAttribute(protobuf, "Pool Max Connections", getPoolMaxConnections()); setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections()); - setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections()); setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis()); - setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned()); - setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds()); + setAttribute(protobuf, "Pool Max Lifetime (ms)", getPoolMaxLifeTimeMillis()); } - private BasicDataSource commonsDbcp() { - return (BasicDataSource) dbClient.getDatabase().getDataSource(); - } } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSectionMBean.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSectionMBean.java index 59fccd64b46..44144825efc 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSectionMBean.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSectionMBean.java @@ -27,21 +27,24 @@ public interface DbConnectionSectionMBean { String getMigrationStatus(); /** - * + * Get the number of currently active connections in the pool. */ int getPoolActiveConnections(); /** - * The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit. + * The maximum number of connections that can be allocated from this pool at the same time, or negative for no limit. */ - int getPoolMaxActiveConnections(); + int getPoolMaxConnections(); - int getPoolIdleConnections(); + /** + * Total number of connections currently in the pool + */ + int getPoolTotalConnections(); /** - * The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit. + * Get the number of currently idle connections in the pool. */ - int getPoolMaxIdleConnections(); + int getPoolIdleConnections(); /** * The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none. @@ -49,9 +52,9 @@ public interface DbConnectionSectionMBean { int getPoolMinIdleConnections(); /** - * The initial number of connections that are created when the pool is started. + * Maximum lifetime of a connection in the pool. */ - int getPoolInitialSize(); + long getPoolMaxLifeTimeMillis(); /** * The maximum number of milliseconds that the pool will wait @@ -59,13 +62,4 @@ public interface DbConnectionSectionMBean { */ long getPoolMaxWaitMillis(); - /** - * Flag to remove abandoned connections if they exceed the {@link #getPoolRemoveAbandonedTimeoutSeconds()}. - */ - boolean getPoolRemoveAbandoned(); - - /** - * Timeout in seconds before an abandoned connection can be removed. - */ - int getPoolRemoveAbandonedTimeoutSeconds(); } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java index 0c728efc507..457b6377951 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java @@ -29,6 +29,7 @@ import org.sonar.api.platform.Server; import org.sonar.api.security.SecurityRealm; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.process.systeminfo.BaseSectionMBean; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.authentication.IdentityProviderRepository; import org.sonar.server.log.ServerLogging; diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java index 556c5a7544f..cb2ed963626 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java @@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class BaseSectionMBeanTest { - private FakeSection underTest = new FakeSection(); + private final FakeSection underTest = new FakeSection(); @Test public void test_registration() throws Exception { diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java index 1363e1b15e2..1086c1c1a8e 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java @@ -38,9 +38,9 @@ public class DbConnectionSectionTest { @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); - private DatabaseVersion databaseVersion = mock(DatabaseVersion.class); - private SonarRuntime runtime = mock(SonarRuntime.class); - private DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime); + private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class); + private final SonarRuntime runtime = mock(SonarRuntime.class); + private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime); @Test public void jmx_name_is_not_empty() { @@ -50,13 +50,12 @@ public class DbConnectionSectionTest { @Test public void pool_info() { ProtobufSystemInfo.Section section = underTest.toProtobuf(); - assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L); - assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L); - assertThat(attribute(section, "Pool Min Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L); - assertThat(attribute(section, "Pool Max Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L); - assertThat(attribute(section, "Pool Max Wait (ms)")).isNotNull(); - assertThat(attribute(section, "Pool Remove Abandoned")).isNotNull(); - assertThat(attribute(section, "Pool Remove Abandoned Timeout (seconds)").getLongValue()).isGreaterThanOrEqualTo(0L); + assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull(); + assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull(); } @Test @@ -64,7 +63,7 @@ public class DbConnectionSectionTest { when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE); assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection"); - when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER ); + when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER); assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection"); } } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java index efc1955fd27..6374fee3d5a 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java @@ -19,6 +19,7 @@ */ package org.sonar.server.platform.monitoring; +import org.sonar.process.systeminfo.BaseSectionMBean; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; public class FakeSection extends BaseSectionMBean implements FakeSectionMBean { diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties index e862e0632b7..d24fd2dc23a 100644 --- a/sonar-application/src/main/assembly/conf/sonar.properties +++ b/sonar-application/src/main/assembly/conf/sonar.properties @@ -64,15 +64,12 @@ # The minimum number of connections that can remain idle in the pool, # without extra ones being created, or zero to create none. -#sonar.jdbc.minIdle=2 +#sonar.jdbc.minIdle=10 # The maximum number of milliseconds that the pool will wait (when there # are no available connections) for a connection to be returned before # throwing an exception, or <= 0 to wait indefinitely. -#sonar.jdbc.maxWait=5000 - -#sonar.jdbc.minEvictableIdleTimeMillis=600000 -#sonar.jdbc.timeBetweenEvictionRunsMillis=30000 +#sonar.jdbc.maxWait=8000 @@ -121,7 +118,7 @@ # The default value is 25. #sonar.web.http.acceptCount=25 -# The number of milliseconds this Connector will wait for another HTTP request before closing the +# The number of milliseconds this Connector will wait for another HTTP request before closing the # connection. The default value is to use the value that has been set for the connectionTimeout # attribute. Use a value of -1 to indicate no (i.e. infinite) timeout. # The default value is 60000 (ms).