You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2020/12/24 13:38:48 UTC

[ignite] branch master updated: IGNITE-13900: Fix C++ Affinity tests (#8605)

This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new a0350f7  IGNITE-13900: Fix C++ Affinity tests (#8605)
a0350f7 is described below

commit a0350f7b15360ed6bbe54895d8ca4e3987864cf0
Author: Igor Sapego <is...@apache.org>
AuthorDate: Thu Dec 24 16:37:14 2020 +0300

    IGNITE-13900: Fix C++ Affinity tests (#8605)
---
 .../cpp/core-test/config/affinity-test-32.xml      |  52 +++
 .../cpp/core-test/config/affinity-test-default.xml |  79 +++++
 .../cpp/core-test/config/affinity-test.xml         |  34 ++
 .../cpp/core-test/config/cache-test-default.xml    |   4 -
 .../cpp/core-test/include/ignite/test_utils.h      |  30 ++
 .../platforms/cpp/core-test/src/affinity_test.cpp  | 123 +++++--
 .../platforms/cpp/core-test/src/compute_test.cpp   | 363 +++++++++++++--------
 modules/platforms/cpp/core-test/src/test_utils.cpp |  36 +-
 modules/platforms/cpp/odbc-test/src/test_utils.cpp |  34 +-
 .../cpp/thin-client-test/src/test_utils.cpp        |  34 +-
 10 files changed, 623 insertions(+), 166 deletions(-)

diff --git a/modules/platforms/cpp/core-test/config/affinity-test-32.xml b/modules/platforms/cpp/core-test/config/affinity-test-32.xml
new file mode 100644
index 0000000..bd00294
--- /dev/null
+++ b/modules/platforms/cpp/core-test/config/affinity-test-32.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    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.
+-->
+
+<!--
+    Ignite Spring configuration file to startup grid cache.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+    <import resource="affinity-test-default.xml"/>
+
+    <bean parent="grid.cfg">
+        <property name="memoryConfiguration">
+            <bean class="org.apache.ignite.configuration.MemoryConfiguration">
+                <property name="systemCacheInitialSize" value="#{10 * 1024 * 1024}"/>
+                <property name="systemCacheMaxSize" value="#{40 * 1024 * 1024}"/>
+                <property name="defaultMemoryPolicyName" value="dfltPlc"/>
+
+                <property name="memoryPolicies">
+                    <list>
+                        <bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
+                            <property name="name" value="dfltPlc"/>
+                            <property name="maxSize" value="#{100 * 1024 * 1024}"/>
+                            <property name="initialSize" value="#{10 * 1024 * 1024}"/>
+                        </bean>
+                    </list>
+                </property>
+            </bean>
+        </property>
+    </bean>
+</beans>
diff --git a/modules/platforms/cpp/core-test/config/affinity-test-default.xml b/modules/platforms/cpp/core-test/config/affinity-test-default.xml
new file mode 100644
index 0000000..8b3861c
--- /dev/null
+++ b/modules/platforms/cpp/core-test/config/affinity-test-default.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    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.
+-->
+
+<!--
+    Ignite Spring configuration file to startup grid cache.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+    <bean id="cache-template" abstract="true" class="org.apache.ignite.configuration.CacheConfiguration">
+        <property name="cacheMode" value="PARTITIONED"/>
+        <property name="atomicityMode" value="TRANSACTIONAL"/>
+        <property name="onheapCacheEnabled" value="true"/>
+        <property name="rebalanceMode" value="SYNC"/>
+        <property name="writeSynchronizationMode" value="FULL_SYNC"/>
+        <property name="backups" value="0"/>
+        <property name="eagerTtl" value="true"/>
+    </bean>
+
+    <bean abstract="true" id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+        <property name="localHost" value="127.0.0.1"/>
+        <property name="connectorConfiguration"><null/></property>
+
+        <property name="includeEventTypes">
+            <util:constant static-field="org.apache.ignite.events.EventType.EVTS_CACHE"/>
+        </property>
+
+        <property name="cacheConfiguration">
+            <list>
+                <bean parent="cache-template">
+                    <property name="name" value="test_backups_0"/>
+                    <property name="backups" value="0"/>
+                </bean>
+
+                <bean parent="cache-template">
+                    <property name="name" value="test_backups_1"/>
+                    <property name="backups" value="1"/>
+                </bean>
+            </list>
+        </property>
+
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <!-- In distributed environment, replace with actual host IP address. -->
+                                <value>127.0.0.1:47500..47501</value>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+                <property name="socketTimeout" value="300" />
+            </bean>
+        </property>
+    </bean>
+</beans>
diff --git a/modules/platforms/cpp/core-test/config/affinity-test.xml b/modules/platforms/cpp/core-test/config/affinity-test.xml
new file mode 100644
index 0000000..d1eef54
--- /dev/null
+++ b/modules/platforms/cpp/core-test/config/affinity-test.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    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.
+-->
+
+<!--
+    Ignite Spring configuration file to startup grid cache.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+    <import resource="affinity-test-default.xml"/>
+
+    <bean parent="grid.cfg"/>
+</beans>
diff --git a/modules/platforms/cpp/core-test/config/cache-test-default.xml b/modules/platforms/cpp/core-test/config/cache-test-default.xml
index 13f0c48..b8a9fbd 100644
--- a/modules/platforms/cpp/core-test/config/cache-test-default.xml
+++ b/modules/platforms/cpp/core-test/config/cache-test-default.xml
@@ -105,10 +105,6 @@
                     <property name="cacheMode" value="REPLICATED"/>
                     <property name="atomicityMode" value="ATOMIC"/>
                 </bean>
-
-                <bean class="org.apache.ignite.configuration.CacheConfiguration">
-                    <property name="name" value="cache1"/>
-                </bean>
             </list>
         </property>
 
diff --git a/modules/platforms/cpp/core-test/include/ignite/test_utils.h b/modules/platforms/cpp/core-test/include/ignite/test_utils.h
index c29e385..2d6bec8 100644
--- a/modules/platforms/cpp/core-test/include/ignite/test_utils.h
+++ b/modules/platforms/cpp/core-test/include/ignite/test_utils.h
@@ -18,6 +18,9 @@
 #ifndef _IGNITE_CORE_TEST_TEST_UTILS
 #define _IGNITE_CORE_TEST_TEST_UTILS
 
+#include <boost/chrono.hpp>
+#include <boost/thread.hpp>
+
 #include "ignite/ignition.h"
 
 #define MUTE_TEST_FOR_TEAMCITY                                              \
@@ -132,6 +135,33 @@ namespace ignite_test
     {
         return ignite::IgniteError(TEST_ERROR, "Test error");
     }
+
+    /**
+     * Wait for condition.
+     * @tparam T Type of condition function.
+     * @param func Function that should check for condition and return true once it's performed.
+     * @param timeout Timeout to wait.
+     * @return True if condition was met, false if timeout has been reached.
+     */
+    template<typename F>
+    bool WaitForCondition(F func, int32_t timeout)
+    {
+        using namespace boost::chrono;
+
+        const int32_t span = 200;
+
+        steady_clock::time_point begin = steady_clock::now();
+
+        while (!func())
+        {
+            boost::this_thread::sleep_for(milliseconds(span));
+
+            if (timeout && duration_cast<milliseconds>(steady_clock::now() - begin).count() >= timeout)
+                return func();
+        }
+
+        return true;
+    }
 }
 
 #endif // _IGNITE_CORE_TEST_TEST_UTILS
diff --git a/modules/platforms/cpp/core-test/src/affinity_test.cpp b/modules/platforms/cpp/core-test/src/affinity_test.cpp
index 533c070..8de7962 100644
--- a/modules/platforms/cpp/core-test/src/affinity_test.cpp
+++ b/modules/platforms/cpp/core-test/src/affinity_test.cpp
@@ -37,26 +37,26 @@ struct AffinityTestSuiteFixture
 {
     Ignite node;
 
-    Cache<int32_t, int32_t> cache;
+    Cache<int32_t, int32_t> cache_backups_0;
     CacheAffinity<int32_t> affinity;
 
-    Ignite MakeNode(const char* name)
+    static Ignite StartNode(const char* name)
     {
 #ifdef IGNITE_TESTS_32
-        const char* config = "cache-test-32.xml";
+        const char* config = "affinity-test-32.xml";
 #else
-        const char* config = "cache-test.xml";
+        const char* config = "affinity-test.xml";
 #endif
-        return StartNode(config, name);
+        return ::StartNode(config, name);
     }
 
     /*
      * Constructor.
      */
     AffinityTestSuiteFixture() :
-        node(MakeNode("AffinityNode1")),
-        cache(node.GetCache<int32_t, int32_t>("partitioned3")),
-        affinity(node.GetAffinity<int32_t>(cache.GetName()))
+        node(StartNode("AffinityNode1")),
+        cache_backups_0(node.GetCache<int32_t, int32_t>("test_backups_0")),
+        affinity(node.GetAffinity<int32_t>(cache_backups_0.GetName()))
     {
         // No-op.
     }
@@ -68,15 +68,43 @@ struct AffinityTestSuiteFixture
     {
         Ignition::StopAll(true);
     }
+
+    /**
+     * Check whether rebalance is complete for the cluster.
+     * @param node0 Ignite node.
+     * @param part Partition to check.
+     * @return true if complete.
+     */
+    bool IsRebalanceComplete(Ignite& node0, int32_t part)
+    {
+        return node0.GetAffinity<int32_t>(cache_backups_0.GetName()).MapKeyToNode(part).IsLocal();
+    }
+
+    /**
+     * Wait for rebalance.
+     * @param node0 Ignite node.
+     * @param part Partition to check.
+     * @param timeout Timeout to wait.
+     * @return True if condition was met, false if timeout has been reached.
+     */
+    bool WaitForRebalance(Ignite& node0, int32_t part = 1, int32_t timeout = 5000)
+    {
+        return WaitForCondition(
+                boost::bind(&AffinityTestSuiteFixture::IsRebalanceComplete, this, node0, part),
+                timeout);
+    }
 };
 
 BOOST_FIXTURE_TEST_SUITE(AffinityTestSuite, AffinityTestSuiteFixture)
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityGetPartition)
 {
-    Ignite node0 = MakeNode("AffinityNode2");
-    Cache<int32_t, int32_t> cache0 = node.GetCache<int32_t, int32_t>("partitioned2");
-    CacheAffinity<int32_t> affinity0 = node.GetAffinity<int32_t>(cache.GetName());
+    Ignite node0 = StartNode("AffinityNode2");
+
+    Cache<int32_t, int32_t> cache0 = node.GetCache<int32_t, int32_t>("test_backups_0");
+    CacheAffinity<int32_t> affinity0 = node.GetAffinity<int32_t>(cache_backups_0.GetName());
+
+    BOOST_REQUIRE(WaitForRebalance(node0));
 
     BOOST_CHECK_EQUAL(affinity.GetPartition(0), affinity0.GetPartition(0));
     BOOST_CHECK_EQUAL(affinity.GetPartition(1), affinity0.GetPartition(1));
@@ -87,27 +115,47 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityGetDifferentPartitions)
 {
     std::vector<ClusterNode> nodes = node.GetCluster().AsClusterGroup().GetNodes();
 
+    BOOST_REQUIRE(WaitForRebalance(node));
+
     BOOST_CHECK_EQUAL(affinity.GetBackupPartitions(nodes.front()).size(), 0);
     BOOST_CHECK_EQUAL(affinity.GetPrimaryPartitions(nodes.front()).size(),
-        affinity.GetAllPartitions(nodes.front()).size());
+                      affinity.GetAllPartitions(nodes.front()).size());
+
+    Ignite node0 = StartNode("AffinityNode2");
 
-    Ignite node0 = MakeNode("AffinityNode2");
-    Cache<int32_t, int32_t> cache0 = node0.GetCache<int32_t, int32_t>("partitioned2");
-    CacheAffinity<int32_t> affinity0 = node0.GetAffinity<int32_t>(cache.GetName());
+    Cache<int32_t, int32_t> cache0 = node0.GetCache<int32_t, int32_t>("test_backups_0");
+    CacheAffinity<int32_t> affinity0 = node0.GetAffinity<int32_t>(cache_backups_0.GetName());
+
+    BOOST_REQUIRE(WaitForRebalance(node0));
 
     BOOST_CHECK_EQUAL(affinity0.GetBackupPartitions(nodes.front()).size(), 0);
     BOOST_CHECK_EQUAL(affinity0.GetPrimaryPartitions(nodes.front()).size(),
-        affinity0.GetAllPartitions(nodes.front()).size());
+                      affinity0.GetAllPartitions(nodes.front()).size());
+
+    Cache<int32_t, int32_t> cache_backups_1 = node0.GetCache<int32_t, int32_t>("test_backups_1");
+    CacheAffinity<int32_t> affinity1 = node0.GetAffinity<int32_t>(cache_backups_1.GetName());
+
+    BOOST_REQUIRE(WaitForRebalance(node0));
+
+    BOOST_CHECK_NE(affinity1.GetBackupPartitions(nodes.front()).size(), 0);
+    BOOST_CHECK_EQUAL(affinity1.GetPrimaryPartitions(nodes.front()).size() +
+        affinity1.GetBackupPartitions(nodes.front()).size(),
+        affinity1.GetAllPartitions(nodes.front()).size());
 }
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityGetAffinityKey)
 {
+    BOOST_REQUIRE(WaitForRebalance(node));
+
     BOOST_CHECK_EQUAL((affinity.GetAffinityKey<int>(10)), 10);
     BOOST_CHECK_EQUAL((affinity.GetAffinityKey<int>(20)), 20);
 
-    Ignite node0 = MakeNode("AffinityNode2");
-    Cache<int32_t, int32_t> cache0 = node.GetCache<int32_t, int32_t>("partitioned2");
-    CacheAffinity<int32_t> affinity0 = node.GetAffinity<int32_t>(cache.GetName());
+    Ignite node0 = StartNode("AffinityNode2");
+
+    Cache<int32_t, int32_t> cache_backups_1 = node.GetCache<int32_t, int32_t>("test_backups_1");
+    CacheAffinity<int32_t> affinity0 = node.GetAffinity<int32_t>(cache_backups_1.GetName());
+
+    BOOST_REQUIRE(WaitForRebalance(node0));
 
     BOOST_CHECK_EQUAL((affinity0.GetAffinityKey<int>(10)), 10);
     BOOST_CHECK_EQUAL((affinity0.GetAffinityKey<int>(20)), 20);
@@ -115,6 +163,8 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityGetAffinityKey)
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityMapKeysToNodes)
 {
+    BOOST_REQUIRE(WaitForRebalance(node));
+
     std::vector<int32_t> keys;
 
 	keys.reserve(10000);
@@ -140,6 +190,8 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityMapKeysToNodes)
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityMapKeyToPrimaryAndBackups)
 {
+    BOOST_REQUIRE(WaitForRebalance(node));
+
     const int32_t key = 1;
 
     std::vector<ClusterNode> nodes = affinity.MapKeyToPrimaryAndBackups(key);
@@ -153,4 +205,37 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityMapKeyToPrimaryAndBackups)
     BOOST_REQUIRE(nodes.front().GetId() == partNodes.front().GetId());
 }
 
