This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 98ef53407b0 IGNITE-25043 Support configuration rename (#5816)
98ef53407b0 is described below
commit 98ef53407b0b502f76e9264f92818641ba03dcca
Author: Phillippko <[email protected]>
AuthorDate: Tue May 27 08:54:25 2025 +0200
IGNITE-25043 Support configuration rename (#5816)
---
.../configuration/annotation/PublicName.java | 12 +-
.../configuration/ConfigurationChanger.java | 34 ++-
.../asm/ConfigurationAsmGenerator.java | 16 +-
.../configuration/asm/InnerNodeAsmGenerator.java | 58 +++-
.../internal/configuration/storage/Data.java | 9 +-
.../configuration/util/ConfigurationFlattener.java | 28 +-
.../util/KeysTrackingConfigurationVisitor.java | 95 +++++-
.../configuration/RenamedConfigurationTest.java | 320 +++++++++++++++++++++
.../deprecation/DeprecatedConfigurationTest.java | 122 ++++++++
.../configuration/util/ConfigurationUtilTest.java | 2 +-
.../storage/TestConfigurationStorage.java | 3 +-
.../storage/LocalFileConfigurationStorage.java | 2 +-
.../storage/LocalFileConfigurationStorageTest.java | 22 +-
13 files changed, 683 insertions(+), 40 deletions(-)
diff --git
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PublicName.java
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PublicName.java
index 32654789537..8df4827f521 100644
---
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PublicName.java
+++
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PublicName.java
@@ -33,7 +33,15 @@ import java.lang.annotation.Target;
@Documented
public @interface PublicName {
/**
- * Public configuration property name.
+ * Public configuration property name. This name, if present, is used to
store this configuration in configuration storage and to render
+ * the configuration to the user. Empty string means that public name
matches the schema field name.
*/
- String value();
+ String value() default "";
+
+ /**
+ * An array of old deprecated names for the configuration. Any of these
names should be accounted for when parsing configuration from
+ * any source, but avoided when showing to the user or saving to the
configuration storage. These names should also be deleted from the
+ * corresponding configuration storage upon encountering.
+ */
+ String[] legacyNames() default {};
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index 35ff2b8b4a8..e4c40dbd1f9 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -71,6 +71,7 @@ import
org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import
org.apache.ignite.internal.configuration.util.KeysTrackingConfigurationVisitor;
import
org.apache.ignite.internal.configuration.validation.ConfigurationValidator;
import org.apache.ignite.internal.lang.IgniteInternalException;
import org.apache.ignite.internal.lang.NodeStoppingException;
@@ -274,7 +275,7 @@ public abstract class ConfigurationChanger implements
DynamicConfigurationChange
throw new ConfigurationChangeException("Failed to initialize
configuration: " + e.getMessage(), e);
}
- Map<String, ? extends Serializable> storageValues = data.values();
+ var storageValues = new HashMap<String, Serializable>(data.values());
ignoredKeys = ignoreDeleted(storageValues, keyIgnorer);
@@ -635,9 +636,11 @@ public abstract class ConfigurationChanger implements
DynamicConfigurationChange
migrator.migrate(new SuperRootChangeImpl(changes));
- Map<String, Serializable> allChanges =
createFlattenedUpdatesMap(localRoots.rootsWithoutDefaults, changes);
-
- dropUnnecessarilyDeletedKeys(allChanges, localRoots);
+ Map<String, Serializable> allChanges = createFlattenedUpdatesMap(
+ localRoots.rootsWithoutDefaults,
+ changes,
+ localRoots.data.values()
+ );
if (onStartup) {
for (String ignoredValue : ignoredKeys) {
@@ -645,6 +648,8 @@ public abstract class ConfigurationChanger implements
DynamicConfigurationChange
}
}
+ dropUnnecessarilyDeletedKeys(allChanges, localRoots);
+
if (allChanges.isEmpty() && onStartup) {
// We don't want an empty storage update if this is the
initialization changer.
return nullCompletedFuture();
@@ -695,16 +700,18 @@ public abstract class ConfigurationChanger implements
DynamicConfigurationChange
StorageRoots oldStorageRoots = storageRoots;
try {
- Map<String, ? extends Serializable> changedValues =
changedEntries.values();
-
- // We need to ignore deletion of deprecated values.
- ignoreDeleted(changedValues, keyIgnorer);
+ var changedValues = new HashMap<String,
Serializable>(changedEntries.values());
SuperRoot oldSuperRoot = oldStorageRoots.roots;
SuperRoot oldSuperRootNoDefaults =
oldStorageRoots.rootsWithoutDefaults;
SuperRoot newSuperRoot = oldSuperRoot.copy();
SuperRoot newSuperNoDefaults = oldSuperRootNoDefaults.copy();
+ // We need to ignore deletion of deprecated values.
+ ignoreDeleted(changedValues, keyIgnorer);
+ // We need to ignore deletion of legacy values.
+ ignoreLegacyKeys(oldStorageRoots, changedValues);
+
Map<String, ?> dataValuesPrefixMap =
toPrefixMap(changedValues);
compressDeletedEntries(dataValuesPrefixMap);
@@ -760,6 +767,17 @@ public abstract class ConfigurationChanger implements
DynamicConfigurationChange
return new Data(newState, delta.changeId());
}
+ private static void ignoreLegacyKeys(StorageRoots oldStorageRoots,
Map<String, ? extends Serializable> changedValues) {
+ oldStorageRoots.roots.traverseChildren(new
KeysTrackingConfigurationVisitor<>() {
+ @Override
+ protected Object doVisitLeafNode(Field field, String key,
Serializable val) {
+ processLegacyPaths(changedValues::remove);
+
+ return null;
+ }
+ }, true);
+ }
+
/**
* Remove keys from {@code allChanges}, that are associated with nulls in
this map, and already absent in {@link StorageRoots#data}.
*/
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
index 63e113b077d..d41ce89e85e 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
@@ -43,6 +43,7 @@ import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.is
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.ArrayUtils.STRING_EMPTY_ARRAY;
import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
import static org.apache.ignite.internal.util.CollectionUtils.concat;
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
@@ -487,7 +488,20 @@ public class ConfigurationAsmGenerator {
public static String publicName(Field f) {
PublicName annotation = f.getAnnotation(PublicName.class);
- return annotation == null ? f.getName() : annotation.value();
+ return annotation == null || annotation.value().isEmpty() ?
f.getName() : annotation.value();
+ }
+
+ /**
+ * Return fields legacy names. If schema field contains {@link PublicName}
annotation, this method will return its
+ * {@link PublicName#legacyNames()} ()}. Otherwise it will return empty
array.
+ *
+ * @param f Configuration schema field.
+ * @return User-facing configuration legacy names.
+ */
+ public static String[] legacyNames(Field f) {
+ PublicName annotation = f.getAnnotation(PublicName.class);
+
+ return annotation == null ? STRING_EMPTY_ARRAY :
annotation.legacyNames();
}
/**
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
index d1841df1a53..f8e940432d4 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
@@ -48,6 +48,7 @@ import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGener
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.fieldName;
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.getThisFieldCode;
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.internalName;
+import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.legacyNames;
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.nodeClassInterfaces;
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.polymorphicIdField;
import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.publicName;
@@ -1188,6 +1189,7 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
);
// this.changePolymorphicTypeId(src == null ? null :
src.unwrap(FieldType.class));
+
switchBuilder.addCase(
publicName,
new BytecodeBlock()
@@ -1196,11 +1198,29 @@ class InnerNodeAsmGenerator extends
AbstractAsmGenerator {
.invokeVirtual(changePolymorphicTypeIdMtd)
.ret()
);
+
+ for (String legacyName : legacyNames(schemaField)) {
+ switchBuilder.addCase(
+ legacyName,
+ new BytecodeBlock()
+ .append(constructMtd.getThis())
+ .append(getTypeIdFromSrcVar)
+ .invokeVirtual(changePolymorphicTypeIdMtd)
+ .ret()
+ );
+ }
} else {
switchBuilder.addCase(
publicName,
treatSourceForConstruct(constructMtd, schemaField,
fieldDef).ret()
);
+
+ for (String legacyName : legacyNames(schemaField)) {
+ switchBuilder.addCase(
+ legacyName,
+ treatSourceForConstruct(constructMtd, schemaField,
fieldDef).ret()
+ );
+ }
}
}
@@ -1222,6 +1242,13 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
publicName,
treatSourceForConstruct(constructMtd, schemaField,
fieldDefs.get(fieldName)).ret()
);
+
+ for (String legacyName : legacyNames(schemaField)) {
+ switchBuilderAllFields.addCase(
+ legacyName,
+ treatSourceForConstruct(constructMtd, schemaField,
fieldDefs.get(fieldName)).ret()
+ );
+ }
}
// if (includeInternal) switch_by_all_fields
@@ -1253,6 +1280,13 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
publicName,
treatSourceForConstruct(constructMtd,
polymorphicField, fieldDef).ret()
);
+
+ for (String legacyName : legacyNames(polymorphicField)) {
+ switchBuilderPolymorphicExtension.addCase(
+ legacyName,
+ treatSourceForConstruct(constructMtd,
polymorphicField, fieldDef).ret()
+ );
+ }
}
switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()),
switchBuilderPolymorphicExtension.build());
@@ -1444,6 +1478,10 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
|| isPolymorphicId(schemaField) &&
!schemaField.getAnnotation(PolymorphicId.class).hasDefault()) {
// return;
switchBuilder.addCase(publicName, new
BytecodeBlock().ret());
+
+ for (String legacyName : legacyNames(schemaField)) {
+ switchBuilder.addCase(legacyName, new
BytecodeBlock().ret());
+ }
} else {
FieldDefinition fieldDef = fieldDefs.get(fieldName);
@@ -1458,6 +1496,13 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
publicName,
addNodeConstructDefault(constructDfltMtd,
schemaField, fieldDef, specFieldDef).ret()
);
+
+ for (String legacyName : legacyNames(schemaField)) {
+ switchBuilder.addCase(
+ legacyName,
+ addNodeConstructDefault(constructDfltMtd,
schemaField, fieldDef, specFieldDef).ret()
+ );
+ }
}
}
}
@@ -1479,6 +1524,10 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
if (!hasDefault(polymorphicField)) {
// return;
switchBuilderPolymorphicExtension.addCase(publicName, new
BytecodeBlock().ret());
+
+ for (String legacyName :
legacyNames(polymorphicField)) {
+
switchBuilderPolymorphicExtension.addCase(legacyName, new
BytecodeBlock().ret());
+ }
} else {
FieldDefinition fieldDef =
fieldDefs.get(fieldName(polymorphicField));
FieldDefinition specFieldDef =
specFields.get(polymorphicField.getDeclaringClass());
@@ -1488,6 +1537,13 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
publicName,
addNodeConstructDefault(constructDfltMtd,
polymorphicField, fieldDef, specFieldDef).ret()
);
+
+ for (String legacyName :
legacyNames(polymorphicField)) {
+ switchBuilderPolymorphicExtension.addCase(
+ legacyName,
+
addNodeConstructDefault(constructDfltMtd, polymorphicField, fieldDef,
specFieldDef).ret()
+ );
+ }
}
}
}
@@ -2024,7 +2080,7 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
return changePolymorphicTypeIdMtd;
}
- private String polymorphicTypeNotDefinedErrorMessage(Field
polymorphicIdField) {
+ private static String polymorphicTypeNotDefinedErrorMessage(Field
polymorphicIdField) {
return "Polymorphic configuration type is not defined: "
+ polymorphicIdField.getDeclaringClass().getName()
+ ". See @" + PolymorphicConfig.class.getSimpleName() + "
documentation.";
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/Data.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/Data.java
index 2abe50e60ca..685e7e19b6e 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/Data.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/Data.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.configuration.storage;
import java.io.Serializable;
+import java.util.Collections;
import java.util.Map;
/**
@@ -37,15 +38,11 @@ public class Data {
* @param changeId Version.
*/
public Data(Map<String, ? extends Serializable> values, long changeId) {
- this.values = values;
+ this.values = Collections.unmodifiableMap(values);
this.changeId = changeId;
}
- /**
- * Get values.
- *
- * @return Values.
- */
+ /** Get values. */
public Map<String, ? extends Serializable> values() {
return values;
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
index 9f174038dec..72e26208e9b 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -32,7 +32,7 @@ import org.apache.ignite.internal.configuration.SuperRoot;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
-/** Utility class that has {@link
ConfigurationFlattener#createFlattenedUpdatesMap(SuperRoot, SuperRoot)} method.
*/
+/** Utility class that has {@link
ConfigurationFlattener#createFlattenedUpdatesMap} method. */
public class ConfigurationFlattener {
/**
* Convert a traversable tree to a map of qualified keys to values.
@@ -41,7 +41,11 @@ public class ConfigurationFlattener {
* @param updates Tree with updates.
* @return Map of changes.
*/
- public static Map<String, Serializable>
createFlattenedUpdatesMap(SuperRoot curRoot, SuperRoot updates) {
+ public static Map<String, Serializable> createFlattenedUpdatesMap(
+ SuperRoot curRoot,
+ SuperRoot updates,
+ Map<String, ? extends Serializable> storageData
+ ) {
// Resulting map.
Map<String, Serializable> resMap = new HashMap<>();
@@ -54,7 +58,7 @@ public class ConfigurationFlattener {
// Explicit access to the children of super root guarantees that
"oldInnerNodesStack" is never empty, and thus
// we don't need null-checks when calling Deque#peek().
- updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack,
resMap), true);
+ updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack,
resMap, storageData), true);
assert oldInnerNodesStack.peek() == curRoot : oldInnerNodesStack;
@@ -91,6 +95,9 @@ public class ConfigurationFlattener {
/** Map with the result. */
private final Map<String, Serializable> resMap;
+ /** Map with values in the configuration storage. */
+ private final Map<String, ? extends Serializable> storageData;
+
/** Flag indicates that "old" and "new" trees are literally the same
at the moment. */
private boolean singleTreeTraversal;
@@ -106,9 +113,14 @@ public class ConfigurationFlattener {
* @param oldInnerNodesStack Old nodes stack for recursion.
* @param resMap Map with the result.
*/
- FlattenerVisitor(Deque<InnerNode> oldInnerNodesStack, Map<String,
Serializable> resMap) {
+ FlattenerVisitor(
+ Deque<InnerNode> oldInnerNodesStack,
+ Map<String, Serializable> resMap,
+ Map<String, ? extends Serializable> storageData
+ ) {
this.oldInnerNodesStack = oldInnerNodesStack;
this.resMap = resMap;
+ this.storageData = storageData;
}
/** {@inheritDoc} */
@@ -126,6 +138,12 @@ public class ConfigurationFlattener {
resMap.put(currentKey(), deletion ? null : newVal);
}
+ processLegacyPaths(legacyKey -> {
+ if (storageData.containsKey(legacyKey)) {
+ resMap.put(legacyKey, null);
+ }
+ });
+
return null;
}
@@ -185,7 +203,7 @@ public class ConfigurationFlattener {
String namedListFullKey = currentKey();
- withTracking(newNodeInternalId.toString(), false, false, () ->
{
+ withTracking(field, newNodeInternalId.toString(), false,
false, () -> {
InnerNode newNamedElement = isDeprecated ? null :
newNode.getInnerNode(newNodeKey);
String oldNodeKey =
oldNode.keyByInternalId(newNodeInternalId);
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
index d6753c10d48..0a5405f3f35 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
@@ -17,17 +17,23 @@
package org.apache.ignite.internal.configuration.util;
+import static
org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator.legacyNames;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.appendKey;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.join;
+import static org.apache.ignite.internal.util.ArrayUtils.STRING_EMPTY_ARRAY;
+
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
-/** Visitor that accumulates keys while descending. */
+/** Visitor that accumulates keys and legacy names while descending. */
public abstract class KeysTrackingConfigurationVisitor<T> implements
ConfigurationVisitor<T> {
/** Current key, aggregated by visitor. */
private final StringBuilder currentKey = new StringBuilder();
@@ -35,10 +41,16 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
/** Current keys list, almost the same as {@link #currentKey}. */
private final List<String> currentPath = new ArrayList<>();
+ /** For every part of the current path stores corresponding legacy names.
*/
+ private final List<String[]> currentLegacyNames = new ArrayList<>();
+
+ /** Total amount of legacy names, corresponding to the current path. */
+ private int currentLegacyNamesCount = 0;
+
/** {@inheritDoc} */
@Override
public final T visitLeafNode(Field field, String key, Serializable val) {
- int prevPos = startVisit(key, false, true);
+ int prevPos = startVisit(field, key, false, true);
try {
return doVisitLeafNode(field, key, val);
@@ -50,7 +62,7 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
/** {@inheritDoc} */
@Override
public final T visitInnerNode(Field field, String key, InnerNode node) {
- int prevPos = startVisit(key, false, false);
+ int prevPos = startVisit(field, key, false, false);
try {
return doVisitInnerNode(field, key, node);
@@ -62,7 +74,7 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
/** {@inheritDoc} */
@Override
public final T visitNamedListNode(Field field, String key,
NamedListNode<?> node) {
- int prevPos = startVisit(key, false, false);
+ int prevPos = startVisit(field, key, false, false);
try {
return doVisitNamedListNode(field, key, node);
@@ -96,7 +108,7 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
}
/**
- * To be used instead of {@link
ConfigurationVisitor#visitNamedListNode(String, NamedListNode)}.
+ * To be used instead of {@link ConfigurationVisitor#visitNamedListNode}}.
*
* @param key Name of the node retrieved from its holder object.
* @param node Named list inner configuration node.
@@ -104,7 +116,7 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
*/
protected T doVisitNamedListNode(Field field, String key, NamedListNode<?>
node) {
for (String namedListKey : node.namedListKeys()) {
- int prevPos = startVisit(namedListKey, true, false);
+ int prevPos = startVisit(field, namedListKey, true, false);
try {
doVisitInnerNode(field, namedListKey,
node.getInnerNode(namedListKey));
@@ -119,14 +131,15 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
/**
* Tracks passed key to reflect it in {@link #currentKey()} and {@link
#currentPath()}.
*
- * @param key Key itself.
- * @param escape Whether the key needs escaping or not.
- * @param leaf Add dot at the end of {@link #currentKey()} if {@code
leaf} is {@code false}.
+ * @param field Node's field.
+ * @param key Key itself.
+ * @param escape Whether the key needs escaping or not.
+ * @param leaf Add dot at the end of {@link #currentKey()} if {@code leaf}
is {@code false}.
* @param closure Closure to execute when {@link #currentKey()} and {@link
#currentPath()} have updated values.
* @return Closure result.
*/
- protected final T withTracking(String key, boolean escape, boolean leaf,
Supplier<T> closure) {
- int prevPos = startVisit(key, escape, leaf);
+ protected final T withTracking(Field field, String key, boolean escape,
boolean leaf, Supplier<T> closure) {
+ int prevPos = startVisit(field, key, escape, leaf);
try {
return closure.get();
@@ -156,11 +169,12 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
/**
* Prepares values of {@link #currentKey} and {@link #currentPath} for
further processing.
*
- * @param key Key.
+ * @param field Field.
+ * @param key Key.
* @param escape Whether we need to escape the key before appending it to
{@link #currentKey}.
* @return Previous length of {@link #currentKey} so it can be passed to
{@link #endVisit(int)} later.
*/
- private int startVisit(String key, boolean escape, boolean leaf) {
+ private int startVisit(Field field, String key, boolean escape, boolean
leaf) {
final int previousKeyLength = currentKey.length();
currentKey.append(escape ? ConfigurationUtil.escape(key) : key);
@@ -171,6 +185,11 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
currentPath.add(key);
+ String[] legacyNames = field == null ? STRING_EMPTY_ARRAY :
legacyNames(field);
+
+ currentLegacyNames.add(legacyNames);
+ currentLegacyNamesCount += legacyNames.length;
+
return previousKeyLength;
}
@@ -183,5 +202,55 @@ public abstract class KeysTrackingConfigurationVisitor<T>
implements Configurati
currentKey.setLength(previousKeyLength);
currentPath.remove(currentPath.size() - 1);
+
+ String[] legacyNames =
currentLegacyNames.remove(currentLegacyNames.size() - 1);
+ currentLegacyNamesCount -= legacyNames.length;
+ }
+
+ /** Calls consumer for all variations of legacy paths, i.e. to remove them
from the storage. */
+ protected void processLegacyPaths(Consumer<String> legacyKeyConsumer) {
+ // Current path doesn't contain any legacy names.
+ if (currentLegacyNamesCount == 0) {
+ return;
+ }
+
+ processLegacyPaths(new ArrayList<>(), legacyKeyConsumer);
+ }
+
+ private void processLegacyPaths(List<String> path, Consumer<String>
legacyKeyConsumer) {
+ // We reached the leaf. If path joined with leaf name != current key,
it is legacy and should be processed.
+ if (path.size() == currentPath().size() - 1) {
+ for (String leafName : currentLegacyNames.get(currentPath().size()
- 1)) {
+ processLeaf(path, legacyKeyConsumer, leafName);
+ }
+
+ // Process current name for cases when legacy name was in the
middle of the path.
+ processLeaf(path, legacyKeyConsumer,
currentPath().get(currentPath().size() - 1));
+
+ return;
+ }
+
+ // For inner nodes we should all legacy names and current name.
+ for (String innerNodeName : currentLegacyNames.get(path.size())) {
+ path.add(innerNodeName);
+
+ processLegacyPaths(path, legacyKeyConsumer);
+
+ path.remove(path.size() - 1);
+ }
+
+ path.add(currentPath.get(path.size()));
+
+ processLegacyPaths(path, legacyKeyConsumer);
+
+ path.remove(path.size() - 1);
+ }
+
+ private void processLeaf(List<String> path, Consumer<String>
legacyKeyConsumer, String leafName) {
+ String key = join(appendKey(path, leafName));
+
+ if (!key.equals(currentKey())) {
+ legacyKeyConsumer.accept(key);
+ }
}
}
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/RenamedConfigurationTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/RenamedConfigurationTest.java
new file mode 100644
index 00000000000..f19758b62b2
--- /dev/null
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/RenamedConfigurationTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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 org.apache.ignite.internal.configuration;
+
+import static
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static
org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+import com.typesafe.config.ConfigFactory;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.PublicName;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.storage.Data;
+import
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import
org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
+import org.apache.ignite.internal.manager.ComponentContext;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class RenamedConfigurationTest extends BaseIgniteAbstractTest {
+
+ private static final ConfigurationTreeGenerator OLD_GENERATOR = new
ConfigurationTreeGenerator(
+ Set.of(RenamedTestOldConfiguration.KEY),
+ Set.of(),
+ Set.of(RenamedPolymorphicInstanceOldConfigurationSchema.class)
+ );
+
+ private static final ConfigurationTreeGenerator NEW_GENERATOR = new
ConfigurationTreeGenerator(
+ Set.of(RenamedTestNewConfiguration.KEY),
+ Set.of(),
+ Set.of(RenamedPolymorphicInstanceNewConfigurationSchema.class)
+ );
+ public static final String OLD_DEFAULT = "oldDefault";
+
+ private final TestConfigurationStorage storage = new
TestConfigurationStorage(LOCAL);
+
+ private ConfigurationRegistry registry;
+
+ @AfterAll
+ public static void afterAll() {
+ OLD_GENERATOR.close();
+ }
+
+ @BeforeEach
+ void setUp() {
+ registry = startRegistry(RenamedTestOldConfiguration.KEY,
OLD_GENERATOR);
+
+ String updatedConfig = "key.listOldName.listInstance.oldName =
oldValue, "
+ + "key.oldPolymorphicName.polymorphicType.oldName = oldValue";
+
+
assertThat(registry.change(hoconSource(ConfigFactory.parseString(updatedConfig).root())),
willCompleteSuccessfully());
+
+ stopRegistry(registry);
+
+ registry = startRegistry(RenamedTestNewConfiguration.KEY,
NEW_GENERATOR);
+ }
+
+ @AfterEach
+ void tearDown() {
+ stopRegistry(registry);
+ }
+
+ @Test
+ public void testLegacyNameIsRecognisedOnStartup() {
+ // Default value was set when registry was started with old
configuration.
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().newName().value(),
+ equalTo(OLD_DEFAULT)
+ );
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().name().value(),
+ equalTo(OLD_DEFAULT)
+ );
+
+ assertThat(storage.readLatest("key.oldInnerName.oldName"),
willBe(nullValue()));
+ assertThat(storage.readLatest("key.oldInnerName.name"),
willBe(nullValue()));
+ }
+
+ @Test
+ public void testLegacyNameIsRecognisedOnUpdate() {
+ String updatedValue = "updatedValue";
+ String configWithFirstLegacyName = "key.oldInnerName.oldName = " +
updatedValue;
+ updateConfig(registry, configWithFirstLegacyName);
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().newName().value(),
+ equalTo(updatedValue)
+ );
+ assertThat(storage.readLatest("key.oldInnerName.oldName"),
willBe(nullValue()));
+
+ String secondUpdatedValue = "secondUpdatedValue";
+ String configWithSecondLegacyName = "key.secondOldInnerName.oldName =
" + secondUpdatedValue;
+
+ updateConfig(registry, configWithSecondLegacyName);
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().newName().value(),
+ equalTo(secondUpdatedValue)
+ );
+ assertThat(storage.readLatest("key.secondOldInnerName.oldName"),
willBe(nullValue()));
+ }
+
+ @Test
+ public void testNewValuePersistsAfterRestart() {
+ String updatedValue = "updatedValue";
+ String updatedConfig = "key.oldInnerName.oldName = " + updatedValue;
+ updateConfig(registry, updatedConfig);
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().newName().value(),
+ equalTo(updatedValue)
+ );
+
+ stopRegistry(registry);
+ registry = startRegistry(RenamedTestNewConfiguration.KEY,
NEW_GENERATOR);
+
+ assertThat(storage.readLatest("key.oldInnerName.oldName"),
willBe(nullValue()));
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newInnerName().newName().value(),
+ equalTo(updatedValue)
+ );
+ }
+
+ @Test
+ @Disabled("IGNITE-25458")
+ public void testNamedListLegacyNameIsRecognisedOnStartup() {
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newListName().get("listInstance").newName().value(),
+ equalTo("oldValue")
+ );
+
+ // TODO IGNITE-25458 Add check that old value is deleted
+ }
+
+ @Test
+ public void testNamedListLegacyNameIsRecognisedOnUpdate() {
+ String newValue = "newValue";
+
+ String updatedConfig = "key.listOldName.listInstance.oldName = " +
newValue;
+ updateConfig(registry, updatedConfig);
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newListName().get("listInstance").newName().value(),
+ equalTo(newValue)
+ );
+
+ // TODO IGNITE-25458 Add check that old value is deleted
+ }
+
+ @Test
+ @Disabled("IGNITE-25458")
+ public void testPolymorphicLegacyNameIsRecognisedOnStartup() {
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newPolymorphicName().get("polymorphicType").newName().value(),
+ equalTo("oldValue")
+ );
+
+ // TODO IGNITE-25458 Add check that old value is deleted
+ }
+
+ @Test
+ public void testPolymorphicLegacyNameIsRecognisedOnUpdate() {
+ String newValue = "newValue";
+ String updatedConfig = "key.oldPolymorphicName.polymorphicType.oldName
= " + newValue;
+ updateConfig(registry, updatedConfig);
+
+ assertThat(
+
registry.getConfiguration(RenamedTestNewConfiguration.KEY).newPolymorphicName().get("polymorphicType").newName().value(),
+ equalTo(newValue)
+ );
+
+ // TODO IGNITE-25458 Add check that old value is deleted
+ }
+
+ private static void updateConfig(ConfigurationRegistry registry, String
updatedConfig) {
+ CompletableFuture<Void> change =
registry.change(hoconSource(ConfigFactory.parseString(updatedConfig).root()));
+ assertThat(change, willCompleteSuccessfully());
+ }
+
+ private ConfigurationRegistry startRegistry(RootKey<?, ?> rootKey,
ConfigurationTreeGenerator generator) {
+ var registry = new ConfigurationRegistry(Set.of(rootKey), storage,
generator, new TestConfigurationValidator());
+
+ assertThat(registry.startAsync(new ComponentContext()),
willCompleteSuccessfully());
+ assertThat(registry.onDefaultsPersisted(), willCompleteSuccessfully());
+
+ return registry;
+ }
+
+ private void stopRegistry(ConfigurationRegistry registry) {
+ assertThat(registry.stopAsync(), willCompleteSuccessfully());
+ // Removes registry update listener.
+ storage.close();
+ }
+
+ private Data getData() {
+ CompletableFuture<Data> dataFuture = storage.readDataOnRecovery();
+
+ assertThat(dataFuture, willCompleteSuccessfully());
+
+ return dataFuture.join();
+ }
+
+ @ConfigurationRoot(rootName = "key", type = LOCAL)
+ public static class RenamedTestOldConfigurationSchema {
+ @ConfigValue
+ @PublicName("oldInnerName")
+ public RenamedLeafOldConfigurationSchema oldInnerName;
+
+ @NamedConfigValue
+ @PublicName("listOldName")
+ public RenamedLeafOldConfigurationSchema oldListName;
+
+ @NamedConfigValue
+ @PublicName("oldPolymorphicName")
+ public RenamedPolymorphicOldConfigurationSchema oldPolymorphicName;
+ }
+
+ @ConfigurationRoot(rootName = "key", type = LOCAL)
+ public static class RenamedTestNewConfigurationSchema {
+ @ConfigValue
+ @PublicName(legacyNames = {"oldInnerName", "secondOldInnerName"})
+ public RenamedLeafNewConfigurationSchema newInnerName;
+
+ @NamedConfigValue
+ @PublicName(legacyNames = {"listOldName"})
+ public RenamedLeafNewConfigurationSchema newListName;
+
+ @NamedConfigValue
+ @PublicName(legacyNames = "oldPolymorphicName")
+ public RenamedPolymorphicNewConfigurationSchema newPolymorphicName;
+ }
+
+ @Config
+ public static class RenamedLeafOldConfigurationSchema {
+ @Value(hasDefault = true)
+ @PublicName("oldName")
+ public String oldName = OLD_DEFAULT;
+
+ @Value(hasDefault = true)
+ public String name = OLD_DEFAULT;
+ }
+
+ @Config
+ public static class RenamedLeafNewConfigurationSchema {
+ @Value(hasDefault = true)
+ @PublicName(legacyNames = {"oldName"})
+ public String newName = "newDefault";
+
+ @Value(hasDefault = true)
+ public String name = "newDefault";
+ }
+
+ @PolymorphicConfig
+ public static class RenamedPolymorphicOldConfigurationSchema {
+ @PolymorphicId(hasDefault = true)
+ public String type = "polymorphicType";
+
+ @InjectedName
+ public String name;
+
+ @Value(hasDefault = true)
+ @PublicName("oldName")
+ public String oldName = OLD_DEFAULT;
+ }
+
+ @PolymorphicConfigInstance("polymorphicType")
+ public static class RenamedPolymorphicInstanceOldConfigurationSchema
extends RenamedPolymorphicOldConfigurationSchema {
+ }
+
+ @PolymorphicConfig
+ public static class RenamedPolymorphicNewConfigurationSchema {
+ @PolymorphicId(hasDefault = true)
+ public String type = "polymorphicType";
+
+ @InjectedName
+ public String name;
+
+ @Value(hasDefault = true)
+ @PublicName(legacyNames = "oldName")
+ public String newName = "newDefault";
+ }
+
+ @PolymorphicConfigInstance("polymorphicType")
+ public static class RenamedPolymorphicInstanceNewConfigurationSchema
extends RenamedPolymorphicNewConfigurationSchema{
+ }
+}
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/deprecation/DeprecatedConfigurationTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/deprecation/DeprecatedConfigurationTest.java
index b7146bfe3de..1952efe9c96 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/deprecation/DeprecatedConfigurationTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/deprecation/DeprecatedConfigurationTest.java
@@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.SuperRootChange;
+import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.ConfigurationType;
@@ -145,6 +146,31 @@ public class DeprecatedConfigurationTest extends
BaseIgniteAbstractTest {
public NamedElementConfigurationSchema list;
}
+ /**
+ * Configuration root schema with deprecated field inside named list
configuration.
+ */
+ @ConfigurationRoot(rootName = "root", type = ConfigurationType.LOCAL)
+ public static class DeprecatedFieldInNamedListConfigurationSchema {
+ @Value(hasDefault = true)
+ public int intValue = 10;
+
+ @ConfigValue
+ public ChildConfigurationSchema child;
+
+ @NamedConfigValue
+ public DeprecatedElementConfigurationSchema list;
+ }
+
+ /**
+ * Configuration schema with deprecated field inside.
+ */
+ @Config
+ public static class DeprecatedElementConfigurationSchema {
+ @Deprecated
+ @Value
+ public String strCfg;
+ }
+
@BeforeEach
void setUp() {
storage = spy(new TestConfigurationStorage(TEST_CONFIGURATION_TYPE));
@@ -227,6 +253,20 @@ public class DeprecatedConfigurationTest extends
BaseIgniteAbstractTest {
});
}
+ @Test
+ void testDeprecationReturnsDefaultValue() {
+ ConfigurationMigrator changeIntValue = change ->
change.changeRoot(BeforeDeprecationConfiguration.KEY).changeIntValue(999);
+
+ withConfigurationChanger(BeforeDeprecationConfiguration.KEY, true,
changeIntValue, changer -> {});
+
+ withConfigurationChanger(DeprecatedValueConfiguration.KEY, false,
change -> {}, changer -> {
+ //noinspection CastToIncompatibleInterface
+ var root = (DeprecatedValueView)
changer.superRoot().getRoot(DeprecatedValueConfiguration.KEY);
+ // Deprecated value should be read as a default.
+ assertEquals(10, root.intValue());
+ });
+ }
+
@Test
void testConfigurationMigrator() {
withConfigurationChanger(BeforeDeprecationConfiguration.KEY, true,
change -> {}, changer -> {});
@@ -396,6 +436,88 @@ public class DeprecatedConfigurationTest extends
BaseIgniteAbstractTest {
});
}
+ @Test
+ void testDeprecatedValueInNamedListConfiguration() {
+ AtomicReference<UUID> internalIdReference = new AtomicReference<>();
+
+ withConfigurationChanger(BeforeDeprecationConfiguration.KEY, true,
change -> {}, changer -> {
+ // Add named list element for the test.
+ changeConfiguration(changer, superRoot -> {
+ superRoot.changeRoot(BeforeDeprecationConfiguration.KEY)
+ .changeList()
+ .create("foo", namedElementChange -> {
+ internalIdReference.set(((InnerNode)
namedElementChange).internalId());
+ namedElementChange.changeStrCfg("value");
+ }
+ );
+ });
+
+ UUID internalId = internalIdReference.get();
+ assertNotNull(internalId);
+
+ // Check that all expected properties have been added.
+ assertEquals(
+ Map.of(
+ "root.list." + internalId + ".<order>", 0,
+ "root.list." + internalId + ".<name>", "foo",
+ "root.list." + internalId + ".strCfg", "value",
+ "root.list.<ids>.foo", internalId
+ ),
+ lastWriteCapture.getValue()
+ );
+
+ // Check storage state.
+ Data data = getData();
+
+ assertEquals(2, data.changeId());
+ assertEquals(
+ Map.of(
+ "root.intValue", 10,
+ "root.child.my-int-cfg", 99,
+ "root.list.<ids>.foo", internalId,
+ "root.list." + internalId + ".<order>", 0,
+ "root.list." + internalId + ".strCfg", "value",
+ "root.list." + internalId + ".<name>", "foo"
+ ),
+ data.values()
+ );
+ });
+
+ withConfigurationChanger(DeprecatedFieldInNamedListConfiguration.KEY,
false, change -> {}, changer -> {
+ //noinspection CastToIncompatibleInterface
+ var root = (DeprecatedFieldInNamedListView)
changer.superRoot().getRoot(DeprecatedFieldInNamedListConfiguration.KEY);
+ assertNotNull(root.list());
+
+ UUID internalId = internalIdReference.get();
+
+ // Check that deprecated value is accessible.
+ assertEquals("value", root.list().get(internalId).strCfg());
+
+ // Check that deprecated value has been deleted while starting the
changer.
+ assertEquals(
+ mapWithNulls(
+ "root.list." + internalId + ".strCfg", null
+ ),
+ lastWriteCapture.getValue()
+ );
+
+ // Check that deprecated value is not present in the storage
anymore.
+ Data data = getData();
+
+ assertEquals(3, data.changeId());
+ assertEquals(
+ Map.of(
+ "root.intValue", 10,
+ "root.child.my-int-cfg", 99,
+ "root.list.<ids>.foo", internalId,
+ "root.list." + internalId + ".<order>", 0,
+ "root.list." + internalId + ".<name>", "foo"
+ ),
+ data.values()
+ );
+ });
+ }
+
private void withConfigurationChanger(
RootKey<?, ?> rootKey,
boolean init,
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index 0ec72f49b7d..18fb0324b4a 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -1192,7 +1192,7 @@ public class ConfigurationUtilTest {
patch.accept(superRoot.getRoot(rootKey));
// Create flat diff between two super trees.
- return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
+ return createFlattenedUpdatesMap(originalSuperRoot, superRoot,
Map.of());
}
/**
diff --git
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
index 3ea658c6997..7bd50a019a9 100644
---
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
+++
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
@@ -63,7 +63,8 @@ public class TestConfigurationStorage implements
ConfigurationStorage {
@Override
public void close() {
- // No-op.
+ // To reuse this instance with new configuration changer.
+ listeners.clear();
}
/**
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
index 358b25cbec6..120f63883b0 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
@@ -175,7 +175,7 @@ public class LocalFileConfigurationStorage implements
ConfigurationStorage {
Config hocon = readHoconFromFile();
HoconConverter.hoconSource(hocon.root(),
keyIgnorer).descend(copiedSuperRoot);
- Map<String, Serializable> flattenedUpdatesMap =
createFlattenedUpdatesMap(superRoot, copiedSuperRoot);
+ Map<String, Serializable> flattenedUpdatesMap =
createFlattenedUpdatesMap(superRoot, copiedSuperRoot, Map.of());
flattenedUpdatesMap.forEach((key, value) -> {
if (value != null) { // Filter defaults.
latest.put(key, value);
diff --git
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
index 51bc16dcef6..962687dcf73 100644
---
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
+++
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.configuration.storage;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
@@ -27,6 +28,7 @@ import static
org.hamcrest.Matchers.equalToCompressingWhiteSpace;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasValue;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import com.typesafe.config.ConfigFactory;
@@ -45,6 +47,7 @@ import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PublicName;
import org.apache.ignite.configuration.annotation.Value;
import
org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
@@ -580,8 +583,24 @@ public class LocalFileConfigurationStorageTest {
Files.write(configFile, fileContent.getBytes(StandardCharsets.UTF_8));
- // Storage ignores deleted property.
+ // Deleted properties are ignored and removed from the storage.
assertDoesNotThrow(changer::start);
+ assertThat(storage.readLatest("top.deleted_property"),
willBe(nullValue()));
+ }
+
+ @Test
+ void testReadDataOnStartupWithRenamedProperty() throws IOException {
+ // Given config in JSON format
+ String fileContent = "top.oldShortValName = 3";
+
+ Path configFile = getConfigFile();
+
+ Files.write(configFile, fileContent.getBytes(StandardCharsets.UTF_8));
+
+ // Storage handles renamed property.
+ assertDoesNotThrow(changer::start);
+
+ assertThat(storage.readLatest("top.shortVal"), willBe((short) 3));
}
private String configFileContent() throws IOException {
@@ -602,6 +621,7 @@ public class LocalFileConfigurationStorageTest {
public InnerConfigurationSchema inner;
@Value(hasDefault = true)
+ @PublicName(legacyNames = "oldShortValName")
public short shortVal = 1;
@Deprecated