You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by mp...@apache.org on 2025/10/23 14:31:52 UTC

(ignite-3) branch IGNITE-26363 created (now d6a83d61860)

This is an automated email from the ASF dual-hosted git repository.

mpochatkin pushed a change to branch IGNITE-26363
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


      at d6a83d61860 IGNITE-26363 Node configuration validation on restart

This branch includes the following new commits:

     new d6a83d61860 IGNITE-26363 Node configuration validation on restart

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(ignite-3) 01/01: IGNITE-26363 Node configuration validation on restart

Posted by mp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mpochatkin pushed a commit to branch IGNITE-26363
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit d6a83d61860d79b11b3d43602f068d7de0985bbe
Author: Pochatkin Mikhail <m....@gmail.com>
AuthorDate: Wed Oct 8 11:29:02 2025 +0300

    IGNITE-26363 Node configuration validation on restart
---
 .../storage/DistributedConfigurationStorage.java   | 15 ++--
 .../storage/LocalFileConfigurationStorage.java     | 91 ++++++++++++++--------
 ...Storage.java => VaultConfigurationStorage.java} | 20 ++---
 .../storage/ConfigurationStorageTest.java          |  2 +-
 .../storage/LocalFileConfigurationStorageTest.java | 38 +++++++--
 ...est.java => VaultConfigurationStorageTest.java} |  6 +-
 modules/configuration/build.gradle                 |  4 +
 .../configuration/ItNodeConfigurationTest.java     | 12 +++
 .../configuration/ConfigurationChanger.java        | 72 +++++++----------
 .../configuration/ConfigurationRegistry.java       |  1 -
 .../configuration/ConfigurationUpdateListener.java | 23 ++++++
 .../storage/ConfigurationStorage.java              |  8 +-
 .../storage/ConfigurationStorageListener.java      |  2 +-
 .../storage/{Data.java => ReadEntry.java}          | 17 +++-
 .../internal/configuration/storage/WriteEntry.java | 12 +++
 .../configuration/storage/WriteEntryImpl.java      | 61 +++++++++++++++
 .../configuration/ConfigurationChangerTest.java    |  4 +-
 .../configuration/LocalFileConfigurationTest.java  |  5 ++
 .../configuration/RenamedConfigurationTest.java    |  8 +-
 .../configuration/TestSubConfigurationSchema.java  |  2 +
 .../deprecation/DeprecatedConfigurationTest.java   | 20 ++---
 .../configuration/tree/NamedListNodeTest.java      | 16 ++--
 .../storage/TestConfigurationStorage.java          | 12 +--
 modules/distribution-zones/build.gradle            |  1 +
 ...niteDistributionZoneManagerNodeRestartTest.java |  3 +-
 .../rebalance/ItRebalanceDistributedTest.java      |  3 +-
 modules/partition-replicator/build.gradle          |  1 +
 .../partition/replicator/fixtures/Node.java        |  3 +-
 modules/runner/build.gradle                        |  2 +
 .../configuration/generator/DefaultsGenerator.java | 11 ++-
 .../ItDistributedConfigurationStorageTest.java     |  4 +-
 .../runner/app/ItIgniteNodeRestartTest.java        |  3 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |  1 +
 33 files changed, 335 insertions(+), 148 deletions(-)

diff --git a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorage.java b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorage.java
index 90c4b5d5c83..2fa676a2c0e 100644
--- a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorage.java
+++ b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorage.java
@@ -176,14 +176,14 @@ public class DistributedConfigurationStorage implements ConfigurationStorage {
     }
 
     @Override
