| /* |
| * 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.testframework.junits.spi; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.UUID; |
| import java.util.concurrent.ConcurrentHashMap; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.cache.CacheMetrics; |
| import org.apache.ignite.cluster.ClusterMetrics; |
| import org.apache.ignite.cluster.ClusterNode; |
| import org.apache.ignite.internal.ClusterMetricsSnapshot; |
| import org.apache.ignite.internal.IgniteNodeAttributes; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.lang.IgniteProductVersion; |
| import org.apache.ignite.plugin.security.SecurityPermission; |
| import org.apache.ignite.plugin.security.SecurityPermissionSet; |
| import org.apache.ignite.spi.IgniteSpi; |
| import org.apache.ignite.spi.communication.CommunicationSpi; |
| import org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi; |
| import org.apache.ignite.spi.discovery.DiscoveryDataBag; |
| import org.apache.ignite.spi.discovery.DiscoveryMetricsProvider; |
| import org.apache.ignite.spi.discovery.DiscoverySpi; |
| import org.apache.ignite.spi.discovery.DiscoverySpiDataExchange; |
| import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; |
| import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; |
| import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder; |
| import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; |
| import org.apache.ignite.testframework.GridSpiTestContext; |
| import org.apache.ignite.testframework.GridTestNode; |
| import org.apache.ignite.testframework.GridTestUtils; |
| import org.apache.ignite.testframework.junits.GridAbstractTest; |
| import org.apache.ignite.testframework.junits.IgniteTestResources; |
| import org.apache.ignite.testframework.junits.spi.GridSpiTestConfig.ConfigType; |
| import org.jetbrains.annotations.Nullable; |
| import org.junit.ClassRule; |
| import org.junit.rules.RuleChain; |
| import org.junit.rules.TestRule; |
| import org.junit.runners.model.Statement; |
| |
| import static org.apache.ignite.lang.IgniteProductVersion.fromString; |
| |
| /** |
| * Base SPI test class. |
| * @param <T> SPI implementation class. |
| */ |
| public abstract class GridSpiAbstractTest<T extends IgniteSpi> extends GridAbstractTest { |
| /** */ |
| private static final IgniteProductVersion VERSION = fromString("99.99.99"); |
| |
| /** */ |
| private static final Map<Class<?>, TestData<?>> tests = new ConcurrentHashMap<>(); |
| |
| /** */ |
| private static final TestRule firstLastTestRuleSpi = (base, description) -> new Statement() { |
| @Override public void evaluate() throws Throwable { |
| GridSpiAbstractTest testClsInstance = (GridSpiAbstractTest)description.getTestClass().newInstance(); |
| try { |
| testClsInstance.beforeFirstTest(); |
| |
| base.evaluate(); |
| } |
| finally { |
| testClsInstance.afterLastTest(); |
| } |
| } |
| }; |
| |
| /** Manages first and last test execution. */ |
| @SuppressWarnings({"TransientFieldInNonSerializableClass"}) |
| @ClassRule public static transient RuleChain firstLastTestRule |
| = RuleChain.outerRule(firstLastTestRuleSpi).around(GridAbstractTest.firstLastTestRule); |
| |
| /** */ |
| private final boolean autoStart; |
| |
| /** Original context classloader. */ |
| private static ClassLoader cl; |
| |
| /** */ |
| protected GridSpiAbstractTest() { |
| super(false); |
| |
| autoStart = true; |
| } |
| |
| /** |
| * @param autoStart Start automatically. |
| */ |
| protected GridSpiAbstractTest(boolean autoStart) { |
| super(false); |
| |
| this.autoStart = autoStart; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected final boolean isJunitFrameworkClass() { |
| return true; |
| } |
| |
| /** |
| * @return Test data. |
| */ |
| protected TestData<T> getTestData() throws IgniteCheckedException { |
| TestData<T> data = (TestData<T>)tests.get(getClass()); |
| |
| if (data == null) |
| tests.put(getClass(), data = new TestData<>()); |
| |
| return data; |
| } |
| |
| /** |
| * @return Allow all permission security set. |
| */ |
| private static SecurityPermissionSet getAllPermissionSet() { |
| return new SecurityPermissionSetImpl(); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| private void resetTestData() throws Exception { |
| tests.put(getClass(), new TestData<T>()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void beforeTest() throws Exception { |
| // Need to change classloader here, although it also handled in the parent class |
| // the current test initialisation procedure doesn't allow us to setUp the parent first. |
| cl = Thread.currentThread().getContextClassLoader(); |
| |
| Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); |
| |
| super.beforeTest(); |
| } |
| |
| /** */ |
| private void beforeFirstTest() throws Exception { |
| if (autoStart) { |
| GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), GridSpiTest.class); |
| |
| assert spiTest != null; |
| |
| beforeSpiStarted(); |
| |
| if (spiTest.trigger()) |
| spiStart(); |
| |
| info("==== Started spi test [test=" + getClass().getSimpleName() + "] ===="); |
| } |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| protected void beforeSpiStarted() throws Exception { |
| // No-op. |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected final IgniteTestResources getTestResources() throws IgniteCheckedException { |
| return getTestData().getTestResources(); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| protected final void spiStart() throws Exception { |
| GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), GridSpiTest.class); |
| |
| assert spiTest != null; |
| |
| T spi = (T)spiTest.spi().newInstance(); |
| |
| // Set spi into test data. |
| getTestData().setSpi(spi); |
| |
| if (!(spi instanceof DiscoverySpi)) { |
| if (spiTest.triggerDiscovery()) |
| configureDiscovery(spiTest); |
| } |
| else |
| getTestData().setDiscoverySpi((DiscoverySpi)spi); |
| |
| getTestResources().inject(spi); |
| |
| spiConfigure(spi); |
| |
| // Set up SPI class name and SPI version. |
| Map<String, Serializable> spiAttrs = initSpiClassAndVersionAttributes(spi); |
| |
| if (spiAttrs != null) |
| getTestData().getAttributes().putAll(spiAttrs); |
| |
| Map<String, Serializable> nodeAttrs = getNodeAttributes(); |
| |
| if (nodeAttrs != null) |
| getTestData().getAttributes().putAll(nodeAttrs); |
| |
| DiscoverySpi discoSpi = getTestData().getDiscoverySpi(); |
| |
| if (spiTest.triggerDiscovery() && !getTestData().isDiscoveryTest()) { |
| getTestData().getAttributes().putAll( |
| initSpiClassAndVersionAttributes(discoSpi)); |
| |
| // Set all local node attributes into discovery SPI. |
| discoSpi.setNodeAttributes(getTestData().getAttributes(), VERSION); |
| |
| discoSpi.setMetricsProvider(createMetricsProvider()); |
| |
| discoSpi.setDataExchange(new DiscoverySpiDataExchange() { |
| |
| @Override public DiscoveryDataBag collect(DiscoveryDataBag dataBag) { |
| return dataBag; |
| } |
| |
| @Override public void onExchange(DiscoveryDataBag dataBag) { |
| // No-op. |
| } |
| }); |
| |
| try { |
| spiStart(discoSpi); |
| } |
| catch (Exception e) { |
| spiStop(discoSpi); |
| |
| throw e; |
| } |
| } |
| |
| if (spi instanceof DiscoverySpi) { |
| getTestData().getAttributes().putAll(initSpiClassAndVersionAttributes(spi)); |
| |
| ((DiscoverySpi)spi).setNodeAttributes(getTestData().getAttributes(), VERSION); |
| |
| ((DiscoverySpi)spi).setMetricsProvider(createMetricsProvider()); |
| } |
| |
| try { |
| spiStart(spi); |
| |
| Map<String, Object> attrs = spi.getNodeAttributes(); |
| |
| if (attrs != null) |
| getTestData().getAttributes().putAll(attrs); |
| } |
| catch (Exception e) { |
| spiStop(spi); |
| |
| if (discoSpi != null && !discoSpi.equals(spi)) |
| spiStop(discoSpi); |
| |
| afterSpiStopped(); |
| |
| throw e; |
| } |
| |
| // Initialize SPI context. |
| getTestData().setSpiContext(initSpiContext()); |
| |
| // Initialize discovery SPI only once. |
| if (discoSpi != null && !discoSpi.equals(spi)) |
| discoSpi.onContextInitialized(getSpiContext()); |
| |
| spi.onContextInitialized(getTestData().getSpiContext()); |
| } |
| |
| /** |
| * @return SPI context. |
| * @throws Exception If anything failed. |
| */ |
| protected GridSpiTestContext initSpiContext() throws Exception { |
| GridSpiTestContext spiCtx = new GridSpiTestContext(); |
| |
| if (getTestData().getDiscoverySpi() != null) { |
| spiCtx.setLocalNode(getTestData().getDiscoverySpi().getLocalNode()); |
| |
| for (ClusterNode node : getTestData().getDiscoverySpi().getRemoteNodes()) |
| spiCtx.addNode(node); |
| } |
| else { |
| GridTestNode node = new GridTestNode(UUID.randomUUID()); |
| |
| spiCtx.setLocalNode(node); |
| |
| if (getSpi() != null) { |
| // Set up SPI class name and SPI version. |
| Map<String, Serializable> attrs = initSpiClassAndVersionAttributes(getSpi()); |
| |
| for (Map.Entry<String, Serializable> entry: attrs.entrySet()) |
| node.addAttribute(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| return spiCtx; |
| } |
| |
| /** |
| * @param spi SPI to create attributes for. |
| * @return Map of attributes. |
| */ |
| private Map<String, Serializable> initSpiClassAndVersionAttributes(IgniteSpi spi) { |
| Map<String, Serializable> attrs = new HashMap<>(); |
| |
| attrs.put(U.spiAttribute(spi, IgniteNodeAttributes.ATTR_SPI_CLASS), spi.getClass().getName()); |
| |
| return attrs; |
| } |
| |
| /** |
| * @return SPI context. |
| * @throws Exception If anything failed. |
| */ |
| protected final GridSpiTestContext getSpiContext() throws Exception { |
| return getTestData().getSpiContext(); |
| } |
| |
| /** |
| * @param spiTest SPI test annotation. |
| * @throws Exception If anything failed. |
| */ |
| private void configureDiscovery(GridSpiTest spiTest) throws Exception { |
| DiscoverySpi discoSpi = spiTest.discoverySpi().newInstance(); |
| |
| if (discoSpi instanceof TcpDiscoverySpi) { |
| TcpDiscoverySpi tcpDisco = (TcpDiscoverySpi)discoSpi; |
| |
| tcpDisco.setIpFinder(new TcpDiscoveryVmIpFinder(true)); |
| } |
| |
| getTestData().setDiscoverySpi(discoSpi); |
| |
| getTestResources().inject(discoSpi); |
| |
| configure(discoSpi); |
| |
| if (discoSpi.getNodeAttributes() != null) |
| getTestData().getAttributes().putAll(discoSpi.getNodeAttributes()); |
| } |
| |
| /** |
| * Creates metrics provider just for testing purposes. The provider |
| * will not return valid node metrics. |
| * |
| * @return Dummy metrics provider. |
| */ |
| protected DiscoveryMetricsProvider createMetricsProvider() { |
| return new DiscoveryMetricsProvider() { |
| /** {@inheritDoc} */ |
| @Override public ClusterMetrics metrics() { return new ClusterMetricsSnapshot(); } |
| |
| /** {@inheritDoc} */ |
| @Override public Map<Integer, CacheMetrics> cacheMetrics() { |
| return Collections.emptyMap(); |
| } |
| }; |
| } |
| |
| /** |
| * @param spi SPI. |
| * @throws Exception If failed. |
| */ |
| protected void spiConfigure(T spi) throws Exception { |
| configure(spi); |
| } |
| |
| /** |
| * @param spi SPI. |
| * @throws Exception If failed. |
| * @throws IllegalAccessException If failed. |
| * @throws InvocationTargetException If failed. |
| */ |
| private void configure(IgniteSpi spi) throws Exception { |
| // Inject Configuration. |
| for (Method m : getClass().getMethods()) { |
| GridSpiTestConfig cfg = m.getAnnotation(GridSpiTestConfig.class); |
| |
| if (cfg != null) { |
| if (getTestData().isDiscoveryTest() || |
| (cfg.type() != ConfigType.DISCOVERY && !(spi instanceof DiscoverySpi)) || |
| (cfg.type() != ConfigType.SELF && spi instanceof DiscoverySpi)) { |
| assert m.getName().startsWith("get") : "Test configuration must be a getter [method=" + |
| m.getName() + ']'; |
| |
| // Determine getter name. |
| String name = cfg.setterName(); |
| |
| if (name == null || name.isEmpty()) |
| name = 's' + m.getName().substring(1); |
| |
| Method setter = getMethod(spi.getClass(), name); |
| |
| assert setter != null : "Spi does not have setter for configuration property [spi=" + |
| spi.getClass().getName() + ", config-prop=" + name + ']'; |
| |
| // Inject configuration parameter into spi. |
| setter.invoke(spi, m.invoke(this)); |
| } |
| } |
| } |
| |
| // Our SPI tests should not have the same parameters otherwise they |
| // will find each other. |
| if (spi instanceof TcpCommunicationSpi) |
| ((TcpCommunicationSpi)spi).setLocalPort(GridTestUtils.getNextCommPort(getClass())); |
| |
| if (spi instanceof TcpDiscoverySpi) { |
| TcpDiscoveryIpFinder ipFinder = ((TcpDiscoverySpi)spi).getIpFinder(); |
| |
| if (ipFinder instanceof TcpDiscoveryMulticastIpFinder) { |
| String mcastAddr = GridTestUtils.getNextMulticastGroup(getClass()); |
| |
| if (mcastAddr != null && !mcastAddr.isEmpty()) { |
| ((TcpDiscoveryMulticastIpFinder)ipFinder).setMulticastGroup(mcastAddr); |
| ((TcpDiscoveryMulticastIpFinder)ipFinder).setMulticastPort( |
| GridTestUtils.getNextMulticastPort(getClass())); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param spi SPI. |
| * @throws Exception If failed. |
| */ |
| protected void spiStart(IgniteSpi spi) throws Exception { |
| // Start SPI with unique Ignite instance name. |
| spi.spiStart(getTestIgniteInstanceName()); |
| |
| info("SPI started [spi=" + spi.getClass() + ']'); |
| } |
| |
| /** |
| * @return Fully initialized and started SPI implementation. |
| */ |
| protected final T getSpi() throws IgniteCheckedException { |
| return getTestData().getSpi(); |
| } |
| |
| /** |
| * Gets class of the SPI implementation. |
| * |
| * @return Class of the SPI implementation. |
| */ |
| protected final Class<? extends T> getSpiClass() { |
| GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), GridSpiTest.class); |
| |
| assert spiTest != null; |
| |
| return (Class<? extends T>)spiTest.spi(); |
| } |
| |
| /** |
| * @return Node UUID. |
| * @throws Exception If failed. |
| */ |
| protected UUID getNodeId() throws Exception { |
| return getTestResources().getNodeId(); |
| } |
| |
| /** |
| * @return Discovery SPI. |
| * @throws Exception If failed. |
| */ |
| @Nullable protected final DiscoverySpi getDiscoverySpi() throws Exception { |
| if (getTestData() != null) |
| return getTestData().getDiscoverySpi(); |
| |
| return null; |
| } |
| |
| /** |
| * Override this method for put local node attributes to discovery SPI. |
| * |
| * @return Always {@code null}. |
| */ |
| @Nullable protected Map<String, Serializable> getNodeAttributes() { |
| return null; |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Override protected void afterTest() throws Exception { |
| super.afterTest(); |
| |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| |
| /** */ |
| private void afterLastTest() throws Exception { |
| if (autoStart) { |
| GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), GridSpiTest.class); |
| |
| assert spiTest != null; |
| |
| if (spiTest.trigger()) { |
| spiStop(); |
| |
| afterSpiStopped(); |
| } |
| |
| info("==== Stopped spi test [test=" + getClass().getSimpleName() + "] ===="); |
| } |
| } |
| |
| /** |
| * @throws Exception If failed.. |
| */ |
| protected void afterSpiStopped() throws Exception { |
| // No-op. |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| protected final void spiStop() throws Exception { |
| TestData<T> testData = getTestData(); |
| |
| if (testData.getSpi() == null) |
| return; |
| |
| testData.getSpi().onContextDestroyed(); |
| |
| spiStop(testData.getSpi()); |
| |
| GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), GridSpiTest.class); |
| |
| assert spiTest != null; |
| |
| if (!testData.isDiscoveryTest() && spiTest.triggerDiscovery()) { |
| testData.getDiscoverySpi().onContextDestroyed(); |
| |
| spiStop(testData.getDiscoverySpi()); |
| } |
| |
| getTestResources().stopThreads(); |
| |
| resetTestData(); |
| } |
| |
| /** |
| * @param spi SPI. |
| * @throws Exception If failed. |
| */ |
| protected void spiStop(IgniteSpi spi) throws Exception { |
| spi.spiStop(); |
| |
| info("SPI stopped [spi=" + spi.getClass().getName() + ']'); |
| } |
| |
| /** |
| * @param cls Class. |
| * @param name Method name. |
| * @return Method. |
| */ |
| @Nullable private Method getMethod(Class<?> cls, String name) { |
| for (; !cls.equals(Object.class); cls = cls.getSuperclass()) { |
| for (Method m : cls.getMethods()) { |
| if (m.getName().equals(name)) |
| return m; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * |
| * @param <T> SPI implementation class. |
| */ |
| protected static class TestData<T> { |
| /** */ |
| private T spi; |
| |
| /** */ |
| private DiscoverySpi discoSpi; |
| |
| /** */ |
| private CommunicationSpi commSpi; |
| |
| /** */ |
| private GridSpiTestContext spiCtx; |
| |
| /** */ |
| private Map<String, Object> allAttrs = new HashMap<>(); |
| |
| /** */ |
| private IgniteTestResources rsrcs; |
| |
| /** |
| * |
| */ |
| TestData() throws IgniteCheckedException { |
| rsrcs = new IgniteTestResources(); |
| } |
| |
| /** |
| * @return Test resources. |
| * |
| */ |
| public IgniteTestResources getTestResources() { |
| return rsrcs; |
| } |
| |
| /** |
| * @return {@code true} in case it is a discovery test. |
| */ |
| @SuppressWarnings({"ObjectEquality"}) |
| public boolean isDiscoveryTest() { |
| return spi == discoSpi; |
| } |
| |
| /** |
| * @return {@code true} in case it is a communication test. |
| */ |
| @SuppressWarnings({"ObjectEquality"}) |
| public boolean isCommunicationTest() { |
| return spi == commSpi; |
| } |
| |
| /** |
| * @return SPI. |
| */ |
| public T getSpi() { |
| return spi; |
| } |
| |
| /** |
| * @param spi SPI. |
| */ |
| public void setSpi(T spi) { |
| this.spi = spi; |
| } |
| |
| /** |
| * @param commSpi Communication SPI. |
| */ |
| public void setCommSpi(CommunicationSpi commSpi) { |
| this.commSpi = commSpi; |
| } |
| |
| /** |
| * @return Attributes. |
| */ |
| public Map<String, Object> getAttributes() { |
| return allAttrs; |
| } |
| |
| /** |
| * @param allAttrs Attributes. |
| */ |
| public void setAllAttrs(Map<String, Object> allAttrs) { |
| this.allAttrs = allAttrs; |
| } |
| |
| /** |
| * @return Discovery SPI. |
| */ |
| public DiscoverySpi getDiscoverySpi() { |
| return discoSpi; |
| } |
| |
| /** |
| * @param discoSpi Discovery SPI. |
| */ |
| public void setDiscoverySpi(DiscoverySpi discoSpi) { |
| this.discoSpi = discoSpi; |
| } |
| |
| /** |
| * @return SPI context. |
| */ |
| public GridSpiTestContext getSpiContext() { |
| return spiCtx; |
| } |
| |
| /** |
| * @param spiCtx SPI context. |
| */ |
| public void setSpiContext(GridSpiTestContext spiCtx) { |
| this.spiCtx = spiCtx; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return getClass().getSimpleName() + |
| " [spi=" + spi + |
| ", discoSpi=" + discoSpi + |
| ", allAttrs=" + allAttrs + ']'; |
| } |
| } |
| |
| /** */ |
| private static class SecurityPermissionSetImpl implements SecurityPermissionSet { |
| /** Serial version uid. */ |
| private static final long serialVersionUID = 0L; |
| |
| /** {@inheritDoc} */ |
| @Override public boolean defaultAllowAll() { |
| return true; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Map<String, Collection<SecurityPermission>> taskPermissions() { |
| return Collections.emptyMap(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Map<String, Collection<SecurityPermission>> cachePermissions() { |
| return Collections.emptyMap(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Map<String, Collection<SecurityPermission>> servicePermissions() { |
| return Collections.emptyMap(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Nullable @Override public Collection<SecurityPermission> systemPermissions() { |
| return null; |
| } |
| } |
| } |