diff --git a/modules/auto-activation-ext/README.md b/modules/auto-activation-ext/README.md new file mode 100644 index 000000000..c3ff86ec9 --- /dev/null +++ b/modules/auto-activation-ext/README.md @@ -0,0 +1,106 @@ +Apache Ignite Auto Activation Plugin +------------------------------------ +Apache Ignite Auto Activation plugin enables cluster activation at startup, subject to configured conditions. + +The plugin skips cluster activation in the following cases: + +- Cluster state is either ACTIVE or ACTIVE_READ_ONLY, log message: +```text + [DateTime][INFO][main][AutoActivationPluginProvider] Auto activation skipped - cluster already activated +``` +- Cluster baseline topology is not empty, log message: +```text + [DateTime][INFO][main][AutoActivationPluginProvider] Auto activation skipped - baseline is not empty +``` +- The baseline topology does not include all nodes listed for activation. A message containing the consistentIds of the missing nodes will be written to ignite.log: +```text + [DateTime][INFO][main][AutoActivationPluginProvider] Auto activation skipped - activation condition not meet (by consistent ID). Missing nodes [, , ...] +``` +- The node attributes in the topology do not contain the full list of values specified in the cluster's auto-activation settings. The log message will indicate the attribute name and list the missing values as follows: +```text +[DateTime][INFO][main][AutoActivationPluginProvider] Auto activation skipped - activation condition not meet (by node attribute). Attribute: , Missing values [, , ...] +``` +- The required nodes list for cluster activation contains any client node. In this case, the node will simply not participate in cluster activation, and the log message will be identical to that of a missing node. + +Depending on how you use Ignite, you can implement an extension using one of the following methods: + +- If you use the binary distribution, move the libs/{module-dir} to the 'libs' directory of the Ignite distribution before starting the node. +- Add libraries from libs/{module-dir} to the classpath of your application. +- Add a module as a Maven dependency to your project. + + +Building Module And Running Tests +--------------------------------- + +To build and run Auto Activation extension use the command below: + +mvn clean package -pl modules/auto-activation-ext + + +Importing Auto Activation Plugin In Maven Project +------------------------------------------------- + +If you are using Maven to manage dependencies of your project, you can add Auto Activation Plugin module +dependency like this: + +```xml + + + 4.0.0 + your.project + ... + + ... + + org.apache.ignite + ignite-auto-activation-ext + 1.0.0-SNAPSHOT + + ... + + ... + +``` + +Usage +----------------------------------- + +To enable cluster auto activation add next properties to your ignite-server.xml configurations +``` + + + + + + + +``` +where "condition" can be one of the following beans: +``` + + + + server-0 + server-1 + + + +``` +where `server-0` and `server-1` are consistent ID's of required server nodes in the activated cluster + +or +``` + + + + + attribute-0 + attribute-1 + + + +``` +where `attribute-0` and `attribute-1` are values of user-defined attribute `ATTR` that will be used to choose server nodes for cluster auto activation. \ No newline at end of file diff --git a/modules/auto-activation-ext/pom.xml b/modules/auto-activation-ext/pom.xml new file mode 100644 index 000000000..732400143 --- /dev/null +++ b/modules/auto-activation-ext/pom.xml @@ -0,0 +1,83 @@ + + + + + + 4.0.0 + + + org.apache.ignite + ignite-parent-ext-internal + 1 + ../../parent-internal/pom.xml + + + ignite-auto-activation-ext + 1.0.0-SNAPSHOT + https://ignite.apache.org + + + + ${project.groupId} + ignite-core + provided + + + + ${project.groupId} + ignite-core + test-jar + test + + + + ${project.groupId} + ignite-log4j2 + test + + + + org.springframework + spring-beans + ${spring.version} + test + + + + org.springframework + spring-context + ${spring.version} + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + + diff --git a/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByConsistentID.java b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByConsistentID.java new file mode 100644 index 000000000..2eb189b37 --- /dev/null +++ b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByConsistentID.java @@ -0,0 +1,73 @@ +/* + * 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 opt.apache.ignite.activation; + +import java.util.HashSet; +import java.util.Set; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteMessaging; +import org.apache.ignite.cluster.ClusterGroup; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.lang.IgnitePredicate; + +/** + * Activate cluster when nodes with specified ConsistentID values join topology. + */ +public class ActivateByConsistentID implements IgnitePredicate { + /** Collection of required nodes ConsistentIDs. */ + private final Set requiredNodes; + + /** + * @param requiredNodes List of ConsistentIDs. + */ + public ActivateByConsistentID(Set requiredNodes) { + if (requiredNodes == null || requiredNodes.isEmpty()) + throw new IllegalArgumentException("requiredNodes must be set"); + + this.requiredNodes = requiredNodes; + } + + /** {@inheritDoc} */ + @Override public boolean apply(Ignite grid) { + Set missingNodes = new HashSet<>(requiredNodes); + ClusterGroup servers = grid.cluster().forServers(); + IgniteMessaging messaging = grid.message(servers); + + for (ClusterNode node : servers.nodes()) { + String nodeConsistentId = node.consistentId().toString(); + + missingNodes.remove(nodeConsistentId); + + if (missingNodes.isEmpty()) { + messaging.send( + "auto-activation-plugin-events", + "Auto activation plugin set cluster state ACTIVE - activation condition meet (by consistent ID)" + ); + + return true; + } + } + + messaging.send( + "auto-activation-plugin-events", + "Auto activation skipped - activation condition not meet (by consistent ID). Missing nodes " + + "[" + String.join(", ", missingNodes) + "]"); + + return false; + } +} diff --git a/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByNodeAttribute.java b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByNodeAttribute.java new file mode 100644 index 000000000..913447caa --- /dev/null +++ b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/ActivateByNodeAttribute.java @@ -0,0 +1,82 @@ +/* + * 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 opt.apache.ignite.activation; + +import java.util.HashSet; +import java.util.Set; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteMessaging; +import org.apache.ignite.cluster.ClusterGroup; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.lang.IgnitePredicate; + +/** + * Activate cluster when nodes with all specified attributes values join topology. + */ +public class ActivateByNodeAttribute implements IgnitePredicate { + /** Node's attribute name. */ + private final String attrName; + + /** Collection of values for node's attribute. */ + private final Set requiredValues; + + /** + * @param attributeName Node's attribute name. + * @param requiredValues List of values for node's attribute. + */ + public ActivateByNodeAttribute(String attributeName, Set requiredValues) { + if (attributeName == null || attributeName.isBlank()) + throw new IllegalArgumentException("attributeName must be set"); + + if (requiredValues == null || requiredValues.isEmpty()) + throw new IllegalArgumentException("requiredValues must be set"); + + this.attrName = attributeName; + this.requiredValues = requiredValues; + } + + /** {@inheritDoc} */ + @Override public boolean apply(Ignite grid) { + Set missingValues = new HashSet<>(requiredValues); + + ClusterGroup servers = grid.cluster().forServers(); + + IgniteMessaging messaging = grid.message(servers); + + for (ClusterNode node : servers.nodes()) { + String attrVal = node.attribute(attrName); + + missingValues.remove(attrVal); + + if (missingValues.isEmpty()) { + messaging.send( + "auto-activation-plugin-events", + "Auto activation plugin set cluster state ACTIVE - activation condition meet (by node attribute)" + ); + + return true; + } + } + + messaging.send("auto-activation-plugin-events", + "Auto activation skipped - activation condition not meet (by node attribute). " + + "Attribute: " + attrName + ", Missing values [" + String.join(", ", missingValues) + "]"); + + return false; + } +} diff --git a/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/AutoActivationPluginProvider.java b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/AutoActivationPluginProvider.java new file mode 100644 index 000000000..4997ad365 --- /dev/null +++ b/modules/auto-activation-ext/src/main/java/opt/apache/ignite/activation/AutoActivationPluginProvider.java @@ -0,0 +1,163 @@ +/* + * 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 opt.apache.ignite.activation; + +import java.io.Serializable; +import java.util.UUID; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCluster; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.cluster.ClusterState; +import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.plugin.CachePluginContext; +import org.apache.ignite.plugin.CachePluginProvider; +import org.apache.ignite.plugin.ExtensionRegistry; +import org.apache.ignite.plugin.IgnitePlugin; +import org.apache.ignite.plugin.PluginConfiguration; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.PluginProvider; +import org.apache.ignite.plugin.PluginValidationException; + +/** + * Activate cluster when specified condition meet. + */ +public class AutoActivationPluginProvider implements PluginProvider { + /** */ + private final IgnitePredicate condition; + + /** */ + private IgniteLogger logger; + + /** */ + private Ignite grid; + + /** + * @param condition Auto activation condition. + */ + public AutoActivationPluginProvider(IgnitePredicate condition) { + if (condition == null) + throw new IllegalArgumentException("Auto activation condition must be set"); + + this.condition = condition; + } + + /** {@inheritDoc} */ + @Override public String name() { + return "Auto Activation Plugin"; + } + + /** {@inheritDoc} */ + @Override public T plugin() { + return (T)new IgnitePlugin() { + // No-op. + }; + } + + /** {@inheritDoc} */ + @Override public String version() { + return "1.0.0-SNAPSHOT"; + } + + /** {@inheritDoc} */ + @Override public String copyright() { + return "Apache Software Foundation"; + } + + /** {@inheritDoc} */ + @Override public void initExtensions(PluginContext pc, ExtensionRegistry er) { + logger = pc.log(this.getClass()); + grid = pc.grid(); + } + + /** {@inheritDoc} */ + @Override public T createComponent(PluginContext pc, Class type) { + return null; + } + + /** {@inheritDoc} */ + @Override public CachePluginProvider createCacheProvider(CachePluginContext cpc) { + return null; + } + + /** {@inheritDoc} */ + @Override public void start(PluginContext pc) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void stop(boolean bln) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void onIgniteStart() { + grid.message(grid.cluster().forServers()).localListen( + "auto-activation-plugin-events", + (nodeId, message) -> { + logger.info(String.valueOf(message)); + + return true; + } + ); + + IgniteCluster cluster = grid.cluster(); + + if (cluster.state() == ClusterState.ACTIVE || cluster.state() == ClusterState.ACTIVE_READ_ONLY) { + if (logger.isInfoEnabled()) + logger.info("Auto activation skipped - cluster already activated"); + + return; + } + + if (cluster.currentBaselineTopology() != null) { + if (logger.isInfoEnabled()) + logger.info("Auto activation skipped - baseline is not empty"); + + return; + } + + if (condition.apply(grid)) + cluster.state(ClusterState.ACTIVE); + } + + /** {@inheritDoc} */ + @Override public void onIgniteStop(boolean bln) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public Serializable provideDiscoveryData(UUID uuid) { + return null; + } + + /** {@inheritDoc} */ + @Override public void receiveDiscoveryData(UUID uuid, Serializable srlzbl) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void validateNewNode(ClusterNode cn) throws PluginValidationException { + // No-op. + } + + /** @return Condition. */ + public IgnitePredicate getCondition() { + return condition; + } +} diff --git a/modules/auto-activation-ext/src/test/java/opt/apache/ignite/activation/AutoActivationTest.java b/modules/auto-activation-ext/src/test/java/opt/apache/ignite/activation/AutoActivationTest.java new file mode 100644 index 000000000..4b297fca7 --- /dev/null +++ b/modules/auto-activation-ext/src/test/java/opt/apache/ignite/activation/AutoActivationTest.java @@ -0,0 +1,457 @@ +/* + * 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 opt.apache.ignite.activation; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.configuration.WALMode; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.plugin.PluginProvider; +import org.apache.ignite.testframework.ListeningTestLogger; +import org.apache.ignite.testframework.LogListener; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.cluster.ClusterState.ACTIVE_READ_ONLY; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; + +/** + * Tests {@link AutoActivationPluginProvider}. + */ +public class AutoActivationTest extends GridCommonAbstractTest { + /** Listening test logger. */ + private final ListeningTestLogger listeningLog = new ListeningTestLogger(log); + + /** */ + private final LogListener lsnrAlreadyAct = LogListener + .matches("Auto activation skipped - cluster already activated").build(); + + /** */ + private final LogListener lsnrBaseline = LogListener + .matches("Auto activation skipped - baseline is not empty").build(); + + /** */ + private final LogListener lsnrActMeet = LogListener + .matches("Auto activation plugin set cluster state ACTIVE - activation condition meet").build(); + + /** */ + private final LogListener lsnrActNotMeet = LogListener + .matches("Auto activation skipped - activation condition not meet").build(); + + /** */ + private LogListener lsnrMissed; + + /** */ + private final String NODE_0 = "node_0"; + + /** */ + private final String NODE_1 = "node_1"; + + /** */ + private final String NODE_2 = "node_2"; + + /** */ + private final String NODE_3 = "node_3"; + + /** */ + private final String ATTR = "CELL"; + + /** */ + private final String ATTR_VAL1 = "CELL_01"; + + /** */ + private final String ATTR_VAL2 = "CELL_02"; + + /** */ + private final String ATTR_VAL3 = "CELL_03"; + + /** */ + private final Set nodesConsistentIds = Set.of(NODE_0, NODE_1, NODE_2); + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + stopAllGrids(); + + cleanPersistenceDir(); + + listeningLog.registerAllListeners(lsnrAlreadyAct, lsnrBaseline, lsnrActMeet, lsnrActNotMeet); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(true); + + cleanPersistenceDir(); + + listeningLog.clearListeners(); + + super.afterTest(); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + return super.getConfiguration(igniteInstanceName) + .setConsistentId(igniteInstanceName) + .setUserAttributes(igniteInstanceName.equals(NODE_2) ? Map.of(ATTR, ATTR_VAL2) : Map.of(ATTR, ATTR_VAL1)) + .setClusterStateOnStart(INACTIVE) + .setGridLogger(listeningLog); + } + + /** */ + private IgniteConfiguration getConfiguration(String igniteInstanceName, PluginProvider autoActivationProvider, + String extraCfg) throws Exception { + IgniteConfiguration cfg = getConfiguration(igniteInstanceName).setPluginProviders(autoActivationProvider); + + if (extraCfg != null) { + switch (extraCfg) { + case "cacheConf": + cfg.setCacheConfiguration(getCacheConfiguration()); + break; + case "clientMode": + cfg.setClientMode(igniteInstanceName.equals(NODE_2)); + case "dataStorageConf": + cfg.setDataStorageConfiguration(getDataStorageConfiguration()); + break; + case "active": + cfg.setClusterStateOnStart(ACTIVE); + break; + case "activeReadOnly": + cfg.setClusterStateOnStart(ACTIVE_READ_ONLY); + break; + } + } + + return cfg; + } + + /** @return DataStorageConfiguration. */ + private DataStorageConfiguration getDataStorageConfiguration() { + return new DataStorageConfiguration() + .setWalSegmentSize(4 * 1024 * 1024) + .setWalMode(WALMode.LOG_ONLY) + .setCheckpointFrequency(1000) + .setWalCompactionEnabled(true) + .setDefaultDataRegionConfiguration(getDataRegionConfiguration()); + } + + /** @return DataRegionConfiguration. */ + private @NotNull DataRegionConfiguration getDataRegionConfiguration() { + return new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(100L * 1024 * 1024); + } + + /** @return CacheConfiguration. */ + private CacheConfiguration getCacheConfiguration() { + return new CacheConfiguration() + .setName(DEFAULT_CACHE_NAME) + .setCacheMode(CacheMode.PARTITIONED) + .setBackups(0) + .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) + .setIndexedTypes(String.class, Integer.class); + } + + /** @return IgniteConfiguration from XML. */ + private IgniteConfiguration getConfigurationFromXml(String xmlPath) { + ApplicationContext ctx = new ClassPathXmlApplicationContext("common-ignite-server-node.xml", xmlPath); + + return ctx.getBean(IgniteConfiguration.class).setGridLogger(listeningLog); + } + + /** @return PluginProvider ActivateByConsistentID. */ + private PluginProvider getPluginProvider(Set consistentIds) { + return new AutoActivationPluginProvider(new ActivateByConsistentID(consistentIds)); + } + + /** @return PluginProvider ActivateByNodeAttribute. */ + private PluginProvider getPluginProvider(String attrName, Set requiredValues) { + return new AutoActivationPluginProvider(new ActivateByNodeAttribute(attrName, requiredValues)); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByConsistentIdAllNodes() throws Exception { + executeTest(3, getPluginProvider(nodesConsistentIds), null, List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByConsistentIdFirstTwoNodes() throws Exception { + executeTest(3, getPluginProvider(Set.of(NODE_0, NODE_1)), null, List.of("actNotMeet", "actMeet", "alreadyAct")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByConsistentIdOnlyLastNode() throws Exception { + executeTest(3, getPluginProvider(Set.of(NODE_2)), null, List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByConsistentIdAllNodesPlusCacheConfig() throws Exception { + executeTest(3, getPluginProvider(Set.of(NODE_2)), "cacheConf", List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testActivationNotMetInMemoryClusterActivationByConsistentId() throws Exception { + executeTest(3, getPluginProvider(Set.of(NODE_3)), null, List.of("actNotMeet", "actNotMeet", "actNotMeet")); + } + + /** */ + @Test + public void testAlreadyActivatedInMemoryClusterActivationByConsistentId() throws Exception { + executeTest(1, getPluginProvider(Set.of(NODE_0)), "active", List.of("alreadyAct")); + } + + /** */ + @Test + public void testAlreadyActivatedInMemoryClusterActivationByConsistentIdActiveReadOnly() throws Exception { + executeTest(1, getPluginProvider(Set.of(NODE_0)), "activeReadOnly", List.of("alreadyActReadOnly")); + } + + /** */ + @Test + public void testBaselineNotEmptyPersistenceClusterActivationByConsistentId() throws Exception { + executeTest(3, getPluginProvider(nodesConsistentIds), + "dataStorageConf", List.of("actNotMeet", "actNotMeet", "actMeet")); + + stopAllGrids(); + + executeTest(3, getPluginProvider(nodesConsistentIds), + "dataStorageConf", List.of("baseline", "baseline", "baseline")); + } + + /** */ + @Test + public void testActivationConditionByConsistentIdNotMeetWithClientNode() throws Exception { + executeTest(3, getPluginProvider(nodesConsistentIds), "clientMode", + List.of("actNotMeet", "actNotMeet", "actNotMeet")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByNodeAttributeAllAttrs() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1, ATTR_VAL2)), null, + List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByNodeAttributeFirstAttr() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1)), null, + List.of("actMeet", "alreadyAct", "alreadyAct")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByNodeAttributeLastAttr() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL2)), null, + List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testSuccessfulInMemoryClusterActivationByNodeAttributeAllAttrsPlusCacheConfig() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1, ATTR_VAL2)), "cacheConf", + List.of("actNotMeet", "actNotMeet", "actMeet")); + } + + /** */ + @Test + public void testActivationNotMetInMemoryClusterActivationByNodeAttribute() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL3)), null, + List.of("actNotMeet", "actNotMeet", "actNotMeet")); + } + + /** */ + @Test + public void testAlreadyActivatedInMemoryClusterActivationByNodeAttribute() throws Exception { + executeTest(1, getPluginProvider(ATTR, Set.of(ATTR_VAL1)), "active", List.of("alreadyAct")); + } + + /** */ + @Test + public void testBaselineNotEmptyPersistenceClusterActivationByNodeAttribute() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1, ATTR_VAL2)), "dataStorageConf", + List.of("actNotMeet", "actNotMeet", "actMeet")); + + stopAllGrids(); + + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1, ATTR_VAL2)), "dataStorageConf", + List.of("baseline", "baseline", "baseline")); + } + + /** */ + @Test + public void testActivationConditionByNodeAttributeNotMeetWithClientNode() throws Exception { + executeTest(3, getPluginProvider(ATTR, Set.of(ATTR_VAL1, ATTR_VAL2)), "clientMode", + List.of("actNotMeet", "actNotMeet", "actNotMeet")); + } + + /** */ + @Test + public void testExceptionActivation() { + executeExceptionTest(ActivateByConsistentID.class, null, null, "requiredNodes must be set"); + + executeExceptionTest(ActivateByConsistentID.class, null, Collections.emptySet(), "requiredNodes must be set"); + + executeExceptionTest(ActivateByNodeAttribute.class, null, null, "attributeName must be set"); + + executeExceptionTest(ActivateByNodeAttribute.class, "", null, "attributeName must be set"); + + executeExceptionTest(ActivateByNodeAttribute.class, ATTR, null, "requiredValues must be set"); + + executeExceptionTest(ActivateByNodeAttribute.class, ATTR, Collections.emptySet(), "requiredValues must be set"); + + executeExceptionTest(null, null, null, "Auto activation condition must be set"); + } + + /** */ + @Test + public void testXmlCfgPersistenceClusterActivationByConsistentId() throws Exception { + executeXmlTest("activate-by-consistent-ID"); + } + + /** */ + @Test + public void testXmlCfgPersistenceClusterActivationByNodeAttribute() throws Exception { + executeXmlTest("activate-by-node-attribute"); + } + + /** */ + private void executeTest(int nodesCount, PluginProvider autoActivationProvider, + String extraCfg, List assertions) throws Exception { + AutoActivationPluginProvider provider = (AutoActivationPluginProvider)autoActivationProvider; + + log.info("Classs. " + provider.getCondition()); + log.info("Classs. " + autoActivationProvider.copyright()); + log.info("Classs. " + autoActivationProvider.version()); + log.info("Classs. " + autoActivationProvider.toString()); + Pattern missed = Pattern + .compile(provider.getCondition().getClass().equals(ActivateByConsistentID.class) + ? "\\(by consistent ID\\)\\. " + + "Missing nodes \\[(?:(?=.*" + NODE_1 + ")|(?=.*" + NODE_2 + ")|(?=.*" + NODE_3 + ")).+]" + : "\\(by node attribute\\)\\. Attribute: " + ATTR + ", " + + "Missing values \\[(?:(?=.*" + ATTR_VAL2 + ")|(?=.*" + ATTR_VAL3 + ")).+]"); + + lsnrMissed = LogListener.matches(missed).build(); + + listeningLog.registerListener(lsnrMissed); + + try (IgniteEx node0 = startGrid(getConfiguration(NODE_0, autoActivationProvider, extraCfg))) { + assertion(node0, assertions.get(0)); + + if (nodesCount > 1) { + for (int i = 1; i < nodesCount; i++) { + startGrid(getConfiguration("node_" + i, autoActivationProvider, extraCfg)); + + assertion(node0, assertions.get(i)); + } + } + } + } + + /** */ + private void executeExceptionTest(Class condition, String attributeName, + Set nodesOrAttrValues, String exceptionMessage) { + assertThrowsAnyCause( + listeningLog, + () -> startGrid(getConfiguration(NODE_0) + .setPluginProviders(new AutoActivationPluginProvider( + (condition == null) ? null : condition == ActivateByConsistentID.class + ? new ActivateByConsistentID(nodesOrAttrValues) + : new ActivateByNodeAttribute(attributeName, nodesOrAttrValues)))), + IllegalArgumentException.class, + exceptionMessage + ); + } + + /** */ + private void executeXmlTest(String conditionType) throws Exception { + lsnrMissed = LogListener + .matches(Pattern.compile(conditionType.equals("activate-by-consistent-ID") + ? "\\(by consistent ID\\)\\. Missing nodes \\[(?:(?=.*cell-2_node-1)|(?=.*cell-1_node-2)).+]" + : "\\(by node attribute\\)\\. Attribute: " + ATTR + ", Missing values \\[(?=.*CELL_2).+]")) + .build(); + + listeningLog.registerListener(lsnrMissed); + + try ( + IgniteEx node0 = + startGrid(getConfigurationFromXml(conditionType + "/ignite-server-node1.xml")) + ) { + assertion(node0, "actNotMeet"); + + startGrid(getConfigurationFromXml(conditionType + "/ignite-server-node2.xml")); + + assertion(node0, "actNotMeet"); + + startGrid(getConfigurationFromXml(conditionType + "/ignite-server-node3.xml")); + + assertion(node0, "actMeet"); + } + } + + /** */ + private void assertion(IgniteEx node0, String assertion) { + switch (assertion) { + case "actNotMeet": + assertTrue(lsnrActNotMeet.check()); + assertTrue(lsnrMissed.check()); + assertFalse(lsnrActMeet.check()); + assertEquals(node0.cluster().state(), INACTIVE); + break; + case "actMeet": + assertTrue(lsnrActMeet.check()); + assertFalse(lsnrAlreadyAct.check()); + assertEquals(node0.cluster().state(), ACTIVE); + break; + case "alreadyActFalseAndActMeet": + assertFalse(lsnrActMeet.check()); + case "alreadyAct": + assertTrue(lsnrAlreadyAct.check()); + assertEquals(node0.cluster().state(), ACTIVE); + break; + case "alreadyActReadOnly": + assertTrue(lsnrAlreadyAct.check()); + assertFalse(lsnrActMeet.check()); + assertEquals(node0.cluster().state(), ACTIVE_READ_ONLY); + break; + case "baseline": + assertTrue(lsnrBaseline.check()); + assertEquals(node0.cluster().state(), INACTIVE); + break; + } + } +} diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node1.xml b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node1.xml new file mode 100644 index 000000000..5dc5c96cf --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node1.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + cell-1_node-1 + cell-1_node-2 + cell-2_node-1 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node2.xml b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node2.xml new file mode 100644 index 000000000..d5294b058 --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + cell-1_node-1 + cell-1_node-2 + cell-2_node-1 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node3.xml b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node3.xml new file mode 100644 index 000000000..9e94e68a3 --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-consistent-ID/ignite-server-node3.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + cell-1_node-1 + cell-1_node-2 + cell-2_node-1 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node1.xml b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node1.xml new file mode 100644 index 000000000..6d4caf9fe --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node1.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + CELL_1 + CELL_2 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node2.xml b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node2.xml new file mode 100644 index 000000000..03a24476b --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + CELL_1 + CELL_2 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node3.xml b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node3.xml new file mode 100644 index 000000000..2761b8f78 --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/activate-by-node-attribute/ignite-server-node3.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + CELL_1 + CELL_2 + + + + diff --git a/modules/auto-activation-ext/src/test/resources/common-ignite-server-node.xml b/modules/auto-activation-ext/src/test/resources/common-ignite-server-node.xml new file mode 100644 index 000000000..6b8a99061 --- /dev/null +++ b/modules/auto-activation-ext/src/test/resources/common-ignite-server-node.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 127.0.0.1:47500..47600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 11fa26d80..4aec53b7e 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ modules/ssh-ext modules/ml-ext modules/gatling-ext + modules/auto-activation-ext