-    public CompletableFuture<Data> readDataOnRecovery() throws StorageException {
-        CompletableFuture<Data> future = metaStorageMgr.recoveryFinishedFuture()
+    public CompletableFuture<ReadEntry> readDataOnRecovery() throws StorageException {
+        CompletableFuture<ReadEntry> future = metaStorageMgr.recoveryFinishedFuture()
                 .thenApplyAsync(revisions -> readDataOnRecovery0(revisions.revision()), threadPool);
 
         return registerFuture(future);
     }
 
-    private Data readDataOnRecovery0(long metaStorageRevision) {
+    private ReadEntry readDataOnRecovery0(long metaStorageRevision) {
         Map<String, Serializable> data = new HashMap<>();
         long cfgRevision = 0;
 
@@ -221,12 +221,13 @@ public class DistributedConfigurationStorage implements ConfigurationStorage {
 
         changeId = cfgRevision;
 
-        return new Data(data, cfgRevision);
+        return new ReadEntry(data, cfgRevision);
     }
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Boolean> write(Map<String, ? extends Serializable> newValues, long curChangeId) {
+    public CompletableFuture<Boolean> write(WriteEntry writeEntry) {
+        long curChangeId = writeEntry.version();
         assert curChangeId <= changeId;
         assert lsnr != null : "Configuration listener must be initialized before write.";
 
@@ -239,7 +240,7 @@ public class DistributedConfigurationStorage implements ConfigurationStorage {
 
         var operations = new ArrayList<Operation>();
 
-        for (Map.Entry<String, ? extends Serializable> entry : newValues.entrySet()) {
+        for (Map.Entry<String, ? extends Serializable> entry : writeEntry.newValues().entrySet()) {
             ByteArray key = new ByteArray(DISTRIBUTED_PREFIX + entry.getKey());
 
             if (entry.getValue() != null) {
@@ -297,7 +298,7 @@ public class DistributedConfigurationStorage implements ConfigurationStorage {
 
             changeId = newChangeId;
 
-            return lsnr.onEntriesChanged(new Data(data, newChangeId));
+            return lsnr.onEntriesChanged(new ReadEntry(data, newChangeId));
         });
     }
 
diff --git a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
index 003c39e0caa..93d7657cd8d 100644
--- a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
+++ b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
@@ -24,7 +24,6 @@ import static org.apache.ignite.internal.configuration.util.ConfigurationFlatten
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
 import static org.apache.ignite.internal.util.CompletableFutures.falseCompletedFuture;
-import static org.apache.ignite.internal.util.CompletableFutures.trueCompletedFuture;
 
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigException.Parse;
@@ -71,7 +70,10 @@ import org.apache.ignite.internal.future.InFlightFutures;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.thread.IgniteThreadFactory;
+import org.apache.ignite.internal.util.CompletableFutures;
 import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.VaultService;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
 
@@ -108,6 +110,8 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
     /** Tracks all running futures. */
     private final InFlightFutures futureTracker = new InFlightFutures();
 
+    private final VaultConfigurationStorage vaultStorage;
+
     /** Last revision for configuration. */
     private long lastRevision = 0L;
 
@@ -119,8 +123,13 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
      * @param module Configuration module, which provides configuration patches.
      */
     @TestOnly
-    public LocalFileConfigurationStorage(Path configPath, ConfigurationTreeGenerator generator, @Nullable ConfigurationModule module) {
-        this("test", configPath, generator, module);
+    public LocalFileConfigurationStorage(
+            Path configPath,
+            ConfigurationTreeGenerator generator,
+            VaultService vaultService,
+            @Nullable ConfigurationModule module
+    ) {
+        this("test", configPath, generator, new VaultManager(vaultService), module);
     }
 
     /**
@@ -131,11 +140,23 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
      * @param module Configuration module, which provides configuration patches.
      */
     public LocalFileConfigurationStorage(
-            String nodeName, Path configPath, ConfigurationTreeGenerator generator, @Nullable ConfigurationModule module) {
+            String nodeName,
+            Path configPath,
+            ConfigurationTreeGenerator generator,
+            VaultManager vaultManager,
+            @Nullable ConfigurationModule module
+    ) {
         this.configPath = configPath;
         this.generator = generator;
         this.tempConfigPath = configPath.resolveSibling(configPath.getFileName() + ".tmp");
         this.module = module;
+        this.vaultStorage = new VaultConfigurationStorage(nodeName, vaultManager);
+        vaultStorage.registerConfigurationListener(new ConfigurationStorageListener() {
+            @Override
+            public CompletableFuture<Void> onEntriesChanged(ReadEntry changedEntries) {
+                return CompletableFutures.nullCompletedFuture();
+            }
+        });
 
         notificationsThreadPool = Executors.newFixedThreadPool(
                 2, IgniteThreadFactory.create(nodeName, "cfg-file", LOG)
@@ -145,29 +166,31 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
     }
 
     @Override
-    public CompletableFuture<Data> readDataOnRecovery() {
+    public CompletableFuture<ReadEntry> readDataOnRecovery() {
         lock.writeLock().lock();
         try {
-            // Here we don't use ConfigurationDynamicDefaultsPatcher because it works only on Hocon string representation level.
-            // But it's not applicable here because we need to produce map presentation with same ids in names lists.
-            // Each tree walk for string to map mapping produce different ids by design.
-            String hocon = readHoconFromFile();
-            SuperRoot superRoot = convertToSuperRoot(hocon);
-
-            Map<String, Serializable> transformedHocon = transformToMap(superRoot);
-
-            transformedHocon.forEach((key, value) -> {
-                if (value != null) { // Filter defaults.
-                    latest.put(key, value);
+            return vaultStorage.readDataOnRecovery().thenApply(data -> {
+                // Here we don't use ConfigurationDynamicDefaultsPatcher because it works only on Hocon string representation level.
+                // But it's not applicable here because we need to produce map presentation with same ids in names lists.
+                // Each tree walk for string to map mapping produce different ids by design.
+                String hocon = readHoconFromFile();
+                SuperRoot superRoot = convertToSuperRoot(hocon);
+
+                Map<String, Serializable> transformedHocon = transformToMap(superRoot);
+
+                transformedHocon.forEach((key, value) -> {
+                    if (value != null) { // Filter defaults.
+                        latest.put(key, value);
+                    }
+                });
+
+                if (module != null) {
+                    module.patchConfigurationWithDynamicDefaults(new SuperRootChangeImpl(superRoot));
+                    transformedHocon = transformToMap(superRoot);
                 }
-            });
 
-            if (module != null) {
-                module.patchConfigurationWithDynamicDefaults(new SuperRootChangeImpl(superRoot));
-                transformedHocon = transformToMap(superRoot);
-            }
-
-            return completedFuture(new Data(transformedHocon, lastRevision));
+                return new ReadEntry(transformedHocon, lastRevision, data.values());
+            });
         } finally {
             lock.writeLock().unlock();
         }
@@ -241,18 +264,25 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
     }
 
     @Override
-    public CompletableFuture<Boolean> write(Map<String, ? extends Serializable> newValues, long ver) {
+    public CompletableFuture<Boolean> write(WriteEntry writeEntry) {
         lock.writeLock().lock();
         try {
+            long ver = writeEntry.version();
             if (ver != lastRevision) {
                 return falseCompletedFuture();
             }
 
-            mergeAndSave(newValues);
+            return vaultStorage.write(writeEntry).thenApply(success -> {
+                if (!success) {
+                    return false;
+                }
 
-            sendNotificationAsync(new Data(newValues, lastRevision));
+                Map<String, ? extends Serializable> newValues = writeEntry.valuesWithFilteredDefaults();
+                mergeAndSave(newValues);
+                sendNotificationAsync(new ReadEntry(newValues, lastRevision));
 
-            return trueCompletedFuture();
+                return true;
+            });
         } finally {
             lock.writeLock().unlock();
         }
@@ -303,11 +333,6 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
         IgniteUtils.shutdownAndAwaitTermination(notificationsThreadPool, 10, TimeUnit.SECONDS);
     }
 
-    @Override
-    public boolean supportDefaults() {
-        return false;
-    }
-
     private void saveConfigFile() {
         if (!Files.isWritable(configPath)) {
             NodeConfigWriteException e = new NodeConfigWriteException(
@@ -394,7 +419,7 @@ public class LocalFileConfigurationStorage implements ConfigurationStorage {
         }
     }
 
-    private void sendNotificationAsync(Data data) {
+    private void sendNotificationAsync(ReadEntry data) {
         CompletableFuture<Void> future = CompletableFuture.runAsync(
                 () -> lsnrRef.get().onEntriesChanged(data),
                 notificationsThreadPool
diff --git a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorage.java b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorage.java
similarity index 91%
rename from modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorage.java
rename to modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorage.java
index edc73d925fc..0b89d4323ed 100644
--- a/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorage.java
+++ b/modules/configuration-storage/src/main/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorage.java
@@ -46,7 +46,7 @@ import org.apache.ignite.internal.vault.VaultManager;
 /**
  * Local configuration storage.
  */
-public class LocalConfigurationStorage implements ConfigurationStorage {
+public class VaultConfigurationStorage implements ConfigurationStorage {
     /** Prefix that we add to configuration keys to distinguish them in the Vault. */
     private static final String LOC_PREFIX = "loc-cfg.";
 
@@ -54,7 +54,7 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
     private static final ByteArray VERSION_KEY = new ByteArray(LOC_PREFIX + "$version");
 
     /** Logger. */
-    private static final IgniteLogger LOG = Loggers.forClass(LocalConfigurationStorage.class);
+    private static final IgniteLogger LOG = Loggers.forClass(VaultConfigurationStorage.class);
 
     /** Vault manager. */
     private final VaultManager vaultMgr;
@@ -89,7 +89,7 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
      *
      * @param vaultMgr Vault manager.
      */
-    public LocalConfigurationStorage(String nodeName, VaultManager vaultMgr) {
+    public VaultConfigurationStorage(String nodeName, VaultManager vaultMgr) {
         this.vaultMgr = vaultMgr;
         this.threadPool = Executors.newFixedThreadPool(4, IgniteThreadFactory.create(nodeName, "loc-cfg", LOG));
     }
@@ -108,7 +108,7 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
 
         var rangeEnd = new ByteArray(incrementLastChar(LOC_PREFIX + prefix));
 
-        return readAll(rangeStart, rangeEnd).thenApply(Data::values);
+        return readAll(rangeStart, rangeEnd).thenApply(ReadEntry::values);
     }
 
     /** {@inheritDoc} */
@@ -127,14 +127,14 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Data> readDataOnRecovery() {
+    public CompletableFuture<ReadEntry> readDataOnRecovery() {
         return readAll(LOC_KEYS_START_RANGE, LOC_KEYS_END_RANGE);
     }
 
     /**
      * Retrieves all data, which keys lie in between {@code [rangeStart, rangeEnd)}.
      */
-    private CompletableFuture<Data> readAll(ByteArray rangeStart, ByteArray rangeEnd) {
+    private CompletableFuture<ReadEntry> readAll(ByteArray rangeStart, ByteArray rangeEnd) {
         return registerFuture(supplyAsync(() -> {
             var data = new HashMap<String, Serializable>();
 
@@ -156,14 +156,16 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
                 throw new StorageException("Exception when closing a Vault cursor", e);
             }
 
-            return new Data(data, version);
+            return new ReadEntry(data, version);
         }, threadPool));
     }
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Boolean> write(Map<String, ? extends Serializable> newValues, long sentVersion) {
+    public CompletableFuture<Boolean> write(WriteEntry writeEntry) {
         synchronized (writeSerializationLock) {
+            long sentVersion = writeEntry.version();
+            Map<String, ? extends Serializable> newValues = writeEntry.newValues();
             CompletableFuture<Boolean> writeFuture = registerFuture(writeSerializationFuture
                     .thenCompose(v -> lastRevision())
                     .thenComposeAsync(version -> {
@@ -192,7 +194,7 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
                             ));
                         }
 
-                        Data entries = new Data(newValues, version + 1);
+                        ReadEntry entries = new ReadEntry(newValues, version + 1);
 
                         vaultMgr.putAll(data);
 
diff --git a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageTest.java b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageTest.java
index 27005298691..b95bd9350dc 100644
--- a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageTest.java
+++ b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageTest.java
@@ -57,7 +57,7 @@ public abstract class ConfigurationStorageTest {
     public void testReadAllLatest() {
         var data = Map.of("foo1", "bar1", "foo2", "bar2");
 
-        assertThat(storage.write(data, 0), willBe(equalTo(true)));
+        assertThat(storage.write(new WriteEntryImpl(data, 0)), willBe(equalTo(true)));
 
         // test that reading without a prefix retrieves all data
         CompletableFuture<Map<String, ? extends Serializable>> latestData = storage.readAllLatest("");
diff --git a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
index 6d46b528df8..b1b6bea8a04 100644
--- a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
+++ b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
@@ -46,6 +46,7 @@ import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.ignite.configuration.ConfigurationChangeException;
 import org.apache.ignite.configuration.KeyIgnorer;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
@@ -58,6 +59,7 @@ 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.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
 import org.apache.ignite.internal.configuration.hocon.HoconConverter;
@@ -65,6 +67,7 @@ import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.validation.ConfigurationValidatorImpl;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -110,7 +113,7 @@ public class LocalFileConfigurationStorageTest {
     @BeforeEach
     void before() {
         LocalFileConfigurationModule module = new LocalFileConfigurationModule();
-        storage = new LocalFileConfigurationStorage(getConfigFile(), treeGenerator, module);
+        storage = new LocalFileConfigurationStorage(getConfigFile(), treeGenerator, new InMemoryVaultService(), module);
 
         changer = new TestConfigurationChanger(
                 List.of(TopConfiguration.KEY),
@@ -478,7 +481,7 @@ public class LocalFileConfigurationStorageTest {
         ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON).setAllowMissing(false);
         assertDoesNotThrow(() -> ConfigFactory.parseFile(configFile.toFile(), parseOptions));
 
-        LocalFileConfigurationStorage storage = new LocalFileConfigurationStorage(configFile, treeGenerator, null);
+        LocalFileConfigurationStorage storage = new LocalFileConfigurationStorage(configFile, treeGenerator, new InMemoryVaultService(), null);
 
         // And storage reads the file successfully
         assertDoesNotThrow(storage::readDataOnRecovery);
@@ -507,7 +510,7 @@ public class LocalFileConfigurationStorageTest {
 
         // And storage detects duplicates
         assertThrows(
-                ConfigurationValidationException.class,
+                ConfigurationChangeException.class,
                 changer::start,
                 "Validation did not pass for keys: [top.inner.boolVal, Duplicated key]"
         );
@@ -551,8 +554,29 @@ public class LocalFileConfigurationStorageTest {
 
         assertFalse(Files.isWritable(configFile));
 
-        var storage = new LocalFileConfigurationStorage(configFile, treeGenerator, new LocalFileConfigurationModule());
-        assertThat(storage.write(Map.of(), storage.localRevision().get() + 1), willCompleteSuccessfully());
+        var storage = new LocalFileConfigurationStorage(
+                configFile,
+                treeGenerator,
+                new InMemoryVaultService(),
+                new LocalFileConfigurationModule()
+        );
+        assertThat(storage.write(new WriteEntryImpl(Map.of(), storage.localRevision().get() + 1)), willCompleteSuccessfully());
+    }
+
+    @Test
+    void test() throws Exception {
+        Path configFile = tmpDir.resolve(CONFIG_NAME + "test");
+        File file = configFile.toFile();
+
+        assertFalse(Files.isWritable(configFile));
+
+        var storage = new LocalFileConfigurationStorage(
+                configFile,
+                treeGenerator,
+                new InMemoryVaultService(),
+                new LocalFileConfigurationModule()
+        );
+        assertThat(storage.write(new WriteEntryImpl(Map.of(), storage.localRevision().get() + 1)), willCompleteSuccessfully());
     }
 
     @Test
@@ -604,6 +628,10 @@ public class LocalFileConfigurationStorageTest {
 
         @NamedConfigValue
         public PolyNamedListConfigurationSchema polyNamedList;
+
+        @Immutable
+        @Value(hasDefault = true)
+        public int immutable = 0;
     }
 
 
diff --git a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorageTest.java b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorageTest.java
similarity index 91%
rename from modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorageTest.java
rename to modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorageTest.java
index e68e597ac21..4fbcabf04ed 100644
--- a/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/LocalConfigurationStorageTest.java
+++ b/modules/configuration-storage/src/test/java/org/apache/ignite/internal/configuration/storage/VaultConfigurationStorageTest.java
@@ -27,9 +27,9 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
 /**
- * Tests for the {@link LocalConfigurationStorage}.
+ * Tests for the {@link VaultConfigurationStorage}.
  */
-public class LocalConfigurationStorageTest extends ConfigurationStorageTest {
+public class VaultConfigurationStorageTest extends ConfigurationStorageTest {
     private final VaultManager vaultManager = new VaultManager(new InMemoryVaultService());
 
     /**
@@ -51,6 +51,6 @@ public class LocalConfigurationStorageTest extends ConfigurationStorageTest {
     /** {@inheritDoc} */
     @Override
     public ConfigurationStorage getStorage() {
-        return new LocalConfigurationStorage("test-node-name", vaultManager);
+        return new VaultConfigurationStorage("test-node-name", vaultManager);
     }
 }
diff --git a/modules/configuration/build.gradle b/modules/configuration/build.gradle
index f0f77c6676c..47c931a0195 100644
--- a/modules/configuration/build.gradle
+++ b/modules/configuration/build.gradle
@@ -19,6 +19,7 @@ apply from: "$rootDir/buildscripts/java-core.gradle"
 apply from: "$rootDir/buildscripts/publishing.gradle"
 apply from: "$rootDir/buildscripts/java-junit5.gradle"
 apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
+apply from: "$rootDir/buildscripts/java-integration-test.gradle"
 
 description = 'ignite-configuration'
 
@@ -39,4 +40,7 @@ dependencies {
     testFixturesImplementation project(':ignite-core')
     testFixturesImplementation testFixtures(project(':ignite-core'))
     testFixturesImplementation libs.typesafe.config
+
+    integrationTestImplementation testFixtures(project(':ignite-core'))
+    integrationTestImplementation testFixtures(project(':ignite-runner'))
 }
diff --git a/modules/configuration/src/integrationTest/java/org/apache/ignite/internal/configuration/ItNodeConfigurationTest.java b/modules/configuration/src/integrationTest/java/org/apache/ignite/internal/configuration/ItNodeConfigurationTest.java
new file mode 100644
index 00000000000..0ba2414c47b
--- /dev/null
+++ b/modules/configuration/src/integrationTest/java/org/apache/ignite/internal/configuration/ItNodeConfigurationTest.java
@@ -0,0 +1,12 @@
+package org.apache.ignite.internal.configuration;
+
+import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
+import org.junit.jupiter.api.Test;
+
+public class ItNodeConfigurationTest extends ClusterPerClassIntegrationTest {
+
+    @Test
+    public void test() {
+
+    }
+}
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 e368a00ff29..6f38f2d5663 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
@@ -48,7 +48,6 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NavigableMap;
 import java.util.NoSuchElementException;
-import java.util.Objects;
 import java.util.RandomAccess;
 import java.util.StringJoiner;
 import java.util.TreeMap;
@@ -69,7 +68,8 @@ import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.internal.configuration.direct.KeyPathNode;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorageListener;
-import org.apache.ignite.internal.configuration.storage.Data;
+import org.apache.ignite.internal.configuration.storage.ReadEntry;
+import org.apache.ignite.internal.configuration.storage.WriteEntryImpl;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -134,25 +134,6 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
     /** Keys that were deleted from the configuration, but were present in the storage. Will be deleted on startup. */
     private Collection<String> ignoredKeys;
 
-    /**
-     * Closure interface to be used by the configuration changer. An instance of this closure is passed into the constructor and invoked
-     * every time when there's an update from any of the storages.
-     */
-    public interface ConfigurationUpdateListener {
-        /**
-         * Invoked every time when the configuration is updated.
-         *
-         * @param oldRoot Old roots values. All these roots always belong to a single storage.
-         * @param newRoot New values for the same roots as in {@code oldRoot}.
-         * @param storageRevision Configuration revision of the storage.
-         * @param notificationNumber Configuration listener notification number.
-         * @return Future that must signify when processing is completed. Exceptional completion is not expected.
-         */
-        CompletableFuture<Void> onConfigurationUpdated(
-                @Nullable SuperRoot oldRoot, SuperRoot newRoot, long storageRevision, long notificationNumber
-        );
-    }
-
     /**
      * Immutable data container to store version and all roots associated with the specific storage.
      */
@@ -280,7 +261,7 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
      * Start component.
      */
     public void start() {
-        Data data;
+        ReadEntry data;
 
         try {
             data = storage.readDataOnRecovery().get();
@@ -292,7 +273,8 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
             throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
         }
 
-        var storageValues = new HashMap<String, Serializable>(data.values());
+        Map<String, ? extends Serializable> values = data.values();
+        var storageValues = new HashMap<String, Serializable>(values);
 
         ignoredKeys = ignoreDeleted(storageValues, keyIgnorer);
 
@@ -324,15 +306,36 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
             initialConfiguration.descend(superRoot);
         }
 
-        // Validate the restored configuration.
-        validateConfiguration(superRoot);
+        Map<String, ? extends Serializable> invariant = data.invariant();
+        if (!invariant.isEmpty()) {
+            SuperRoot invariantRoot = new SuperRoot(rootCreator());
+            Map<String, ?> invariantPrefixMap = toPrefixMap(invariant);
+
+            for (RootKey<?, ?, ?> rootKey : rootKeys.values()) {
+                Map<String, ?> rootPrefixMap = (Map<String, ?>) invariantPrefixMap.get(rootKey.key());
+
+                InnerNode rootNode = createRootNode(rootKey);
+
+                if (rootPrefixMap != null) {
+                    fillFromPrefixMap(rootNode, rootPrefixMap);
+                }
+
+                invariantRoot.addRoot(rootKey, rootNode);
+            }
+
+            // Validate the restored configuration.
+            validateConfiguration(invariantRoot, superRoot);
+        } else {
+            validateConfiguration(superRoot);
+        }
+
         // We store two configuration roots, one with the defaults set and another one without them.
         // The root WITH the defaults is used when we calculate who to notify of a configuration change or
         // when we provide the configuration outside.
         // The root WITHOUT the defaults is used to calculate which properties to write to the underlying storage,
         // in other words it allows us to persist the defaults from the code.
         // After the storage listener fires for the first time both roots are supposed to become equal.
-        storageRoots = new StorageRoots(superRootNoDefaults, superRoot, data.changeId(), new TreeMap<>(data.values()));
+        storageRoots = new StorageRoots(superRootNoDefaults, superRoot, data.changeId(), new TreeMap<>(values));
 
         storage.registerConfigurationListener(configurationStorageListener());
 
@@ -676,17 +679,11 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
 
             validateConfiguration(curRoots, changes);
 
-            // In some cases some storages may not want to persist configuration defaults.
-            // Need to filter it from change map before write to storage.
-            if (!storage.supportDefaults()) {
-                removeDefaultValues(allChanges);
-            }
-
             // "allChanges" map can be empty here in case the given update matches the current state of the local configuration. We
             // still try to write the empty update, because local configuration can be obsolete. If this is the case, then the CAS will
             // fail and the update will be recalculated and there is a chance that the new local configuration will produce a non-empty
             // update.
-            return storage.write(allChanges, localRoots.changeId)
+            return storage.write(new WriteEntryImpl(allChanges, defaultsMap.get(), localRoots.changeId))
                     .thenCompose(casWroteSuccessfully -> {
                         if (casWroteSuccessfully) {
                             return localRoots.changeFuture;
@@ -798,15 +795,6 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
         allChanges.entrySet().removeIf(entry -> entry.getValue() == null && !localRoots.storageData.containsKey(entry.getKey()));
     }
 
-    private void removeDefaultValues(Map<String, Serializable> allChanges) {
-        defaultsMap.get().forEach((key, defaultValue) -> {
-            Serializable change = allChanges.get(key);
-            if (Objects.deepEquals(change, defaultValue)) {
-                allChanges.put(key, null);
-            }
-        });
-    }
-
     private Map<String, Serializable> createDefaultsMap() {
         SuperRoot superRoot = new SuperRoot(rootCreator());
         for (RootKey<?, ?, ?> rootKey : rootKeys.values()) {
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
index c5a17433a52..445f34f6532 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
@@ -33,7 +33,6 @@ import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.KeyIgnorer;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.SuperRootChange;
-import org.apache.ignite.internal.configuration.ConfigurationChanger.ConfigurationUpdateListener;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationUpdateListener.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationUpdateListener.java
new file mode 100644
index 00000000000..bb6a72117cd
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationUpdateListener.java
@@ -0,0 +1,23 @@
+package org.apache.ignite.internal.configuration;
+
+import java.util.concurrent.CompletableFuture;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Closure interface to be used by the configuration changer. An instance of this closure is passed into the constructor and invoked every
+ * time when there's an update from any of the storages.
+ */
+public interface ConfigurationUpdateListener {
+    /**
+     * Invoked every time when the configuration is updated.
+     *
+     * @param oldRoot Old roots values. All these roots always belong to a single storage.
+     * @param newRoot New values for the same roots as in {@code oldRoot}.
+     * @param storageRevision Configuration revision of the storage.
+     * @param notificationNumber Configuration listener notification number.
+     * @return Future that must signify when processing is completed. Exceptional completion is not expected.
+     */
+    CompletableFuture<Void> onConfigurationUpdated(
+            @Nullable SuperRoot oldRoot, SuperRoot newRoot, long storageRevision, long notificationNumber
+    );
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorage.java
index b2a8da48b69..ff1ac34b000 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorage.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorage.java
@@ -32,7 +32,7 @@ public interface ConfigurationStorage extends ManuallyCloseable {
      *
      * @return Future that resolves into extracted values and version or a {@link StorageException} if the data could not be read.
      */
-    CompletableFuture<Data> readDataOnRecovery();
+    CompletableFuture<ReadEntry> readDataOnRecovery();
 
     /**
      * Retrieves the most recent values which keys start with the given prefix, regardless of the current storage version.
@@ -58,7 +58,7 @@ public interface ConfigurationStorage extends ManuallyCloseable {
      * @return Future that gives you {@code true} if successfully written, {@code false} if version of the storage is different from the
      *      passed argument and {@link StorageException} if failed to write data.
      */
-    CompletableFuture<Boolean> write(Map<String, ? extends Serializable> newValues, long ver);
+    CompletableFuture<Boolean> write(WriteEntry writeEntry);
 
     /**
      * Add listener to the storage that notifies of data changes.
@@ -82,10 +82,6 @@ public interface ConfigurationStorage extends ManuallyCloseable {
     /** Returns a future that will be completed with the latest revision of the configuration storage. */
     CompletableFuture<Long> localRevision();
 
-    default boolean supportDefaults() {
-        return true;
-    }
-
     /**
      * Closes the storage.
      */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageListener.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageListener.java
index 928050e9c1f..42716ba3fc1 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageListener.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ConfigurationStorageListener.java
@@ -30,5 +30,5 @@ public interface ConfigurationStorageListener {
      * @param changedEntries Changed entries, key-value pairs and new version of the storage.
      * @return Completable future that signifies the completion of all custom user listeners execution.
      */
-    CompletableFuture<Void> onEntriesChanged(Data changedEntries);
+    CompletableFuture<Void> onEntriesChanged(ReadEntry changedEntries);
 }
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/ReadEntry.java
similarity index 74%
rename from modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/Data.java
rename to modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/ReadEntry.java
index 685e7e19b6e..5debc55e7b0 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/ReadEntry.java
@@ -24,10 +24,12 @@ import java.util.Map;
 /**
  * Represents data in configuration storage.
  */
-public class Data {
+public class ReadEntry {
     /** Values. */
     private final Map<String, ? extends Serializable> values;
 
+    private final Map<String, ? extends Serializable> invariant;
+
     /** Configuration storage version. */
     private final long changeId;
 
@@ -37,9 +39,16 @@ public class Data {
      * @param values   Values.
      * @param changeId Version.
      */
-    public Data(Map<String, ? extends Serializable> values, long changeId) {
+    public ReadEntry(Map<String, ? extends Serializable> values, long changeId) {
         this.values = Collections.unmodifiableMap(values);
         this.changeId = changeId;
+        this.invariant = Collections.emptyMap();
+    }
+
+    public ReadEntry(Map<String, ? extends Serializable> values, long changeId, Map<String, ? extends Serializable> invariant) {
+        this.values = values;
+        this.changeId = changeId;
+        this.invariant = invariant;
     }
 
     /** Get values. */
@@ -47,6 +56,10 @@ public class Data {
         return values;
     }
 
+    public Map<String, ? extends Serializable> invariant() {
+        return invariant;
+    }
+
     /**
      * Get version.
      *
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntry.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntry.java
new file mode 100644
index 00000000000..901a7512cf3
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntry.java
@@ -0,0 +1,12 @@
+package org.apache.ignite.internal.configuration.storage;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public interface WriteEntry {
+    Map<String, ? extends Serializable> newValues();
+
+    Map<String, ? extends Serializable> valuesWithFilteredDefaults();
+
+    long version();
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntryImpl.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntryImpl.java
new file mode 100644
index 00000000000..dbb3a9990df
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/storage/WriteEntryImpl.java
@@ -0,0 +1,61 @@
+package org.apache.ignite.internal.configuration.storage;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.internal.util.Lazy;
+
+public class WriteEntryImpl implements WriteEntry {
+    private final Map<String, ? extends Serializable> defaults;
+
+    private final Map<String, ? extends Serializable> allChanges;
+
+    private final Lazy<Map<String, ? extends Serializable>> filtered = new Lazy<>(this::filteredDefaults);
+
+    private final long version;
+
+    public WriteEntryImpl(
+            Map<String, ? extends Serializable> changes,
+            long version
+    ) {
+        this(changes, Map.of(), version);
+    }
+
+    public WriteEntryImpl(
+            Map<String, ? extends Serializable> allChanges,
+            Map<String, ? extends Serializable> defaults,
+            long version
+    ) {
+        this.defaults = defaults;
+        this.allChanges = allChanges;
+        this.version = version;
+    }
+
+    @Override
+    public Map<String, ? extends Serializable> newValues() {
+        return Collections.unmodifiableMap(allChanges);
+    }
+
+    @Override
+    public Map<String, ? extends Serializable> valuesWithFilteredDefaults() {
+        return filtered.get();
+    }
+
+    @Override
+    public long version() {
+        return version;
+    }
+
+    private Map<String, ? extends Serializable> filteredDefaults() {
+        Map<String, ? extends Serializable> result = new HashMap<>(allChanges);
+        defaults.forEach((key, defaultValue) -> {
+            Serializable change = result.get(key);
+            if (Objects.deepEquals(change, defaultValue)) {
+                result.put(key, null);
+            }
+        });
+        return result;
+    }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index 41f00cfe175..fe5ecd93867 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -62,7 +62,7 @@ import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.direct.KeyPathNode;
 import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
-import org.apache.ignite.internal.configuration.storage.Data;
+import org.apache.ignite.internal.configuration.storage.ReadEntry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -274,7 +274,7 @@ public class ConfigurationChangerTest {
 
         storage.fail(false);
 
-        CompletableFuture<Map<String, ? extends Serializable>> dataFuture = storage.readDataOnRecovery().thenApply(Data::values);
+        CompletableFuture<Map<String, ? extends Serializable>> dataFuture = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(dataFuture, willCompleteSuccessfully());
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/LocalFileConfigurationTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/LocalFileConfigurationTest.java
new file mode 100644
index 00000000000..88e0dad3457
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/LocalFileConfigurationTest.java
@@ -0,0 +1,5 @@
+package org.apache.ignite.internal.configuration;
+
+public class LocalFileConfigurationTest {
+    private final
+}
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
index 6ec3abb5884..140c6668ac5 100644
--- 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
@@ -48,7 +48,7 @@ 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.ReadEntry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
@@ -272,7 +272,7 @@ class RenamedConfigurationTest extends BaseIgniteAbstractTest {
      */
     @SafeVarargs
     private void validateStorageContent(Map.Entry<String, Serializable> ...values) {
-        CompletableFuture<Data> dataFuture = storage.readDataOnRecovery();
+        CompletableFuture<ReadEntry> dataFuture = storage.readDataOnRecovery();
         assertThat(dataFuture, willCompleteSuccessfully());
 
         RenamedTestNewView node = registry.getConfiguration(RenamedTestNewConfiguration.KEY).value();
@@ -318,8 +318,8 @@ class RenamedConfigurationTest extends BaseIgniteAbstractTest {
         storage.close();
     }
 
-    private Data getData() {
-        CompletableFuture<Data> dataFuture = storage.readDataOnRecovery();
+    private ReadEntry getData() {
+        CompletableFuture<ReadEntry> dataFuture = storage.readDataOnRecovery();
 
         assertThat(dataFuture, willCompleteSuccessfully());
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestSubConfigurationSchema.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestSubConfigurationSchema.java
index 5f2287a24e5..6d5cd9a3dc5 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestSubConfigurationSchema.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestSubConfigurationSchema.java
@@ -35,4 +35,6 @@ public class TestSubConfigurationSchema {
 
     @Value(hasDefault = true)
     public int testInt;
+
+
 }
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 4f0e9c63b08..8f793a93b68 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
@@ -49,7 +49,7 @@ import org.apache.ignite.internal.configuration.SuperRoot;
 import org.apache.ignite.internal.configuration.SuperRootChangeImpl;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
-import org.apache.ignite.internal.configuration.storage.Data;
+import org.apache.ignite.internal.configuration.storage.ReadEntry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -197,7 +197,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
                     lastWriteCapture.getValue()
             );
 
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(1, data.changeId());
             assertEquals(
@@ -227,7 +227,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check that deprecated value is not present in the storage anymore.
-            Data data = getData();
+            ReadEntry data = getData();
             assertEquals(2, data.changeId());
             assertEquals(
                     Map.of("root.child.my-int-cfg", 99),
@@ -315,7 +315,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check that deprecated value is not present in the storage anymore.
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(2, data.changeId());
             assertEquals(
@@ -370,7 +370,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check storage state.
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(2, data.changeId());
             assertEquals(
@@ -403,7 +403,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check that deprecated value is not present in the storage anymore.
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(3, data.changeId());
             assertEquals(
@@ -467,7 +467,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check storage state.
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(2, data.changeId());
             assertEquals(
@@ -502,7 +502,7 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
             );
 
             // Check that deprecated value is not present in the storage anymore.
-            Data data = getData();
+            ReadEntry data = getData();
 
             assertEquals(3, data.changeId());
             assertEquals(
@@ -557,8 +557,8 @@ public class DeprecatedConfigurationTest extends BaseIgniteAbstractTest {
         assertThat(changeFuture, willCompleteSuccessfully());
     }
 
-    private Data getData() {
-        CompletableFuture<Data> dataFuture = storage.readDataOnRecovery();
+    private ReadEntry getData() {
+        CompletableFuture<ReadEntry> dataFuture = storage.readDataOnRecovery();
 
         assertThat(dataFuture, willCompleteSuccessfully());
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index 7c334618a5f..aaed6127bcf 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -39,7 +39,7 @@ import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
-import org.apache.ignite.internal.configuration.storage.Data;
+import org.apache.ignite.internal.configuration.storage.ReadEntry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
@@ -140,7 +140,7 @@ public class NamedListNodeTest {
         UUID x0Id = ((NamedListNode<?>) a.second().value()).internalId("X");
         UUID z0Id = ((NamedListNode<?>) a.second().get("X").third().value()).internalId("Z0");
 
-        CompletableFuture<Map<String, ? extends Serializable>> storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        CompletableFuture<Map<String, ? extends Serializable>> storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -164,7 +164,7 @@ public class NamedListNodeTest {
 
         UUID z5Id = ((NamedListNode<?>) a.second().get("X").third().value()).internalId("Z5");
 
-        storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -190,7 +190,7 @@ public class NamedListNodeTest {
 
         UUID z2Id = ((NamedListNode<?>) a.second().get("X").third().value()).internalId("Z2");
 
-        storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -220,7 +220,7 @@ public class NamedListNodeTest {
 
         UUID z3Id = ((NamedListNode<?>) a.second().get("X").third().value()).internalId("Z3");
 
-        storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -252,7 +252,7 @@ public class NamedListNodeTest {
         // Delete keys from the middle. Indexes of Z3 should be updated to 1.
         x.third().change(xb -> xb.delete("Z2").delete("Z5")).get();
 
-        storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -276,7 +276,7 @@ public class NamedListNodeTest {
         // Delete keys from the middle. Indexes of Z3 should be updated to 1.
         x.third().change(xb -> xb.rename("Z0", "Z1")).get();
 
-        storageValues = storage.readDataOnRecovery().thenApply(Data::values);
+        storageValues = storage.readDataOnRecovery().thenApply(ReadEntry::values);
 
         assertThat(
                 storageValues,
@@ -300,7 +300,7 @@ public class NamedListNodeTest {
         // Delete values on several layers simultaneously. Storage must be empty after that.
         a.second().change(b -> b.delete("X")).get();
 
-        assertThat(storage.readDataOnRecovery().thenApply(Data::values), willBe(anEmptyMap()));
+        assertThat(storage.readDataOnRecovery().thenApply(ReadEntry::values), willBe(anEmptyMap()));
     }
 
     /** Tests exceptions described in methods signatures. */
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 7bd50a019a9..bf4a2a5a372 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
@@ -108,32 +108,32 @@ public class TestConfigurationStorage implements ConfigurationStorage {
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Data> readDataOnRecovery() {
+    public CompletableFuture<ReadEntry> readDataOnRecovery() {
         return supplyAsync(() -> {
             synchronized (this) {
                 if (fail) {
                     throw new StorageException("Failed to read data");
                 }
 
-                return new Data(new HashMap<>(map), version);
+                return new ReadEntry(new HashMap<>(map), version);
             }
         });
     }
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Boolean> write(Map<String, ? extends Serializable> newValues, long sentVersion) {
+    public CompletableFuture<Boolean> write(WriteEntry writeEntry) {
         return supplyAsync(() -> {
             synchronized (this) {
                 if (fail) {
                     throw new StorageException("Failed to write data");
                 }
 
-                if (sentVersion != version) {
+                if (writeEntry.version() != version) {
                     return false;
                 }
 
-                for (Map.Entry<String, ? extends Serializable> entry : newValues.entrySet()) {
+                for (Map.Entry<String, ? extends Serializable> entry : writeEntry.newValues().entrySet()) {
                     if (entry.getValue() != null) {
                         map.put(entry.getKey(), entry.getValue());
                     } else {
@@ -143,7 +143,7 @@ public class TestConfigurationStorage implements ConfigurationStorage {
 
                 version++;
 
-                var data = new Data(newValues, version);
+                var data = new ReadEntry(writeEntry.newValues(), version);
 
                 listeners.forEach(listener -> listener.onEntriesChanged(data).join());
 
diff --git a/modules/distribution-zones/build.gradle b/modules/distribution-zones/build.gradle
index 1d6bc17185e..7f4aa5535ea 100644
--- a/modules/distribution-zones/build.gradle
+++ b/modules/distribution-zones/build.gradle
@@ -129,6 +129,7 @@ dependencies {
     integrationTestImplementation testFixtures(project(':ignite-transactions'))
     integrationTestImplementation testFixtures(project(':ignite-sql-engine'))
     integrationTestImplementation testFixtures(project(':ignite-configuration-system'))
+    integrationTestImplementation testFixtures(project(':ignite-vault'))
 
 }
 
diff --git a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
index 3673a1138f9..84e7cf7019a 100644
--- a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
+++ b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
@@ -130,6 +130,7 @@ import org.apache.ignite.internal.testframework.ExecutorServiceExtension;
 import org.apache.ignite.internal.testframework.InjectExecutorService;
 import org.apache.ignite.internal.testframework.TestIgnitionManager;
 import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.apache.ignite.internal.version.DefaultIgniteProductVersionSource;
 import org.apache.ignite.internal.worker.fixtures.NoOpCriticalWorkerRegistry;
 import org.apache.ignite.network.NetworkAddress;
@@ -218,7 +219,7 @@ public class ItIgniteDistributionZoneManagerNodeRestartTest extends BaseIgniteRe
 
         var nodeCfgMgr = new ConfigurationManager(
                 modules.local().rootKeys(),
-                new LocalFileConfigurationStorage(configFile, localConfigurationGenerator, modules.local()),
+                new LocalFileConfigurationStorage(configFile, localConfigurationGenerator, new InMemoryVaultService(), modules.local()),
                 localConfigurationGenerator,
                 ConfigurationValidatorImpl.withDefaultValidators(localConfigurationGenerator, modules.local().validators())
         );
diff --git a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceDistributedTest.java b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceDistributedTest.java
index 80f853be282..91ff99d20ea 100644
--- a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceDistributedTest.java
+++ b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceDistributedTest.java
@@ -270,6 +270,7 @@ import org.apache.ignite.internal.tx.storage.state.rocksdb.TxStateRocksDbSharedS
 import org.apache.ignite.internal.tx.storage.state.test.TestTxStateStorage;
 import org.apache.ignite.internal.tx.test.TestLocalRwTxCounter;
 import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.raft.jraft.rpc.CliRequests.ChangePeersAndLearnersAsyncRequest;
@@ -1296,7 +1297,7 @@ public class ItRebalanceDistributedTest extends BaseIgniteAbstractTest {
 
             nodeCfgMgr = new ConfigurationManager(
                     List.of(NodeConfiguration.KEY),
-                    new LocalFileConfigurationStorage(configPath, nodeCfgGenerator, null),
+                    new LocalFileConfigurationStorage(configPath, nodeCfgGenerator, new InMemoryVaultService(), null),
                     nodeCfgGenerator,
                     new TestConfigurationValidator()
             );
diff --git a/modules/partition-replicator/build.gradle b/modules/partition-replicator/build.gradle
index 5d725fa9611..8fd2503557f 100644
--- a/modules/partition-replicator/build.gradle
+++ b/modules/partition-replicator/build.gradle
@@ -71,6 +71,7 @@ dependencies {
     integrationTestImplementation testFixtures(project(':ignite-storage-api'))
     integrationTestImplementation testFixtures(project(':ignite-table'))
     integrationTestImplementation testFixtures(project(':ignite-transactions'))
+    integrationTestImplementation testFixtures(project(':ignite-vault'))
 
     integrationTestImplementation project(':ignite-raft')
     integrationTestImplementation project(':ignite-raft-api')
diff --git a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/fixtures/Node.java b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/fixtures/Node.java
index e8c616a44dd..359e8296f40 100644
--- a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/fixtures/Node.java
+++ b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/fixtures/Node.java
@@ -195,6 +195,7 @@ import org.apache.ignite.internal.tx.storage.state.TxStateStorage;
 import org.apache.ignite.internal.tx.storage.state.rocksdb.TxStateRocksDbSharedStorage;
 import org.apache.ignite.internal.tx.test.TestLocalRwTxCounter;
 import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupEventsClientListener;
 import org.apache.ignite.sql.IgniteSql;
@@ -370,7 +371,7 @@ public class Node {
 
         nodeCfgMgr = new ConfigurationManager(
                 List.of(NodeConfiguration.KEY),
-                new LocalFileConfigurationStorage(configPath, nodeCfgGenerator, null),
+                new LocalFileConfigurationStorage(configPath, nodeCfgGenerator, new InMemoryVaultService(), null),
                 nodeCfgGenerator,
                 new TestConfigurationValidator()
         );
diff --git a/modules/runner/build.gradle b/modules/runner/build.gradle
index b7edce5679a..0c88a8226f9 100644
--- a/modules/runner/build.gradle
+++ b/modules/runner/build.gradle
@@ -109,6 +109,8 @@ dependencies {
     testAnnotationProcessor project(':ignite-configuration-annotation-processor')
     testAnnotationProcessor libs.auto.service
 
+    defaultsGeneratorImplementation testFixtures(project(':ignite-vault'))
+
     testImplementation project(':ignite-cluster-management')
     testImplementation project(':ignite-configuration')
     testImplementation project(':ignite-core')
diff --git a/modules/runner/src/defaultsGenerator/java/org/apache/ignite/internal/configuration/generator/DefaultsGenerator.java b/modules/runner/src/defaultsGenerator/java/org/apache/ignite/internal/configuration/generator/DefaultsGenerator.java
index 332e5575254..a3bfbec4c56 100644
--- a/modules/runner/src/defaultsGenerator/java/org/apache/ignite/internal/configuration/generator/DefaultsGenerator.java
+++ b/modules/runner/src/defaultsGenerator/java/org/apache/ignite/internal/configuration/generator/DefaultsGenerator.java
@@ -28,7 +28,7 @@ import org.apache.ignite.configuration.KeyIgnorer;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.internal.configuration.ConfigurationChanger;
-import org.apache.ignite.internal.configuration.ConfigurationChanger.ConfigurationUpdateListener;
+import org.apache.ignite.internal.configuration.ConfigurationUpdateListener;
 import org.apache.ignite.internal.configuration.ConfigurationModules;
 import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
@@ -37,6 +37,8 @@ import org.apache.ignite.internal.configuration.storage.LocalFileConfigurationSt
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.validation.ConfigurationValidator;
 import org.apache.ignite.internal.configuration.validation.ConfigurationValidatorImpl;
+import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -89,7 +91,12 @@ public class DefaultsGenerator {
         );
 
         ConfigurationStorage storage = new LocalFileConfigurationStorage(
-                "defaultGen", configPath, localConfigurationGenerator, modules.local());
+                "defaultGen",
+                configPath,
+                localConfigurationGenerator,
+                new VaultManager(new InMemoryVaultService()),
+                modules.local()
+        );
 
         ConfigurationValidator configurationValidator =
                 ConfigurationValidatorImpl.withDefaultValidators(localConfigurationGenerator, modules.local().validators());
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index 765daea938c..24c6bff6587 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -341,9 +341,9 @@ public class ItDistributedConfigurationStorageTest extends BaseIgniteAbstractTes
 
             node2.waitWatches();
 
-            CompletableFuture<Data> storageData = node2.cfgStorage.readDataOnRecovery();
+            CompletableFuture<ReadEntry> storageData = node2.cfgStorage.readDataOnRecovery();
 
-            assertThat(storageData.thenApply(Data::values), willBe(equalTo(data)));
+            assertThat(storageData.thenApply(ReadEntry::values), willBe(equalTo(data)));
         } finally {
             node2.stop();
         }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index 6f3454d6d65..b3ea7d3f894 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -236,6 +236,7 @@ import org.apache.ignite.internal.tx.test.TestLocalRwTxCounter;
 import org.apache.ignite.internal.util.ByteUtils;
 import org.apache.ignite.internal.util.Cursor;
 import org.apache.ignite.internal.vault.VaultManager;
+import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.apache.ignite.internal.version.DefaultIgniteProductVersionSource;
 import org.apache.ignite.internal.worker.CriticalWorkerWatchdog;
 import org.apache.ignite.internal.worker.configuration.CriticalWorkersConfiguration;
@@ -373,7 +374,7 @@ public class ItIgniteNodeRestartTest extends BaseIgniteRestartTest {
 
         var nodeCfgMgr = new ConfigurationManager(
                 modules.local().rootKeys(),
-                new LocalFileConfigurationStorage(configFile, localConfigurationGenerator, modules.local()),
+                new LocalFileConfigurationStorage(configFile, localConfigurationGenerator, new InMemoryVaultService(), modules.local()),
                 localConfigurationGenerator,
                 ConfigurationValidatorImpl.withDefaultValidators(localConfigurationGenerator, modules.local().validators())
         );
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index f318532845d..db5c74b21e6 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -557,6 +557,7 @@ public class IgniteImpl implements Ignite {
                 name,
                 configPath,
                 localConfigurationGenerator,
+                vaultMgr,
                 modules.local()
         );