diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/pom.xml b/sofa-boot-project/sofa-boot-actuator-autoconfigure/pom.xml
index 21f62de55..67cae2753 100644
--- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/pom.xml
+++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/pom.xml
@@ -31,6 +31,11 @@
spring-boot-actuator-autoconfigure
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
org.springframework.boot
spring-boot-configuration-processor
diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/HealthProperties.java b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/HealthProperties.java
index 1779edf6b..723b64664 100644
--- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/HealthProperties.java
+++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/HealthProperties.java
@@ -17,8 +17,10 @@
package com.alipay.sofa.boot.actuator.autoconfigure.health;
import com.alipay.sofa.boot.actuator.health.HealthCheckerConfig;
+import jakarta.validation.constraints.Positive;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.HashMap;
@@ -33,6 +35,7 @@
* Created on 2020/5/18
*/
@ConfigurationProperties("sofa.boot.actuator.health")
+@Validated
public class HealthProperties {
/**
@@ -54,6 +57,7 @@ public class HealthProperties {
/**
* Timeout duration of parallel health check, in milliseconds.
*/
+ @Positive(message = "Parallel health check timeout must be greater than 0")
private long parallelCheckTimeout = 120 * 1000;
/**
@@ -84,6 +88,7 @@ public class HealthProperties {
/**
* Global {@link com.alipay.sofa.boot.actuator.health.HealthChecker} health check timeout,in milliseconds.
*/
+ @Positive(message = "Global HealthChecker timeout must be greater than 0")
private int globalHealthCheckerTimeout = 60 * 1000;
/**
@@ -95,6 +100,7 @@ public class HealthProperties {
/**
* Global {@link org.springframework.boot.actuate.health.HealthIndicator} health check timeout,in milliseconds.
*/
+ @Positive(message = "Global HealthIndicator timeout must be greater than 0")
private int globalHealthIndicatorTimeout = 60 * 1000;
/**
diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java
index caf3ada77..2c3f8f49c 100644
--- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java
+++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java
@@ -32,6 +32,7 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.core.NestedExceptionUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
@@ -148,6 +149,17 @@ void customHealthCheckParallelCheck() {
});
}
+ @Test
+ void invalidHealthPropertiesShouldFailFast() {
+ this.contextRunner
+ .withPropertyValues("sofa.boot.actuator.health.parallelCheckTimeout=0")
+ .run((context) -> {
+ assertThat(context).hasFailed();
+ assertThat(NestedExceptionUtils.getMostSpecificCause(context.getStartupFailure()).getMessage())
+ .contains("Parallel health check timeout must be greater than 0");
+ });
+ }
+
@Test
void runWhenWithIsleConfiguration() {
this.contextRunner
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/pom.xml b/sofa-boot-project/sofa-boot-autoconfigure/pom.xml
index 49894462a..9ceb80a01 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/pom.xml
+++ b/sofa-boot-project/sofa-boot-autoconfigure/pom.xml
@@ -26,6 +26,11 @@
spring-boot-autoconfigure
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
org.springframework.boot
spring-boot-configuration-processor
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleProperties.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleProperties.java
index 1abb50d54..8407ecde2 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleProperties.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleProperties.java
@@ -16,7 +16,9 @@
*/
package com.alipay.sofa.boot.autoconfigure.isle;
+import jakarta.validation.constraints.Positive;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
@@ -28,6 +30,7 @@
* @author huzijie
*/
@ConfigurationProperties("sofa.boot.isle")
+@Validated
public class SofaModuleProperties {
/**
@@ -78,16 +81,19 @@ public class SofaModuleProperties {
/**
* Thead pool size factor used in parallel sofa module application context refresh.
*/
+ @Positive(message = "Parallel module refresh thread pool size factor must be greater than 0")
private float parallelRefreshPoolSizeFactor = 5.0f;
/**
* Timeout used in parallel sofa module application context refresh, in milliseconds.
*/
+ @Positive(message = "Parallel module refresh timeout must be greater than 0")
private long parallelRefreshTimeout = 60;
/**
* Monitor period used in parallel sofa module application context refresh, in milliseconds.
*/
+ @Positive(message = "Parallel module refresh check period must be greater than 0")
private long parallelRefreshCheckPeriod = 30;
public List getActiveProfiles() {
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcProperties.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcProperties.java
index cbeaa6890..a27dbf34a 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcProperties.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcProperties.java
@@ -22,6 +22,7 @@
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils;
+import org.springframework.validation.annotation.Validated;
import java.util.HashMap;
import java.util.List;
@@ -33,6 +34,8 @@
* @author khotyn
*/
@ConfigurationProperties("sofa.boot.rpc")
+@Validated
+@ValidSofaBootRpcProperties
public class SofaBootRpcProperties implements EnvironmentAware {
public static final String PREFIX = "sofa.boot.rpc";
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidator.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidator.java
new file mode 100644
index 000000000..5be2cc19b
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidator.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.boot.autoconfigure.rpc;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+
+/**
+ * Validator for {@link SofaBootRpcProperties}.
+ *
+ * @author huzijie
+ */
+public class SofaBootRpcPropertiesValidator
+ implements
+ ConstraintValidator {
+
+ @Override
+ public boolean isValid(SofaBootRpcProperties value, ConstraintValidatorContext context) {
+ if (value == null) {
+ return true;
+ }
+ context.disableDefaultConstraintViolation();
+ boolean valid = true;
+
+ valid = validatePort(context, valid, "boltPort", value.getBoltPort(),
+ "Bolt port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "h2cPort", value.getH2cPort(),
+ "H2C port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "restPort", value.getRestPort(),
+ "REST port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "dubboPort", value.getDubboPort(),
+ "Dubbo port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "httpPort", value.getHttpPort(),
+ "HTTP port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "triplePort", value.getTriplePort(),
+ "Triple port must be between 1024 and 65535");
+ valid = validatePort(context, valid, "virtualPort", value.getVirtualPort(),
+ "Virtual port must be between 1024 and 65535");
+
+ valid = validatePositiveInteger(context, valid, "boltThreadPoolCoreSize",
+ value.getBoltThreadPoolCoreSize(), "Bolt thread pool core size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "boltThreadPoolMaxSize",
+ value.getBoltThreadPoolMaxSize(), "Bolt thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "boltThreadPoolQueueSize",
+ value.getBoltThreadPoolQueueSize(),
+ "Bolt thread pool queue size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "boltAcceptsSize",
+ value.getBoltAcceptsSize(), "Bolt accepts size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "h2cThreadPoolCoreSize",
+ value.getH2cThreadPoolCoreSize(), "H2C thread pool core size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "h2cThreadPoolMaxSize",
+ value.getH2cThreadPoolMaxSize(), "H2C thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "h2cThreadPoolQueueSize",
+ value.getH2cThreadPoolQueueSize(), "H2C thread pool queue size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "h2cAcceptsSize",
+ value.getH2cAcceptsSize(), "H2C accepts size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "restIoThreadSize",
+ value.getRestIoThreadSize(), "REST IO thread size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "restThreadPoolMaxSize",
+ value.getRestThreadPoolMaxSize(), "REST thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "restMaxRequestSize",
+ value.getRestMaxRequestSize(), "REST max request size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "dubboIoThreadSize",
+ value.getDubboIoThreadSize(), "Dubbo IO thread size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "dubboThreadPoolMaxSize",
+ value.getDubboThreadPoolMaxSize(), "Dubbo thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "dubboAcceptsSize",
+ value.getDubboAcceptsSize(), "Dubbo accepts size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "httpThreadPoolCoreSize",
+ value.getHttpThreadPoolCoreSize(), "HTTP thread pool core size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "httpThreadPoolMaxSize",
+ value.getHttpThreadPoolMaxSize(), "HTTP thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "httpThreadPoolQueueSize",
+ value.getHttpThreadPoolQueueSize(),
+ "HTTP thread pool queue size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "httpAcceptsSize",
+ value.getHttpAcceptsSize(), "HTTP accepts size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "tripleThreadPoolCoreSize",
+ value.getTripleThreadPoolCoreSize(),
+ "Triple thread pool core size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "tripleThreadPoolMaxSize",
+ value.getTripleThreadPoolMaxSize(),
+ "Triple thread pool max size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "tripleThreadPoolQueueSize",
+ value.getTripleThreadPoolQueueSize(),
+ "Triple thread pool queue size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "tripleAcceptsSize",
+ value.getTripleAcceptsSize(), "Triple accepts size must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "consumerRepeatedReferenceLimit",
+ value.getConsumerRepeatedReferenceLimit(),
+ "Consumer repeated reference limit must be greater than 0");
+
+ valid = validateBooleanString(context, valid, "aftRegulationEffective",
+ value.getAftRegulationEffective(), "AFT regulation switch must be true or false");
+ valid = validateBooleanString(context, valid, "aftDegradeEffective",
+ value.getAftDegradeEffective(), "AFT degrade switch must be true or false");
+ valid = validateBooleanString(context, valid, "restTelnet", value.getRestTelnet(),
+ "REST telnet switch must be true or false");
+ valid = validateBooleanString(context, valid, "restDaemon", value.getRestDaemon(),
+ "REST daemon switch must be true or false");
+
+ valid = validatePositiveInteger(context, valid, "aftTimeWindow", value.getAftTimeWindow(),
+ "AFT time window must be greater than 0");
+ valid = validatePositiveInteger(context, valid, "aftLeastWindowCount",
+ value.getAftLeastWindowCount(), "AFT least window count must be greater than 0");
+ valid = validatePositiveDecimal(context, valid, "aftLeastWindowExceptionRateMultiple",
+ value.getAftLeastWindowExceptionRateMultiple(),
+ "AFT least window exception rate multiple must be greater than 0");
+ valid = validateRange(context, valid, "aftDegradeLeastWeight",
+ value.getAftDegradeLeastWeight(), 0, 100,
+ "AFT degrade least weight must be between 0 and 100");
+ valid = validatePositiveInteger(context, valid, "aftDegradeMaxIpCount",
+ value.getAftDegradeMaxIpCount(), "AFT degrade max IP count must be greater than 0");
+ valid = validateDecimalRange(context, valid, "aftWeightDegradeRate",
+ value.getAftWeightDegradeRate(), BigDecimal.ZERO, false, BigDecimal.ONE, true,
+ "AFT weight degrade rate must be greater than 0 and less than or equal to 1");
+ valid = validatePositiveDecimal(context, valid, "aftWeightRecoverRate",
+ value.getAftWeightRecoverRate(), "AFT weight recover rate must be greater than 0");
+
+ return valid;
+ }
+
+ private boolean validatePort(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, String message) {
+ return validateRange(context, valid, fieldName, rawValue, 1024, 65535, message);
+ }
+
+ private boolean validatePositiveInteger(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, String message) {
+ if (!StringUtils.hasText(rawValue)) {
+ return valid;
+ }
+ Integer parsedValue = parseInteger(rawValue);
+ if (parsedValue == null || parsedValue <= 0) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+ return valid;
+ }
+
+ private boolean validateRange(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, int min, int max,
+ String message) {
+ if (!StringUtils.hasText(rawValue)) {
+ return valid;
+ }
+ Integer parsedValue = parseInteger(rawValue);
+ if (parsedValue == null || parsedValue < min || parsedValue > max) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+ return valid;
+ }
+
+ private boolean validatePositiveDecimal(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, String message) {
+ if (!StringUtils.hasText(rawValue)) {
+ return valid;
+ }
+ BigDecimal parsedValue = parseDecimal(rawValue);
+ if (parsedValue == null || parsedValue.compareTo(BigDecimal.ZERO) <= 0) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+ return valid;
+ }
+
+ private boolean validateDecimalRange(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, BigDecimal min,
+ boolean minInclusive, BigDecimal max,
+ boolean maxInclusive, String message) {
+ if (!StringUtils.hasText(rawValue)) {
+ return valid;
+ }
+ BigDecimal parsedValue = parseDecimal(rawValue);
+ if (parsedValue == null) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+
+ boolean underMin = minInclusive ? parsedValue.compareTo(min) < 0 : parsedValue
+ .compareTo(min) <= 0;
+ boolean overMax = maxInclusive ? parsedValue.compareTo(max) > 0 : parsedValue
+ .compareTo(max) >= 0;
+ if (underMin || overMax) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+ return valid;
+ }
+
+ private boolean validateBooleanString(ConstraintValidatorContext context, boolean valid,
+ String fieldName, String rawValue, String message) {
+ if (!StringUtils.hasText(rawValue)) {
+ return valid;
+ }
+ if (!"true".equalsIgnoreCase(rawValue) && !"false".equalsIgnoreCase(rawValue)) {
+ addViolation(context, fieldName, message);
+ return false;
+ }
+ return valid;
+ }
+
+ private Integer parseInteger(String rawValue) {
+ try {
+ return Integer.valueOf(rawValue);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+
+ private BigDecimal parseDecimal(String rawValue) {
+ try {
+ return new BigDecimal(rawValue);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+
+ private void addViolation(ConstraintValidatorContext context, String fieldName, String message) {
+ context.buildConstraintViolationWithTemplate(message).addPropertyNode(fieldName)
+ .addConstraintViolation();
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/ValidSofaBootRpcProperties.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/ValidSofaBootRpcProperties.java
new file mode 100644
index 000000000..0c290f0a8
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/ValidSofaBootRpcProperties.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.boot.autoconfigure.rpc;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class-level constraint for {@link SofaBootRpcProperties}.
+ *
+ * @author huzijie
+ */
+@Documented
+@Constraint(validatedBy = SofaBootRpcPropertiesValidator.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ValidSofaBootRpcProperties {
+
+ String message() default "SOFA RPC configuration is invalid";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+}
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeProperties.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeProperties.java
index af4de9345..1952fa392 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeProperties.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeProperties.java
@@ -17,7 +17,10 @@
package com.alipay.sofa.boot.autoconfigure.runtime;
import com.alipay.sofa.boot.constant.SofaBootConstants;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.PositiveOrZero;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
@@ -29,6 +32,7 @@
* @author huzijie
*/
@ConfigurationProperties("sofa.boot.runtime")
+@Validated
public class SofaRuntimeProperties {
/**
@@ -80,11 +84,13 @@ public class SofaRuntimeProperties {
/**
* Custom async init executor core size.
*/
+ @PositiveOrZero(message = "Async init executor core size must not be less than 0")
private int asyncInitExecutorCoreSize = SofaBootConstants.CPU_CORE + 1;
/**
* Custom async init executor max size.
*/
+ @PositiveOrZero(message = "Async init executor max size must not be less than 0")
private int asyncInitExecutorMaxSize = SofaBootConstants.CPU_CORE + 1;
/**
@@ -200,4 +206,9 @@ public boolean isServiceCanBeDuplicate() {
public void setServiceCanBeDuplicate(boolean serviceCanBeDuplicate) {
this.serviceCanBeDuplicate = serviceCanBeDuplicate;
}
+
+ @AssertTrue(message = "Async init executor max size must not be less than core size")
+ public boolean isAsyncInitExecutorPoolSizeValid() {
+ return asyncInitExecutorMaxSize >= asyncInitExecutorCoreSize;
+ }
}
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerProperties.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerProperties.java
index 413da6327..7640f70ce 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerProperties.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerProperties.java
@@ -19,7 +19,11 @@
import com.alipay.common.tracer.core.appender.file.TimedRollingFileAppender;
import com.alipay.common.tracer.core.configuration.SofaTracerConfiguration;
import com.alipay.common.tracer.core.reporter.stat.manager.SofaTracerStatisticReporterManager;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
import java.util.HashMap;
import java.util.Map;
@@ -34,11 +38,13 @@
* @since 2018/04/30
*/
@ConfigurationProperties("sofa.boot.tracer")
+@Validated
public class SofaTracerProperties {
/**
* Disable digest log.
*/
+ @Pattern(regexp = "^(?i:true|false)$", message = "disableDigestLog must be true or false")
private String disableDigestLog = "false";
/**
@@ -54,17 +60,20 @@ public class SofaTracerProperties {
/**
* Tracer's global log retention days configuration.
*/
+ @Pattern(regexp = "^\\d+$", message = "Log reserve days must be a non-negative integer")
private String tracerGlobalLogReserveDay = String.valueOf(DEFAULT_LOG_RESERVE_DAY);
/**
* The interval for printing the stat log.
*/
+ @Pattern(regexp = "^\\d+$", message = "Stat log interval must be a non-negative integer")
private String statLogInterval = String
.valueOf(SofaTracerStatisticReporterManager.DEFAULT_CYCLE_SECONDS);
/**
* Threshold, the length of the service transparent field
*/
+ @Pattern(regexp = "^\\d+$", message = "Baggage max length must be a non-negative integer")
private String baggageMaxLength = String
.valueOf(SofaTracerConfiguration.PEN_ATTRS_LENGTH_TRESHOLD);
@@ -76,6 +85,8 @@ public class SofaTracerProperties {
/**
* Sampling policy percentage.
*/
+ @DecimalMin(value = "0.0", message = "Sampler percentage must not be less than 0")
+ @DecimalMax(value = "100.0", message = "Sampler percentage must not be greater than 100")
private float samplerPercentage = 100;
/**
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java
index d1aa6698a..0690fe365 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java
@@ -36,6 +36,7 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.core.NestedExceptionUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
@@ -154,6 +155,17 @@ void customSofaModuleRefreshExecutor() {
});
}
+ @Test
+ void invalidParallelRefreshConfigShouldFailFast() {
+ this.contextRunner
+ .withPropertyValues("sofa.boot.isle.parallelRefreshPoolSizeFactor=0")
+ .run((context) -> {
+ assertThat(context).hasFailed();
+ assertThat(NestedExceptionUtils.getMostSpecificCause(context.getStartupFailure()).getMessage())
+ .contains("Parallel module refresh thread pool size factor must be greater than 0");
+ });
+ }
+
@Test
void customSofaModuleProfileChecker() {
this.contextRunner
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidatorTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidatorTests.java
new file mode 100644
index 000000000..95fcd64e7
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaBootRpcPropertiesValidatorTests.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.boot.autoconfigure.rpc;
+
+import jakarta.validation.ConstraintValidatorContext;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.env.StandardEnvironment;
+
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+/**
+ * Tests for {@link SofaBootRpcPropertiesValidator}.
+ *
+ * @author huzijie
+ */
+public class SofaBootRpcPropertiesValidatorTests {
+
+ private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+
+ @Test
+ void nullPropertiesShouldBeValid() {
+ SofaBootRpcPropertiesValidator propertiesValidator = new SofaBootRpcPropertiesValidator();
+ ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+
+ assertThat(propertiesValidator.isValid(null, context)).isTrue();
+ verifyNoInteractions(context);
+ }
+
+ @Test
+ void emptyPropertiesShouldBeValid() {
+ Set> violations = validator
+ .validate(newProperties());
+
+ assertThat(violations).isEmpty();
+ }
+
+ @Test
+ void boundaryValuesShouldBeValid() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setBoltPort("1024");
+ properties.setVirtualPort("65535");
+ properties.setBoltThreadPoolCoreSize("1");
+ properties.setBoltThreadPoolMaxSize("1");
+ properties.setBoltThreadPoolQueueSize("1");
+ properties.setBoltAcceptsSize("1");
+ properties.setConsumerRepeatedReferenceLimit("1");
+ properties.setAftRegulationEffective("true");
+ properties.setAftDegradeEffective("FALSE");
+ properties.setRestTelnet("true");
+ properties.setRestDaemon("false");
+ properties.setAftTimeWindow("1");
+ properties.setAftLeastWindowCount("1");
+ properties.setAftLeastWindowExceptionRateMultiple("0.1");
+ properties.setAftDegradeLeastWeight("100");
+ properties.setAftDegradeMaxIpCount("1");
+ properties.setAftWeightDegradeRate("1");
+ properties.setAftWeightRecoverRate("0.1");
+
+ Set> violations = validator.validate(properties);
+
+ assertThat(violations).isEmpty();
+ }
+
+ @Test
+ void invalidTextValuesShouldProduceFieldViolations() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setBoltPort("abc");
+ properties.setBoltThreadPoolMaxSize("abc");
+ properties.setAftRegulationEffective("enabled");
+ properties.setRestDaemon("daemon");
+ properties.setAftLeastWindowExceptionRateMultiple("NaN");
+ properties.setAftWeightDegradeRate("fast");
+ properties.setAftWeightRecoverRate("slow");
+
+ Map violations = messagesByField(validator.validate(properties));
+
+ assertThat(violations)
+ .containsEntry("boltPort", "Bolt port must be between 1024 and 65535")
+ .containsEntry("boltThreadPoolMaxSize",
+ "Bolt thread pool max size must be greater than 0")
+ .containsEntry("aftRegulationEffective", "AFT regulation switch must be true or false")
+ .containsEntry("restDaemon", "REST daemon switch must be true or false")
+ .containsEntry("aftLeastWindowExceptionRateMultiple",
+ "AFT least window exception rate multiple must be greater than 0")
+ .containsEntry("aftWeightDegradeRate",
+ "AFT weight degrade rate must be greater than 0 and less than or equal to 1")
+ .containsEntry("aftWeightRecoverRate", "AFT weight recover rate must be greater than 0");
+ }
+
+ @Test
+ void outOfRangeValuesShouldProduceFieldViolations() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setBoltPort("80");
+ properties.setVirtualPort("65536");
+ properties.setBoltThreadPoolCoreSize("0");
+ properties.setConsumerRepeatedReferenceLimit("0");
+ properties.setAftTimeWindow("0");
+ properties.setAftDegradeLeastWeight("-1");
+ properties.setAftWeightDegradeRate("0");
+ properties.setAftWeightRecoverRate("0");
+
+ Map violations = messagesByField(validator.validate(properties));
+
+ assertThat(violations)
+ .containsEntry("boltPort", "Bolt port must be between 1024 and 65535")
+ .containsEntry("virtualPort", "Virtual port must be between 1024 and 65535")
+ .containsEntry("boltThreadPoolCoreSize",
+ "Bolt thread pool core size must be greater than 0")
+ .containsEntry("consumerRepeatedReferenceLimit",
+ "Consumer repeated reference limit must be greater than 0")
+ .containsEntry("aftTimeWindow", "AFT time window must be greater than 0")
+ .containsEntry("aftDegradeLeastWeight",
+ "AFT degrade least weight must be between 0 and 100")
+ .containsEntry("aftWeightDegradeRate",
+ "AFT weight degrade rate must be greater than 0 and less than or equal to 1")
+ .containsEntry("aftWeightRecoverRate", "AFT weight recover rate must be greater than 0");
+ }
+
+ @Test
+ void decimalValuesAboveMaxShouldBeRejected() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setAftWeightDegradeRate("1.1");
+
+ Map violations = messagesByField(validator.validate(properties));
+
+ assertThat(violations).containsEntry("aftWeightDegradeRate",
+ "AFT weight degrade rate must be greater than 0 and less than or equal to 1");
+ }
+
+ @Test
+ void blankValuesShouldBeIgnored() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setBoltPort(" ");
+ properties.setAftRegulationEffective(" ");
+ properties.setAftWeightDegradeRate(" ");
+ properties.setAftWeightRecoverRate(" ");
+
+ Set> violations = validator.validate(properties);
+
+ assertThat(violations).isEmpty();
+ }
+
+ @Test
+ void invalidDecimalFormatShouldBeRejected() {
+ SofaBootRpcProperties properties = newProperties();
+ properties.setAftWeightDegradeRate(".");
+ properties.setAftWeightRecoverRate(".");
+
+ Map violations = messagesByField(validator.validate(properties));
+
+ assertThat(violations)
+ .containsEntry("aftWeightDegradeRate",
+ "AFT weight degrade rate must be greater than 0 and less than or equal to 1")
+ .containsEntry("aftWeightRecoverRate", "AFT weight recover rate must be greater than 0");
+ }
+
+ @Test
+ void inclusiveAndExclusiveDecimalBoundariesShouldBeHandled() throws Exception {
+ SofaBootRpcPropertiesValidator propertiesValidator = new SofaBootRpcPropertiesValidator();
+ ConstraintValidatorContext context = mock(ConstraintValidatorContext.class,
+ RETURNS_DEEP_STUBS);
+
+ assertThat(
+ invokeValidateDecimalRange(propertiesValidator, context, "-0.1", BigDecimal.ZERO, true,
+ BigDecimal.ONE, false)).isFalse();
+ assertThat(
+ invokeValidateDecimalRange(propertiesValidator, context, "0", BigDecimal.ZERO, true,
+ BigDecimal.ONE, false)).isTrue();
+ assertThat(
+ invokeValidateDecimalRange(propertiesValidator, context, "1", BigDecimal.ZERO, true,
+ BigDecimal.ONE, false)).isFalse();
+ }
+
+ private SofaBootRpcProperties newProperties() {
+ SofaBootRpcProperties properties = new SofaBootRpcProperties();
+ properties.setEnvironment(new StandardEnvironment());
+ return properties;
+ }
+
+ private boolean invokeValidateDecimalRange(SofaBootRpcPropertiesValidator propertiesValidator,
+ ConstraintValidatorContext context, String rawValue,
+ BigDecimal min, boolean minInclusive,
+ BigDecimal max, boolean maxInclusive)
+ throws Exception {
+ Method method = SofaBootRpcPropertiesValidator.class.getDeclaredMethod(
+ "validateDecimalRange", ConstraintValidatorContext.class, boolean.class, String.class,
+ String.class, BigDecimal.class, boolean.class, BigDecimal.class, boolean.class,
+ String.class);
+ method.setAccessible(true);
+ return (boolean) method.invoke(propertiesValidator, context, true, "field", rawValue, min,
+ minInclusive, max, maxInclusive, "message");
+ }
+
+ private Map messagesByField(
+ Set> violations) {
+ return violations.stream().collect(Collectors.toMap(
+ violation -> violation.getPropertyPath().toString(), ConstraintViolation::getMessage,
+ (left, right) -> left));
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfigurationTests.java
index 8e8ac4cf1..a6689b984 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfigurationTests.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfigurationTests.java
@@ -46,6 +46,7 @@
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.NestedExceptionUtils;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseFilter;
@@ -215,6 +216,18 @@ void customAftConfig() {
});
}
+ @Test
+ void invalidRpcPropertiesShouldFailFast() {
+ this.contextRunner.withPropertyValues("sofa.boot.rpc.boltPort=80",
+ "sofa.boot.rpc.aftDegradeLeastWeight=-10")
+ .run(context -> {
+ assertThat(context).hasFailed();
+ assertThat(NestedExceptionUtils.getMostSpecificCause(context.getStartupFailure()).getMessage())
+ .contains("Bolt port must be between 1024 and 65535")
+ .contains("AFT degrade least weight must be between 0 and 100");
+ });
+ }
+
@Test
void customServerConfigContainer() {
this.contextRunner.withPropertyValues(
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java
index c6ab92b71..058fcee34 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java
@@ -33,6 +33,7 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.core.NestedExceptionUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
@@ -130,6 +131,18 @@ public void customAsyncInitMethodManager() {
});
}
+ @Test
+ public void invalidAsyncInitExecutorConfigShouldFailFast() {
+ this.contextRunner
+ .withPropertyValues("sofa.boot.runtime.asyncInitExecutorCoreSize=10")
+ .withPropertyValues("sofa.boot.runtime.asyncInitExecutorMaxSize=5")
+ .run((context) -> {
+ assertThat(context).hasFailed();
+ assertThat(NestedExceptionUtils.getMostSpecificCause(context.getStartupFailure()).getMessage())
+ .contains("Async init executor max size must not be less than core size");
+ });
+ }
+
@Test
@EnabledOnJre(JRE.JAVA_21)
public void useAsyncInitMethodVirtualExecutor() {
diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerAutoConfigurationTests.java
index 4ec6834b2..ad44ba399 100644
--- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerAutoConfigurationTests.java
+++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/tracer/SofaTracerAutoConfigurationTests.java
@@ -23,6 +23,7 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.core.NestedExceptionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -59,6 +60,17 @@ public void withoutSpanReportListenerHolder() {
.run((context) -> assertThat(context.getBean("sofaTracerSpanReportListener").toString()).isEqualTo("null"));
}
+ @Test
+ public void invalidTracerPropertiesShouldFailFast() {
+ this.contextRunner
+ .withPropertyValues("sofa.boot.tracer.samplerPercentage=101")
+ .run((context) -> {
+ assertThat(context).hasFailed();
+ assertThat(NestedExceptionUtils.getMostSpecificCause(context.getStartupFailure()).getMessage())
+ .contains("Sampler percentage must not be greater than 100");
+ });
+ }
+
static class SampleSpanReportListener implements SpanReportListener {
@Override
diff --git a/sofa-boot-project/sofaboot-dependencies/pom.xml b/sofa-boot-project/sofaboot-dependencies/pom.xml
index 7bac50f75..5494fab3f 100644
--- a/sofa-boot-project/sofaboot-dependencies/pom.xml
+++ b/sofa-boot-project/sofaboot-dependencies/pom.xml
@@ -121,6 +121,12 @@
import
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ ${spring.boot.version}
+
+
com.alipay.sofa
sofa-boot