Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions conf/springConfigXml/DatabaseFacade.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

<bean id="DbFacadeDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="${DbFacadeDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack}"/>
<property name="jdbcUrl" value="${DbFacadeDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack?connectTimeout=5000&amp;socketTimeout=60000&amp;tcpKeepAlive=true}"/>
<property name="user" value="${DbFacadeDataSource.user:root}"/>
<property name="password" value="${DbFacadeDataSource.password:}"/>
<property name="initialPoolSize" value="10"/>
Expand All @@ -42,7 +42,7 @@

<bean id="ExtraDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="${DbFacadeDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack}"/>
<property name="jdbcUrl" value="${DbFacadeDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack?connectTimeout=5000&amp;socketTimeout=60000&amp;tcpKeepAlive=true}"/>
<property name="user" value="${DbFacadeDataSource.user:root}"/>
<property name="password" value="${DbFacadeDataSource.password:}"/>
<property name="maxPoolSize" value="5"/>
Expand Down
2 changes: 1 addition & 1 deletion conf/springConfigXml/RESTFacade.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<bean id="RESTApiDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="initialPoolSize" value="10" />
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="${RESTApiDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack_rest}" />
<property name="jdbcUrl" value="${RESTApiDataSource.jdbcUrl:jdbc:mysql://localhost:3306/zstack_rest?connectTimeout=5000&amp;socketTimeout=60000&amp;tcpKeepAlive=true}" />
<property name="user" value="${DbFacadeDataSource.user:root}" />
<property name="password" value="${DbFacadeDataSource.password:}" />
<property name="maxPoolSize" value="${RestFacadeDataSource.maxPoolSize:20}" />
Expand Down
99 changes: 78 additions & 21 deletions core/src/main/java/org/zstack/core/Platform.java
Original file line number Diff line number Diff line change
Expand Up @@ -312,39 +312,22 @@ private static void writePidFile() throws IOException {
private static void prepareDefaultDbProperties() {
if (DatabaseGlobalProperty.DbUrl != null) {
String dbUrl = DatabaseGlobalProperty.DbUrl;
if (dbUrl.endsWith("/")) {
dbUrl = dbUrl.substring(0, dbUrl.length()-1);
}

if (getGlobalProperty("DbFacadeDataSource.jdbcUrl") == null) {
String url;
if (dbUrl.contains("{database}")) {
url = ln(dbUrl).formatByMap(
map(e("database", "zstack"))
);
url = url.trim();
} else {
url = String.format("%s/zstack", dbUrl);
}
String url = buildDbJdbcUrl(dbUrl, "zstack");

System.setProperty("DbFacadeDataSource.jdbcUrl", url);
logger.debug(String.format("default DbFacadeDataSource.jdbcUrl to DB.url [%s]", url));
}
if (getGlobalProperty("RESTApiDataSource.jdbcUrl") == null) {
String url;
if (dbUrl.contains("{database}")) {
url = ln(dbUrl).formatByMap(
map(e("database", "zstack_rest"))
);
url = url.trim();
} else {
url = String.format("%s/zstack_rest", dbUrl);
}
String url = buildDbJdbcUrl(dbUrl, "zstack_rest");

System.setProperty("RESTApiDataSource.jdbcUrl", url);
logger.debug(String.format("default RESTApiDataSource.jdbcUrl to DB.url [%s]", url));
}
}
prepareDefaultDbJdbcUrl("DbFacadeDataSource.jdbcUrl");
prepareDefaultDbJdbcUrl("RESTApiDataSource.jdbcUrl");
if (DatabaseGlobalProperty.DbUser != null) {
if (getGlobalProperty("DbFacadeDataSource.user") == null) {
System.setProperty("DbFacadeDataSource.user", DatabaseGlobalProperty.DbUser);
Expand Down Expand Up @@ -395,6 +378,80 @@ private static void prepareDefaultDbProperties() {
}
}

private static void prepareDefaultDbJdbcUrl(String propertyName) {
String jdbcUrl = getGlobalProperty(propertyName);
if (StringUtils.isBlank(jdbcUrl)) {
return;
}

String defaultedJdbcUrl = appendDefaultDbJdbcParameters(jdbcUrl);
if (!jdbcUrl.equals(defaultedJdbcUrl)) {
System.setProperty(propertyName, defaultedJdbcUrl);
logger.debug(String.format("append default JDBC parameters to %s [%s]", propertyName, defaultedJdbcUrl));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

避免在日志中输出完整 JDBC URL(可能包含敏感参数)。

Line 390 直接打印 defaultedJdbcUrl,若 URL 携带凭据或其他敏感 query,会写入日志。建议只记录属性名,或先脱敏再打印。

建议修改
-            logger.debug(String.format("append default JDBC parameters to %s [%s]", propertyName, defaultedJdbcUrl));
+            logger.debug(String.format("append default JDBC parameters to %s", propertyName));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/main/java/org/zstack/core/Platform.java` at line 390, The debug log
currently prints the full JDBC URL (defaultedJdbcUrl) which may contain
sensitive info; update the logging in class Platform where propertyName and
defaultedJdbcUrl are used so it does not emit the raw URL—either log only the
propertyName or sanitize/mask credentials and query parameters from
defaultedJdbcUrl before including it in the logger.debug call (use a helper to
strip user:pass@ and sensitive query keys), ensuring the log message retains
context but never contains unmasked connection secrets.

}
}

private static String buildDbJdbcUrl(String dbUrl, String database) {
String url;
if (dbUrl.contains("{database}")) {
url = ln(dbUrl).formatByMap(
map(e("database", database))
).trim();
} else {
url = appendDatabaseToJdbcUrl(dbUrl, database);
}

return appendDefaultDbJdbcParameters(url);
}

private static String appendDatabaseToJdbcUrl(String dbUrl, String database) {
String trimmed = dbUrl.trim();
int queryIndex = trimmed.indexOf('?');
String base = queryIndex >= 0 ? trimmed.substring(0, queryIndex) : trimmed;
String query = queryIndex >= 0 ? trimmed.substring(queryIndex) : "";

while (base.endsWith("/")) {
base = base.substring(0, base.length() - 1);
}

return String.format("%s/%s%s", base, database, query);
}

private static String appendDefaultDbJdbcParameters(String jdbcUrl) {
String url = jdbcUrl;
url = appendJdbcParameterIfAbsent(url, "connectTimeout", DatabaseGlobalProperty.DbConnectTimeout);
url = appendJdbcParameterIfAbsent(url, "socketTimeout", DatabaseGlobalProperty.DbSocketTimeout);
url = appendJdbcParameterIfAbsent(url, "tcpKeepAlive", DatabaseGlobalProperty.DbTcpKeepAlive);
return url;
}

private static String appendJdbcParameterIfAbsent(String jdbcUrl, String key, String value) {
if (StringUtils.isBlank(value) || hasJdbcParameter(jdbcUrl, key)) {
return jdbcUrl;
}

String separator = jdbcUrl.contains("?") ? "&" : "?";
return String.format("%s%s%s=%s", jdbcUrl, separator, key, value.trim());
}

private static boolean hasJdbcParameter(String jdbcUrl, String key) {
int queryIndex = jdbcUrl.indexOf('?');
if (queryIndex < 0 || queryIndex == jdbcUrl.length() - 1) {
return false;
}

String query = jdbcUrl.substring(queryIndex + 1);
for (String parameter : query.split("&")) {
int equalsIndex = parameter.indexOf('=');
String parameterKey = equalsIndex >= 0 ? parameter.substring(0, equalsIndex) : parameter;
if (key.equalsIgnoreCase(parameterKey.trim())) {
return true;
}
}

return false;
}

private static void prepareHibernateSearchProperties() {
if (!SearchGlobalProperty.SearchAutoRegister) {
System.setProperty("Search.autoRegister", "false");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public class DatabaseGlobalProperty {
public static String DbIdleConnectionTestPeriod;
@GlobalProperty(name="DB.maxIdleTime", defaultValue = "60")
public static String DbMaxIdleTime;
@GlobalProperty(name="DB.connectTimeout", defaultValue = "5000")
public static String DbConnectTimeout;
@GlobalProperty(name="DB.socketTimeout", defaultValue = "60000")
public static String DbSocketTimeout;
@GlobalProperty(name="DB.tcpKeepAlive", defaultValue = "true")
public static String DbTcpKeepAlive;
Comment on lines +25 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

为新增 JDBC 参数增加格式校验,避免错误配置延迟到运行期暴露。

Line 25-30 的新属性当前无格式约束。建议在属性层直接限制:超时必须为数字,tcpKeepAlive 必须为布尔值,这样可在启动阶段尽早失败并给出明确错误。

建议修改
-    `@GlobalProperty`(name="DB.connectTimeout", defaultValue = "5000")
+    `@GlobalProperty`(name="DB.connectTimeout", defaultValue = "5000")
+    `@RegexValues`(value = "^[0-9]+$")
     public static String DbConnectTimeout;
-    `@GlobalProperty`(name="DB.socketTimeout", defaultValue = "60000")
+    `@GlobalProperty`(name="DB.socketTimeout", defaultValue = "60000")
+    `@RegexValues`(value = "^[0-9]+$")
     public static String DbSocketTimeout;
-    `@GlobalProperty`(name="DB.tcpKeepAlive", defaultValue = "true")
+    `@GlobalProperty`(name="DB.tcpKeepAlive", defaultValue = "true")
+    `@RegexValues`(value = "^(?i:true|false)$")
     public static String DbTcpKeepAlive;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/main/java/org/zstack/core/db/DatabaseGlobalProperty.java` around
lines 25 - 30, 在 DatabaseGlobalProperty 类中为新加的全局属性
DbConnectTimeout、DbSocketTimeout、DbTcpKeepAlive
增加启动时格式校验:把超时属性(DbConnectTimeout、DbSocketTimeout)从无约束的 String 改为数值类型(int/long 或
Integer/Long)或在类的静态初始化/属性加载路径中立即对其进行 Integer.parseInt/Long.parseLong
校验并在解析失败时抛出明确异常;对 DbTcpKeepAlive 进行布尔解析(Boolean.parseBoolean 或更严格的接受
"true"/"false" 校验)并在格式不符合时抛出错误。确保改动出现在 DatabaseGlobalProperty 的定义/初始化逻辑中(涉及字段
DbConnectTimeout、DbSocketTimeout、DbTcpKeepAlive),以便在应用启动阶段就能发现并报出配置错误。

@GlobalProperty(name="DB.glock.waitTimeout", defaultValue = "28800")
public static Long GLockWaitTimeout;
@GlobalProperty(name="RESTFacade.hostname")
Expand Down
116 changes: 116 additions & 0 deletions test/src/test/java/org/zstack/test/core/TestDefaultDbJdbcUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.zstack.test.core;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.zstack.core.Platform;
import org.zstack.core.db.DatabaseGlobalProperty;

import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;

public class TestDefaultDbJdbcUrl {
private String oldDbUrl;
private String oldConnectTimeout;
private String oldSocketTimeout;
private String oldTcpKeepAlive;
private String oldDbFacadeJdbcUrl;
private String oldRestApiJdbcUrl;

@Before
public void setUp() {
oldDbUrl = DatabaseGlobalProperty.DbUrl;
oldConnectTimeout = DatabaseGlobalProperty.DbConnectTimeout;
oldSocketTimeout = DatabaseGlobalProperty.DbSocketTimeout;
oldTcpKeepAlive = DatabaseGlobalProperty.DbTcpKeepAlive;
oldDbFacadeJdbcUrl = System.getProperty("DbFacadeDataSource.jdbcUrl");
oldRestApiJdbcUrl = System.getProperty("RESTApiDataSource.jdbcUrl");

DatabaseGlobalProperty.DbConnectTimeout = "5000";
DatabaseGlobalProperty.DbSocketTimeout = "60000";
DatabaseGlobalProperty.DbTcpKeepAlive = "true";
System.clearProperty("DbFacadeDataSource.jdbcUrl");
System.clearProperty("RESTApiDataSource.jdbcUrl");
}

@After
public void tearDown() {
DatabaseGlobalProperty.DbUrl = oldDbUrl;
DatabaseGlobalProperty.DbConnectTimeout = oldConnectTimeout;
DatabaseGlobalProperty.DbSocketTimeout = oldSocketTimeout;
DatabaseGlobalProperty.DbTcpKeepAlive = oldTcpKeepAlive;
restoreProperty("DbFacadeDataSource.jdbcUrl", oldDbFacadeJdbcUrl);
restoreProperty("RESTApiDataSource.jdbcUrl", oldRestApiJdbcUrl);
}

@Test
public void testAppendTimeoutsToDefaultDbUrl() throws Exception {
assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack?connectTimeout=5000&socketTimeout=60000&tcpKeepAlive=true",
buildDbJdbcUrl("jdbc:mysql://172.20.0.37:3306", "zstack")
);
}

@Test
public void testAppendTimeoutsAfterExistingQuery() throws Exception {
assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack?useSSL=false&connectTimeout=5000&socketTimeout=60000&tcpKeepAlive=true",
buildDbJdbcUrl("jdbc:mysql://172.20.0.37:3306?useSSL=false", "zstack")
);
}

@Test
public void testDoNotOverrideExplicitTimeouts() throws Exception {
assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack?socketTimeout=10000&connectTimeout=1000&tcpKeepAlive=false",
buildDbJdbcUrl("jdbc:mysql://172.20.0.37:3306?socketTimeout=10000&connectTimeout=1000&tcpKeepAlive=false", "zstack")
);
}

@Test
public void testTemplateDbUrlKeepsQueryInPlace() throws Exception {
assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack_rest?useSSL=false&connectTimeout=5000&socketTimeout=60000&tcpKeepAlive=true",
buildDbJdbcUrl("jdbc:mysql://172.20.0.37:3306/{database}?useSSL=false", "zstack_rest")
);
}

@Test
public void testAppendTimeoutsToExplicitDatasourceJdbcUrls() throws Exception {
DatabaseGlobalProperty.DbUrl = null;
System.setProperty("DbFacadeDataSource.jdbcUrl", "jdbc:mysql://172.20.0.37:3306/zstack");
System.setProperty("RESTApiDataSource.jdbcUrl", "jdbc:mysql://172.20.0.37:3306/zstack_rest?useSSL=false");

prepareDefaultDbProperties();

assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack?connectTimeout=5000&socketTimeout=60000&tcpKeepAlive=true",
System.getProperty("DbFacadeDataSource.jdbcUrl")
);
assertEquals(
"jdbc:mysql://172.20.0.37:3306/zstack_rest?useSSL=false&connectTimeout=5000&socketTimeout=60000&tcpKeepAlive=true",
System.getProperty("RESTApiDataSource.jdbcUrl")
);
}

private String buildDbJdbcUrl(String dbUrl, String database) throws Exception {
Method method = Platform.class.getDeclaredMethod("buildDbJdbcUrl", String.class, String.class);
method.setAccessible(true);
return (String) method.invoke(null, dbUrl, database);
}

private void prepareDefaultDbProperties() throws Exception {
Method method = Platform.class.getDeclaredMethod("prepareDefaultDbProperties");
method.setAccessible(true);
method.invoke(null);
}

private void restoreProperty(String key, String value) {
if (value == null) {
System.clearProperty(key);
} else {
System.setProperty(key, value);
}
}
}