+BOOST_AUTO_TEST_CASE(IgniteAffinityMapPartitionsToNodes)
+{
+    Ignite node0 = StartNode("AffinityNode2");
+    Ignite node1 = StartNode("AffinityNode3");
+    Ignite node2 = StartNode("AffinityNode4");
+
+    BOOST_REQUIRE(WaitForRebalance(node2, 0));
+
+    std::vector<ClusterNode> nodes = node.GetCluster().AsClusterGroup().GetNodes();
+
+    BOOST_REQUIRE(nodes.size() == 4);
+
+    std::vector<int32_t> primary = affinity.GetPrimaryPartitions(nodes[0]);
+    std::vector<int32_t> primary0 = affinity.GetPrimaryPartitions(nodes[1]);
+
+    std::sort(primary.begin(), primary.end());
+    std::sort(primary0.begin(), primary0.end());
+
+    BOOST_REQUIRE(primary != primary0);
+
+    primary.insert(primary.end(), primary0.begin(), primary0.end());
+    std::map<int32_t, ClusterNode> map = affinity.MapPartitionsToNodes(primary);
+
+    for (std::map<int32_t, ClusterNode>::const_iterator it = map.begin(); it != map.end(); ++it)
+    {
+        std::vector<cluster::ClusterNode> nodes = affinity.MapPartitionToPrimaryAndBackups(it->first);
+
+        Guid nodeId = it->second.GetId();
+        BOOST_REQUIRE_EQUAL(nodes.front().GetId(), nodeId);
+        BOOST_REQUIRE(nodeId == nodes[0].GetId() || nodeId == nodes[1].GetId());
+    }
+}
+
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/modules/platforms/cpp/core-test/src/compute_test.cpp b/modules/platforms/cpp/core-test/src/compute_test.cpp
index 6d8d320..7e7fa20 100644
--- a/modules/platforms/cpp/core-test/src/compute_test.cpp
+++ b/modules/platforms/cpp/core-test/src/compute_test.cpp
@@ -39,6 +39,8 @@ enum { RETRIES_FOR_STABLE_TOPOLOGY = 5};
  */
 struct ComputeTestSuiteFixtureAffinity
 {
+    static const char* cacheName;
+
     Ignite node0;
     Ignite node1;
     Ignite node2;
@@ -48,7 +50,7 @@ struct ComputeTestSuiteFixtureAffinity
 #ifdef IGNITE_TESTS_32
         const char* config = "cache-test-32.xml";
 #else
-        const char* config = "cache-test.xml";
+        const char* config = "affinity-test.xml";
 #endif
         return StartNode(config, name);
     }
