This is an automated email from the ASF dual-hosted git repository.
apkhmv 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 b48ddcba7c IGNITE-19152 Use schema information in
LocalFileConfigurationStorage (#1988)
b48ddcba7c is described below
commit b48ddcba7cd2bd3b9a053ae131c25b44a0400e27
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Wed Apr 26 20:02:34 2023 +0400
IGNITE-19152 Use schema information in LocalFileConfigurationStorage (#1988)
---------
Co-authored-by: Ivan Bessonov <[email protected]>
---
.../configuration/ConfigurationManager.java | 46 ++-
.../configuration/ConfigurationRegistry.java | 189 ++-------
.../configuration/ConfigurationTreeGenerator.java | 283 +++++++++++++
.../configuration/tree/ConverterToMapVisitor.java | 42 +-
.../internal/configuration/tree/NamedListNode.java | 2 +-
.../configuration/util/ConfigurationFlattener.java | 6 +
.../configuration/ConfigurationChangerTest.java | 12 +-
...st.java => ConfigurationTreeGeneratorTest.java} | 59 +--
.../configuration/direct/DirectPropertiesTest.java | 16 +-
.../configuration/tree/InternalIdTest.java | 26 +-
.../configuration/tree/NamedListNodeTest.java | 12 +-
.../configuration/TestConfigurationChanger.java | 28 +-
.../DistributionZoneManagerTest.java | 16 +-
.../ItSslConfigurationValidationTest.java | 2 +
.../storage/ItRebalanceDistributedTest.java | 14 +-
.../runner/app/ItIgniteNodeRestartTest.java | 20 +-
.../org/apache/ignite/internal/app/IgniteImpl.java | 21 +-
.../storage/LocalFileConfigurationStorage.java | 180 +++++---
.../DistributedConfigurationCatchUpTest.java | 11 +-
.../storage/LocalFileConfigurationStorageTest.java | 460 +++++++++++++++++++--
20 files changed, 1054 insertions(+), 391 deletions(-)
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
index b6e832ee2c..4a3b3beec4 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
@@ -17,19 +17,10 @@
package org.apache.ignite.internal.configuration;
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigObject;
-import java.nio.file.Path;
import java.util.Collection;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.InternalConfiguration;
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.hocon.HoconConverter;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.manager.IgniteComponent;
@@ -40,6 +31,30 @@ public class ConfigurationManager implements IgniteComponent
{
/** Configuration registry. */
private final ConfigurationRegistry registry;
+ /**
+ * Constructor.
+ *
+ * @param rootKeys Configuration root keys.
+ * @param validators Validators.
+ * @param storage Configuration storage.
+ * @param generator Configuration tree generator.
+ * @throws IllegalArgumentException If the configuration type of the root
keys is not equal to the storage type, or if the schema or its
+ * extensions are not valid.
+ */
+ public ConfigurationManager(
+ Collection<RootKey<?, ?>> rootKeys,
+ Set<Validator<?, ?>> validators,
+ ConfigurationStorage storage,
+ ConfigurationTreeGenerator generator
+ ) {
+ registry = new ConfigurationRegistry(
+ rootKeys,
+ validators,
+ storage,
+ generator
+ );
+ }
+
/**
* Constructor.
*
@@ -81,19 +96,6 @@ public class ConfigurationManager implements IgniteComponent
{
registry.stop();
}
- /**
- * Bootstrap configuration manager with customer user cfg.
- *
- * @param configPath Customer configuration in hocon format.
- * @throws InterruptedException If thread is interrupted during bootstrap.
- * @throws ExecutionException If configuration update failed for some
reason.
- */
- public void bootstrap(Path configPath) throws InterruptedException,
ExecutionException {
- ConfigObject hoconCfg =
ConfigFactory.parseFile(configPath.toFile()).root();
-
- registry.change(HoconConverter.hoconSource(hoconCfg)).get();
- }
-
/**
* Get configuration registry.
*
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 bff666bffa..5bea124c61 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
@@ -18,24 +18,13 @@
package org.apache.ignite.internal.configuration;
import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.function.Predicate.not;
-import static java.util.stream.Collectors.toList;
import static
org.apache.ignite.internal.configuration.notifications.ConfigurationNotifier.notifyListeners;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
-import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
-import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
-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.polymorphicSchemaExtensions;
-import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.touch;
-import static org.apache.ignite.internal.util.CollectionUtils.difference;
-import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
import java.io.Serializable;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -53,13 +42,11 @@ import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
-import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.notifications.ConfigurationListener;
import
org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
import
org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
import org.apache.ignite.configuration.validation.Validator;
import
org.apache.ignite.internal.configuration.ConfigurationChanger.ConfigurationUpdateListener;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import
org.apache.ignite.internal.configuration.notifications.ConfigurationStorageRevisionListener;
import
org.apache.ignite.internal.configuration.notifications.ConfigurationStorageRevisionListenerHolder;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -96,8 +83,11 @@ public class ConfigurationRegistry implements
IgniteComponent, ConfigurationStor
/** Configuration change handler. */
private final ConfigurationChanger changer;
- /** Configuration generator. */
- private final ConfigurationAsmGenerator cgen = new
ConfigurationAsmGenerator();
+ /** Runtime implementations generator for node classes. */
+ private final ConfigurationTreeGenerator generator;
+
+ /** Flag that indicates if the {@link ConfigurationTreeGenerator} instance
is owned by this object or not. */
+ private boolean ownConfigTreeGenerator = false;
/** Configuration storage revision change listeners. */
private final
ConfigurationListenerHolder<ConfigurationStorageRevisionListener>
storageRevisionListeners =
@@ -122,13 +112,34 @@ public class ConfigurationRegistry implements
IgniteComponent, ConfigurationStor
Collection<Class<?>> internalSchemaExtensions,
Collection<Class<?>> polymorphicSchemaExtensions
) {
- checkConfigurationType(rootKeys, storage);
-
- Set<Class<?>> allSchemas = collectAllSchemas(rootKeys,
internalSchemaExtensions, polymorphicSchemaExtensions);
+ this(
+ rootKeys,
+ validators,
+ storage,
+ new ConfigurationTreeGenerator(rootKeys,
internalSchemaExtensions, polymorphicSchemaExtensions)
+ );
+
+ this.ownConfigTreeGenerator = true;
+ }
- Map<Class<?>, Set<Class<?>>> internalExtensions =
internalExtensionsWithCheck(allSchemas, internalSchemaExtensions);
+ /**
+ * Constructor.
+ *
+ * @param rootKeys Configuration root keys.
+ * @param validators Validators.
+ * @param storage Configuration storage.
+ * @throws IllegalArgumentException If the configuration type of the root
keys is not equal to the storage type, or if the schema or its
+ * extensions are not valid.
+ */
+ public ConfigurationRegistry(
+ Collection<RootKey<?, ?>> rootKeys,
+ Set<Validator<?, ?>> validators,
+ ConfigurationStorage storage,
+ ConfigurationTreeGenerator generator
+ ) {
+ this.generator = generator;
- Map<Class<?>, Set<Class<?>>> polymorphicExtensions =
polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
+ checkConfigurationType(rootKeys, storage);
this.rootKeys = rootKeys;
@@ -143,39 +154,17 @@ public class ConfigurationRegistry implements
IgniteComponent, ConfigurationStor
changer = new ConfigurationChanger(notificationUpdateListener(),
rootKeys, validators0, storage) {
@Override
public InnerNode createRootNode(RootKey<?, ?> rootKey) {
- return cgen.instantiateNode(rootKey.schemaClass());
+ return generator.instantiateNode(rootKey.schemaClass());
}
};
rootKeys.forEach(rootKey -> {
- cgen.compileRootSchema(rootKey.schemaClass(), internalExtensions,
polymorphicExtensions);
-
- DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey,
changer);
+ DynamicConfiguration<?, ?> cfg = generator.instantiateCfg(rootKey,
changer);
configs.put(rootKey.key(), cfg);
});
}
- /**
- * Collects all schemas and subschemas (recursively) from root keys,
internal and polymorphic schema extensions.
- *
- * @param rootKeys root keys
- * @param internalSchemaExtensions internal schema extensions
- * @param polymorphicSchemaExtensions polymorphic schema extensions
- * @return set of all schema classes
- */
- private static Set<Class<?>> collectAllSchemas(Collection<RootKey<?, ?>>
rootKeys,
- Collection<Class<?>> internalSchemaExtensions,
- Collection<Class<?>> polymorphicSchemaExtensions) {
- Set<Class<?>> allSchemas = new HashSet<>();
-
- allSchemas.addAll(collectSchemas(viewReadOnly(rootKeys,
RootKey::schemaClass)));
- allSchemas.addAll(collectSchemas(internalSchemaExtensions));
- allSchemas.addAll(collectSchemas(polymorphicSchemaExtensions));
-
- return allSchemas;
- }
-
/**
* Registers default validator implementation to the validators map.
*
@@ -204,6 +193,10 @@ public class ConfigurationRegistry implements
IgniteComponent, ConfigurationStor
changer.stop();
storageRevisionListeners.clear();
+
+ if (ownConfigTreeGenerator) {
+ generator.close();
+ }
}
/**
@@ -387,114 +380,6 @@ public class ConfigurationRegistry implements
IgniteComponent, ConfigurationStor
return changer.notifyCurrentConfigurationListeners();
}
- /**
- * Get configuration schemas and their validated internal extensions with
checks.
- *
- * @param allSchemas All configuration schemas.
- * @param internalSchemaExtensions Internal extensions ({@link
InternalConfiguration}) of configuration schemas ({@link
- * ConfigurationRoot} and {@link Config}).
- * @return Mapping: original of the schema -> internal schema extensions.
- * @throws IllegalArgumentException If the schema extension is invalid.
- */
- private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
- Set<Class<?>> allSchemas,
- Collection<Class<?>> internalSchemaExtensions
- ) {
- if (internalSchemaExtensions.isEmpty()) {
- return Map.of();
- }
-
- Map<Class<?>, Set<Class<?>>> internalExtensions =
internalSchemaExtensions(internalSchemaExtensions);
-
- Set<Class<?>> notInAllSchemas =
difference(internalExtensions.keySet(), allSchemas);
-
- if (!notInAllSchemas.isEmpty()) {
- throw new IllegalArgumentException(
- "Internal extensions for which no parent configuration
schemas were found: " + notInAllSchemas
- );
- }
-
- return internalExtensions;
- }
-
- /**
- * Get polymorphic extensions of configuration schemas with checks.
- *
- * @param allSchemas All configuration schemas.
- * @param polymorphicSchemaExtensions Polymorphic extensions ({@link
PolymorphicConfigInstance}) of configuration schemas.
- * @return Mapping: polymorphic scheme -> extensions (instances) of
polymorphic configuration.
- * @throws IllegalArgumentException If the schema extension is invalid.
- */
- private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
- Set<Class<?>> allSchemas,
- Collection<Class<?>> polymorphicSchemaExtensions
- ) {
- Map<Class<?>, Set<Class<?>>> polymorphicExtensionsByParent =
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
-
- Set<Class<?>> notInAllSchemas =
difference(polymorphicExtensionsByParent.keySet(), allSchemas);
-
- if (!notInAllSchemas.isEmpty()) {
- throw new IllegalArgumentException(
- "Polymorphic extensions for which no polymorphic
configuration schemas were found: " + notInAllSchemas
- );
- }
-
- Collection<Class<?>> noPolymorphicExtensionsSchemas =
allSchemas.stream()
- .filter(ConfigurationUtil::isPolymorphicConfig)
- .filter(not(polymorphicExtensionsByParent::containsKey))
- .collect(toList());
-
- if (!noPolymorphicExtensionsSchemas.isEmpty()) {
- throw new IllegalArgumentException(
- "Polymorphic configuration schemas for which no extensions
were found: " + noPolymorphicExtensionsSchemas
- );
- }
-
- checkPolymorphicConfigIds(polymorphicExtensionsByParent);
-
- for (Map.Entry<Class<?>, Set<Class<?>>> e :
polymorphicExtensionsByParent.entrySet()) {
- Class<?> schemaClass = e.getKey();
-
- Field typeIdField = schemaFields(schemaClass).get(0);
-
- if (!isPolymorphicId(typeIdField)) {
- throw new IllegalArgumentException(String.format(
- "First field in a polymorphic configuration schema
must contain @%s: %s",
- PolymorphicId.class,
- schemaClass.getName()
- ));
- }
- }
-
- return polymorphicExtensionsByParent;
- }
-
- /**
- * Checks that there are no conflicts between ids of a polymorphic
configuration and its extensions (instances).
- *
- * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions
(instances) of polymorphic configuration.
- * @throws IllegalArgumentException If a polymorphic configuration id
conflict is found.
- * @see PolymorphicConfigInstance#value
- */
- private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>>
polymorphicExtensions) {
- // Mapping: id -> configuration schema.
- Map<String, Class<?>> ids = new HashMap<>();
-
- for (Map.Entry<Class<?>, Set<Class<?>>> e :
polymorphicExtensions.entrySet()) {
- for (Class<?> schemaClass : e.getValue()) {
- String id = polymorphicInstanceId(schemaClass);
- Class<?> prev = ids.put(id, schemaClass);
-
- if (prev != null) {
- throw new IllegalArgumentException("Found an id conflict
for a polymorphic configuration [id="
- + id + ", schemas=" + List.of(prev, schemaClass));
- }
- }
-
- ids.clear();
- }
- }
-
private Collection<CompletableFuture<?>>
notifyStorageRevisionListeners(long storageRevision, long notificationNumber) {
// Lazy init.
List<CompletableFuture<?>> futures = null;
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
new file mode 100644
index 0000000000..1360b51a29
--- /dev/null
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
@@ -0,0 +1,283 @@
+/*
+ * 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 java.util.function.Function.identity;
+import static java.util.function.Predicate.not;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+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.polymorphicSchemaExtensions;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.internal.close.ManuallyCloseable;
+import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+/** Schema-aware configuration generator. */
+public class ConfigurationTreeGenerator implements ManuallyCloseable {
+
+ private final Map<String, RootKey<?, ?>> rootKeys;
+
+ @Nullable
+ private ConfigurationAsmGenerator generator = new
ConfigurationAsmGenerator();
+
+ /**
+ * Constructor that takes a collection of root keys. Internal and
polymorphic schema extensions are empty by default.
+ *
+ * @param rootKeys Root keys.
+ */
+ @TestOnly
+ public ConfigurationTreeGenerator(RootKey<?, ?>... rootKeys) {
+ this(List.of(rootKeys), Set.of(), Set.of());
+ }
+
+ /**
+ * Constructor that takes a collection of root keys and a collection of
internal schema extensions.
+ *
+ * @param rootKeys Root keys.
+ * @param internalSchemaExtensions Internal schema extensions.
+ * @param polymorphicSchemaExtensions Polymorphic schema extensions.
+ */
+ public ConfigurationTreeGenerator(
+ Collection<RootKey<?, ?>> rootKeys,
+ Collection<Class<?>> internalSchemaExtensions,
+ Collection<Class<?>> polymorphicSchemaExtensions) {
+
+ this.rootKeys = rootKeys.stream().collect(toMap(RootKey::key,
identity()));
+
+ Set<Class<?>> allSchemas = collectAllSchemas(rootKeys,
internalSchemaExtensions, polymorphicSchemaExtensions);
+
+ Map<Class<?>, Set<Class<?>>> internalExtensions =
internalExtensionsWithCheck(allSchemas, internalSchemaExtensions);
+ Map<Class<?>, Set<Class<?>>> polymorphicExtensions =
polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
+
+ rootKeys.forEach(key -> generator.compileRootSchema(key.schemaClass(),
internalExtensions, polymorphicExtensions));
+ }
+
+ /**
+ * Creates a new instance of {@link SuperRoot} with all the roots created.
+ *
+ * @return New instance of {@link SuperRoot}.
+ */
+ public synchronized SuperRoot createSuperRoot() {
+ assert generator != null : "ConfigurationTreeGenerator is already
closed";
+
+ SuperRoot superRoot = new SuperRoot(rootCreator());
+ for (RootKey<?, ?> rootKey : rootKeys.values()) {
+ superRoot.addRoot(rootKey, createRootNode(rootKey));
+ }
+
+ return superRoot;
+ }
+
+ /**
+ * Creates new instance of {@code *Configuration} class corresponding to
the given Configuration Schema.
+ *
+ * @param rootKey Root key of the configuration root.
+ * @param changer Configuration changer instance to pass into constructor.
+ * @return Configuration instance.
+ */
+ public synchronized DynamicConfiguration<?, ?> instantiateCfg(RootKey<?,
?> rootKey, DynamicConfigurationChanger changer) {
+ assert generator != null : "ConfigurationTreeGenerator is already
closed";
+
+ return generator.instantiateCfg(rootKey, changer);
+ }
+
+ /**
+ * Creates new instance of {@code *Node} class corresponding to the given
Configuration Schema class.
+ *
+ * @param schemaClass Configuration Schema class.
+ * @return Node instance.
+ */
+ public synchronized InnerNode instantiateNode(Class<?> schemaClass) {
+ assert generator != null : "ConfigurationTreeGenerator is already
closed";
+
+ return generator.instantiateNode(schemaClass);
+ }
+
+ /**
+ * Creates new instance of root {@code *Node} class corresponding to the
given root key.
+ */
+ public synchronized InnerNode createRootNode(RootKey<?, ?> rootKey) {
+ return instantiateNode(rootKey.schemaClass());
+ }
+
+ private Function<String, RootInnerNode> rootCreator() {
+ return key -> {
+ RootKey<?, ?> rootKey = rootKeys.get(key);
+
+ return rootKey == null ? null : new RootInnerNode(rootKey,
createRootNode(rootKey));
+ };
+ }
+
+ @Override
+ public synchronized void close() {
+ generator = null;
+ }
+
+ /**
+ * Collects all schemas and subschemas (recursively) from root keys,
internal and polymorphic schema extensions.
+ *
+ * @param rootKeys root keys
+ * @param internalSchemaExtensions internal schema extensions
+ * @param polymorphicSchemaExtensions polymorphic schema extensions
+ * @return set of all schema classes
+ */
+ private static Set<Class<?>> collectAllSchemas(Collection<RootKey<?, ?>>
rootKeys,
+ Collection<Class<?>> internalSchemaExtensions,
+ Collection<Class<?>> polymorphicSchemaExtensions) {
+ Set<Class<?>> allSchemas = new HashSet<>();
+
+ allSchemas.addAll(collectSchemas(viewReadOnly(rootKeys,
RootKey::schemaClass)));
+ allSchemas.addAll(collectSchemas(internalSchemaExtensions));
+ allSchemas.addAll(collectSchemas(polymorphicSchemaExtensions));
+
+ return allSchemas;
+ }
+
+ /**
+ * Get configuration schemas and their validated internal extensions with
checks.
+ *
+ * @param allSchemas All configuration schemas.
+ * @param internalSchemaExtensions Internal extensions ({@link
InternalConfiguration}) of configuration schemas
+ * ({@link ConfigurationRoot} and {@link Config}).
+ * @return Mapping: original of the schema -> internal schema extensions.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ */
+ private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
+ Set<Class<?>> allSchemas,
+ Collection<Class<?>> internalSchemaExtensions
+ ) {
+ if (internalSchemaExtensions.isEmpty()) {
+ return Map.of();
+ }
+
+ Map<Class<?>, Set<Class<?>>> internalExtensions =
internalSchemaExtensions(internalSchemaExtensions);
+
+ Set<Class<?>> notInAllSchemas =
difference(internalExtensions.keySet(), allSchemas);
+
+ if (!notInAllSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Internal extensions for which no parent configuration
schemas were found: " + notInAllSchemas
+ );
+ }
+
+ return internalExtensions;
+ }
+
+ /**
+ * Get polymorphic extensions of configuration schemas with checks.
+ *
+ * @param allSchemas All configuration schemas.
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link
PolymorphicConfigInstance}) of configuration schemas.
+ * @return Mapping: polymorphic scheme -> extensions (instances) of
polymorphic configuration.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ */
+ private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
+ Set<Class<?>> allSchemas,
+ Collection<Class<?>> polymorphicSchemaExtensions
+ ) {
+ Map<Class<?>, Set<Class<?>>> polymorphicExtensionsByParent =
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
+
+ Set<Class<?>> notInAllSchemas =
difference(polymorphicExtensionsByParent.keySet(), allSchemas);
+
+ if (!notInAllSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Polymorphic extensions for which no polymorphic
configuration schemas were found: " + notInAllSchemas
+ );
+ }
+
+ Collection<Class<?>> noPolymorphicExtensionsSchemas =
allSchemas.stream()
+ .filter(ConfigurationUtil::isPolymorphicConfig)
+ .filter(not(polymorphicExtensionsByParent::containsKey))
+ .collect(toList());
+
+ if (!noPolymorphicExtensionsSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Polymorphic configuration schemas for which no extensions
were found: " + noPolymorphicExtensionsSchemas
+ );
+ }
+
+ checkPolymorphicConfigIds(polymorphicExtensionsByParent);
+
+ for (Map.Entry<Class<?>, Set<Class<?>>> e :
polymorphicExtensionsByParent.entrySet()) {
+ Class<?> schemaClass = e.getKey();
+
+ Field typeIdField = schemaFields(schemaClass).get(0);
+
+ if (!isPolymorphicId(typeIdField)) {
+ throw new IllegalArgumentException(String.format(
+ "First field in a polymorphic configuration schema
must contain @%s: %s",
+ PolymorphicId.class,
+ schemaClass.getName()
+ ));
+ }
+ }
+
+ return polymorphicExtensionsByParent;
+ }
+
+ /**
+ * Checks that there are no conflicts between ids of a polymorphic
configuration and its extensions (instances).
+ *
+ * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions
(instances) of polymorphic configuration.
+ * @throws IllegalArgumentException If a polymorphic configuration id
conflict is found.
+ * @see PolymorphicConfigInstance#value
+ */
+ private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>>
polymorphicExtensions) {
+ // Mapping: id -> configuration schema.
+ Map<String, Class<?>> ids = new HashMap<>();
+
+ for (Map.Entry<Class<?>, Set<Class<?>>> e :
polymorphicExtensions.entrySet()) {
+ for (Class<?> schemaClass : e.getValue()) {
+ String id = polymorphicInstanceId(schemaClass);
+ Class<?> prev = ids.put(id, schemaClass);
+
+ if (prev != null) {
+ throw new IllegalArgumentException("Found an id conflict
for a polymorphic configuration [id="
+ + id + ", schemas=" + List.of(prev, schemaClass));
+ }
+ }
+
+ ids.clear();
+ }
+ }
+}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index fb2655b8cd..ac77696ed7 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -38,6 +38,9 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
/** Include internal configuration nodes (private configuration
extensions). */
private final boolean includeInternal;
+ /** Skip nulls, empty Maps and empty lists. */
+ private final boolean skipEmptyValues;
+
/** Stack with intermediate results. Used to store values during recursive
calls. */
private final Deque<Object> deque = new ArrayDeque<>();
@@ -45,9 +48,20 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
* Constructor.
*
* @param includeInternal Include internal configuration nodes (private
configuration extensions).
+ * @param skipEmptyValues Skip empty values.
*/
- public ConverterToMapVisitor(boolean includeInternal) {
+ public ConverterToMapVisitor(boolean includeInternal, boolean
skipEmptyValues) {
this.includeInternal = includeInternal;
+ this.skipEmptyValues = skipEmptyValues;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param includeInternal Include internal configuration nodes (private
configuration extensions).
+ */
+ public ConverterToMapVisitor(boolean includeInternal) {
+ this(includeInternal, false);
}
/** {@inheritDoc} */
@@ -69,6 +83,10 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
/** {@inheritDoc} */
@Override
public Object visitInnerNode(String key, InnerNode node) {
+ if (skipEmptyValues && node == null) {
+ return null;
+ }
+
Map<String, Object> innerMap = new HashMap<>();
deque.push(innerMap);
@@ -85,6 +103,10 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
/** {@inheritDoc} */
@Override
public Object visitNamedListNode(String key, NamedListNode<?> node) {
+ if (skipEmptyValues && node.size() == 0) {
+ return null;
+ }
+
List<Object> list = new ArrayList<>(node.size());
deque.push(list);
@@ -111,9 +133,27 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
private void addToParent(String key, Object val) {
Object parent = deque.peek();
+ if (skipEmptyValues && val == null) {
+ return;
+ }
+
if (parent instanceof Map) {
+ if (skipEmptyValues && val instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) val;
+ if (map.isEmpty()) {
+ return;
+ }
+ }
+
((Map<String, Object>) parent).put(key, val);
} else if (parent instanceof List) {
+ if (skipEmptyValues && val instanceof List) {
+ List<?> list = (List<?>) val;
+ if (list.isEmpty()) {
+ return;
+ }
+ }
+
((Collection<Object>) parent).add(val);
}
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index f37525eb89..d7e40314cf 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -55,7 +55,7 @@ public final class NamedListNode<N> implements
NamedListChange<N, N>, Traversabl
/** Name of a synthetic configuration property that's used to store "key"
of the named list element in the storage. */
public static final String NAME = "<name>";
- /** Name of a synthetic configuration property that's used to store
mapping fron names to internal identifiers in the storage. */
+ /** Name of a synthetic configuration property that's used to store
mapping from names to internal identifiers in the storage. */
public static final String IDS = "<ids>";
/** Configuration name for the synthetic key. */
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 bab4a1667e..ef6c0da18f 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
@@ -136,6 +136,12 @@ public class ConfigurationFlattener {
return null;
}
+ // In case inner node is null in both trees,
+ // see
LocalFileConfigurationStorageTest#innerNodeWithPartialContent – the node is
someConfigurationValue.
+ if (oldNode == null && newNode == null) {
+ return null;
+ }
+
if (oldNode == null) {
visitAsymmetricInnerNode(newNode, false);
} else if (oldNode.schemaType() != newNode.schemaType()) {
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 59b1be5292..38bea01769 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
@@ -58,7 +58,6 @@ import org.apache.ignite.configuration.validation.Immutable;
import org.apache.ignite.configuration.validation.ValidationContext;
import org.apache.ignite.configuration.validation.ValidationIssue;
import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.direct.KeyPathNode;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.configuration.storage.Data;
@@ -113,13 +112,13 @@ public class ConfigurationChangerTest {
public String strCfg;
}
- private static ConfigurationAsmGenerator cgen = new
ConfigurationAsmGenerator();
+ private static ConfigurationTreeGenerator generator = new
ConfigurationTreeGenerator(KEY, DefaultsConfiguration.KEY);
private final TestConfigurationStorage storage = new
TestConfigurationStorage(LOCAL);
@AfterAll
public static void afterAll() {
- cgen = null;
+ generator.close();
}
/**
@@ -218,12 +217,10 @@ public class ConfigurationChangerTest {
};
ConfigurationChanger changer2 = new TestConfigurationChanger(
- cgen,
List.of(KEY),
Set.of(validator),
storage,
- List.of(),
- List.of()
+ generator
);
changer2.start();
@@ -592,7 +589,8 @@ public class ConfigurationChangerTest {
}
private ConfigurationChanger createChanger(RootKey<?, ?> rootKey) {
- return new TestConfigurationChanger(cgen, List.of(rootKey), Set.of(),
storage, List.of(), List.of());
+
+ return new TestConfigurationChanger(List.of(rootKey), Set.of(),
storage, generator);
}
private static KeyPathNode node(String key) {
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
similarity index 96%
rename from
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
rename to
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
index 36efcd9a66..af7cdd9359 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
@@ -40,6 +40,7 @@ import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.apache.ignite.configuration.ConfigurationReadOnlyException;
import
org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
+import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.AbstractConfiguration;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
@@ -54,6 +55,7 @@ import
org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.ConfigurationChanger;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.DynamicConfiguration;
import org.apache.ignite.internal.configuration.TestConfigurationChanger;
import
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -66,51 +68,56 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
- * Testing the {@link ConfigurationAsmGenerator}.
+ * Testing the {@link ConfigurationTreeGenerator}.
*/
-public class ConfigurationAsmGeneratorTest {
+public class ConfigurationTreeGeneratorTest {
/** Configuration generator. */
- private static ConfigurationAsmGenerator generator;
+ private static ConfigurationTreeGenerator generator;
+
+
+ private static Collection<Class<?>> internalExtensions = List.of(
+ ExtendedTestRootConfigurationSchema.class,
+ ExtendedSecondTestRootConfigurationSchema.class,
+ ExtendedTestConfigurationSchema.class,
+ ExtendedSecondTestConfigurationSchema.class
+ );
+
+ private static Collection<Class<?>> polymorphicExtensions = List.of(
+ FirstPolymorphicInstanceTestConfigurationSchema.class,
+ SecondPolymorphicInstanceTestConfigurationSchema.class,
+ NonDefaultPolymorphicInstanceTestConfigurationSchema.class,
+ FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
+ SecondPolymorphicNamedInstanceTestConfigurationSchema.class,
+ PolyInst0InjectedNameConfigurationSchema.class,
+ PolyInst1InjectedNameConfigurationSchema.class
+ );
+
+ private static Collection<RootKey<?, ?>> rootKeys = List.of(
+ TestRootConfiguration.KEY,
+ InjectedNameRootConfiguration.KEY,
+ RootFromAbstractConfiguration.KEY
+ );
/** Configuration changer. */
private ConfigurationChanger changer;
@BeforeAll
public static void beforeAll() {
- generator = new ConfigurationAsmGenerator();
+ generator = new ConfigurationTreeGenerator(rootKeys,
internalExtensions, polymorphicExtensions);
}
@AfterAll
public static void afterAll() {
- generator = null;
+ generator.close();
}
@BeforeEach
void beforeEach() {
- Collection<Class<?>> internalExtensions = List.of(
- ExtendedTestRootConfigurationSchema.class,
- ExtendedSecondTestRootConfigurationSchema.class,
- ExtendedTestConfigurationSchema.class,
- ExtendedSecondTestConfigurationSchema.class
- );
-
- Collection<Class<?>> polymorphicExtensions = List.of(
- FirstPolymorphicInstanceTestConfigurationSchema.class,
- SecondPolymorphicInstanceTestConfigurationSchema.class,
- NonDefaultPolymorphicInstanceTestConfigurationSchema.class,
- FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
- SecondPolymorphicNamedInstanceTestConfigurationSchema.class,
- PolyInst0InjectedNameConfigurationSchema.class,
- PolyInst1InjectedNameConfigurationSchema.class
- );
-
changer = new TestConfigurationChanger(
- generator,
- List.of(TestRootConfiguration.KEY,
InjectedNameRootConfiguration.KEY, RootFromAbstractConfiguration.KEY),
+ rootKeys,
Set.of(),
new TestConfigurationStorage(LOCAL),
- internalExtensions,
- polymorphicExtensions
+ generator
);
changer.start();
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
index 286c2a57ba..ed56c12c8d 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
@@ -99,16 +99,18 @@ public class DirectPropertiesTest {
public String str = "bar";
}
- private final ConfigurationRegistry registry = new ConfigurationRegistry(
- List.of(DirectConfiguration.KEY),
- Set.of(),
- new TestConfigurationStorage(LOCAL),
- List.of(),
- List.of()
- );
+ private ConfigurationRegistry registry;
@BeforeEach
void setUp() {
+ registry = new ConfigurationRegistry(
+ List.of(DirectConfiguration.KEY),
+ Set.of(),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of()
+ );
+
registry.start();
registry.initializeDefaults();
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
index dfcc0a5d3a..74a38ebf6f 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
@@ -37,6 +37,7 @@ 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.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.direct.DirectPropertiesTest;
import
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
@@ -77,16 +78,25 @@ public class InternalIdTest {
public static class InternalIdFooConfigurationSchema extends
InternalIdPolymorphicConfigurationSchema {
}
- private final ConfigurationRegistry registry = new ConfigurationRegistry(
- List.of(InternalIdParentConfiguration.KEY),
- Set.of(),
- new TestConfigurationStorage(LOCAL),
- List.of(InternalIdInternalConfigurationSchema.class),
- List.of(InternalIdFooConfigurationSchema.class)
- );
+ private ConfigurationRegistry registry;
+
+ private ConfigurationTreeGenerator generator;
@BeforeEach
void setUp() {
+ generator = new ConfigurationTreeGenerator(
+ List.of(InternalIdParentConfiguration.KEY),
+ List.of(InternalIdInternalConfigurationSchema.class),
+ List.of(InternalIdFooConfigurationSchema.class)
+ );
+
+ registry = new ConfigurationRegistry(
+ List.of(InternalIdParentConfiguration.KEY),
+ Set.of(),
+ new TestConfigurationStorage(LOCAL),
+ generator
+ );
+
registry.start();
registry.initializeDefaults();
@@ -95,6 +105,8 @@ public class InternalIdTest {
@AfterEach
void tearDown() throws Exception {
registry.stop();
+
+ generator.close();
}
/**
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 83418ce89b..7ab55d9d36 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
@@ -38,8 +38,8 @@ import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
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.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.Data;
import
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
@@ -79,7 +79,7 @@ public class NamedListNodeTest {
}
/** Runtime implementations generator. */
- private static ConfigurationAsmGenerator cgen;
+ private static ConfigurationTreeGenerator cgen;
/** Test configuration storage. */
private TestConfigurationStorage storage;
@@ -90,13 +90,13 @@ public class NamedListNodeTest {
/** Instantiates {@link #cgen}. */
@BeforeAll
public static void beforeAll() {
- cgen = new ConfigurationAsmGenerator();
+ cgen = new ConfigurationTreeGenerator(FirstConfiguration.KEY);
}
/** Nullifies {@link #cgen} to prevent memory leak from having runtime
ClassLoader accessible from GC root. */
@AfterAll
public static void afterAll() {
- cgen = null;
+ cgen.close();
}
/**
@@ -107,12 +107,10 @@ public class NamedListNodeTest {
storage = new TestConfigurationStorage(LOCAL);
changer = new TestConfigurationChanger(
- cgen,
List.of(FirstConfiguration.KEY),
Set.of(),
storage,
- List.of(),
- List.of()
+ cgen
);
changer.start();
diff --git
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
index 4b72a70d2a..2bda7a0fe7 100644
---
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
+++
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -18,20 +18,12 @@
package org.apache.ignite.internal.configuration;
import static java.util.concurrent.CompletableFuture.completedFuture;
-import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
-import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
import java.util.Collection;
-import java.util.Map;
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.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.InternalConfiguration;
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.jetbrains.annotations.Nullable;
@@ -39,36 +31,26 @@ import org.jetbrains.annotations.Nullable;
/** Implementation of {@link ConfigurationChanger} to be used in tests. Has no
support of listeners. */
public class TestConfigurationChanger extends ConfigurationChanger {
/** Runtime implementations generator for node classes. */
- private final ConfigurationAsmGenerator cgen;
+ private final ConfigurationTreeGenerator generator;
/**
* Constructor.
*
- * @param cgen Runtime implementations generator
for node classes. Will be used to instantiate nodes objects.
* @param rootKeys Configuration root keys.
* @param validators Validators.
* @param storage Configuration storage.
- * @param internalSchemaExtensions Internal extensions ({@link
InternalConfiguration}) of configuration schemas ({@link
- * ConfigurationRoot} and {@link
Config}).
- * @param polymorphicSchemaExtensions Polymorphic extensions ({@link
PolymorphicConfigInstance}) of configuration schemas.
+ * @param generator Runtime implementations tree
generator for node classes.
* @throws IllegalArgumentException If the configuration type of the root
keys is not equal to the storage type.
*/
public TestConfigurationChanger(
- ConfigurationAsmGenerator cgen,
Collection<RootKey<?, ?>> rootKeys,
Set<Validator<?, ?>> validators,
ConfigurationStorage storage,
- Collection<Class<?>> internalSchemaExtensions,
- Collection<Class<?>> polymorphicSchemaExtensions
+ ConfigurationTreeGenerator generator
) {
super(noOpListener(), rootKeys, validators, storage);
- this.cgen = cgen;
-
- Map<Class<?>, Set<Class<?>>> internalExtensions =
internalSchemaExtensions(internalSchemaExtensions);
- Map<Class<?>, Set<Class<?>>> polymorphicExtensions =
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
-
- rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(),
internalExtensions, polymorphicExtensions));
+ this.generator = generator;
}
private static ConfigurationUpdateListener noOpListener() {
@@ -89,6 +71,6 @@ public class TestConfigurationChanger extends
ConfigurationChanger {
/** {@inheritDoc} */
@Override
public InnerNode createRootNode(RootKey<?, ?> rootKey) {
- return cgen.instantiateNode(rootKey.schemaClass());
+ return generator.createRootNode(rootKey);
}
}
diff --git
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
index 09f7eb2207..04896f567b 100644
---
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
+++
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
@@ -64,13 +64,7 @@ class DistributionZoneManagerTest extends IgniteAbstractTest
{
private static final String NEW_ZONE_NAME = "zone2";
- private final ConfigurationRegistry registry = new ConfigurationRegistry(
- List.of(DistributionZonesConfiguration.KEY),
- Set.of(FilterValidator.INSTANCE),
- new TestConfigurationStorage(DISTRIBUTED),
- List.of(),
- List.of(TestPersistStorageConfigurationSchema.class)
- );
+ private ConfigurationRegistry registry;
private DistributionZoneManager distributionZoneManager;
@@ -79,6 +73,14 @@ class DistributionZoneManagerTest extends IgniteAbstractTest
{
@BeforeEach
public void setUp() {
+ registry = new ConfigurationRegistry(
+ List.of(DistributionZonesConfiguration.KEY),
+ Set.of(FilterValidator.INSTANCE),
+ new TestConfigurationStorage(DISTRIBUTED),
+ List.of(),
+ List.of(TestPersistStorageConfigurationSchema.class)
+ );
+
registry.start();
registry.initializeDefaults();
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
index 36b858f33d..db1144c138 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
@@ -25,6 +25,7 @@ import
org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
import org.apache.ignite.internal.testframework.TestIgnitionManager;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@@ -34,6 +35,7 @@ import org.junit.jupiter.params.provider.ValueSource;
* Integration test for checking SSL configuration validation.
*/
@ExtendWith(WorkDirectoryExtension.class)
+@Disabled("https://issues.apache.org/jira/browse/IGNITE-19315")
public class ItSslConfigurationValidationTest {
@ParameterizedTest
@ValueSource(strings = {"clientConnector", "network", "rest"})
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
index 0172a9c54d..e78b37d878 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
@@ -76,6 +76,7 @@ import
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyImp
import
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyServiceImpl;
import
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologyService;
import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.SecurityConfiguration;
import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
@@ -583,6 +584,8 @@ public class ItRebalanceDistributedTest {
private List<IgniteComponent> nodeComponents;
+ private final ConfigurationTreeGenerator generator;
+
private final Map<TablePartitionId, CompletableFuture<Void>>
finishHandleChangeStableAssignmentEventFutures
= new ConcurrentHashMap<>();
@@ -600,15 +603,18 @@ public class ItRebalanceDistributedTest {
vaultManager = createVault(name, dir);
+ generator = new ConfigurationTreeGenerator(
+ NetworkConfiguration.KEY, RestConfiguration.KEY,
ClientConnectorConfiguration.KEY
+ );
+
Path configPath = workDir.resolve(testInfo.getDisplayName());
nodeCfgMgr = new ConfigurationManager(
List.of(NetworkConfiguration.KEY,
RestConfiguration.KEY,
ClientConnectorConfiguration.KEY),
Set.of(),
- new LocalFileConfigurationStorage(configPath),
- List.of(),
- List.of()
+ new LocalFileConfigurationStorage(configPath, generator),
+ generator
);
clusterService = ClusterServiceTestUtils.clusterService(
@@ -852,6 +858,8 @@ public class ItRebalanceDistributedTest {
}
});
+
+ generator.close();
}
NetworkAddress address() {
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 c4eef0882a..9db9950775 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
@@ -70,6 +70,7 @@ import
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyImp
import
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyServiceImpl;
import org.apache.ignite.internal.configuration.ConfigurationManager;
import org.apache.ignite.internal.configuration.ConfigurationModules;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.NodeConfigWriteException;
import org.apache.ignite.internal.configuration.SecurityConfiguration;
import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
@@ -118,7 +119,6 @@ import org.apache.ignite.internal.tx.message.TxMessageGroup;
import org.apache.ignite.internal.util.IgniteUtils;
import org.apache.ignite.internal.vault.VaultManager;
import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
-import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.IgniteInternalException;
import org.apache.ignite.lang.IgniteStringFormatter;
import org.apache.ignite.lang.IgniteSystemProperties;
@@ -261,14 +261,20 @@ public class ItIgniteNodeRestartTest extends
IgniteAbstractTest {
} catch (IOException e) {
throw new NodeConfigWriteException("Failed to write config content
to file.", e);
}
- var nodeCfgMgr = new ConfigurationManager(
+
+ var generator = new ConfigurationTreeGenerator(
modules.local().rootKeys(),
- modules.local().validators(),
- new LocalFileConfigurationStorage(configFile),
modules.local().internalSchemaExtensions(),
modules.local().polymorphicSchemaExtensions()
);
+ var nodeCfgMgr = new ConfigurationManager(
+ modules.local().rootKeys(),
+ modules.local().validators(),
+ new LocalFileConfigurationStorage(configFile, generator),
+ generator
+ );
+
NetworkConfiguration networkConfiguration =
nodeCfgMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY);
var nettyBootstrapFactory = new
NettyBootstrapFactory(networkConfiguration, name);
@@ -433,12 +439,6 @@ public class ItIgniteNodeRestartTest extends
IgniteAbstractTest {
nodeCfgMgr.start();
- try {
- nodeCfgMgr.bootstrap(configFile);
- } catch (Exception e) {
- throw new IgniteException("Unable to parse user-specific
configuration.", e);
- }
-
// Start the remaining components.
List<IgniteComponent> otherComponents = List.of(
nettyBootstrapFactory,
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 03eb58f250..f785d423aa 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
@@ -61,7 +61,7 @@ import
org.apache.ignite.internal.configuration.AuthenticationConfiguration;
import org.apache.ignite.internal.configuration.ConfigurationManager;
import org.apache.ignite.internal.configuration.ConfigurationModules;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
-import org.apache.ignite.internal.configuration.NodeConfigReadException;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.SecurityConfiguration;
import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -299,14 +299,19 @@ public class IgniteImpl implements Ignite {
ConfigurationModules modules =
loadConfigurationModules(serviceProviderClassLoader);
- nodeCfgMgr = new ConfigurationManager(
+ ConfigurationTreeGenerator generator = new ConfigurationTreeGenerator(
modules.local().rootKeys(),
- modules.local().validators(),
- new LocalFileConfigurationStorage(configPath),
modules.local().internalSchemaExtensions(),
modules.local().polymorphicSchemaExtensions()
);
+ nodeCfgMgr = new ConfigurationManager(
+ modules.local().rootKeys(),
+ modules.local().validators(),
+ new LocalFileConfigurationStorage(configPath, generator),
+ generator
+ );
+
ConfigurationRegistry nodeConfigRegistry =
nodeCfgMgr.configurationRegistry();
NetworkConfiguration networkConfiguration =
nodeConfigRegistry.getConfiguration(NetworkConfiguration.KEY);
@@ -642,14 +647,6 @@ public class IgniteImpl implements Ignite {
// Node configuration manager startup.
lifecycleManager.startComponent(nodeCfgMgr);
- // Node configuration manager bootstrap.
-
- try {
- nodeCfgMgr.bootstrap(configPath);
- } catch (Exception e) {
- throw new NodeConfigReadException("Unable to parse
user-specific configuration", e);
- }
-
// Start the components that are required to join the cluster.
lifecycleManager.startComponents(
nettyBootstrapFactory,
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 b43b069329..d730dd353d 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
@@ -17,10 +17,18 @@
package org.apache.ignite.internal.configuration.storage;
+import static java.util.stream.Collectors.toMap;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
+
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValue;
+import com.typesafe.config.impl.ConfigImpl;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
@@ -28,8 +36,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
@@ -40,10 +46,13 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.stream.Collectors;
import org.apache.ignite.configuration.annotation.ConfigurationType;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.NodeConfigCreateException;
import org.apache.ignite.internal.configuration.NodeConfigWriteException;
+import org.apache.ignite.internal.configuration.SuperRoot;
+import org.apache.ignite.internal.configuration.hocon.HoconConverter;
+import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
import org.apache.ignite.internal.future.InFlightFutures;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
@@ -56,60 +65,88 @@ import org.apache.ignite.internal.util.IgniteUtils;
public class LocalFileConfigurationStorage implements ConfigurationStorage {
private static final IgniteLogger LOG =
Loggers.forClass(LocalFileConfigurationStorage.class);
- /**
- * Path to config file.
- */
+ /** Path to config file. */
private final Path configPath;
- /**
- * Path to temporary configuration storage.
- */
+ /** Path to temporary configuration storage. */
private final Path tempConfigPath;
+ /** R/W lock to guard the latest configuration and config file. */
private final ReadWriteLock lock = new ReentrantReadWriteLock();
- /**
- * Latest state of last applied configuration.
- */
+ /** Latest state of last applied configuration. */
private final Map<String, Serializable> latest = new ConcurrentHashMap<>();
- /**
- * Configuration changes listener.
- * */
+ /** Configuration tree generator. */
+ private final ConfigurationTreeGenerator generator;
+
+ /** Configuration changes listener. */
private final AtomicReference<ConfigurationStorageListener> lsnrRef = new
AtomicReference<>();
- private final ExecutorService threadPool = Executors.newFixedThreadPool(2,
new NamedThreadFactory("loc-cfg-file", LOG));
+ /** Thread pool for configuration updates notifications. */
+ private final ExecutorService notificationsThreadPool =
Executors.newFixedThreadPool(
+ 2, new NamedThreadFactory("cfg-file", LOG)
+ );
+ /** Tracks all running futures. */
private final InFlightFutures futureTracker = new InFlightFutures();
+ /** Last revision for configuration. */
private long lastRevision = 0L;
/**
* Constructor.
*
* @param configPath Path to node bootstrap configuration file.
+ * @param generator Configuration tree generator.
*/
- public LocalFileConfigurationStorage(Path configPath) {
+ public LocalFileConfigurationStorage(Path configPath,
ConfigurationTreeGenerator generator) {
this.configPath = configPath;
- tempConfigPath = configPath.resolveSibling(configPath.getFileName() +
".tmp");
+ this.generator = generator;
+ this.tempConfigPath =
configPath.resolveSibling(configPath.getFileName() + ".tmp");
+
checkAndRestoreConfigFile();
}
@Override
public CompletableFuture<Data> readDataOnRecovery() {
- return CompletableFuture.completedFuture(new
Data(Collections.emptyMap(), 0));
+ lock.writeLock().lock();
+ try {
+ SuperRoot superRoot = generator.createSuperRoot();
+ SuperRoot copiedSuperRoot = superRoot.copy();
+
+ Config hocon = readHoconFromFile();
+ HoconConverter.hoconSource(hocon.root()).descend(copiedSuperRoot);
+
+ Map<String, Serializable> flattenedUpdatesMap =
createFlattenedUpdatesMap(superRoot, copiedSuperRoot);
+ flattenedUpdatesMap.forEach((key, value) -> {
+ if (value != null) { // Filter defaults.
+ latest.put(key, value);
+ }
+ });
+
+ return CompletableFuture.completedFuture(new Data(latest,
lastRevision));
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private Config readHoconFromFile() {
+ checkAndRestoreConfigFile();
+
+ return ConfigFactory.parseFile(configPath.toFile(),
ConfigParseOptions.defaults().setAllowMissing(false));
}
@Override
public CompletableFuture<Map<String, ? extends Serializable>>
readAllLatest(String prefix) {
lock.readLock().lock();
try {
- checkAndRestoreConfigFile();
- Map<String, Serializable> map = latest.entrySet()
- .stream()
- .filter(entry -> entry.getKey().startsWith(prefix))
- .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
- return CompletableFuture.completedFuture(map);
+ return CompletableFuture.completedFuture(
+ latest.entrySet()
+ .stream()
+ .filter(entry -> entry.getKey().startsWith(prefix))
+ .collect(toMap(Entry::getKey, Entry::getValue))
+ );
} finally {
lock.readLock().unlock();
}
@@ -119,7 +156,6 @@ public class LocalFileConfigurationStorage implements
ConfigurationStorage {
public CompletableFuture<Serializable> readLatest(String key) {
lock.readLock().lock();
try {
- checkAndRestoreConfigFile();
return CompletableFuture.completedFuture(latest.get(key));
} finally {
lock.readLock().unlock();
@@ -133,22 +169,31 @@ public class LocalFileConfigurationStorage implements
ConfigurationStorage {
if (ver != lastRevision) {
return CompletableFuture.completedFuture(false);
}
- checkAndRestoreConfigFile();
- // TODO: https://issues.apache.org/jira/browse/IGNITE-19152
- //saveValues(newValues);
- latest.putAll(newValues);
- lastRevision++;
- runAsync(() -> lsnrRef.get().onEntriesChanged(new Data(newValues,
lastRevision)));
+
+ mergeAndSave(newValues);
+
+ sendNotificationAsync(new Data(newValues, lastRevision));
+
return CompletableFuture.completedFuture(true);
} finally {
lock.writeLock().unlock();
}
}
- private void runAsync(Runnable runnable) {
- CompletableFuture<Void> future = CompletableFuture.runAsync(runnable,
threadPool);
+ private void mergeAndSave(Map<String, ? extends Serializable> newValues) {
+ updateLatestState(newValues);
+ saveConfigFile();
+ lastRevision++;
+ }
- futureTracker.registerFuture(future);
+ private void updateLatestState(Map<String, ? extends Serializable>
newValues) {
+ newValues.forEach((key, value) -> {
+ if (value == null) { // Null means that we should remove this
entry.
+ latest.remove(key);
+ } else {
+ latest.put(key, value);
+ }
+ });
}
@Override
@@ -175,56 +220,62 @@ public class LocalFileConfigurationStorage implements
ConfigurationStorage {
@Override
public void close() {
- IgniteUtils.shutdownAndAwaitTermination(threadPool, 10,
TimeUnit.SECONDS);
-
futureTracker.cancelInFlightFutures();
+
+ IgniteUtils.shutdownAndAwaitTermination(notificationsThreadPool, 10,
TimeUnit.SECONDS);
}
- private void saveValues(Map<String, ? extends Serializable> values) {
+ private void saveConfigFile() {
try {
- Files.write(tempConfigPath,
renderHoconString(values).getBytes(StandardCharsets.UTF_8),
- StandardOpenOption.SYNC, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
- Files.move(tempConfigPath, configPath,
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ Files.write(
+ tempConfigPath,
+ renderHoconString().getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.SYNC, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
+ );
+
+ Files.move(
+ tempConfigPath,
+ configPath,
+ StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING
+ );
} catch (IOException e) {
throw new NodeConfigWriteException(
- "Failed to write values " + values + " to config file.",
e);
+ "Failed to write values to config file.", e);
}
}
/**
* Convert provided map to Hocon String representation.
*
- * @param values Values of configuration.
* @return Configuration file string representation in HOCON format.
*/
- private String renderHoconString(Map<String, ? extends Serializable>
values) {
- Map<String, Object> map =
values.entrySet().stream().collect(Collectors.toMap(Entry::getKey, stringEntry
-> {
- Serializable value = stringEntry.getValue();
- if (value.getClass().isArray()) {
- return Arrays.asList((Object[]) value);
- }
- return value;
- }));
- Config other = ConfigFactory.parseMap(map);
- Config newConfig = other.withFallback(parseConfigOptions()).resolve();
+ private String renderHoconString() {
+ // Super root that'll be filled from the storage data.
+ SuperRoot rootNode = generator.createSuperRoot();
+
+ fillFromPrefixMap(rootNode, toPrefixMap(latest));
+
+ Object transformed = rootNode.accept(null, new
ConverterToMapVisitor(false, true));
+
+ ConfigValue conf = ConfigImpl.fromAnyRef(transformed, null);
+
+ return renderConfig((ConfigObject) conf);
+ }
+
+ private static String renderConfig(ConfigObject conf) {
+ Config newConfig = conf.toConfig().resolve();
return newConfig.isEmpty()
? ""
:
newConfig.root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(false));
}
- private Config parseConfigOptions() {
- return ConfigFactory.parseFile(
- configPath.toFile(),
- ConfigParseOptions.defaults().setAllowMissing(false));
- }
-
/** Check that configuration file still exists and restore it with latest
applied state in case it was deleted. */
private void checkAndRestoreConfigFile() {
if (!configPath.toFile().exists()) {
try {
if (configPath.toFile().createNewFile()) {
if (!latest.isEmpty()) {
- saveValues(latest);
+ saveConfigFile();
}
} else {
throw new NodeConfigCreateException("Failed to re-create
config file");
@@ -234,4 +285,13 @@ public class LocalFileConfigurationStorage implements
ConfigurationStorage {
}
}
}
+
+ private void sendNotificationAsync(Data data) {
+ CompletableFuture<Void> future = CompletableFuture.runAsync(
+ () -> lsnrRef.get().onEntriesChanged(data),
+ notificationsThreadPool
+ );
+
+ futureTracker.registerFuture(future);
+ }
}
diff --git
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
index 776fa792be..32dc3e7d9c 100644
---
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
+++
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
@@ -29,7 +29,6 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -38,8 +37,8 @@ import java.util.function.Consumer;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
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.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.internal.metastorage.EntryEvent;
@@ -95,15 +94,14 @@ public class DistributedConfigurationCatchUpTest {
public void testMetaStorageRevisionDifferentFromConfigurationOnRestart()
throws Exception {
RootKey<DistributedTestConfiguration, DistributedTestView> rootKey =
DistributedTestConfiguration.KEY;
- ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
+ ConfigurationTreeGenerator generator = new
ConfigurationTreeGenerator(rootKey);
MetaStorageMockWrapper wrapper = new MetaStorageMockWrapper();
DistributedConfigurationStorage storage = storage(wrapper);
try {
- var changer = new TestConfigurationChanger(cgen, List.of(rootKey),
Set.of(),
- storage, Collections.emptyList(), Collections.emptyList());
+ var changer = new TestConfigurationChanger(List.of(rootKey),
Set.of(), storage, generator);
try {
changer.start();
@@ -133,8 +131,7 @@ public class DistributedConfigurationCatchUpTest {
try {
- var changer = new TestConfigurationChanger(cgen, List.of(rootKey),
Set.of(),
- storage, Collections.emptyList(), Collections.emptyList());
+ var changer = new TestConfigurationChanger(List.of(rootKey),
Set.of(), storage, generator);
try {
changer.start();
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 568d33e267..6ffd2cacac 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
@@ -17,75 +17,457 @@
package org.apache.ignite.internal.configuration.storage;
-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;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.equalTo;
+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 java.io.IOException;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+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.Value;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
+import org.apache.ignite.internal.configuration.TestConfigurationChanger;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.junit.jupiter.api.Disabled;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-/**
- * Tests for the {@link LocalFileConfigurationStorage}.
- */
+/** Test for local file configurations storage. */
@ExtendWith(WorkDirectoryExtension.class)
-@Disabled("https://issues.apache.org/jira/browse/IGNITE-19152")
-public class LocalFileConfigurationStorageTest extends
ConfigurationStorageTest {
+//TODO: https://issues.apache.org/jira/browse/IGNITE-19303
+public class LocalFileConfigurationStorageTest {
private static final String CONFIG_NAME = "ignite-config.conf";
+ private static ConfigurationTreeGenerator treeGenerator;
+
@WorkDirectory
private Path tmpDir;
- @Override
- public ConfigurationStorage getStorage() {
- return new LocalFileConfigurationStorage(getConfigFile());
+ private LocalFileConfigurationStorage storage;
+
+ private TestConfigurationChanger changer;
+
+ @BeforeAll
+ public static void beforeAll() {
+ treeGenerator = new ConfigurationTreeGenerator(TopConfiguration.KEY,
TopEmptyConfiguration.KEY);
+ }
+
+ @AfterAll
+ public static void afterAll() {
+ treeGenerator.close();
+ }
+
+ private Path getConfigFile() {
+ return tmpDir.resolve(CONFIG_NAME);
+ }
+
+ @BeforeEach
+ void before() {
+ storage = new LocalFileConfigurationStorage(getConfigFile(),
treeGenerator);
+
+ changer = new TestConfigurationChanger(
+ List.of(TopConfiguration.KEY),
+ Set.of(),
+ storage,
+ treeGenerator
+ );
+
+ changer.start();
+ }
+
+ @AfterEach
+ void after() {
+ changer.stop();
+ }
+
+
+ /** Default values are not enriched on read when the config file is empty.
*/
+ @Test
+ void empty() throws IOException {
+ // Given
+ assertThat(configFileContent(), emptyString());
+
+ // When
+ var storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues.entrySet(), hasSize(0));
+ }
+
+ /** Named list entities can be added. */
+ @Test
+ void add() throws Exception {
+ // Given
+ assertThat(configFileContent(), emptyString());
+
+ // And
+ var topConfiguration = (TopConfiguration)
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+ topConfiguration.namedList().change(b -> b.create("name1", x -> {
+ x.changeStrVal("strVal1");
+ x.changeIntVal(-1);
+ })).get();
+
+ // When
+ var storageValues = readAllLatest();
+
+ // Then the map has updated values
+ //
+ // top.namedList.<generatedUUID>.strVal -> strVal1
+ // top.namedList.<generatedUUID>.intVal -> -1
+ // top.namedList.<generatedUUID>.<name> -> name1
+ // top.namedList.<ids>.name1 -> "<generatedUUID>"
+ // top.namedList.<generatedUUID>.<order> -> 0
+
+ assertThat(storageValues, allOf(aMapWithSize(5), hasValue(-1)));
+ assertThat(storageValues, allOf(aMapWithSize(5), hasValue("strVal1")));
+
+ // And
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " strVal=strVal1\n"
+ + " }\n"
+ + " ]\n"
+ + "}"
+ ));
+
+ // When
+ topConfiguration.namedList().change(b -> b.create("name2", x -> {
+ x.changeStrVal("strVal2");
+ x.changeIntVal(-2);
+ })).get();
+ // And
+ storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-2)));
+ assertThat(storageValues, allOf(aMapWithSize(10),
hasValue("strVal2")));
+ // And
+ assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-1)));
+ assertThat(storageValues, allOf(aMapWithSize(10),
hasValue("strVal1")));
+ // And
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " strVal=strVal1\n"
+ + " },\n"
+ + " {\n"
+ + " intVal=-2\n"
+ + " name=name2\n"
+ + " strVal=strVal2\n"
+ + " }\n"
+ + " ]\n"
+ + "}\n"
+ ));
+ }
+
+ /** Update values. */
+ @Test
+ void update() throws Exception {
+ // Given
+ assertThat(configFileContent(), emptyString());
+
+ // When
+ var topConfiguration = (TopConfiguration)
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+ topConfiguration.shortVal().update((short) 3).get();
+ // And
+ var storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+ // And
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " shortVal=3\n"
+ + "}\n"
+ ));
+
+ // When create named list entity with defaults
+ topConfiguration.namedList().change(b -> b.create("name1", x -> {
+ })).get();
+ // And
+ storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(6), hasValue(1)));
+ assertThat(storageValues, allOf(aMapWithSize(6), hasValue("foo")));
+ // And
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=1\n"
+ + " name=name1\n"
+ + " strVal=foo\n"
+ + " }\n"
+ + " ]\n"
+ + " shortVal=3\n"
+ + "}"
+ ));
+
+ // When update named list entity
+ topConfiguration.namedList().change(b -> b.update("name1", x -> {
+ x.changeStrVal("strVal1");
+ x.changeIntVal(-1);
+ })).get();
+ // And
+ storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(6), hasValue(-1)));
+ assertThat(storageValues, allOf(aMapWithSize(6), hasValue("strVal1")));
+ // And
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " strVal=strVal1\n"
+ + " }\n"
+ + " ]\n"
+ + " shortVal=3\n"
+ + "}"
+ ));
}
+ /** Remove values. */
@Test
- void testHocon() throws IOException {
- // All of this is needed because write expects serializable values and
only concrete classes are serializable
- HashMap<String, ArrayList<String>> map = new HashMap<>(Map.of("list",
new ArrayList<>(List.of("val1", "val2"))));
- var data = Map.of("foo1", "bar1", "foo2", "bar2", "map", map);
-
- assertThat(storage.write(data, 0), willBe(true));
-
- String contents = Files.readString(getConfigFile());
-
- // \n instead of System.lineSeparator because Config library writes \n
only
- assertThat(contents, is("foo1=bar1\n"
- + "foo2=bar2\n"
- + "map {\n"
- + " list=[\n"
- + " val1,\n"
- + " val2\n"
+ void remove() throws Exception {
+ // Given
+ var topConfiguration = (TopConfiguration)
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+ topConfiguration.namedList().change(b -> {
+ b.create("name1", x -> {
+ x.changeStrVal("strVal1");
+ x.changeIntVal(-1);
+ });
+ b.create("name2", x -> {
+ x.changeStrVal("strVal2");
+ x.changeIntVal(-2);
+ });
+ }).get();
+
+ topConfiguration.shortVal().update((short) 3).get();
+ // And values are saved to file
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " strVal=strVal1\n"
+ + " },\n"
+ + " {\n"
+ + " intVal=-2\n"
+ + " name=name2\n"
+ + " strVal=strVal2\n"
+ + " }\n"
+ + " ]\n"
+ + " shortVal=3\n"
+ + "}\n"
+ ));
+
+ // When remove named list entity
+ topConfiguration.namedList().change(b -> b.delete("name1")).get();
+ // And
+ var storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(6),
Matchers.not(hasValue("strVal1"))));
+ // And entity removed from file
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-2\n"
+ + " name=name2\n"
+ + " strVal=strVal2\n"
+ + " }\n"
+ + " ]\n"
+ + " shortVal=3\n"
+ + "}\n"
+ ));
+
+ // When remove the last entity
+ topConfiguration.namedList().change(b -> b.delete("name2")).get();
+ // And
+ storageValues = readAllLatest();
+
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+ // And entity removed from file
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " shortVal=3\n"
+ + "}\n"
+ ));
+ }
+
+ /** Delete file before read on recovery. */
+ @Test
+ void deleteFileBeforeReadOnRecovery() throws IOException {
+ // Given
+ Files.delete(getConfigFile());
+
+ // When
+ var storageValues = storage.readDataOnRecovery().join().values();
+
+ // Then
+ assertThat(storageValues.entrySet(), hasSize(0));
+ // And empty file was created
+ assertThat(configFileContent(), equalTo(""));
+ }
+
+
+ /** File content is not changed when read data on recovery. */
+ @Test
+ void fileContentIsNotChanged() throws IOException {
+ // Given
+ String fileContent = "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " }\n"
+ " ]\n"
- + "}\n"));
+ + "}\n";
+
+ Files.write(getConfigFile(),
fileContent.getBytes(StandardCharsets.UTF_8));
+
+ // When
+ var storageValues = storage.readDataOnRecovery().join().values();
+ // Then
+ assertThat(storageValues, allOf(aMapWithSize(5), hasValue(-1)));
+ assertThat(storageValues, allOf(aMapWithSize(5), hasValue("foo"))); //
default value
+ // And file was not changed
+ assertThat(configFileContent(), equalTo(fileContent));
}
+ /** Delete file before read all. */
@Test
- void testMergeHocon() throws IOException {
- var data = Map.of("foo1", "bar");
- assertThat(storage.write(data, 0), willBe(true));
+ void deleteFileBeforeReadAll() throws Exception {
+ // Given
+ Files.delete(getConfigFile());
+
+ // When
+ var storageValues = readAllLatest();
- var append = Map.of("foo1", "baz", "foo2", "bar");
- assertThat(storage.write(append, 1), willBe(true));
+ // Then
+ assertThat(storageValues.entrySet(), hasSize(0));
+ // And there is no file
+ assertThat(Files.exists(getConfigFile()), is(false));
- String contents = Files.readString(getConfigFile());
- assertThat(contents, is("foo1=baz\n"
- + "foo2=bar\n"));
+ // When update configuration
+ var topConfiguration = (TopConfiguration)
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+ topConfiguration.namedList().change(b -> b.create("name1", x -> {
+ x.changeStrVal("strVal1");
+ x.changeIntVal(-1);
+ })).get();
+
+ // Then file is created
+ assertThat(configFileContent(), equalToCompressingWhiteSpace(
+ "top {\n"
+ + " namedList=[\n"
+ + " {\n"
+ + " intVal=-1\n"
+ + " name=name1\n"
+ + " strVal=strVal1\n"
+ + " }\n"
+ + " ]\n"
+ + "}\n"
+ ));
}
- private Path getConfigFile() {
- return tmpDir.resolve(CONFIG_NAME);
+ /** Read configuration when inner node configured with partial content
(some fields are empty). */
+ @Test
+ void innerNodeWithPartialContent() throws Exception {
+ // Given
+ String content = "top: { inner.boolVal: true }";
+ Files.write(getConfigFile(), content.getBytes(StandardCharsets.UTF_8));
+
+ // Expect
+ assertThat(storage.readDataOnRecovery().get().values(),
aMapWithSize(1));
+ }
+
+ private String configFileContent() throws IOException {
+ return Files.readString(getConfigFile());
+ }
+
+ private Map<String, ? extends Serializable> readAllLatest() {
+ return storage.readAllLatest("").join();
+ }
+
+ /** Root that has a single named list. */
+ @ConfigurationRoot(rootName = "top")
+ public static class TopConfigurationSchema {
+ @NamedConfigValue
+ public NamedListConfigurationSchema namedList;
+
+ @ConfigValue
+ public InnerConfigurationSchema inner;
+
+ @Value(hasDefault = true)
+ public short shortVal = 1;
+ }
+
+
+ /** Empty root that is needed to test empty configuration root rendering
to file. */
+ @ConfigurationRoot(rootName = "emptyTop")
+ public static class TopEmptyConfigurationSchema {
+ @Value(hasDefault = true)
+ public short ignore = 1;
+ }
+
+ /** Inner config to test that it won't be saved to the file if not changed
by the user.*/
+ @Config
+ public static class InnerConfigurationSchema {
+ @Value(hasDefault = true)
+ public String strVal = "foo";
+
+ @Value(hasDefault = true)
+ public boolean boolVal = false;
+
+ @ConfigValue
+ public NamedListConfigurationSchema someConfigurationValue;
+ }
+
+ /** Named list element node that contains another named list. */
+ @Config
+ public static class NamedListConfigurationSchema {
+ @Value(hasDefault = true)
+ public String strVal = "foo";
+
+ @Value(hasDefault = true)
+ public int intVal = 1;
}
}