feat: validate shadow v3 snapshot settings
This commit is contained in:
@@ -9,6 +9,8 @@ public class ShadowObservationProperties {
|
|||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String warehouseId = "";
|
private String warehouseId = "";
|
||||||
private int timeoutSeconds = 3;
|
private int timeoutSeconds = 3;
|
||||||
|
private int snapshotMaxAgeSeconds = 60;
|
||||||
|
private int quietIntervalMillis = 1000;
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -33,4 +35,20 @@ public class ShadowObservationProperties {
|
|||||||
public void setTimeoutSeconds(int timeoutSeconds) {
|
public void setTimeoutSeconds(int timeoutSeconds) {
|
||||||
this.timeoutSeconds = timeoutSeconds;
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSnapshotMaxAgeSeconds() {
|
||||||
|
return snapshotMaxAgeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotMaxAgeSeconds(int snapshotMaxAgeSeconds) {
|
||||||
|
this.snapshotMaxAgeSeconds = snapshotMaxAgeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuietIntervalMillis() {
|
||||||
|
return quietIntervalMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuietIntervalMillis(int quietIntervalMillis) {
|
||||||
|
this.quietIntervalMillis = quietIntervalMillis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,16 @@ public class ShadowSafetyGuard implements InitializingBean {
|
|||||||
if (!algorithmProperties.isDatabaseWritesEnabled()) {
|
if (!algorithmProperties.isDatabaseWritesEnabled()) {
|
||||||
throw new IllegalStateException("Shadow observation requires algorithm database writes for alg.* read models");
|
throw new IllegalStateException("Shadow observation requires algorithm database writes for alg.* read models");
|
||||||
}
|
}
|
||||||
if (shadowProperties.getTimeoutSeconds() <= 0) {
|
if (shadowProperties.getTimeoutSeconds() < 1 || shadowProperties.getTimeoutSeconds() > 30) {
|
||||||
throw new IllegalStateException("Shadow observation timeout must be positive");
|
throw new IllegalStateException("Shadow observation timeout must be between 1 and 30 seconds");
|
||||||
|
}
|
||||||
|
if (shadowProperties.getSnapshotMaxAgeSeconds() < 5
|
||||||
|
|| shadowProperties.getSnapshotMaxAgeSeconds() > 300) {
|
||||||
|
throw new IllegalStateException("Shadow snapshot maximum age must be between 5 and 300 seconds");
|
||||||
|
}
|
||||||
|
if (shadowProperties.getQuietIntervalMillis() < 100
|
||||||
|
|| shadowProperties.getQuietIntervalMillis() > 10000) {
|
||||||
|
throw new IllegalStateException("Shadow quiet interval must be between 100 and 10000 milliseconds");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.algorithm.shadow;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
final class WarehouseIdNormalizer {
|
||||||
|
static boolean equivalent(String first, String second) {
|
||||||
|
if (first == null || second == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String left = first.trim();
|
||||||
|
String right = second.trim();
|
||||||
|
if (left.matches("[0-9]+") && right.matches("[0-9]+")) {
|
||||||
|
return new BigInteger(left).equals(new BigInteger(right));
|
||||||
|
}
|
||||||
|
return left.equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WarehouseIdNormalizer() { }
|
||||||
|
}
|
||||||
@@ -185,6 +185,8 @@ fortucky:
|
|||||||
enabled: ${FORTUCKY_SHADOW_ENABLED:false}
|
enabled: ${FORTUCKY_SHADOW_ENABLED:false}
|
||||||
warehouse-id: ${FORTUCKY_SHADOW_WAREHOUSE_ID:}
|
warehouse-id: ${FORTUCKY_SHADOW_WAREHOUSE_ID:}
|
||||||
timeout-seconds: ${FORTUCKY_SHADOW_TIMEOUT_SECONDS:3}
|
timeout-seconds: ${FORTUCKY_SHADOW_TIMEOUT_SECONDS:3}
|
||||||
|
snapshot-max-age-seconds: ${FORTUCKY_SHADOW_SNAPSHOT_MAX_AGE_SECONDS:60}
|
||||||
|
quiet-interval-millis: ${FORTUCKY_SHADOW_QUIET_INTERVAL_MILLIS:1000}
|
||||||
algorithm:
|
algorithm:
|
||||||
mode: ${FORTUCKY_ALGORITHM_MODE:SHADOW}
|
mode: ${FORTUCKY_ALGORITHM_MODE:SHADOW}
|
||||||
warehouse-id: ${FORTUCKY_ALGORITHM_WAREHOUSE_ID:01}
|
warehouse-id: ${FORTUCKY_ALGORITHM_WAREHOUSE_ID:01}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class ShadowConfigurationTest {
|
|||||||
assertThat(shadow.isEnabled()).isFalse();
|
assertThat(shadow.isEnabled()).isFalse();
|
||||||
assertThat(shadow.getWarehouseId()).isEmpty();
|
assertThat(shadow.getWarehouseId()).isEmpty();
|
||||||
assertThat(shadow.getTimeoutSeconds()).isEqualTo(3);
|
assertThat(shadow.getTimeoutSeconds()).isEqualTo(3);
|
||||||
|
assertThat(shadow.getSnapshotMaxAgeSeconds()).isEqualTo(60);
|
||||||
|
assertThat(shadow.getQuietIntervalMillis()).isEqualTo(1000);
|
||||||
assertThat(algorithm.getMode()).isEqualTo(SchedulingMode.SHADOW);
|
assertThat(algorithm.getMode()).isEqualTo(SchedulingMode.SHADOW);
|
||||||
assertThat(algorithm.isDatabaseWritesEnabled()).isTrue();
|
assertThat(algorithm.isDatabaseWritesEnabled()).isTrue();
|
||||||
assertThat(algorithm.getWarehouseId()).isEqualTo("01");
|
assertThat(algorithm.getWarehouseId()).isEqualTo("01");
|
||||||
|
|||||||
@@ -59,6 +59,23 @@ class ShadowSafetyGuardTest {
|
|||||||
.doesNotThrowAnyException();
|
.doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsSnapshotAndQuietIntervalOutsideValidatedRanges() {
|
||||||
|
ShadowObservationProperties shadow = enabledShadow();
|
||||||
|
AlgorithmProperties algorithm = new AlgorithmProperties();
|
||||||
|
algorithm.setMode(SchedulingMode.SHADOW);
|
||||||
|
algorithm.setDatabaseWritesEnabled(true);
|
||||||
|
|
||||||
|
shadow.setSnapshotMaxAgeSeconds(4);
|
||||||
|
assertThatThrownBy(() -> new ShadowSafetyGuard(shadow, algorithm).validate())
|
||||||
|
.hasMessageContaining("between 5 and 300");
|
||||||
|
|
||||||
|
shadow.setSnapshotMaxAgeSeconds(60);
|
||||||
|
shadow.setQuietIntervalMillis(10001);
|
||||||
|
assertThatThrownBy(() -> new ShadowSafetyGuard(shadow, algorithm).validate())
|
||||||
|
.hasMessageContaining("between 100 and 10000");
|
||||||
|
}
|
||||||
|
|
||||||
private ShadowObservationProperties enabledShadow() {
|
private ShadowObservationProperties enabledShadow() {
|
||||||
ShadowObservationProperties properties = new ShadowObservationProperties();
|
ShadowObservationProperties properties = new ShadowObservationProperties();
|
||||||
properties.setEnabled(true);
|
properties.setEnabled(true);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.algorithm.shadow;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class WarehouseIdNormalizerTest {
|
||||||
|
@Test
|
||||||
|
void comparesNumericIdsByValueAndOtherIdsAfterTrimming() {
|
||||||
|
assertThat(WarehouseIdNormalizer.equivalent("1", "01")).isTrue();
|
||||||
|
assertThat(WarehouseIdNormalizer.equivalent(" 01 ", "1")).isTrue();
|
||||||
|
assertThat(WarehouseIdNormalizer.equivalent("W02", "01")).isFalse();
|
||||||
|
assertThat(WarehouseIdNormalizer.equivalent(" W02 ", "W02")).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user