blob: cd2e3c1e1127b15238e3ab343544f7b3c14984f6 [file] [log] [blame]
/*
* 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 java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.apache.ignite.configuration.ConfigurationChangeException;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
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.storage.Data;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.AConfiguration.KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Test configuration changer.
*/
public class ConfigurationChangerTest {
/** Annotation used to test failing validation. */
@Target(FIELD)
@Retention(RUNTIME)
@interface MaybeInvalid {
}
/** */
@ConfigurationRoot(rootName = "key", type = LOCAL)
public static class AConfigurationSchema {
/** */
@ConfigValue
@MaybeInvalid
public BConfigurationSchema child;
/** */
@NamedConfigValue
public CConfigurationSchema elements;
}
/** */
@Config
public static class BConfigurationSchema {
/** */
@Value
@Immutable
public int intCfg;
/** */
@Value
public String strCfg;
}
/** */
@Config
public static class CConfigurationSchema {
/** */
@Value
public String strCfg;
}
private static ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
@AfterAll
public static void afterAll() {
cgen = null;
}
/**
* Test simple change of configuration.
*/
@Test
public void testSimpleConfigurationChange() throws Exception {
var storage = new TestConfigurationStorage(LOCAL);
ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
changer.start();
changer.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
.changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
)).get(1, SECONDS);
AView newRoot = (AView)changer.getRootNode(KEY);
assertEquals(1, newRoot.child().intCfg());
assertEquals("1", newRoot.child().strCfg());
assertEquals("1", newRoot.elements().get("a").strCfg());
}
/**
* Test subsequent change of configuration via different changers.
*/
@Test
public void testModifiedFromAnotherStorage() throws Exception {
var storage = new TestConfigurationStorage(LOCAL);
ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
changer1.start();
ConfigurationChanger changer2 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
changer2.start();
changer1.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
.changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
)).get(1, SECONDS);
changer2.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
.changeElements(change -> change
.createOrUpdate("a", element -> element.changeStrCfg("2"))
.create("b", element -> element.changeStrCfg("2"))
)
)).get(1, SECONDS);
AView newRoot1 = (AView)changer1.getRootNode(KEY);
assertEquals(2, newRoot1.child().intCfg());
assertEquals("2", newRoot1.child().strCfg());
assertEquals("2", newRoot1.elements().get("a").strCfg());
assertEquals("2", newRoot1.elements().get("b").strCfg());
AView newRoot2 = (AView)changer2.getRootNode(KEY);
assertEquals(2, newRoot2.child().intCfg());
assertEquals("2", newRoot2.child().strCfg());
assertEquals("2", newRoot2.elements().get("a").strCfg());
assertEquals("2", newRoot2.elements().get("b").strCfg());
}
/**
* Test that subsequent change of configuration is failed if changes are incompatible.
*/
@Test
public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws Exception {
var storage = new TestConfigurationStorage(LOCAL);
ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
changer1.start();
Validator<MaybeInvalid, Object> validator = new Validator<>() {
/** {@inheritDoc} */
@Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
ctx.addIssue(new ValidationIssue("foo"));
}
};
ConfigurationChanger changer2 = new TestConfigurationChanger(
cgen,
List.of(KEY),
Map.of(MaybeInvalid.class, Set.of(validator)),
storage,
List.of()
);
changer2.start();
changer1.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
.changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
)).get(1, SECONDS);
assertThrows(ExecutionException.class, () -> changer2.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
.changeElements(change -> change
.create("a", element -> element.changeStrCfg("2"))
.create("b", element -> element.changeStrCfg("2"))
)
)).get(1, SECONDS));
AView newRoot = (AView)changer2.getRootNode(KEY);
assertEquals(1, newRoot.child().intCfg());
assertEquals("1", newRoot.child().strCfg());
assertEquals("1", newRoot.elements().get("a").strCfg());
}
/**
* Test that change fails with right exception if storage is inaccessible.
*/
@Test
public void testFailedToWrite() {
var storage = new TestConfigurationStorage(LOCAL);
ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
storage.fail(true);
assertThrows(ConfigurationChangeException.class, changer::start);
storage.fail(false);
changer.start();
storage.fail(true);
assertThrows(ExecutionException.class, () -> changer.change(source(KEY, (AChange parent) -> parent
.changeChild(child -> child.changeIntCfg(1))
)).get(1, SECONDS));
storage.fail(false);
final Data dataFromStorage = storage.readAll();
final Map<String, Serializable> dataMap = dataFromStorage.values();
assertEquals(0, dataMap.size());
AView newRoot = (AView)changer.getRootNode(KEY);
assertNull(newRoot.child());
}
/** */
@ConfigurationRoot(rootName = "def", type = LOCAL)
public static class DefaultsConfigurationSchema {
/** */
@ConfigValue
public DefaultsChildConfigurationSchema child;
/** */
@NamedConfigValue
public DefaultsChildConfigurationSchema childsList;
/** */
@Value(hasDefault = true)
public String defStr = "foo";
}
/** */
@Config
public static class DefaultsChildConfigurationSchema {
/** */
@Value(hasDefault = true)
public String defStr = "bar";
/** */
@Value(hasDefault = true)
public String[] arr = {"xyz"};
}
@Test
public void defaultsOnInit() throws Exception {
var storage = new TestConfigurationStorage(LOCAL);
var changer = new TestConfigurationChanger(
cgen,
List.of(DefaultsConfiguration.KEY),
Map.of(),
storage,
List.of()
);
changer.start();
changer.initializeDefaults();
DefaultsView root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
assertEquals("foo", root.defStr());
assertEquals("bar", root.child().defStr());
assertEquals(List.of("xyz"), Arrays.asList(root.child().arr()));
changer.change(source(DefaultsConfiguration.KEY, (DefaultsChange def) -> def
.changeChildsList(childs ->
childs.create("name", child -> {})
)
)).get(1, SECONDS);
root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
assertEquals("bar", root.childsList().get("name").defStr());
}
private static <Change> ConfigurationSource source(RootKey<?, ? super Change> rootKey, Consumer<Change> changer) {
return new ConfigurationSource() {
@Override public void descend(ConstructableTreeNode node) {
ConfigurationSource changerSrc = new ConfigurationSource() {
@Override public void descend(ConstructableTreeNode node) {
changer.accept((Change)node);
}
};
node.construct(rootKey.key(), changerSrc, true);
}
};
}
}