diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridPluginComponent.java b/modules/core/src/main/java/org/apache/ignite/internal/GridPluginComponent.java index 3864e9f73a621..f8e4d2f6a1f0c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridPluginComponent.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridPluginComponent.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal; +import java.io.Serializable; +import java.util.Map; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.lang.IgniteFuture; @@ -81,7 +83,7 @@ public PluginProvider plugin() { /** {@inheritDoc} */ @Nullable @Override public DiscoveryDataExchangeType discoveryDataType() { - return null; + return DiscoveryDataExchangeType.PLUGIN; } /** {@inheritDoc} */ @@ -106,8 +108,19 @@ public PluginProvider plugin() { /** {@inheritDoc} */ @Nullable @Override public IgniteNodeValidationResult validateNode(ClusterNode node) { + return null; + } + + /** {@inheritDoc} */ + @Nullable @Override public IgniteNodeValidationResult validateNode(ClusterNode node, + JoiningNodeDiscoveryData discoData) { try { - plugin.validateNewNode(node); + Map map = (Map)discoData.joiningNodeData(); + + if (map != null) + plugin.validateNewNode(node, map.get(plugin.name())); + else + plugin.validateNewNode(node, null); return null; } @@ -116,11 +129,6 @@ public PluginProvider plugin() { } } - /** {@inheritDoc} */ - @Nullable @Override public IgniteNodeValidationResult validateNode(ClusterNode node, JoiningNodeDiscoveryData discoData) { - return null; - } - /** {@inheritDoc} */ @Override public void printMemoryStats() { // No-op. diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/PluginProvider.java b/modules/core/src/main/java/org/apache/ignite/plugin/PluginProvider.java index 7f95d34855a60..6aeb619ff5c8e 100644 --- a/modules/core/src/main/java/org/apache/ignite/plugin/PluginProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/plugin/PluginProvider.java @@ -21,6 +21,7 @@ import java.util.ServiceLoader; import java.util.UUID; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteCluster; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.IgniteConfiguration; import org.jetbrains.annotations.Nullable; @@ -141,6 +142,22 @@ public interface PluginProvider { * * @param node Joining node. * @throws PluginValidationException If cluster-wide plugin validation failed. + * + * @deprecated Use {@link #validateNewNode(ClusterNode, Serializable)} instead. */ + @Deprecated public void validateNewNode(ClusterNode node) throws PluginValidationException; + + /** + * Validates that new node can join grid topology, this method is called on coordinator + * node before new node joins topology. + * + * @param node Joining node. + * @param data Discovery data object or {@code null} if nothing was + * sent for this component. + * @throws PluginValidationException If cluster-wide plugin validation failed. + */ + public default void validateNewNode(ClusterNode node, Serializable data) { + validateNewNode(node); + } } \ No newline at end of file diff --git a/modules/core/src/test/java/META-INF/services/org.apache.ignite.plugin.PluginProvider b/modules/core/src/test/java/META-INF/services/org.apache.ignite.plugin.PluginProvider index e9e9d41ec7f27..5805dfd95409a 100644 --- a/modules/core/src/test/java/META-INF/services/org.apache.ignite.plugin.PluginProvider +++ b/modules/core/src/test/java/META-INF/services/org.apache.ignite.plugin.PluginProvider @@ -2,3 +2,4 @@ org.apache.ignite.spi.discovery.tcp.TestReconnectPluginProvider org.apache.ignite.internal.processors.cache.persistence.standbycluster.IgniteStandByClusterTest$StanByClusterTestProvider org.apache.ignite.internal.processors.cache.persistence.wal.memtracker.PageMemoryTrackerPluginProvider org.apache.ignite.internal.processors.configuration.distributed.TestDistibutedConfigurationPlugin +org.apache.ignite.plugin.NodeValidationPluginProvider \ No newline at end of file diff --git a/modules/core/src/test/java/org/apache/ignite/plugin/NodeValidationPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/plugin/NodeValidationPluginProvider.java new file mode 100644 index 0000000000000..76c8fdcafae3e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/plugin/NodeValidationPluginProvider.java @@ -0,0 +1,192 @@ +/* + * 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.plugin; + +import java.io.Serializable; +import java.util.UUID; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.jetbrains.annotations.Nullable; + +/** + * Validates node on join, it requires nodes to provide token that matches configured on primary node. + */ +public class NodeValidationPluginProvider implements PluginProvider, IgnitePlugin { + + /** */ + private NodeValidationPluginConfiguration pluginConfiguration; + /** */ + private static volatile boolean enabled; + + /** */ + public static boolean isEnabled() { + return enabled; + } + + /** */ + public static void setEnabled(boolean enabled) { + NodeValidationPluginProvider.enabled = enabled; + } + + /** {@inheritDoc} */ + @Override public String name() { + return "NodeValidationPluginProvider"; + } + + /** {@inheritDoc} */ + @Override public String version() { + return "1.0"; + } + + /** {@inheritDoc} */ + @Override public String copyright() { + return ""; + } + + /** {@inheritDoc} */ + @Override public IgnitePlugin plugin() { + return this; + } + + /** {@inheritDoc} */ + @Override public void initExtensions(PluginContext ctx, ExtensionRegistry registry) { + if (!enabled) + return; + + IgniteConfiguration igniteCfg = ctx.igniteConfiguration(); + + if (igniteCfg.getPluginConfigurations() != null) { + for (PluginConfiguration pluginCfg : igniteCfg.getPluginConfigurations()) { + if (pluginCfg instanceof NodeValidationPluginConfiguration) { + pluginConfiguration = (NodeValidationPluginConfiguration)pluginCfg; + + break; + } + } + } + } + + /** {@inheritDoc} */ + @Nullable @Override public Object createComponent(PluginContext ctx, Class cls) { + return null; + } + + /** {@inheritDoc} */ + @Override public CachePluginProvider createCacheProvider(CachePluginContext ctx) { + return null; + } + + /** {@inheritDoc} */ + @Override public void start(PluginContext ctx) throws IgniteCheckedException { + //no-op + } + + /** {@inheritDoc} */ + @Override public void stop(boolean cancel) throws IgniteCheckedException { + //no-op + } + + /** {@inheritDoc} */ + @Override public void onIgniteStart() throws IgniteCheckedException { + //no-op + } + + /** {@inheritDoc} */ + @Override public void onIgniteStop(boolean cancel) { + //no-op + } + + /** {@inheritDoc} */ + @Nullable @Override public Serializable provideDiscoveryData(UUID nodeId) { + if (!enabled) + return null; + + MyDiscoData data = new MyDiscoData(pluginConfiguration.getToken()); + + return data; + } + + /** {@inheritDoc} */ + @Override public void receiveDiscoveryData(UUID nodeId, Serializable data) { + if (!enabled) + return; + } + + /** {@inheritDoc} */ + @Override public void validateNewNode(ClusterNode node) throws PluginValidationException { + // no-op + } + + /** {@inheritDoc} */ + @Override public void validateNewNode(ClusterNode node, Serializable serializable) { + if (!enabled) + return; + + MyDiscoData newNodeDiscoData = serializable instanceof MyDiscoData ? (MyDiscoData)serializable : null; + + if (newNodeDiscoData == null || !newNodeDiscoData.getToken().equals(pluginConfiguration.getToken())) { + String msg = newNodeDiscoData == null ? "no token provided" : "bad token provided: " + newNodeDiscoData.getToken(); + + throw new PluginValidationException(msg, msg, node.id()); + } + } + + /** + * + */ + private static class MyDiscoData implements Serializable { + /** */ + String token; + + /** */ + MyDiscoData(String token) { + this.token = token; + } + + /** */ + public String getToken() { + return token; + } + + /** */ + @Override public String toString() { + return "MyDiscoData{" + + "token='" + token + '\'' + + '}'; + } + } + + /** + * + */ + public static class NodeValidationPluginConfiguration implements PluginConfiguration { + /** */ + private final String token; + + /** */ + NodeValidationPluginConfiguration(String token) { + this.token = token; + } + + /** */ + public String getToken() { + return token; + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/plugin/PluginNodeValidationTest.java b/modules/core/src/test/java/org/apache/ignite/plugin/PluginNodeValidationTest.java new file mode 100644 index 0000000000000..8ae55fcd0e46e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/plugin/PluginNodeValidationTest.java @@ -0,0 +1,104 @@ +/* + * 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.plugin; + +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.spi.IgniteSpiException; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test node validation on join by plugin. + */ +@RunWith(JUnit4.class) +public class PluginNodeValidationTest extends GridCommonAbstractTest { + + /** */ + private volatile String token; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setDataStorageConfiguration(new DataStorageConfiguration() + .setDefaultDataRegionConfiguration(new DataRegionConfiguration() + .setMaxSize(100L * 1024 * 1024) + .setPersistenceEnabled(true))); + + cfg.setConsistentId(igniteInstanceName); + + cfg.setPluginConfigurations(new NodeValidationPluginProvider.NodeValidationPluginConfiguration(token)); + + return cfg; + } + + /** Tests that node join fails due failure in node validation. */ + @Test + public void testValidationException() throws Exception { + token = "123456"; + + startGrid(0); + + token = "abcdef"; + + try { + startGrid(1); + } catch (Exception ex) { + assertTrue("Wrong exception type for validation error", X.hasCause(ex, IgniteSpiException.class)); + + return; + } + + fail("Exception is expected due validation error in plugin"); + } + + /** Tests that node joins on successful node validation by plugin. */ + @Test + public void testSuccessfulValidation() throws Exception { + token = "123456"; + + startGrid(0); + startGrid(1); + } + + /** Stop all nodes after each test. */ + @After + public void after() { + stopAllGrids(); + } + + /** Enables plugin before test start. */ + @BeforeClass + public static void enablePlugin() { + NodeValidationPluginProvider.setEnabled(true); + } + + /** Disable plugin after test end. */ + @AfterClass + public static void disablePlugin() { + NodeValidationPluginProvider.setEnabled(false); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestReconnectPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestReconnectPluginProvider.java index ccbf24340e631..2074ec963bd78 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestReconnectPluginProvider.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestReconnectPluginProvider.java @@ -97,7 +97,7 @@ public class TestReconnectPluginProvider implements PluginProvider { @Override public void validateNewNode(ClusterNode node) throws PluginValidationException { // No-op } - + /** {@inheritDoc} */ @Nullable @Override public Object createComponent(PluginContext ctx, Class cls) { if (enabled && GridSecurityProcessor.class.equals(cls)) diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java index b55f140b02d27..f145ae424d5ed 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java @@ -83,6 +83,7 @@ import org.apache.ignite.messaging.GridMessagingSelfTest; import org.apache.ignite.messaging.IgniteMessagingSendAsyncTest; import org.apache.ignite.messaging.IgniteMessagingWithClientTest; +import org.apache.ignite.plugin.PluginNodeValidationTest; import org.apache.ignite.plugin.security.SecurityPermissionSetBuilderTest; import org.apache.ignite.spi.GridSpiLocalHostInjectionTest; import org.apache.ignite.startup.properties.NotStringSystemPropertyTest; @@ -208,6 +209,8 @@ ListeningTestLoggerTest.class, CacheLocalGetSerializationTest.class, + + PluginNodeValidationTest.class, }) public class IgniteBasicTestSuite { }