/*
 * 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.processors.cache.persistence;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.cluster.BaselineNode;
import org.apache.ignite.cluster.ClusterNode;
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.internal.cluster.DetachedClusterNode;
import org.apache.ignite.internal.processors.cluster.BaselineTopology;
import org.apache.ignite.internal.processors.cluster.BaselineTopologyHistory;
import org.apache.ignite.internal.processors.cluster.BaselineTopologyHistoryItem;
import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
import org.apache.ignite.internal.util.lang.GridAbsPredicate;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.spi.IgniteSpiException;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Assert;

/**
 *
 */
public class IgniteBaselineAffinityTopologyActivationTest extends GridCommonAbstractTest {
    /** */
    private String consId;

    /** Entries count to add to cache. */
    private static final int ENTRIES_COUNT = 100;

    /** {@inheritDoc} */
    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);

        if (consId != null)
            cfg.setConsistentId(consId);

        cfg.setDataStorageConfiguration(
            new DataStorageConfiguration().setDefaultDataRegionConfiguration(
                new DataRegionConfiguration()
                    .setPersistenceEnabled(true).setMaxSize(10 * 1024 * 1024)

            ).setWalMode(WALMode.LOG_ONLY)
        );

        return cfg;
    }

    /** {@inheritDoc} */
    @Override protected void beforeTest() throws Exception {
        super.beforeTest();

        cleanPersistenceDir();
    }

    /** {@inheritDoc} */
    @Override protected void afterTest() throws Exception {
        stopAllGrids(false);

        cleanPersistenceDir();
    }

    /**
     * Verifies that when old but compatible node
     * (it is the node that once wasn't presented in branchingHistory but hasn't participated in any branching point)
     * joins the cluster after restart, cluster gets activated.
     */
    public void testAutoActivationWithCompatibleOldNode() throws Exception {
        startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C").active(true);

        stopAllGrids(false);

        startGridWithConsistentId("A");
        startGridWithConsistentId("B").active(true);

        {
            IgniteEx nodeA = grid("A");

            assertNotNull(nodeA.cluster().currentBaselineTopology());
            assertEquals(3, nodeA.cluster().currentBaselineTopology().size());

            assertTrue(nodeA.cluster().active());
        }

        stopAllGrids(false);

        startGridWithConsistentId("A");
        startGridWithConsistentId("B");

        {
            IgniteEx nodeA = grid("A");

            assertNotNull(nodeA.cluster().currentBaselineTopology());
            assertEquals(3, nodeA.cluster().currentBaselineTopology().size());

            assertFalse(nodeA.cluster().active());
        }

        final Ignite nodeC = startGridWithConsistentId("C");

        boolean active = GridTestUtils.waitForCondition(
            new GridAbsPredicate() {
                @Override public boolean apply() {
                    return nodeC.active();
                }
            },
            10_000
        );

        assertTrue(active);
    }

    /**
     * IgniteCluster::setBaselineTopology(long topVer) should throw an exception
     * when online node from current BaselineTopology is not presented in topology version.
     */
    public void testBltChangeTopVerRemoveOnlineNodeFails() throws Exception {
        Ignite ignite = startGridWithConsistentId("A");

        ignite.active(true);

        long singleNodeTopVer = ignite.cluster().topologyVersion();

        startGridWithConsistentId("OnlineConsID");

        ignite.cluster().setBaselineTopology(baselineNodes(ignite.cluster().forServers().nodes()));

        boolean expectedExceptionThrown = false;

        try {
            ignite.cluster().setBaselineTopology(singleNodeTopVer);
        }
        catch (IgniteException e) {
            String errMsg = e.getMessage();
            assertTrue(errMsg.startsWith("Removing online nodes"));
            assertTrue(errMsg.contains("[OnlineConsID]"));

            expectedExceptionThrown = true;
        }

        assertTrue(expectedExceptionThrown);
    }

    /**
     * Verifies that online nodes cannot be removed from BaselineTopology (this may change in future).
     */
    public void testOnlineNodesCannotBeRemovedFromBaselineTopology() throws Exception {
        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("OnlineConsID");

        nodeC.active(true);

        boolean expectedExceptionIsThrown = false;

        try {
            nodeC.cluster().setBaselineTopology(Arrays.asList((BaselineNode) nodeA.cluster().localNode(),
                nodeB.cluster().localNode()));
        } catch (IgniteException e) {
            String errMsg = e.getMessage();
            assertTrue(errMsg.startsWith("Removing online nodes"));
            assertTrue(errMsg.contains("[OnlineConsID]"));

            expectedExceptionIsThrown = true;
        }

        assertTrue(expectedExceptionIsThrown);
    }

    /**
     *
     */
    public void testNodeFailsToJoinWithIncompatiblePreviousBaselineTopology() throws Exception {
        startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);

        stopAllGrids(false);

        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B").active(true);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        stopAllGrids(false);

        startGridWithConsistentId("C").active(true);

        stopGrid("C", false);

        startGridWithConsistentId("A");
        startGridWithConsistentId("B");

        boolean expectedExceptionThrown = false;

        try {
            startGridWithConsistentId("C");
        }
        catch (IgniteCheckedException e) {
            expectedExceptionThrown = true;

            if (e.getCause() != null && e.getCause().getCause() != null) {
                Throwable rootCause = e.getCause().getCause();

                if (!(rootCause instanceof IgniteSpiException) || !rootCause.getMessage().contains("not compatible"))
                    Assert.fail("Unexpected ignite exception was thrown: " + e);
            }
            else
                throw e;
        }

        assertTrue("Expected exception wasn't thrown.", expectedExceptionThrown);
    }

    /**
     * Verifies scenario when parts of grid were activated independently they are not allowed to join
     * into the same grid again (due to risks of incompatible data modifications).
     */
    public void testIncompatibleBltNodeIsProhibitedToJoinCluster() throws Exception {
        startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C").active(true);

        stopAllGrids(false);

        startGridWithConsistentId("A");
        startGridWithConsistentId("B").active(true);

        stopAllGrids(false);

        startGridWithConsistentId("C").active(true);

        stopAllGrids(false);

        startGridWithConsistentId("A");
        startGridWithConsistentId("B");

        boolean expectedExceptionThrown = false;

        try {
            startGridWithConsistentId("C");
        }
        catch (IgniteCheckedException e) {
            expectedExceptionThrown = true;

            if (e.getCause() != null && e.getCause().getCause() != null) {
                Throwable rootCause = e.getCause().getCause();

                if (!(rootCause instanceof IgniteSpiException) || !rootCause.getMessage().contains("not compatible"))
                    Assert.fail("Unexpected ignite exception was thrown: " + e);
            }
            else
                throw e;
        }

        assertTrue("Expected exception wasn't thrown.", expectedExceptionThrown);
    }

    /**
     * Test verifies that node with out-of-data but still compatible Baseline Topology is allowed to join the cluster.
     */
    public void testNodeWithOldBltIsAllowedToJoinCluster() throws Exception {
        final long expectedHash1 = (long)"A".hashCode() + "B".hashCode() + "C".hashCode();

        BaselineTopologyVerifier verifier1 = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNotNull(blt);

                assertEquals(3, blt.consistentIds().size());

                long activationHash = U.field(blt, "branchingPntHash");

                assertEquals(expectedHash1, activationHash);
            }
        };

        final long expectedHash2 = (long)"A".hashCode() + "B".hashCode();

        BaselineTopologyVerifier verifier2 = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNotNull(blt);

                assertEquals(3, blt.consistentIds().size());

                long activationHash = U.field(blt, "branchingPntHash");

                assertEquals(expectedHash2, activationHash);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);
        verifyBaselineTopologyOnNodes(verifier1, new Ignite[] {nodeA, nodeB, nodeC});

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");
        nodeB = startGridWithConsistentId("B");

        nodeB.active(true);

        verifyBaselineTopologyOnNodes(verifier2, new Ignite[] {nodeA, nodeB});

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");
        nodeB = startGridWithConsistentId("B");
        nodeC = startGridWithConsistentId("C");

        verifyBaselineTopologyOnNodes(verifier2, new Ignite[] {nodeA, nodeB, nodeC});
    }

    /**
     * Verifies that when new node outside of baseline topology joins active cluster with BLT already set
     * it receives BLT from the cluster and stores it locally.
     */
    public void testNewNodeJoinsToActiveCluster() throws Exception {
        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);

        BaselineTopologyVerifier verifier1 = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNotNull(blt);

                assertEquals(3, blt.consistentIds().size());
            }
        };

        verifyBaselineTopologyOnNodes(verifier1, new Ignite[] {nodeA, nodeB, nodeC});

        Ignite nodeD = startGridWithConsistentId("D");

        verifyBaselineTopologyOnNodes(verifier1, new Ignite[] {nodeD});

        stopAllGrids(false);

        nodeD = startGridWithConsistentId("D");

        assertFalse(nodeD.active());

        verifyBaselineTopologyOnNodes(verifier1, new Ignite[] {nodeD});
    }

    /**
     *
     */
    public void testRemoveNodeFromBaselineTopology() throws Exception {
        final long expectedActivationHash = (long)"A".hashCode() + "C".hashCode();

        BaselineTopologyVerifier verifier = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNotNull(blt);

                assertEquals(2, blt.consistentIds().size());

                long activationHash = U.field(blt, "branchingPntHash");

                assertEquals(expectedActivationHash, activationHash);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);

        stopGrid("B", false);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        boolean activated = GridTestUtils.waitForCondition(new GridAbsPredicate() {
            @Override public boolean apply() {
                return grid("A").active();
            }
        }, 10_000);

        assertEquals(true, activated);

        verifyBaselineTopologyOnNodes(verifier, new Ignite[] {nodeA, nodeC});

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");
        nodeC = startGridWithConsistentId("C");

        activated = GridTestUtils.waitForCondition(new GridAbsPredicate() {
            @Override public boolean apply() {
                return grid("A").active();
            }
        }, 10_000);

        assertTrue(activated);

        verifyBaselineTopologyOnNodes(verifier, new Ignite[] {nodeA, nodeC});
    }

    /**
     *
     */
    public void testAddNodeToBaselineTopology() throws Exception {
        final long expectedActivationHash = (long)"A".hashCode() + "B".hashCode() + "C".hashCode() + "D".hashCode();

        BaselineTopologyVerifier verifier = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNotNull(blt);

                assertEquals(4, blt.consistentIds().size());

                long activationHash = U.field(blt, "branchingPntHash");

                assertEquals(expectedActivationHash, activationHash);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);

        IgniteEx nodeD = (IgniteEx) startGridWithConsistentId("D");

        nodeD.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        verifyBaselineTopologyOnNodes(verifier, new Ignite[]{nodeA, nodeB, nodeC, nodeD});
    }

    /**
     * Verifies that baseline topology is removed successfully through baseline changing API.
     */
    public void testRemoveBaselineTopology() throws Exception {
        BaselineTopologyVerifier verifier = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNull(blt);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");
        Ignite nodeC = startGridWithConsistentId("C");

        nodeA.active(true);

        nodeA.cluster().setBaselineTopology(null);

        verifyBaselineTopologyOnNodes(verifier, new Ignite[] {nodeA, nodeB, nodeC});
    }

    /** */
    private interface BaselineTopologyVerifier {
        /** */
        public void verify(BaselineTopology blt);
    }

    /** */
    private interface BaselineTopologyHistoryVerifier {
        /** */
        public void verify(BaselineTopologyHistory bltHist);
    }

    /** */
    private void verifyBaselineTopologyOnNodes(BaselineTopologyVerifier bltVerifier, Ignite[] igs) {
        for (Ignite ig : igs) {
            BaselineTopology blt = getBaselineTopology(ig);

            bltVerifier.verify(blt);
        }
    }

    /** */
    private void verifyBaselineTopologyHistoryOnNodes(BaselineTopologyHistoryVerifier bltHistVerifier, Ignite[] igs) {
        for (Ignite ig : igs) {
            BaselineTopologyHistory blt = getBaselineTopologyHistory(ig);

            bltHistVerifier.verify(blt);
        }
    }

    /** */
    private Ignite startGridWithConsistentId(String consId) throws Exception {
        this.consId = consId;

        return startGrid(consId);
    }

    /**
     * Verifies that when new node joins already active cluster and new activation request is issued,
     * no changes to BaselineTopology branching history happen.
     */
    public void testActivationHashIsNotUpdatedOnMultipleActivationRequests() throws Exception {
        final long expectedActivationHash = (long)"A".hashCode();

        BaselineTopologyVerifier verifier = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                long activationHash = U.field(blt, "branchingPntHash");

                assertEquals(expectedActivationHash, activationHash);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");

        nodeA.active(true);

        Ignite nodeB = startGridWithConsistentId("B");

        nodeA.active(true);

        verifyBaselineTopologyOnNodes(verifier, new Ignite[] {nodeA, nodeB});
    }

    /**
     * Verifies that grid is autoactivated when full BaselineTopology is preset even on one node
     * and then all other nodes from BaselineTopology are started.
     */
    public void testAutoActivationWithBaselineTopologyPreset() throws Exception {
        Ignite ig = startGridWithConsistentId("A");

        ig.active(true);

        ig.cluster().setBaselineTopology(Arrays.asList(new BaselineNode[] {
            createBaselineNodeWithConsId("A"), createBaselineNodeWithConsId("B"), createBaselineNodeWithConsId("C")}));

        stopAllGrids();

        final Ignite ig1 = startGridWithConsistentId("A");

        startGridWithConsistentId("B");

        startGridWithConsistentId("C");

        boolean activated = GridTestUtils.waitForCondition(
            new GridAbsPredicate() {
               @Override public boolean apply() {
                   return ig1.active();
               }
            },
            10_000
        );

        assertTrue(activated);
    }

    /**
     * Creates BaselineNode with specific attribute indicating that this node is not client.
     */
    private BaselineNode createBaselineNodeWithConsId(String consId) {
        Map<String, Object> attrs = new HashMap<>();

        attrs.put("org.apache.ignite.cache.client", false);

        return new DetachedClusterNode(consId, attrs);
    }

    /** */
    public void testAutoActivationSimple() throws Exception {
        startGrids(3);

        IgniteEx srv = grid(0);

        srv.active(true);

        createAndFillCache(srv);

        // TODO: final implementation should work with cancel == true.
        stopAllGrids();

        //note: no call for activation after grid restart
        startGrids(3);

        final Ignite ig = grid(0);

        boolean clusterActive = GridTestUtils.waitForCondition(
            new GridAbsPredicate() {
                @Override public boolean apply() {
                    return ig.active();
                }
            },
            10_000);

        assertTrue(clusterActive);

        checkDataInCache((IgniteEx) ig);
    }

    /**
     *
     */
    public void testNoAutoActivationOnJoinNewNodeToInactiveCluster() throws Exception {
        startGrids(2);

        IgniteEx srv = grid(0);

        srv.active(true);

        awaitPartitionMapExchange();

        assertTrue(srv.active());

        srv.active(false);

        assertFalse(srv.active());

        startGrid(2);

        Thread.sleep(3_000);

        assertFalse(srv.active());
    }

    /**
     * Verifies that neither BaselineTopology nor BaselineTopologyHistory are changed when cluster is deactivated.
     */
    public void testBaselineTopologyRemainsTheSameOnClusterDeactivation() throws Exception {
        startGrids(2);

        IgniteEx srv = grid(0);

        srv.active(true);

        awaitPartitionMapExchange();

        assertTrue(srv.active());

        srv.active(false);

        BaselineTopology blt = getBaselineTopology(srv);

        BaselineTopologyHistory bltHist = getBaselineTopologyHistory(srv);

        assertEquals(0, blt.id());

        assertEquals(2, blt.consistentIds().size());

        assertEquals(1, blt.branchingHistory().size());

        assertEquals(0, bltHist.history().size());
    }

    /**
     *
     */
    public void testBaselineHistorySyncWithNewNode() throws Exception {
        final long expectedBranchingHash = "A".hashCode() + "B".hashCode() + "C".hashCode();

        BaselineTopologyHistoryVerifier verifier = new BaselineTopologyHistoryVerifier() {
            @Override public void verify(BaselineTopologyHistory bltHist) {
                assertNotNull(bltHist);

                assertEquals(1, bltHist.history().size());

                BaselineTopologyHistoryItem histItem = bltHist.history().get(0);

                assertEquals(1, histItem.branchingHistory().size());

                long actualBranchingHash = histItem.branchingHistory().get(0);

                assertEquals(expectedBranchingHash, actualBranchingHash);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C");

        nodeA.active(true);

        stopGrid("C", false);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        startGridWithConsistentId("D");

        stopAllGrids(false);

        Ignite nodeD = startGridWithConsistentId("D");

        verifyBaselineTopologyHistoryOnNodes(verifier, new Ignite[] {nodeD});
    }

    /**
     *
     */
    public void testBaselineHistorySyncWithOldNodeWithCompatibleHistory() throws Exception {
        final long expectedBranchingHash0 = "A".hashCode() + "B".hashCode() + "C".hashCode();

        final long expectedBranchingHash1 = "A".hashCode() + "B".hashCode();

        BaselineTopologyHistoryVerifier verifier = new BaselineTopologyHistoryVerifier() {
            @Override public void verify(BaselineTopologyHistory bltHist) {
                assertNotNull(bltHist);

                assertEquals(2, bltHist.history().size());

                BaselineTopologyHistoryItem histItem = bltHist.history().get(0);

                assertEquals(1, histItem.branchingHistory().size());

                long actualBranchingHash0 = histItem.branchingHistory().get(0);

                assertEquals(expectedBranchingHash0, actualBranchingHash0);

                histItem = bltHist.history().get(1);

                assertEquals(1, histItem.branchingHistory().size());

                long actualBranchingHash1 = histItem.branchingHistory().get(0);

                assertEquals(expectedBranchingHash1, actualBranchingHash1);
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C");

        nodeA.active(true);

        stopGrid("C", false);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        stopGrid("B", false);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        startGridWithConsistentId("B");

        stopAllGrids(false);

        startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");

        verifyBaselineTopologyHistoryOnNodes(verifier, new Ignite[] {nodeB});
    }

    /**
     * @throws Exception if failed.
     */
    public void testBaselineNotDeletedOnDeactivation() throws Exception {
        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C");

        nodeA.active(true);

        assertNotNull(nodeA.cluster().currentBaselineTopology());

        nodeA.active(false);

        stopAllGrids();

        nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C");

        final Ignite ig = nodeA;

        boolean clusterActive = GridTestUtils.waitForCondition(
            new GridAbsPredicate() {
                @Override public boolean apply() {
                    return ig.active();
                }
            },
            10_000);

        assertNotNull(nodeA.cluster().currentBaselineTopology());

        assertTrue(clusterActive);
    }

    /**
     *
     */
    public void testNodeWithBltIsProhibitedToJoinNewCluster() throws Exception {
        BaselineTopologyVerifier nullVerifier = new BaselineTopologyVerifier() {
            @Override public void verify(BaselineTopology blt) {
                assertNull(blt);
            }
        };

        Ignite nodeC = startGridWithConsistentId("C");

        nodeC.active(true);

        stopGrid("C", false);

        Ignite nodeA = startGridWithConsistentId("A");
        Ignite nodeB = startGridWithConsistentId("B");

        verifyBaselineTopologyOnNodes(nullVerifier, new Ignite[] {nodeA, nodeB});

        boolean expectedExceptionThrown = false;

        try {
            startGridWithConsistentId("C");
        }
        catch (IgniteCheckedException e) {
            expectedExceptionThrown = true;

            if (e.getCause() != null && e.getCause().getCause() != null) {
                Throwable rootCause = e.getCause().getCause();

                if (!(rootCause instanceof IgniteSpiException) || !rootCause.getMessage().contains("Node with set up BaselineTopology"))
                    Assert.fail("Unexpected ignite exception was thrown: " + e);
            }
            else
                throw e;
        }

        assertTrue("Expected exception wasn't thrown.", expectedExceptionThrown);
    }

    /**
     * Restore this test when requirements for BaselineTopology deletion are clarified and this feature
     * is covered with more tests.
     */
    public void _testBaselineTopologyHistoryIsDeletedOnBaselineDelete() throws Exception {
        BaselineTopologyHistoryVerifier verifier = new BaselineTopologyHistoryVerifier() {
            @Override public void verify(BaselineTopologyHistory bltHist) {
                assertNotNull(bltHist);

                assertEquals(0, bltHist.history().size());
            }
        };

        Ignite nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");
        startGridWithConsistentId("C");

        nodeA.active(true);

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");
        startGridWithConsistentId("B");

        nodeA.active(true);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");

        nodeA.active(true);

        nodeA.cluster().setBaselineTopology(baselineNodes(nodeA.cluster().forServers().nodes()));

        stopAllGrids(false);

        final Ignite node = startGridWithConsistentId("A");

        boolean activated = GridTestUtils.waitForCondition(new GridAbsPredicate() {
            @Override public boolean apply() {
                return node.active();
            }
        }, 10_000);

        assertTrue(activated);

        node.cluster().setBaselineTopology(null);

        verifyBaselineTopologyHistoryOnNodes(verifier, new Ignite[] {node});

        stopAllGrids(false);

        nodeA = startGridWithConsistentId("A");

        verifyBaselineTopologyHistoryOnNodes(verifier, new Ignite[] {nodeA});
    }

    /**
     * Retrieves baseline topology from ignite node instance.
     *
     * @param ig Ig.
     */
    private BaselineTopology getBaselineTopology(Ignite ig) {
        return ((DiscoveryDataClusterState) U.field(
            (Object) U.field(
                (Object) U.field(ig, "ctx"),
                "stateProc"),
            "globalState"))
            .baselineTopology();
    }

    /** */
    private BaselineTopologyHistory getBaselineTopologyHistory(Ignite ig) {
        return U.field(
            (Object) U.field(
                (Object) U.field(ig, "ctx"),
                "stateProc"),
            "bltHist");
    }

    /** */
    private void checkDataInCache(IgniteEx srv) {
        IgniteCache<Object, Object> cache = srv.cache(DEFAULT_CACHE_NAME);

        for (int i = 0; i < ENTRIES_COUNT; i++) {
            TestValue testVal = (TestValue) cache.get(i);

            assertNotNull(testVal);

            assertEquals(i, testVal.id);
        }
    }

    /** */
    private void createAndFillCache(Ignite srv) {
        IgniteCache cache = srv.getOrCreateCache(cacheConfiguration());

        for (int i = 0; i < ENTRIES_COUNT; i++)
            cache.put(i, new TestValue(i, "str" + i));
    }

    /** */
    private CacheConfiguration cacheConfiguration() {
        return new CacheConfiguration()
            .setName(DEFAULT_CACHE_NAME)
            .setCacheMode(CacheMode.PARTITIONED)
            .setAtomicityMode(CacheAtomicityMode.ATOMIC)
            .setBackups(2)
            .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
    }

    /** */
    private static final class TestValue {
        /** */
        private final int id;

        /** */
        private final String strId;

        /** */
        private TestValue(int id, String strId) {
            this.id = id;
            this.strId = strId;
        }
    }

    /** */
    private Collection<BaselineNode> baselineNodes(Collection<ClusterNode> clNodes) {
        Collection<BaselineNode> res = new ArrayList<>(clNodes.size());

        for (ClusterNode clN : clNodes)
            res.add(clN);

        return res;
    }

}