@@ -56,12 +58,19 @@ struct ComputeTestSuiteFixtureAffinity
     /*
      * Constructor.
      */
-    ComputeTestSuiteFixtureAffinity() :
-        node0(MakeNode("ComputeAffinityNode1")),
-        node1(MakeNode("ComputeAffinityNode2")),
-        node2(MakeNode("ComputeAffinityNode3"))
+    ComputeTestSuiteFixtureAffinity()
     {
-        // No-op.
+        node0 = MakeNode("ComputeAffinityNode0");
+
+        BOOST_REQUIRE(WaitForRebalance0());
+
+        node1 = MakeNode("ComputeAffinityNode1");
+
+        BOOST_REQUIRE(WaitForRebalance1());
+
+        node2 = MakeNode("ComputeAffinityNode2");
+
+        BOOST_REQUIRE(WaitForRebalance2());
     }
 
     /*
@@ -69,10 +78,78 @@ struct ComputeTestSuiteFixtureAffinity
      */
     ~ComputeTestSuiteFixtureAffinity()
     {
-        Ignition::StopAll(true);
+        Ignition::StopAll(false);
+    }
+
+    /**
+     * Check whether rebalance is complete for the cluster.
+     * @return true if complete.
+     */
+    bool IsRebalanceComplete0()
+    {
+        return
+            node0.GetAffinity<int32_t>(cacheName).MapKeyToNode(0).IsLocal() &&
+            node0.GetAffinity<int32_t>(cacheName).MapKeyToNode(1).IsLocal() &&
+            node0.GetAffinity<int32_t>(cacheName).MapKeyToNode(6).IsLocal();
+    }
+
+    /**
+     * Check whether rebalance is complete for the cluster.
+     * @return true if complete.
+     */
+    bool IsRebalanceComplete1()
+    {
+        return
+            node0.GetAffinity<int32_t>(cacheName).MapKeyToNode(0).IsLocal() &&
+            node1.GetAffinity<int32_t>(cacheName).MapKeyToNode(1).IsLocal() &&
+            node1.GetAffinity<int32_t>(cacheName).MapKeyToNode(6).IsLocal();
+    }
+
+    /**
+     * Check whether rebalance is complete for the cluster.
+     * @return true if complete.
+     */
+    bool IsRebalanceComplete2()
+    {
+        return
+            node0.GetAffinity<int32_t>(cacheName).MapKeyToNode(0).IsLocal() &&
+            node1.GetAffinity<int32_t>(cacheName).MapKeyToNode(1).IsLocal() &&
+            node2.GetAffinity<int32_t>(cacheName).MapKeyToNode(6).IsLocal();
+    }
+
+    /**
+     * Wait for rebalance.
+     * @param timeout Timeout to wait.
+     * @return True if condition was met, false if timeout has been reached.
+     */
+    bool WaitForRebalance0(int32_t timeout = 5000)
+    {
+        return WaitForCondition(boost::bind(&ComputeTestSuiteFixtureAffinity::IsRebalanceComplete0, this), timeout);
+    }
+
+    /**
+     * Wait for rebalance.
+     * @param timeout Timeout to wait.
+     * @return True if condition was met, false if timeout has been reached.
+     */
+    bool WaitForRebalance1(int32_t timeout = 5000)
+    {
+        return WaitForCondition(boost::bind(&ComputeTestSuiteFixtureAffinity::IsRebalanceComplete1, this), timeout);
+    }
+
+    /**
+     * Wait for rebalance.
+     * @param timeout Timeout to wait.
+     * @return True if condition was met, false if timeout has been reached.
+     */
+    bool WaitForRebalance2(int32_t timeout = 5000)
+    {
+        return WaitForCondition(boost::bind(&ComputeTestSuiteFixtureAffinity::IsRebalanceComplete2, this), timeout);
     }
 };
 
