You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/02/03 13:10:11 UTC
[ignite-3] branch main updated: IGNITE-14094 Storage interface for
configuration module + basic algorithm to store configuration in there.
This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 05992d2 IGNITE-14094 Storage interface for configuration module + basic algorithm to store configuration in there.
05992d2 is described below
commit 05992d2c6b90cdb46980814f4114e12399aec559
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Wed Feb 3 16:09:54 2021 +0300
IGNITE-14094 Storage interface for configuration module + basic algorithm to store configuration in there.
Signed-off-by: ibessonov <be...@gmail.com>
---
.../processor/internal/Processor.java | 4 +-
.../ignite/configuration/sample/UsageTest.java | 26 +-
.../sample/storage/ConfigurationChangerTest.java | 280 +++++++++++++++++++++
.../sample/storage/TestConfigurationStorage.java | 98 ++++++++
...tion.java => ConfigurationChangeException.java} | 23 +-
.../ignite/configuration/ConfigurationChanger.java | 245 ++++++++++++++++++
.../apache/ignite/configuration/Configurator.java | 32 +--
.../storage/ConfigurationStorage.java | 53 ++--
.../ConfigurationStorageListener.java} | 16 +-
.../Data.java} | 41 ++-
.../ConfigurationValidationException.java | 13 +
...lidationException.java => ValidationIssue.java} | 10 +-
.../java/org/apache/ignite/rest/RestModule.java | 15 +-
.../java/org/apache/ignite/app/IgniteRunner.java | 27 +-
.../ignite/configuration/ConfigurationModule.java | 6 +-
15 files changed, 756 insertions(+), 133 deletions(-)
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
index 43c6fee..f8fe5ef 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -966,7 +966,7 @@ public class Processor extends AbstractProcessor {
MethodSpec.Builder nodeChangeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
- .returns(changeClsName);
+ .returns(nodeClsName);
if (valAnnotation != null) {
nodeChangeMtdBuilder
@@ -1024,7 +1024,7 @@ public class Processor extends AbstractProcessor {
MethodSpec.Builder nodeInitMtdBuilder = MethodSpec.methodBuilder(initMtdName)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
- .returns(initClsName);
+ .returns(nodeClsName);
if (valAnnotation != null) {
nodeInitMtdBuilder
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
index b78d62c..89ee565 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
@@ -17,14 +17,10 @@
package org.apache.ignite.configuration.sample;
-import java.io.Serializable;
import java.util.Collections;
-import java.util.function.Consumer;
import org.apache.ignite.configuration.ConfigurationRegistry;
import org.apache.ignite.configuration.Configurator;
import org.apache.ignite.configuration.internal.NamedList;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
-import org.apache.ignite.configuration.storage.StorageException;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -35,21 +31,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* Simple usage test of generated configuration schema.
*/
public class UsageTest {
- private final ConfigurationStorage storage = new ConfigurationStorage() {
- @Override public <T extends Serializable> void save(String propertyName, T object) throws StorageException {
-
- }
-
- @Override public <T extends Serializable> T get(String propertyName) throws StorageException {
- return null;
- }
-
- @Override
- public <T extends Serializable> void listen(String key, Consumer<T> listener) throws StorageException {
-
- }
- };
-
/**
* Test creation of configuration and calling configuration API methods.
*/
@@ -66,7 +47,6 @@ public class UsageTest {
);
final Configurator<LocalConfigurationImpl> configurator = Configurator.create(
- storage,
LocalConfigurationImpl::new,
initLocal
);
@@ -111,13 +91,11 @@ public class UsageTest {
.withTimeout(autoAdjustTimeout))
);
- Configurator<LocalConfigurationImpl> localConf = Configurator.create(storage,
- LocalConfigurationImpl::new, initLocal);
+ Configurator<LocalConfigurationImpl> localConf = Configurator.create(LocalConfigurationImpl::new, initLocal);
sysConf.registerConfigurator(localConf);
- Configurator<NetworkConfigurationImpl> networkConf = Configurator.create(storage,
- NetworkConfigurationImpl::new, initNetwork);
+ Configurator<NetworkConfigurationImpl> networkConf = Configurator.create(NetworkConfigurationImpl::new, initNetwork);
sysConf.registerConfigurator(networkConf);
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
new file mode 100644
index 0000000..9eab60f
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.configuration.sample.storage;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.ignite.configuration.ConfigurationChangeException;
+import org.apache.ignite.configuration.ConfigurationChanger;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.storage.impl.ANode;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsMapContaining.hasEntry;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test configuration changer.
+ */
+public class ConfigurationChangerTest {
+ /** Root configuration key. */
+ private static final RootKey<?> KEY = () -> "key";
+
+ /** */
+ @Config
+ public static class AConfigurationSchema {
+ /** */
+ @ConfigValue
+ private BConfigurationSchema child;
+
+ /** */
+ @NamedConfigValue
+ private CConfigurationSchema elements;
+ }
+
+ /** */
+ @Config
+ public static class BConfigurationSchema {
+ /** */
+ @Value(immutable = true)
+ private int intCfg;
+
+ /** */
+ @Value
+ private String strCfg;
+ }
+
+ /** */
+ @Config
+ public static class CConfigurationSchema {
+ /** */
+ @Value
+ private String strCfg;
+ }
+
+ /**
+ * Test simple change of configuration.
+ */
+ @Test
+ public void testSimpleConfigurationChange() {
+ final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+ final ConfiguratorController configuratorController = new ConfiguratorController();
+ final Configurator<?> configurator = configuratorController.configurator();
+
+ ANode data = new ANode()
+ .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+ .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+ final ConfigurationChanger changer = new ConfigurationChanger(storage);
+ changer.init();
+
+ changer.registerConfiguration(KEY, configurator);
+
+ changer.change(Collections.singletonMap(KEY, data));
+
+ final Data dataFromStorage = storage.readAll();
+ final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+ assertEquals(3, dataMap.size());
+ assertThat(dataMap, hasEntry("key.child.intCfg", 1));
+ assertThat(dataMap, hasEntry("key.child.strCfg", "1"));
+ assertThat(dataMap, hasEntry("key.elements.a.strCfg", "1"));
+ }
+
+ /**
+ * Test subsequent change of configuration via different changers.
+ */
+ @Test
+ public void testModifiedFromAnotherStorage() {
+ final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+ final ConfiguratorController configuratorController = new ConfiguratorController();
+ final Configurator<?> configurator = configuratorController.configurator();
+
+ ANode data1 = new ANode()
+ .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+ .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+ ANode data2 = new ANode()
+ .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+ .initElements(change -> change
+ .put("a", init -> init.initStrCfg("2"))
+ .put("b", init -> init.initStrCfg("2"))
+ );
+
+ final ConfigurationChanger changer1 = new ConfigurationChanger(storage);
+ changer1.init();
+
+ final ConfigurationChanger changer2 = new ConfigurationChanger(storage);
+ changer2.init();
+
+ changer1.registerConfiguration(KEY, configurator);
+ changer2.registerConfiguration(KEY, configurator);
+
+ changer1.change(Collections.singletonMap(KEY, data1));
+ changer2.change(Collections.singletonMap(KEY, data2));
+
+ final Data dataFromStorage = storage.readAll();
+ final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+ assertEquals(4, dataMap.size());
+ assertThat(dataMap, hasEntry("key.child.intCfg", 2));
+ assertThat(dataMap, hasEntry("key.child.strCfg", "2"));
+ assertThat(dataMap, hasEntry("key.elements.a.strCfg", "2"));
+ assertThat(dataMap, hasEntry("key.elements.b.strCfg", "2"));
+ }
+
+ /**
+ * Test that subsequent change of configuration is failed if changes are incompatible.
+ */
+ @Test
+ public void testModifiedFromAnotherStorageWithIncompatibleChanges() {
+ final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+ final ConfiguratorController configuratorController = new ConfiguratorController();
+ final Configurator<?> configurator = configuratorController.configurator();
+
+ ANode data1 = new ANode()
+ .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+ .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+ ANode data2 = new ANode()
+ .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+ .initElements(change -> change
+ .put("a", init -> init.initStrCfg("2"))
+ .put("b", init -> init.initStrCfg("2"))
+ );
+
+ final ConfigurationChanger changer1 = new ConfigurationChanger(storage);
+ changer1.init();
+
+ final ConfigurationChanger changer2 = new ConfigurationChanger(storage);
+ changer2.init();
+
+ changer1.registerConfiguration(KEY, configurator);
+ changer2.registerConfiguration(KEY, configurator);
+
+ changer1.change(Collections.singletonMap(KEY, data1));
+
+ configuratorController.hasIssues(true);
+
+ assertThrows(ConfigurationValidationException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)));
+
+ final Data dataFromStorage = storage.readAll();
+ final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+ assertEquals(3, dataMap.size());
+ assertThat(dataMap, hasEntry("key.child.intCfg", 1));
+ assertThat(dataMap, hasEntry("key.child.strCfg", "1"));
+ assertThat(dataMap, hasEntry("key.elements.a.strCfg", "1"));
+ }
+
+ /**
+ * Test that init and change fail with right exception if storage is inaccessible.
+ */
+ @Test
+ public void testFailedToWrite() {
+ final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+ final ConfiguratorController configuratorController = new ConfiguratorController();
+ final Configurator<?> configurator = configuratorController.configurator();
+
+ ANode data = new ANode();
+
+ final ConfigurationChanger changer = new ConfigurationChanger(storage);
+
+ storage.fail(true);
+
+ assertThrows(ConfigurationChangeException.class, changer::init);
+
+ storage.fail(false);
+
+ changer.init();
+
+ changer.registerConfiguration(KEY, configurator);
+
+ storage.fail(true);
+
+ assertThrows(ConfigurationChangeException.class, () -> changer.change(Collections.singletonMap(KEY, data)));
+
+ storage.fail(false);
+
+ final Data dataFromStorage = storage.readAll();
+ final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+ assertEquals(0, dataMap.size());
+ }
+
+ /**
+ * Wrapper for Configurator mock to control validation.
+ */
+ private static class ConfiguratorController {
+ /** Configurator. */
+ final Configurator<?> configurator;
+
+ /** Whether validate method should return issues. */
+ private boolean hasIssues;
+
+ /** Constructor. */
+ private ConfiguratorController() {
+ this(false);
+ }
+
+ /** Constructor. */
+ private ConfiguratorController(boolean hasIssues) {
+ this.hasIssues = hasIssues;
+
+ configurator = Mockito.mock(Configurator.class);
+
+ Mockito.when(configurator.validateChanges(Mockito.any())).then(mock -> {
+ if (this.hasIssues)
+ return Collections.singletonList(new ValidationIssue());
+
+ return Collections.emptyList();
+ });
+ }
+
+ /**
+ * Set has issues flag.
+ * @param hasIssues Has issues flag.
+ */
+ public void hasIssues(boolean hasIssues) {
+ this.hasIssues = hasIssues;
+ }
+
+ /**
+ * Get configurator.
+ * @return Configurator.
+ */
+ public Configurator<?> configurator() {
+ return configurator;
+ }
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
new file mode 100644
index 0000000..e5b2228
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
@@ -0,0 +1,98 @@
+/*
+ * 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.configuration.sample.storage;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.ConfigurationStorageListener;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.storage.StorageException;
+
+/**
+ * Test configuration storage.
+ */
+public class TestConfigurationStorage implements ConfigurationStorage {
+ /** Map to store values. */
+ private Map<String, Serializable> map = new ConcurrentHashMap<>();
+
+ /** Change listeners. */
+ private List<ConfigurationStorageListener> listeners = new ArrayList<>();
+
+ /** Storage version. */
+ private AtomicInteger version = new AtomicInteger(0);
+
+ /** Should fail on every operation. */
+ private boolean fail = false;
+
+ /**
+ * Set fail flag.
+ * @param fail Fail flag.
+ */
+ public void fail(boolean fail) {
+ this.fail = fail;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Data readAll() throws StorageException {
+ if (fail)
+ throw new StorageException("Failed to read data");
+
+ return new Data(new HashMap<>(map), version.get());
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized boolean write(Map<String, Serializable> newValues, int sentVersion) throws StorageException {
+ if (fail)
+ throw new StorageException("Failed to write data");
+
+ if (sentVersion != version.get())
+ return false;
+
+ map.putAll(newValues);
+
+ version.incrementAndGet();
+
+ listeners.forEach(listener -> listener.onEntriesChanged(new Data(newValues, version.get())));
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Set<String> keys() throws StorageException {
+ if (fail)
+ throw new StorageException("Failed to get keys");
+
+ return map.keySet();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void addListener(ConfigurationStorageListener listener) {
+ listeners.add(listener);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void removeListener(ConfigurationStorageListener listener) {
+ listeners.remove(listener);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChangeException.java
similarity index 63%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChangeException.java
index 20aec33..e9c2097 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChangeException.java
@@ -14,15 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package org.apache.ignite.configuration.validation;
+package org.apache.ignite.configuration;
/**
- * Configuration validation exception.
+ * Configuration change exception.
*/
-public class ConfigurationValidationException extends RuntimeException {
- /** Constructor. */
- public ConfigurationValidationException(String message) {
+public class ConfigurationChangeException extends RuntimeException {
+ /**
+ * Constructor.
+ * @param message Error message.
+ */
+ public ConfigurationChangeException(String message) {
super(message);
}
+
+ /**
+ * Constructor.
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public ConfigurationChangeException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
new file mode 100644
index 0000000..5c884ce
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
@@ -0,0 +1,245 @@
+/*
+ * 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.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.storage.StorageException;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
+import org.apache.ignite.configuration.util.ConfigurationUtil;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+
+/**
+ * Class that handles configuration changes, by validating them, passing to storage and listening to storage updates.
+ */
+public class ConfigurationChanger {
+ /** Map of configurations' configurators. */
+ private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+ /** Storage. */
+ private ConfigurationStorage configurationStorage;
+
+ /** Changer's last known version of storage. */
+ private final AtomicInteger version = new AtomicInteger(0);
+
+ /** Constructor. */
+ public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+ this.configurationStorage = configurationStorage;
+ }
+
+ /**
+ * Initialize changer.
+ */
+ public void init() throws ConfigurationChangeException {
+ final Data data;
+
+ try {
+ data = configurationStorage.readAll();
+ }
+ catch (StorageException e) {
+ throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
+ }
+
+ version.set(data.version());
+
+ configurationStorage.addListener(this::updateFromListener);
+
+ // TODO: IGNITE-14118 iterate over data and fill Configurators
+ }
+
+ /**
+ * Add configurator.
+ * @param key Root configuration key of the configurator.
+ * @param configurator Configuration's configurator.
+ */
+ public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+ registry.put(key, configurator);
+ }
+
+ /**
+ * Change configuration.
+ * @param changes Map of changes by root key.
+ */
+ public void change(Map<RootKey<?>, TraversableTreeNode> changes) throws ConfigurationChangeException,
+ ConfigurationValidationException {
+ Map<String, Serializable> allChanges = changes.entrySet().stream()
+ .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+ .flatMap(map -> map.entrySet().stream())
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ boolean writing = true;
+
+ List<ValidationIssue> validationIssues = Collections.emptyList();
+
+ while (writing) {
+ final ValidationResult validationResult = validate(changes);
+
+ validationIssues = validationResult.issues();
+
+ final int version = validationResult.version();
+
+ if (validationIssues.isEmpty())
+ try {
+ writing = !configurationStorage.write(allChanges, version);
+ }
+ catch (StorageException e) {
+ throw new ConfigurationChangeException("Failed to change configuration: " + e.getMessage(), e);
+ }
+ else
+ break;
+ }
+
+ if (!validationIssues.isEmpty())
+ throw new ConfigurationValidationException(validationIssues);
+ }
+
+ /**
+ * Update configuration from storage listener.
+ * @param changedEntries Changed data.
+ */
+ private synchronized void updateFromListener(Data changedEntries) {
+ // TODO: IGNITE-14118 add tree update
+ version.set(changedEntries.version());
+ }
+
+ /**
+ * Validate configuration changes.
+ * @param changes Configuration changes.
+ * @return Validation results.
+ */
+ private synchronized ValidationResult validate(Map<RootKey<?>, TraversableTreeNode> changes) {
+ final int version = this.version.get();
+
+ List<ValidationIssue> issues = new ArrayList<>();
+
+ for (Map.Entry<RootKey<?>, TraversableTreeNode> entry : changes.entrySet()) {
+ RootKey<?> rootKey = entry.getKey();
+ TraversableTreeNode changesForRoot = entry.getValue();
+
+ final Configurator<?> configurator = registry.get(rootKey);
+
+ List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
+ issues.addAll(list);
+ }
+
+ return new ValidationResult(issues, version);
+ }
+
+ /**
+ * Convert a traversable tree to a map of qualified keys to values.
+ * @param rootKey Root configuration key.
+ * @param node Tree.
+ * @return Map of changes.
+ */
+ private Map<String, Serializable> convertChangesToMap(RootKey<?> rootKey, TraversableTreeNode node) {
+ Map<String, Serializable> values = new HashMap<>();
+
+ node.accept(rootKey.key(), new ConfigurationVisitor() {
+ /** Current key, aggregated by visitor. */
+ StringBuilder currentKey = new StringBuilder();
+
+ /** {@inheritDoc} */
+ @Override public void visitLeafNode(String key, Serializable val) {
+ values.put(currentKey.toString() + key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void visitInnerNode(String key, InnerNode node) {
+ if (node == null)
+ return;
+
+ int previousKeyLength = currentKey.length();
+
+ currentKey.append(key).append('.');
+
+ node.traverseChildren(this);
+
+ currentKey.setLength(previousKeyLength);
+ }
+
+ /** {@inheritDoc} */
+ @Override public <N extends InnerNode> void visitNamedListNode(String key, NamedListNode<N> node) {
+ int previousKeyLength = currentKey.length();
+
+ if (key != null)
+ currentKey.append(key).append('.');
+
+ for (String namedListKey : node.namedListKeys()) {
+ int loopPreviousKeyLength = currentKey.length();
+
+ currentKey.append(ConfigurationUtil.escape(namedListKey)).append('.');
+
+ node.get(namedListKey).traverseChildren(this);
+
+ currentKey.setLength(loopPreviousKeyLength);
+ }
+
+ currentKey.setLength(previousKeyLength);
+ }
+ });
+ return values;
+ }
+
+ /**
+ * Results of the validation.
+ */
+ private static final class ValidationResult {
+ /** List of issues. */
+ private final List<ValidationIssue> issues;
+
+ /** Version of configuration that changes were validated against. */
+ private final int version;
+
+ /**
+ * Constructor.
+ * @param issues List of issues.
+ * @param version Version.
+ */
+ private ValidationResult(List<ValidationIssue> issues, int version) {
+ this.issues = issues;
+ this.version = version;
+ }
+
+ /**
+ * Get issues.
+ * @return Issues.
+ */
+ public List<ValidationIssue> issues() {
+ return issues;
+ }
+
+ /**
+ * Get version.
+ * @return Version.
+ */
+ public int version() {
+ return version;
+ }
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
index 3e3592a..d3f42c9 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
@@ -29,10 +29,11 @@ import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.configuration.internal.DynamicProperty;
import org.apache.ignite.configuration.internal.Modifier;
import org.apache.ignite.configuration.internal.selector.Selector;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.configuration.validation.FieldValidator;
-import org.apache.ignite.configuration.internal.validation.MemberKey;
+import org.apache.ignite.configuration.validation.ValidationIssue;
/**
* Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions
@@ -40,9 +41,6 @@ import org.apache.ignite.configuration.internal.validation.MemberKey;
* @param <T> Type of configuration root.
*/
public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
- /** Storage for the configuration tree. */
- private final ConfigurationStorage storage;
-
/** Root of the configuration tree. */
private final T root;
@@ -51,7 +49,6 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
/**
*
- * @param storage
* @param rootBuilder
* @param <VIEW>
* @param <INIT>
@@ -60,15 +57,13 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
* @return
*/
public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
- ConfigurationStorage storage,
Function<Configurator<CONF>, CONF> rootBuilder
) {
- return new Configurator<>(storage, rootBuilder, null);
+ return new Configurator<>(rootBuilder, null);
}
/**
*
- * @param storage
* @param rootBuilder
* @param init
* @param <VIEW>
@@ -78,25 +73,20 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
* @return
*/
public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
- ConfigurationStorage storage,
Function<Configurator<CONF>, CONF> rootBuilder,
INIT init
) {
- return new Configurator<>(storage, rootBuilder, init);
+ return new Configurator<>(rootBuilder, init);
}
/**
* Constructor.
- * @param storage Configuration storage.
* @param rootBuilder Function, that creates configuration root.
*/
private <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator(
- ConfigurationStorage storage,
Function<Configurator<CONF>, CONF> rootBuilder,
INIT init
) {
- this.storage = storage;
-
final CONF built = rootBuilder.apply((Configurator<CONF>) this);
if (init != null)
@@ -121,7 +111,7 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
}
/**
- *
+ *
*/
public Class<?> getChangeType() {
Type sClass = root.getClass().getGenericSuperclass();
@@ -154,6 +144,7 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
final TARGET select = selector.select(copy);
select.changeWithoutValidation(newValue);
copy.validate(root);
+
selector.select(root).changeWithoutValidation(newValue);
}
@@ -210,13 +201,16 @@ public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
* @param <PROP> Type of the property.
*/
public <PROP extends Serializable> void onAttached(DynamicProperty<PROP> property) {
- final String key = property.key();
property.addListener(new PropertyListener<PROP, PROP>() {
/** {@inheritDoc} */
@Override public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
- storage.save(key, newValue);
+// storage.save(key, newValue);
}
});
- storage.listen(key, property::setSilently);
+// storage.listen(key, property::setSilently);
+ }
+
+ public List<ValidationIssue> validateChanges(TraversableTreeNode changes) {
+ return Collections.emptyList();
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
index 1c77a9a..66b8e83 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
@@ -17,39 +17,46 @@
package org.apache.ignite.configuration.storage;
import java.io.Serializable;
-import java.util.function.Consumer;
+import java.util.Map;
+import java.util.Set;
/**
- * Storage interface for configuration.
+ * Common interface for configuration storage.
*/
public interface ConfigurationStorage {
/**
- * Save configuration property.
- *
- * @param propertyName Fully qualified name of the property.
- * @param object Object, that represents the value of the property.
- * @param <T> Type of the property.
- * @throws StorageException If failed to save object.
+ * Read all configuration values and current storage version.
+ * @return Values and version.
+ * @throws StorageException If failed to retrieve data.
*/
- <T extends Serializable> void save(String propertyName, T object) throws StorageException;
+ Data readAll() throws StorageException;
/**
- * Get property value from storage.
- *
- * @param propertyName Fully qualified name of the property.
- * @param <T> Type of the property.
- * @return Object, that represents the value of the property.
- * @throws StorageException If failed to retrieve object frm configuration storage.
+ * Write key-value pairs into the storage with last known version.
+ * @param newValues Key-value pairs.
+ * @param version Last known version.
+ * @return {@code true} if successfully written, {@code false} if version of the storage is different from the passed
+ * argument.
+ * @throws StorageException If failed to write data.
*/
- <T extends Serializable> T get(String propertyName) throws StorageException;
+ boolean write(Map<String, Serializable> newValues, int version) throws StorageException;
/**
- * Listen for the property change in the storage.
- *
- * @param key Key to listen on.
- * @param listener Listener function.
- * @param <T> Type of the property.
- * @throws StorageException If failed to attach listener to configuration storage.
+ * Get all the keys of the configuration storage.
+ * @return Set of keys.
+ * @throws StorageException If failed to retrieve keys.
*/
- <T extends Serializable> void listen(String key, Consumer<T> listener) throws StorageException;
+ Set<String> keys() throws StorageException;
+
+ /**
+ * Add listener to the storage that notifies of data changes..
+ * @param listener Listener.
+ */
+ void addListener(ConfigurationStorageListener listener);
+
+ /**
+ * Remove storage listener.
+ * @param listener Listener.
+ */
+ void removeListener(ConfigurationStorageListener listener);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
similarity index 69%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
index 20aec33..f8cb7b4 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
@@ -14,15 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package org.apache.ignite.configuration.validation;
+package org.apache.ignite.configuration.storage;
/**
- * Configuration validation exception.
+ * Configuration storage listener for changes.
*/
-public class ConfigurationValidationException extends RuntimeException {
- /** Constructor. */
- public ConfigurationValidationException(String message) {
- super(message);
- }
+public interface ConfigurationStorageListener {
+ /**
+ * Method called when entries in storage change.
+ * @param changedEntries Changed entries, key-value pairs and new version of the storage.
+ */
+ void onEntriesChanged(Data changedEntries);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
similarity index 51%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
index 20aec33..eea2afb 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
@@ -14,15 +14,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.ignite.configuration.storage;
-package org.apache.ignite.configuration.validation;
+import java.io.Serializable;
+import java.util.Map;
/**
- * Configuration validation exception.
+ * Represents data in configuration storage.
*/
-public class ConfigurationValidationException extends RuntimeException {
- /** Constructor. */
- public ConfigurationValidationException(String message) {
- super(message);
+public class Data {
+ /** Values. */
+ private final Map<String, Serializable> values;
+
+ /** Configuration storage version. */
+ private final int version;
+
+ /**
+ * Constructor.
+ * @param values Values.
+ * @param version Version.
+ */
+ public Data(Map<String, Serializable> values, int version) {
+ this.values = values;
+ this.version = version;
+ }
+
+ /**
+ * Get values.
+ * @return Values.
+ */
+ public Map<String, Serializable> values() {
+ return values;
+ }
+
+ /**
+ * Get version.
+ * @return version.
+ */
+ public int version() {
+ return version;
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
index 20aec33..73af1f2 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
@@ -17,12 +17,25 @@
package org.apache.ignite.configuration.validation;
+import java.util.List;
+
/**
* Configuration validation exception.
*/
public class ConfigurationValidationException extends RuntimeException {
+ /** List of configuration validation issues. */
+ private List<ValidationIssue> issues;
+
/** Constructor. */
public ConfigurationValidationException(String message) {
super(message);
}
+
+ public ConfigurationValidationException(List<ValidationIssue> issues) {
+ this.issues = issues;
+ }
+
+ public List<ValidationIssue> getIssues() {
+ return issues;
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
similarity index 78%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
index 20aec33..4594a53 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
@@ -14,15 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.ignite.configuration.validation;
-/**
- * Configuration validation exception.
- */
-public class ConfigurationValidationException extends RuntimeException {
- /** Constructor. */
- public ConfigurationValidationException(String message) {
- super(message);
- }
+public class ValidationIssue {
}
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
index 7a77b05..a00e217 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
@@ -20,18 +20,17 @@ package org.apache.ignite.rest;
import com.google.gson.JsonSyntaxException;
import io.javalin.Javalin;
import java.io.Reader;
-import org.apache.ignite.configuration.Configurator;
import org.apache.ignite.configuration.ConfigurationRegistry;
+import org.apache.ignite.configuration.Configurator;
import org.apache.ignite.configuration.internal.selector.SelectorNotFoundException;
-import org.apache.ignite.rest.presentation.ConfigurationPresentation;
-import org.apache.ignite.rest.presentation.FormatConverter;
-import org.apache.ignite.rest.presentation.json.JsonConverter;
-import org.apache.ignite.rest.presentation.json.JsonPresentation;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.rest.configuration.InitRest;
import org.apache.ignite.rest.configuration.RestConfigurationImpl;
import org.apache.ignite.rest.configuration.Selectors;
+import org.apache.ignite.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.rest.presentation.FormatConverter;
+import org.apache.ignite.rest.presentation.json.JsonConverter;
+import org.apache.ignite.rest.presentation.json.JsonPresentation;
import org.slf4j.Logger;
/**
@@ -65,7 +64,7 @@ public class RestModule {
}
/** */
- public void prepareStart(ConfigurationRegistry sysConfig, Reader moduleConfReader, ConfigurationStorage storage) {
+ public void prepareStart(ConfigurationRegistry sysConfig, Reader moduleConfReader) {
try {
Class.forName(Selectors.class.getName());
}
@@ -79,7 +78,7 @@ public class RestModule {
FormatConverter converter = new JsonConverter();
- Configurator<RestConfigurationImpl> restConf = Configurator.create(storage, RestConfigurationImpl::new,
+ Configurator<RestConfigurationImpl> restConf = Configurator.create(RestConfigurationImpl::new,
converter.convertFrom(moduleConfReader, "rest", InitRest.class));
sysConfig.registerConfigurator(restConf);
diff --git a/modules/runner/src/main/java/org/apache/ignite/app/IgniteRunner.java b/modules/runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
index cb26484..3e5aaf8 100644
--- a/modules/runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
+++ b/modules/runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
@@ -21,13 +21,10 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.StringReader;
import java.util.Arrays;
import java.util.stream.Collectors;
-import java.io.Serializable;
-import java.io.StringReader;
-import java.util.function.Consumer;
import org.apache.ignite.configuration.ConfigurationModule;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.rest.RestModule;
import org.apache.ignite.utils.IgniteProperties;
import org.slf4j.Logger;
@@ -66,24 +63,6 @@ public class IgniteRunner {
/** */
private static final Logger log = LoggerFactory.getLogger(IgniteRunner.class);
- /** */
- private static final ConfigurationStorage STORAGE = new ConfigurationStorage() {
- /** {@inheritDoc} */
- @Override public <T extends Serializable> void save(String propertyName, T object) {
-
- }
-
- /** {@inheritDoc} */
- @Override public <T extends Serializable> T get(String propertyName) {
- return null;
- }
-
- /** {@inheritDoc} */
- @Override public <T extends Serializable> void listen(String key, Consumer<T> listener) {
-
- }
- };
-
/**
* It is possible to start application with a custom configuration in form of json file other than that in resources.
*
@@ -124,9 +103,9 @@ public class IgniteRunner {
bldr.append(str);
}
- restModule.prepareStart(confModule.configurationRegistry(), new StringReader(bldr.toString()), STORAGE);
+ restModule.prepareStart(confModule.configurationRegistry(), new StringReader(bldr.toString()));
- confModule.bootstrap(new StringReader(bldr.toString()), STORAGE);
+ confModule.bootstrap(new StringReader(bldr.toString()));
}
finally {
if (confReader != null)
diff --git a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
index d6155f0..fb222ca 100644
--- a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
+++ b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
@@ -18,11 +18,9 @@
package org.apache.ignite.configuration;
import java.io.Reader;
-
import org.apache.ignite.configuration.extended.InitLocal;
import org.apache.ignite.configuration.extended.LocalConfigurationImpl;
import org.apache.ignite.configuration.extended.Selectors;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.rest.presentation.FormatConverter;
import org.apache.ignite.rest.presentation.json.JsonConverter;
@@ -49,11 +47,11 @@ public class ConfigurationModule {
private final ConfigurationRegistry confRegistry = new ConfigurationRegistry();
/** */
- public void bootstrap(Reader confReader, ConfigurationStorage storage) {
+ public void bootstrap(Reader confReader) {
FormatConverter converter = new JsonConverter();
Configurator<LocalConfigurationImpl> configurator =
- Configurator.create(storage, LocalConfigurationImpl::new, converter.convertFrom(confReader, "local", InitLocal.class));
+ Configurator.create(LocalConfigurationImpl::new, converter.convertFrom(confReader, "local", InitLocal.class));
localConfigurator = configurator;