blob: 555dc6be163e607436158b2203b0af88cf36b109 [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.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;
}
}
}