+const char* ComputeTestSuiteFixtureAffinity::cacheName = "test_backups_0";
+
 /*
  * Test setup fixture.
  */
@@ -297,19 +374,19 @@ void EmptyDeleter(IgniteEnvironment*)
 struct FuncAffinityCall : ComputeFunc<int32_t>
 {
     FuncAffinityCall() :
-        nodeName(), cacheName(), cacheKey(), err()
+        cacheName(), cacheKey(), err()
     {
         // No-op.
     }
 
-    FuncAffinityCall(std::string nodeName, std::string cacheName, int32_t cacheKey) :
-        nodeName(nodeName), cacheName(cacheName), cacheKey(cacheKey), err()
+    FuncAffinityCall(std::string cacheName, int32_t cacheKey) :
+        cacheName(cacheName), cacheKey(cacheKey), err()
     {
         // No-op.
     }
 
     FuncAffinityCall(IgniteError err) :
-        nodeName(), cacheName(), cacheKey(), err(err)
+        cacheName(), cacheKey(), err(err)
     {
         // No-op.
     }
@@ -317,12 +394,12 @@ struct FuncAffinityCall : ComputeFunc<int32_t>
     virtual int32_t Call()
     {
         Ignite& node = GetIgnite();
+
         Cache<int32_t, int32_t> cache = node.GetCache<int32_t, int32_t>(cacheName.c_str());
 
-        return cache.LocalPeek(cacheKey, CachePeekMode::ALL);
+        return cache.LocalPeek(cacheKey, CachePeekMode::PRIMARY);
     }
 
-    std::string nodeName;
     std::string cacheName;
     int32_t cacheKey;
     IgniteError err;
@@ -331,19 +408,19 @@ struct FuncAffinityCall : ComputeFunc<int32_t>
 struct FuncAffinityRun : ComputeFunc<void>
 {
     FuncAffinityRun() :
-        nodeName(), cacheName(), cacheKey(), err()
+        cacheName(), cacheKey(), err()
     {
         // No-op.
     }
 
-    FuncAffinityRun(std::string nodeName, std::string cacheName, int32_t cacheKey, int32_t checkKey) :
-        nodeName(nodeName), cacheName(cacheName), cacheKey(cacheKey), checkKey(checkKey), err()
+    FuncAffinityRun(std::string cacheName, int32_t cacheKey, int32_t checkKey) :
+        cacheName(cacheName), cacheKey(cacheKey), checkKey(checkKey), err()
     {
         // No-op.
     }
 
     FuncAffinityRun(IgniteError err) :
-        nodeName(), cacheName(), cacheKey(), err(err)
+        cacheName(), cacheKey(), err(err)
     {
         // No-op.
     }
@@ -353,12 +430,11 @@ struct FuncAffinityRun : ComputeFunc<void>
         Ignite& node = GetIgnite();
         Cache<int32_t, int32_t> cache = node.GetCache<int32_t, int32_t>(cacheName.c_str());
 
-        int32_t res = cache.LocalPeek(cacheKey, CachePeekMode::ALL);
+        int32_t res = cache.LocalPeek(cacheKey, CachePeekMode::PRIMARY);
         cache.Put(checkKey, res);
         res = cache.Get(checkKey);
     }
 
-    std::string nodeName;
     std::string cacheName;
     int32_t cacheKey;
     int32_t checkKey;
@@ -448,7 +524,6 @@ namespace ignite
 
             static void Write(BinaryWriter& writer, const FuncAffinityCall& obj)
             {
-                writer.WriteString("nodeName", obj.nodeName);
                 writer.WriteString("cacheName", obj.cacheName);
                 writer.WriteInt32("cacheKey", obj.cacheKey);
                 writer.WriteObject<IgniteError>("err", obj.err);
@@ -456,7 +531,6 @@ namespace ignite
 
             static void Read(BinaryReader& reader, FuncAffinityCall& dst)
             {
-                dst.nodeName = reader.ReadString("nodeName");
                 dst.cacheName = reader.ReadString("cacheName");
                 dst.cacheKey = reader.ReadInt32("cacheKey");
                 dst.err = reader.ReadObject<IgniteError>("err");
@@ -473,7 +547,6 @@ namespace ignite
 
             static void Write(BinaryWriter& writer, const FuncAffinityRun& obj)
             {
-                writer.WriteString("nodeName", obj.nodeName);
                 writer.WriteString("cacheName", obj.cacheName);
                 writer.WriteInt32("cacheKey", obj.cacheKey);
                 writer.WriteInt32("checkKey", obj.checkKey);
@@ -482,7 +555,6 @@ namespace ignite
 
             static void Read(BinaryReader& reader, FuncAffinityRun& dst)
             {
-                dst.nodeName = reader.ReadString("nodeName");
                 dst.cacheName = reader.ReadString("cacheName");
                 dst.cacheKey = reader.ReadInt32("cacheKey");
                 dst.checkKey = reader.ReadInt32("checkKey");
@@ -504,19 +576,22 @@ IGNITE_EXPORTED_CALL void IgniteModuleInit1(IgniteBindingContext& context)
 }
 
 template<typename TK>
-std::vector<int32_t> GetPrimaryKeys(int32_t num, ClusterNode& node, CacheAffinity<TK>& affinity)
+std::vector<int32_t> GetPrimaryKeys(size_t num, ClusterNode& node, CacheAffinity<TK>& affinity)
 {
     std::vector<int32_t> ret;
-    int32_t count = 0;
+
+    if (!num)
+        return ret;
 
     for (int32_t i = 0; i < INT_MAX; i++)
-        if (affinity.IsPrimary(node, i))
-        {
-            if (count++ < num)
-                ret.push_back(i);
-            else
+    {
+        if (affinity.IsPrimary(node, i)) {
+            ret.push_back(i);
+
+            if (ret.size() >= num)
                 return ret;
         }
+    }
 
     BOOST_CHECK(false);
 
@@ -530,39 +605,44 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityCall)
     const int32_t key = 100;
     const int32_t value = 500;
 
-    WITH_RETRY_BEGIN
-    {
-        std::vector<ClusterNode> nodes = node0.GetCluster().AsClusterGroup().GetNodes();
-        Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>("cache1");
+    Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>(cacheName);
 
-        cache.Put(key, value);
+    cache.Put(key, value);
 
-        CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
-        Compute compute = node0.GetCompute();
+    CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
+    Compute compute = node0.GetCompute();
 
-        BOOST_TEST_CHECKPOINT("Starting local calls loop");
+    ClusterNode clusterNode0 = affinity.MapKeyToNode(100);
 
-        std::vector<int32_t> aKeys = GetPrimaryKeys(10, nodes.front(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            int32_t res = compute.AffinityCall<int32_t>(cache.GetName(), aKeys[i],
-                FuncAffinityCall(node0.GetName(), cache.GetName(), key));
+    BOOST_TEST_CHECKPOINT("Starting local calls loop");
 
-            CHECK_EQUAL_OR_RETRY(res, value);
-        }
+    std::vector<int32_t> aKeys = GetPrimaryKeys(10, clusterNode0, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        int32_t res = compute.AffinityCall<int32_t>(cache.GetName(), aKeys[i],
+            FuncAffinityCall(cache.GetName(), key));
 
-        BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+        BOOST_CHECK_EQUAL(res, value);
+    }
 
-        aKeys = GetPrimaryKeys(10, nodes.back(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            int32_t res = compute.AffinityCall<int32_t>(cache.GetName(), aKeys[i],
-                FuncAffinityCall(node0.GetName(), cache.GetName(), key));
+    ClusterNode clusterNode1 = node0.GetCluster().GetLocalNode();
 
-            CHECK_EQUAL_OR_RETRY(res, 0);
-        }
+    if (clusterNode0.GetId() == clusterNode1.GetId())
+        clusterNode1 = node1.GetCluster().GetLocalNode();
+
+    BOOST_REQUIRE_NE(clusterNode0.GetId(), clusterNode1.GetId());
+    BOOST_REQUIRE(!affinity.IsPrimary(clusterNode1, key));
+
+    BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+
+    aKeys = GetPrimaryKeys(10, clusterNode1, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        int32_t res = compute.AffinityCall<int32_t>(cache.GetName(), aKeys[i],
+            FuncAffinityCall(cache.GetName(), key));
+
+        BOOST_CHECK_EQUAL(res, 0);
     }
-    WITH_RETRY_END
 }
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityCallAsync)
@@ -570,41 +650,46 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityCallAsync)
     const int32_t key = 100;
     const int32_t value = 500;
 
-    WITH_RETRY_BEGIN
-    {
-        std::vector<ClusterNode> nodes = node0.GetCluster().AsClusterGroup().GetNodes();
-        Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>("cache1");
+    Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>(cacheName);
 
-        cache.Put(key, value);
+    cache.Put(key, value);
 
-        CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
-        Compute compute = node0.GetCompute();
+    CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
+    Compute compute = node0.GetCompute();
 
-        BOOST_TEST_CHECKPOINT("Starting calls loop");
+    ClusterNode clusterNode0 = affinity.MapKeyToNode(100);
 
-        std::vector<int32_t> aKeys = GetPrimaryKeys(10, nodes.front(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            Future<int32_t> res = compute.AffinityCallAsync<int32_t>(cache.GetName(), aKeys[i],
-                FuncAffinityCall(node0.GetName(), cache.GetName(), key));
+    BOOST_TEST_CHECKPOINT("Starting calls loop");
 
-            int32_t resVal = res.GetValue();
-            CHECK_EQUAL_OR_RETRY(value, resVal);
-        }
+    std::vector<int32_t> aKeys = GetPrimaryKeys(10, clusterNode0, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        Future<int32_t> res = compute.AffinityCallAsync<int32_t>(cache.GetName(), aKeys[i],
+            FuncAffinityCall(cache.GetName(), key));
 
-        BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+        int32_t resVal = res.GetValue();
+        BOOST_CHECK_EQUAL(value, resVal);
+    }
 
-        aKeys = GetPrimaryKeys(10, nodes.back(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            Future<int32_t> res = compute.AffinityCallAsync<int32_t>(cache.GetName(), aKeys[i],
-                FuncAffinityCall(node0.GetName(), cache.GetName(), key));
+    ClusterNode clusterNode1 = node0.GetCluster().GetLocalNode();
 
-            int32_t resVal = res.GetValue();
-            CHECK_EQUAL_OR_RETRY(0, resVal);
-        }
+    if (clusterNode0.GetId() == clusterNode1.GetId())
+        clusterNode1 = node1.GetCluster().GetLocalNode();
+
+    BOOST_REQUIRE_NE(clusterNode0.GetId(), clusterNode1.GetId());
+    BOOST_REQUIRE(!affinity.IsPrimary(clusterNode1, key));
+
+    BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+
+    aKeys = GetPrimaryKeys(10, clusterNode1, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        Future<int32_t> res = compute.AffinityCallAsync<int32_t>(cache.GetName(), aKeys[i],
+            FuncAffinityCall(cache.GetName(), key));
+
+        int32_t resVal = res.GetValue();
+        BOOST_CHECK_EQUAL(0, resVal);
     }
-    WITH_RETRY_END
 }
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityRun)
@@ -613,41 +698,46 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityRun)
     const int32_t checkKey = -1;
     const int32_t value = 500;
 
-    WITH_RETRY_BEGIN
-    {
-        std::vector<ClusterNode> nodes = node0.GetCluster().AsClusterGroup().GetNodes();
-        Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>("cache1");
+    Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>(cacheName);
 
-        cache.Put(key, value);
+    cache.Put(key, value);
 
-        CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
-        Compute compute = node0.GetCompute();
+    CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
+    Compute compute = node0.GetCompute();
 
-        BOOST_TEST_CHECKPOINT("Starting calls loop");
+    ClusterNode clusterNode0 = affinity.MapKeyToNode(100);
 
-        std::vector<int32_t> aKeys = GetPrimaryKeys(10, nodes.front(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            compute.AffinityRun(cache.GetName(), aKeys[i],
-                FuncAffinityRun(node0.GetName(), cache.GetName(), key, checkKey));
+    BOOST_TEST_CHECKPOINT("Starting calls loop");
 
-            int32_t resVal = cache.Get(checkKey);
-            CHECK_EQUAL_OR_RETRY(500, resVal);
-        }
+    std::vector<int32_t> aKeys = GetPrimaryKeys(10, clusterNode0, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        compute.AffinityRun(cache.GetName(), aKeys[i],
+            FuncAffinityRun(cache.GetName(), key, checkKey));
+
+        int32_t resVal = cache.Get(checkKey);
+        BOOST_CHECK_EQUAL(500, resVal);
+    }
 
-        BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+    ClusterNode clusterNode1 = node0.GetCluster().GetLocalNode();
 
-        aKeys = GetPrimaryKeys(10, nodes.back(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            compute.AffinityRun(cache.GetName(), aKeys[i],
-                FuncAffinityRun(node0.GetName(), cache.GetName(), key, checkKey));
+    if (clusterNode0.GetId() == clusterNode1.GetId())
+        clusterNode1 = node1.GetCluster().GetLocalNode();
 
-            int32_t resVal = cache.Get(checkKey);
-            CHECK_EQUAL_OR_RETRY(0, resVal);
-        }
+    BOOST_REQUIRE_NE(clusterNode0.GetId(), clusterNode1.GetId());
+    BOOST_REQUIRE(!affinity.IsPrimary(clusterNode1, key));
+
+    BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+
+    aKeys = GetPrimaryKeys(10, clusterNode1, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        compute.AffinityRun(cache.GetName(), aKeys[i],
+            FuncAffinityRun(cache.GetName(), key, checkKey));
+
+        int32_t resVal = cache.Get(checkKey);
+        BOOST_CHECK_EQUAL(0, resVal);
     }
-    WITH_RETRY_END
 }
 
 BOOST_AUTO_TEST_CASE(IgniteAffinityRunAsync)
@@ -656,45 +746,50 @@ BOOST_AUTO_TEST_CASE(IgniteAffinityRunAsync)
     const int32_t checkKey = -1;
     const int32_t value = 500;
 
-    WITH_RETRY_BEGIN
-    {
-        std::vector<ClusterNode> nodes = node0.GetCluster().AsClusterGroup().GetNodes();
-        Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>("cache1");
+    Cache<int32_t, int32_t> cache = node0.GetCache<int32_t, int32_t>(cacheName);
 
-        cache.Put(key, value);
+    cache.Put(key, value);
 
-        CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
-        Compute compute = node0.GetCompute();
+    CacheAffinity<int> affinity = node0.GetAffinity<int32_t>(cache.GetName());
+    Compute compute = node0.GetCompute();
 
-        BOOST_TEST_CHECKPOINT("Starting calls loop");
+    ClusterNode clusterNode0 = affinity.MapKeyToNode(100);
 
-        std::vector<int32_t> aKeys = GetPrimaryKeys(10, nodes.front(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            Future<void> res = compute.AffinityRunAsync(cache.GetName(), aKeys[i],
-                FuncAffinityRun(node0.GetName(), cache.GetName(), key, checkKey));
+    BOOST_TEST_CHECKPOINT("Starting calls loop");
 
-            res.GetValue();
+    std::vector<int32_t> aKeys = GetPrimaryKeys(10, clusterNode0, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        Future<void> res = compute.AffinityRunAsync(cache.GetName(), aKeys[i],
+            FuncAffinityRun(cache.GetName(), key, checkKey));
 
-            int32_t resVal = cache.Get(checkKey);
-            CHECK_EQUAL_OR_RETRY(500, resVal);
-        }
+        res.GetValue();
 
-        BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+        int32_t resVal = cache.Get(checkKey);
+        BOOST_CHECK_EQUAL(500, resVal);
+    }
 
-        aKeys = GetPrimaryKeys(10, nodes.back(), affinity);
-        for (size_t i = 0; i < aKeys.size(); i++)
-        {
-            Future<void> res = compute.AffinityRunAsync<int32_t>(cache.GetName(), aKeys[i],
-                FuncAffinityRun(node0.GetName(), cache.GetName(), key, checkKey));
+    ClusterNode clusterNode1 = node0.GetCluster().GetLocalNode();
 
-            res.GetValue();
+    if (clusterNode0.GetId() == clusterNode1.GetId())
+        clusterNode1 = node1.GetCluster().GetLocalNode();
 
-            int32_t resVal = cache.Get(checkKey);
-            CHECK_EQUAL_OR_RETRY(0, resVal);
-        }
+    BOOST_REQUIRE_NE(clusterNode0.GetId(), clusterNode1.GetId());
+    BOOST_REQUIRE(!affinity.IsPrimary(clusterNode1, key));
+
+    BOOST_TEST_CHECKPOINT("Starting remote calls loop");
+
+    aKeys = GetPrimaryKeys(10, clusterNode1, affinity);
+    for (size_t i = 0; i < aKeys.size(); i++)
+    {
+        Future<void> res = compute.AffinityRunAsync<int32_t>(cache.GetName(), aKeys[i],
+            FuncAffinityRun(cache.GetName(), key, checkKey));
+
+        res.GetValue();
+
+        int32_t resVal = cache.Get(checkKey);
+        BOOST_CHECK_EQUAL(0, resVal);
     }
-    WITH_RETRY_END
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/modules/platforms/cpp/core-test/src/test_utils.cpp b/modules/platforms/cpp/core-test/src/test_utils.cpp
index 227ab7b..31c0ef2 100644
--- a/modules/platforms/cpp/core-test/src/test_utils.cpp
+++ b/modules/platforms/cpp/core-test/src/test_utils.cpp
@@ -23,6 +23,32 @@
 
 namespace ignite_test
 {
+    std::string GetTestConfigDir()
+    {
+        using namespace ignite;
+
+        std::string cfgPath = common::GetEnv("IGNITE_NATIVE_TEST_CPP_CONFIG_PATH");
+
+        if (!cfgPath.empty())
+            return cfgPath;
+
+        std::string home = jni::ResolveIgniteHome();
+
+        if (home.empty())
+            return home;
+
+        std::stringstream path;
+
+        path << home << common::Fs
+             << "modules" << common::Fs
+             << "platforms" << common::Fs
+             << "cpp" << common::Fs
+             << "core-test" << common::Fs
+             << "config";
+
+        return path.str();
+    }
+
     void InitConfig(ignite::IgniteConfiguration& cfg, const char* cfgFile)
     {
         using namespace ignite;
@@ -51,12 +77,16 @@ namespace ignite_test
         cfg.jvmInitMem = 1024;
         cfg.jvmMaxMem = 4096;
 #endif
+        std::string cfgDir = GetTestConfigDir();
+
+        if (cfgDir.empty())
+            throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Failed to resolve test config directory");
 
-        char* cfgPath = getenv("IGNITE_NATIVE_TEST_CPP_CONFIG_PATH");
+        std::stringstream path;
 
-        assert(cfgPath != 0);
+        path << cfgDir << common::Fs << cfgFile;
 
-        cfg.springCfgPath = std::string(cfgPath).append("/").append(cfgFile);
+        cfg.springCfgPath = path.str();
     }
 
     ignite::Ignite StartNode(const char* cfgFile)
diff --git a/modules/platforms/cpp/odbc-test/src/test_utils.cpp b/modules/platforms/cpp/odbc-test/src/test_utils.cpp
index 1519e73..bdd32d8 100644
--- a/modules/platforms/cpp/odbc-test/src/test_utils.cpp
+++ b/modules/platforms/cpp/odbc-test/src/test_utils.cpp
@@ -75,7 +75,28 @@ namespace ignite_test
 
     std::string GetTestConfigDir()
     {
-        return ignite::common::GetEnv("IGNITE_NATIVE_TEST_ODBC_CONFIG_PATH");
+        using namespace ignite;
+
+        std::string cfgPath = common::GetEnv("IGNITE_NATIVE_TEST_ODBC_CONFIG_PATH");
+
+        if (!cfgPath.empty())
+            return cfgPath;
+
+        std::string home = jni::ResolveIgniteHome();
+
+        if (home.empty())
+            return home;
+
+        std::stringstream path;
+
+        path << home << common::Fs
+             << "modules" << common::Fs
+             << "platforms" << common::Fs
+             << "cpp" << common::Fs
+             << "odbc-test" << common::Fs
+             << "config";
+
+        return path.str();
     }
 
     void InitConfig(ignite::IgniteConfiguration& cfg, const char* cfgFile)
@@ -109,11 +130,16 @@ namespace ignite_test
         cfg.jvmMaxMem = 4096;
 #endif
 
-        char* cfgPath = getenv("IGNITE_NATIVE_TEST_ODBC_CONFIG_PATH");
+        std::string cfgDir = GetTestConfigDir();
+
+        if (cfgDir.empty())
+            throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Failed to resolve test config directory");
+
+        std::stringstream path;
 
-        assert(cfgPath != 0);
+        path << cfgDir << common::Fs << cfgFile;
 
-        cfg.springCfgPath = std::string(cfgPath).append("/").append(cfgFile);
+        cfg.springCfgPath = path.str();
     }
 
     ignite::Ignite StartNode(const char* cfgFile)
diff --git a/modules/platforms/cpp/thin-client-test/src/test_utils.cpp b/modules/platforms/cpp/thin-client-test/src/test_utils.cpp
index b6fef63..427490b 100644
--- a/modules/platforms/cpp/thin-client-test/src/test_utils.cpp
+++ b/modules/platforms/cpp/thin-client-test/src/test_utils.cpp
@@ -27,7 +27,28 @@ namespace ignite_test
 {
     std::string GetTestConfigDir()
     {
-        return ignite::common::GetEnv("IGNITE_NATIVE_TEST_CPP_THIN_CONFIG_PATH");
+        using namespace ignite;
+
+        std::string cfgPath = common::GetEnv("IGNITE_NATIVE_TEST_CPP_THIN_CONFIG_PATH");
+
+        if (!cfgPath.empty())
+            return cfgPath;
+
+        std::string home = jni::ResolveIgniteHome();
+
+        if (home.empty())
+            return home;
+
+        std::stringstream path;
+
+        path << home << common::Fs
+             << "modules" << common::Fs
+             << "platforms" << common::Fs
+             << "cpp" << common::Fs
+             << "thin-client-test" << common::Fs
+             << "config";
+
+        return path.str();
     }
 
     void InitConfig(ignite::IgniteConfiguration& cfg, const char* cfgFile)
@@ -61,7 +82,16 @@ namespace ignite_test
         cfg.jvmMaxMem = 4096;
 #endif
 
-        cfg.springCfgPath = GetTestConfigDir().append("/").append(cfgFile);
+        std::string cfgDir = GetTestConfigDir();
+
+        if (cfgDir.empty())
+            throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Failed to resolve test config directory");
+
+        std::stringstream path;
+
+        path << cfgDir << common::Fs << cfgFile;
+
+        cfg.springCfgPath = path.str();
     }
 
     ignite::Ignite StartServerNode(const char* cfgFile, const char* name)