mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
fix(config): correct duplicate config checking
This commit is contained in:
@@ -36,14 +36,14 @@ object ConfigCenter : AutoCloseable {
|
|||||||
declared.forEach { (path, registration) ->
|
declared.forEach { (path, registration) ->
|
||||||
val normalizedPath = normalizeRelativePath(path)
|
val normalizedPath = normalizeRelativePath(path)
|
||||||
|
|
||||||
check(normalized.putIfAbsent(normalizedPath, registration) != null) {
|
check(normalized.putIfAbsent(normalizedPath, registration) == null) {
|
||||||
"Duplicated config path declared in the same configurable: $normalizedPath"
|
"Duplicated config path declared in the same configurable: $normalizedPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized.forEach { (path, registration) ->
|
normalized.forEach { (path, registration) ->
|
||||||
check(registrations.putIfAbsent(path, registration) != null) {
|
check(registrations.putIfAbsent(path, registration) == null) {
|
||||||
"Config path already registered: $path"
|
"Config path already registered: $path"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,14 +77,16 @@ object ConfigCenter : AutoCloseable {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun initAll() {
|
fun initAll() {
|
||||||
registrations.forEach { (path, registration) ->
|
registrations.forEach { (path, registration) ->
|
||||||
try {
|
|
||||||
val config = loadConfig(path, registration)
|
val config = loadConfig(path, registration)
|
||||||
|
if (config != null) {
|
||||||
(registration as ConfigRegistration<Config>).init(config)
|
(registration as ConfigRegistration<Config>).init(config)
|
||||||
} catch (e: Exception) {
|
return
|
||||||
if (registration.configRequired()) {
|
|
||||||
throw AgentLaunchFailedException("Failed to init config", e)
|
|
||||||
}
|
}
|
||||||
|
val defaultConfig = registration.defaultConfig()
|
||||||
|
if (defaultConfig != null) {
|
||||||
|
(registration as ConfigRegistration<Config>).init(defaultConfig)
|
||||||
}
|
}
|
||||||
|
throw AgentLaunchFailedException("Failed to init config, related path: $path")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -125,18 +127,21 @@ object ConfigCenter : AutoCloseable {
|
|||||||
val registration = registrations[relativePath] ?: return
|
val registration = registrations[relativePath] ?: return
|
||||||
try {
|
try {
|
||||||
val config = loadConfig(file, registration)
|
val config = loadConfig(file, registration)
|
||||||
(registration as ConfigRegistration<Config>).init(config)
|
if (config != null) {
|
||||||
|
(registration as ConfigRegistration<Config>).onReload(config)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Config reload failed: {}", relativePath, e)
|
log.error("Config reload failed: {}", relativePath, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadConfig(file: Path, registration: ConfigRegistration<out Config>): Config {
|
private fun loadConfig(file: Path, registration: ConfigRegistration<out Config>): Config? {
|
||||||
return JSON.parseObject(Files.readString(file, StandardCharsets.UTF_8), registration.type()) as Config
|
return try {
|
||||||
|
JSON.parseObject(Files.readString(file, StandardCharsets.UTF_8), registration.type()) as Config
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Config reload failed: {}", file, e)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyReload(registration: ConfigRegistration<Config>, config: Config) {
|
|
||||||
registration.onReload(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toRelativeConfigPath(file: Path): Path? {
|
private fun toRelativeConfigPath(file: Path): Path? {
|
||||||
@@ -186,5 +191,5 @@ interface ConfigRegistration<T : Config> {
|
|||||||
fun type(): Class<T>
|
fun type(): Class<T>
|
||||||
fun init(config: T)
|
fun init(config: T)
|
||||||
fun onReload(config: T) {}
|
fun onReload(config: T) {}
|
||||||
fun configRequired(): Boolean
|
fun defaultConfig(): T?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package work.slhaf.partner.api.agent.runtime.config;
|
package work.slhaf.partner.api.agent.runtime.config;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
@@ -16,14 +17,16 @@ import java.util.function.BooleanSupplier;
|
|||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
class ConfigCenterTest {
|
class ConfigCenterTest {
|
||||||
|
|
||||||
private static final Path INITIAL_PATH = Path.of("root-initial.json");
|
private static final Path TEST_ROOT = Path.of("target", "config-center-test");
|
||||||
private static final Path NESTED_PATH = Path.of("nested", "child.json");
|
private static final Path INITIAL_PATH = TEST_ROOT.resolve("root-initial.json");
|
||||||
private static final Path DELETE_PATH = Path.of("delete", "target.json");
|
private static final Path NESTED_PATH = TEST_ROOT.resolve(Path.of("nested", "child.json"));
|
||||||
private static final Path INVALID_PATH = Path.of("invalid", "target.json");
|
private static final Path DELETE_PATH = TEST_ROOT.resolve(Path.of("delete", "target.json"));
|
||||||
private static final Path IDEMPOTENT_PATH = Path.of("idempotent", "target.json");
|
private static final Path INVALID_PATH = TEST_ROOT.resolve(Path.of("invalid", "target.json"));
|
||||||
|
private static final Path IDEMPOTENT_PATH = TEST_ROOT.resolve(Path.of("idempotent", "target.json"));
|
||||||
|
|
||||||
private static String originalUserHome;
|
private static String originalUserHome;
|
||||||
private static Path configDir;
|
private static Path configDir;
|
||||||
|
private static Path workingDir;
|
||||||
private static TrackingRegistration initialRegistration;
|
private static TrackingRegistration initialRegistration;
|
||||||
private static TrackingRegistration nestedRegistration;
|
private static TrackingRegistration nestedRegistration;
|
||||||
private static TrackingRegistration deleteRegistration;
|
private static TrackingRegistration deleteRegistration;
|
||||||
@@ -44,13 +47,19 @@ class ConfigCenterTest {
|
|||||||
invalidRegistration = new TrackingRegistration();
|
invalidRegistration = new TrackingRegistration();
|
||||||
idempotentRegistration = new TrackingRegistration();
|
idempotentRegistration = new TrackingRegistration();
|
||||||
|
|
||||||
|
workingDir = Path.of("").toAbsolutePath().normalize();
|
||||||
configDir = ConfigCenter.INSTANCE.getPaths().getConfigDir();
|
configDir = ConfigCenter.INSTANCE.getPaths().getConfigDir();
|
||||||
Files.createDirectories(configDir);
|
Files.createDirectories(configDir);
|
||||||
Files.createDirectories(configDir.resolve(NESTED_PATH).getParent());
|
Files.createDirectories(configDir.resolve(NESTED_PATH).getParent());
|
||||||
Files.createDirectories(configDir.resolve(DELETE_PATH).getParent());
|
Files.createDirectories(configDir.resolve(DELETE_PATH).getParent());
|
||||||
Files.createDirectories(configDir.resolve(INVALID_PATH).getParent());
|
Files.createDirectories(configDir.resolve(INVALID_PATH).getParent());
|
||||||
Files.createDirectories(configDir.resolve(IDEMPOTENT_PATH).getParent());
|
Files.createDirectories(configDir.resolve(IDEMPOTENT_PATH).getParent());
|
||||||
writeJson(configDir.resolve(INITIAL_PATH), "initial", 1);
|
writeJson(workingDir.resolve(INITIAL_PATH), "initial-init", 1);
|
||||||
|
writeJson(workingDir.resolve(NESTED_PATH), "nested-init", 1);
|
||||||
|
writeJson(workingDir.resolve(DELETE_PATH), "delete-init", 1);
|
||||||
|
writeJson(workingDir.resolve(INVALID_PATH), "invalid-init", 1);
|
||||||
|
writeJson(workingDir.resolve(IDEMPOTENT_PATH), "idempotent-init", 1);
|
||||||
|
writeJson(configDir.resolve(INITIAL_PATH), "initial-config-dir", 1);
|
||||||
|
|
||||||
ConfigCenter.INSTANCE.register(() -> {
|
ConfigCenter.INSTANCE.register(() -> {
|
||||||
Map<Path, ConfigRegistration<? extends Config>> declared = new LinkedHashMap<>();
|
Map<Path, ConfigRegistration<? extends Config>> declared = new LinkedHashMap<>();
|
||||||
@@ -67,6 +76,7 @@ class ConfigCenterTest {
|
|||||||
@AfterAll
|
@AfterAll
|
||||||
static void afterAll() {
|
static void afterAll() {
|
||||||
ConfigCenter.INSTANCE.close();
|
ConfigCenter.INSTANCE.close();
|
||||||
|
deleteRecursivelyIfExists(workingDir.resolve(TEST_ROOT));
|
||||||
if (originalUserHome == null) {
|
if (originalUserHome == null) {
|
||||||
System.clearProperty("user.home");
|
System.clearProperty("user.home");
|
||||||
} else {
|
} else {
|
||||||
@@ -74,6 +84,14 @@ class ConfigCenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int totalInitCount() {
|
||||||
|
return initialRegistration.initCount()
|
||||||
|
+ nestedRegistration.initCount()
|
||||||
|
+ deleteRegistration.initCount()
|
||||||
|
+ invalidRegistration.initCount()
|
||||||
|
+ idempotentRegistration.initCount();
|
||||||
|
}
|
||||||
|
|
||||||
private static int totalReloadCount() {
|
private static int totalReloadCount() {
|
||||||
return initialRegistration.reloadCount()
|
return initialRegistration.reloadCount()
|
||||||
+ nestedRegistration.reloadCount()
|
+ nestedRegistration.reloadCount()
|
||||||
@@ -82,6 +100,22 @@ class ConfigCenterTest {
|
|||||||
+ idempotentRegistration.reloadCount();
|
+ idempotentRegistration.reloadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void deleteRecursivelyIfExists(Path root) {
|
||||||
|
if (!Files.exists(root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (var stream = Files.walk(root)) {
|
||||||
|
stream.sorted((left, right) -> right.getNameCount() - left.getNameCount())
|
||||||
|
.forEach(path -> {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void writeJson(Path file, String name, int version) throws IOException {
|
private static void writeJson(Path file, String name, int version) throws IOException {
|
||||||
Files.createDirectories(file.getParent());
|
Files.createDirectories(file.getParent());
|
||||||
Files.writeString(file,
|
Files.writeString(file,
|
||||||
@@ -119,12 +153,9 @@ class ConfigCenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
void testInitialReconcileReloadsRegisteredJson() throws Exception {
|
void testStartOnlyInitializesOneRegisteredConfigAndDoesNotTriggerReload() {
|
||||||
waitForCount(initialRegistration, 1, 3000);
|
Assertions.assertEquals(1, totalInitCount());
|
||||||
|
Assertions.assertEquals(0, totalReloadCount());
|
||||||
Assertions.assertEquals(1, initialRegistration.reloadCount());
|
|
||||||
Assertions.assertEquals("initial", initialRegistration.lastConfig().name);
|
|
||||||
Assertions.assertEquals(1, initialRegistration.lastConfig().version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -226,6 +257,7 @@ class ConfigCenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class TrackingRegistration implements ConfigRegistration<TestConfig> {
|
private static class TrackingRegistration implements ConfigRegistration<TestConfig> {
|
||||||
|
private final AtomicInteger initCount = new AtomicInteger();
|
||||||
private final AtomicInteger reloadCount = new AtomicInteger();
|
private final AtomicInteger reloadCount = new AtomicInteger();
|
||||||
private final AtomicReference<TestConfig> lastConfig = new AtomicReference<>();
|
private final AtomicReference<TestConfig> lastConfig = new AtomicReference<>();
|
||||||
|
|
||||||
@@ -236,6 +268,7 @@ class ConfigCenterTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(TestConfig config) {
|
public void init(TestConfig config) {
|
||||||
|
initCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -244,6 +277,10 @@ class ConfigCenterTest {
|
|||||||
reloadCount.incrementAndGet();
|
reloadCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int initCount() {
|
||||||
|
return initCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
int reloadCount() {
|
int reloadCount() {
|
||||||
return reloadCount.get();
|
return reloadCount.get();
|
||||||
}
|
}
|
||||||
@@ -253,8 +290,8 @@ class ConfigCenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configRequired() {
|
public @Nullable TestConfig defaultConfig() {
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user