http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/ssh/src/test/java/org/apache/ignite/internal/GridNodeStartUtilsSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/ssh/src/test/java/org/apache/ignite/internal/GridNodeStartUtilsSelfTest.java
 
b/modules/ssh/src/test/java/org/apache/ignite/internal/GridNodeStartUtilsSelfTest.java
deleted file mode 100644
index 992954d..0000000
--- 
a/modules/ssh/src/test/java/org/apache/ignite/internal/GridNodeStartUtilsSelfTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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;
-
-import org.apache.ignite.internal.util.nodestart.*;
-import org.apache.ignite.internal.util.typedef.internal.*;
-import org.apache.ignite.lang.*;
-import org.apache.ignite.testframework.junits.common.*;
-
-import java.io.*;
-import java.util.*;
-
-import static org.apache.ignite.internal.util.nodestart.GridNodeStartUtils.*;
-
-/**
- * Tests for {@link GridNodeStartUtils}.
- */
-public class GridNodeStartUtilsSelfTest extends GridCommonAbstractTest {
-    /**
-     * @throws Exception If failed.
-     */
-    public void testParseFile() throws Exception {
-        File file = 
U.resolveGridGainPath("modules/core/src/test/config/start-nodes.ini");
-
-        IgniteBiTuple<Collection<Map<String, Object>>, Map<String, Object>> t 
= parseFile(file);
-
-        assert t != null;
-
-        Collection<Map<String, Object>> hosts = t.get1();
-
-        assert hosts != null;
-        assert hosts.size() == 2;
-
-        for (Map<String, Object> host : hosts) {
-            assert host != null;
-
-            assert "192.168.1.1".equals(host.get(HOST)) || 
"192.168.1.2".equals(host.get(HOST));
-
-            if ("192.168.1.1".equals(host.get(HOST))) {
-                assert (Integer)host.get(PORT) == 1;
-                assert "uname1".equals(host.get(UNAME));
-                assert "passwd1".equals(host.get(PASSWD));
-                assert new File("key1").equals(host.get(KEY));
-                assert (Integer)host.get(NODES) == 1;
-                assert "ggHome1".equals(host.get(IGNITE_HOME));
-                assert "cfg1".equals(host.get(CFG));
-                assert "script1".equals(host.get(SCRIPT));
-            }
-            else if ("192.168.1.2".equals(host.get(HOST))) {
-                assert (Integer)host.get(PORT) == 2;
-                assert "uname2".equals(host.get(UNAME));
-                assert "passwd2".equals(host.get(PASSWD));
-                assert new File("key2").equals(host.get(KEY));
-                assert (Integer)host.get(NODES) == 2;
-                assert "ggHome2".equals(host.get(IGNITE_HOME));
-                assert "cfg2".equals(host.get(CFG));
-                assert "script2".equals(host.get(SCRIPT));
-            }
-        }
-
-        Map<String, Object> dflts = t.get2();
-
-        assert dflts != null;
-
-        assert (Integer)dflts.get(PORT) == 3;
-        assert "uname3".equals(dflts.get(UNAME));
-        assert "passwd3".equals(dflts.get(PASSWD));
-        assert new File("key3").equals(dflts.get(KEY));
-        assert (Integer)dflts.get(NODES) == 3;
-        assert "ggHome3".equals(dflts.get(IGNITE_HOME));
-        assert "cfg3".equals(dflts.get(CFG));
-        assert "script3".equals(dflts.get(SCRIPT));
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/ssh/src/test/java/org/apache/ignite/internal/GridProjectionStartStopRestartSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/ssh/src/test/java/org/apache/ignite/internal/GridProjectionStartStopRestartSelfTest.java
 
b/modules/ssh/src/test/java/org/apache/ignite/internal/GridProjectionStartStopRestartSelfTest.java
deleted file mode 100644
index 20c52d5..0000000
--- 
a/modules/ssh/src/test/java/org/apache/ignite/internal/GridProjectionStartStopRestartSelfTest.java
+++ /dev/null
@@ -1,1032 +0,0 @@
-/*
- * 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;
-
-import org.apache.ignite.*;
-import org.apache.ignite.cluster.*;
-import org.apache.ignite.events.*;
-import org.apache.ignite.internal.util.lang.*;
-import org.apache.ignite.internal.util.nodestart.*;
-import org.apache.ignite.internal.util.typedef.*;
-import org.apache.ignite.internal.util.typedef.internal.*;
-import org.apache.ignite.lang.*;
-import org.apache.ignite.testframework.junits.common.*;
-import org.jetbrains.annotations.*;
-
-import java.io.*;
-import java.nio.file.*;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.*;
-
-import static java.util.concurrent.TimeUnit.*;
-import static org.apache.ignite.events.IgniteEventType.*;
-import static org.apache.ignite.internal.util.nodestart.GridNodeStartUtils.*;
-
-/**
- * Tests for {@code startNodes(..)}, {@code stopNodes(..)}
- * and {@code restartNodes(..)} methods.
- * <p>
- * {@code tests.properties} file must specify username ({@code ssh.username} 
property)
- * and one (and only one) of password ({@code ssh.password} property) or
- * private key path ({@code ssh.key} property).
- */
-@SuppressWarnings("ConstantConditions")
-public class GridProjectionStartStopRestartSelfTest extends 
GridCommonAbstractTest {
-    /** */
-    private static final String SSH_UNAME = System.getenv("test.ssh.username");
-
-    /** */
-    private static final String SSH_PWD = System.getenv("test.ssh.password");
-
-    /** */
-    private static final String SSH_KEY = System.getenv("ssh.key");
-
-    /** */
-    private static final String CUSTOM_SCRIPT_WIN = 
"modules/core/src/test/bin/start-nodes-custom.bat";
-
-    /** */
-    private static final String CUSTOM_SCRIPT_LINUX = 
"modules/core/src/test/bin/start-nodes-custom.sh";
-
-    /** */
-    private static final String CFG_NO_ATTR = 
"modules/core/src/test/config/spring-start-nodes.xml";
-
-    /** */
-    private static final String CFG_ATTR = 
"modules/core/src/test/config/spring-start-nodes-attr.xml";
-
-    /** */
-    private static final String CUSTOM_CFG_ATTR_KEY = "grid.node.ssh.started";
-
-    /** */
-    private static final String CUSTOM_CFG_ATTR_VAL = "true";
-
-    /** */
-    private static final long WAIT_TIMEOUT = 40 * 1000;
-
-    /** */
-    private String pwd;
-
-    /** */
-    private File key;
-
-    /** */
-    private Ignite ignite;
-
-    /** */
-    private static final String HOST = "127.0.0.1";
-
-    /** */
-    private final AtomicInteger joinedCnt = new AtomicInteger();
-
-    /** */
-    private final AtomicInteger leftCnt = new AtomicInteger();
-
-    /** */
-    private volatile CountDownLatch joinedLatch;
-
-    /** */
-    private volatile CountDownLatch leftLatch;
-
-    /** {@inheritDoc} */
-    @Override protected void beforeTest() throws Exception {
-        if (SSH_KEY != null) {
-            key = new File(SSH_KEY);
-
-            assert key.exists() : "Private key doesn't exist: " + 
key.getAbsolutePath();
-            assert key.isFile() : "Private key is not a file: " + 
key.getAbsolutePath();
-        }
-        else
-            pwd = SSH_PWD;
-
-        log.info("Username: " + SSH_UNAME);
-        log.info("Password: " + pwd);
-        log.info("Key path: " + key);
-
-        G.setDaemon(true);
-
-        ignite = G.start(CFG_NO_ATTR);
-
-        G.setDaemon(false);
-
-        ignite.events().localListen(new IgnitePredicate<IgniteEvent>() {
-            @Override public boolean apply(IgniteEvent evt) {
-                info("Received event: " + evt.shortDisplay());
-
-                if (evt.type() == EVT_NODE_JOINED) {
-                    joinedCnt.incrementAndGet();
-
-                    if (joinedLatch != null)
-                        joinedLatch.countDown();
-                } else if (evt.type() == EVT_NODE_LEFT) {
-                    leftCnt.incrementAndGet();
-
-                    if (leftLatch != null)
-                        leftLatch.countDown();
-                }
-
-                return true;
-            }
-        }, EVT_NODE_JOINED, EVT_NODE_LEFT);
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void afterTest() throws Exception {
-        if (!ignite.cluster().nodes().isEmpty()) {
-            leftLatch = new CountDownLatch(ignite.cluster().nodes().size());
-
-            ignite.cluster().stopNodes();
-
-            assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        }
-
-        boolean wasEmpty = ignite.cluster().nodes().isEmpty();
-
-        G.stop(true);
-
-        joinedCnt.set(0);
-        leftCnt.set(0);
-
-        joinedLatch = null;
-        leftLatch = null;
-
-        assert wasEmpty : "grid.isEmpty() returned false after all nodes were 
stopped [nodes=" + ignite.cluster().nodes() + ']';
-    }
-
-    /** {@inheritDoc} */
-    @Override protected long getTestTimeout() {
-        return 90 * 1000;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartOneNode() throws Exception {
-        joinedLatch = new CountDownLatch(1);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 1;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 1;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 1;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartThreeNodes() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, DFLT_TIMEOUT, 1);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartThreeNodesAndDoEmptyCall() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, false, 0, 16);
-
-        assert res.isEmpty();
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartThreeNodesAndTryToStartOneNode() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, false, 0, 16);
-
-        assert res.isEmpty();
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartFiveNodesInTwoCalls() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(2);
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 5, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, false, 0, 16);
-
-        assert res.size() == 2;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 5;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 5;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartFiveWithTwoSpecs() throws Exception {
-        joinedLatch = new CountDownLatch(5);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                F.asList(map(HOST, SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                    map(HOST, SSH_UNAME, pwd, key, 3, U.getGridGainHome(), 
CFG_NO_ATTR, null)),
-                null, false, 0, 16);
-
-        assert res.size() == 5;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 5;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 5;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStartThreeNodesAndRestart() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 3;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(3);
-        leftLatch = new CountDownLatch(3);
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, true, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 6;
-        assert leftCnt.get() == 3;
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testCustomScript() throws Exception {
-        joinedLatch = new CountDownLatch(1);
-
-        String script = U.isWindows() ? CUSTOM_SCRIPT_WIN : 
CUSTOM_SCRIPT_LINUX;
-
-        script = 
Paths.get(U.getGridGainHome()).relativize(U.resolveGridGainPath(script).toPath()).toString();
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), null, script),
-                null, false, 0, 16);
-
-        assert res.size() == 1;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert joinedCnt.get() == 1;
-        assert leftCnt.get() == 0;
-
-        assert ignite.cluster().nodes().size() == 1;
-
-        assert 
CUSTOM_CFG_ATTR_VAL.equals(F.first(ignite.cluster().nodes()).<String>attribute(CUSTOM_CFG_ATTR_KEY));
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStopNodes() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, null, 3, 
U.getGridGainHome(), CFG_NO_ATTR,
-                null), null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        leftLatch = new CountDownLatch(3);
-
-        ignite.cluster().stopNodes();
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().isEmpty();
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStopNodesFiltered() throws Exception {
-        joinedLatch = new CountDownLatch(2);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 2;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        joinedLatch = new CountDownLatch(1);
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, false, 0, 16);
-
-        assert res.size() == 1;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        leftLatch = new CountDownLatch(2);
-
-        Collection<UUID> ids = 
F.transform(ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes(),
-            new IgniteClosure<ClusterNode, UUID>() {
-            @Override public UUID apply(ClusterNode node) {
-                return node.id();
-            }
-        });
-
-        ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes();
-
-        ignite.cluster().stopNodes(ids);
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 1;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStopNodeById() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        leftLatch = new CountDownLatch(1);
-
-        
ignite.cluster().stopNodes(Collections.singleton(F.first(ignite.cluster().forRemotes().nodes()).id()));
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 2;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStopNodesByIds() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        leftLatch = new CountDownLatch(2);
-
-        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
-
-        Collection<UUID> ids = new HashSet<>();
-
-        ids.add(it.next().id());
-        ids.add(it.next().id());
-
-        ignite.cluster().stopNodes(ids);
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 1;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testStopNodesByIdsC() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        leftLatch = new CountDownLatch(2);
-
-        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
-
-        ignite.cluster().stopNodes(F.asList(it.next().id(), it.next().id()));
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 1;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testRestartNodes() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(3);
-        leftLatch = new CountDownLatch(3);
-
-        ignite.cluster().restartNodes();
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testRestartNodesFiltered() throws Exception {
-        joinedLatch = new CountDownLatch(2);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 2;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        joinedLatch = new CountDownLatch(1);
-
-        res = startNodes(ignite.cluster(),
-            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-            null, false, 0, 16);
-
-        assert res.size() == 1;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(2);
-        leftLatch = new CountDownLatch(2);
-
-        X.println("Restarting nodes with " + CUSTOM_CFG_ATTR_KEY);
-
-        Collection<UUID> ids = 
F.transform(ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes(),
-            new IgniteClosure<ClusterNode, UUID>() {
-                @Override public UUID apply(ClusterNode node) {
-                    return node.id();
-                }
-            }
-        );
-
-        ignite.cluster().restartNodes(ids);
-
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testRestartNodeById() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(1);
-        leftLatch = new CountDownLatch(1);
-
-        
ignite.cluster().restartNodes(Collections.singleton(F.first(ignite.cluster().forRemotes().nodes()).id()));
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testRestartNodesByIds() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(2);
-        leftLatch = new CountDownLatch(2);
-
-        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
-
-        ignite.cluster().restartNodes(F.asList(it.next().id(), 
it.next().id()));
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    public void testRestartNodesByIdsC() throws Exception {
-        joinedLatch = new CountDownLatch(3);
-
-        Collection<GridTuple3<String, Boolean, String>> res =
-            startNodes(ignite.cluster(),
-                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
-                null, false, 0, 16);
-
-        assert res.size() == 3;
-
-        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
-            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
-                assert t.get1().equals(HOST);
-
-                if (!t.get2())
-                    throw new IgniteException(t.get3());
-            }
-        });
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-
-        joinedLatch = new CountDownLatch(2);
-        leftLatch = new CountDownLatch(2);
-
-        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
-
-        ignite.cluster().restartNodes(F.asList(it.next().id(), 
it.next().id()));
-
-        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
-
-        assert ignite.cluster().nodes().size() == 3;
-    }
-
-    /**
-     * @param host Hostname.
-     * @param uname Username.
-     * @param passwd Password.
-     * @param key Private key file.
-     * @param nodes Number of nodes.
-     * @param ggHome GridGain home.
-     * @param cfg Configuration file path.
-     * @param script Startup script path.
-     * @return Parameters map.
-     */
-    private Map<String, Object> map(
-        String host,
-        @Nullable String uname,
-        @Nullable String passwd,
-        @Nullable File key,
-        @Nullable Integer nodes,
-        @Nullable String ggHome,
-        @Nullable String cfg,
-        @Nullable String script) {
-        assert host != null;
-
-        Map<String, Object> params = new HashMap<>();
-
-        params.put(GridNodeStartUtils.HOST, host);
-        params.put(UNAME, uname);
-        params.put(PASSWD, passwd);
-        params.put(KEY, key);
-        params.put(NODES, nodes);
-        params.put(IGNITE_HOME, ggHome);
-        params.put(CFG, cfg);
-        params.put(SCRIPT, script);
-
-        return params;
-    }
-
-    /**
-     * @param hosts Hostnames.
-     * @param uname Username.
-     * @param passwd Password.
-     * @param key Private key file.
-     * @param nodes Number of nodes.
-     * @param ggHome GridGain home.
-     * @param cfg Configuration file path.
-     * @param script Startup script path.
-     * @return Parameters map.
-     */
-    private Collection<Map<String, Object>> maps(
-        Collection<String> hosts,
-        @Nullable String uname,
-        @Nullable String passwd,
-        @Nullable File key,
-        @Nullable Integer nodes,
-        @Nullable String ggHome,
-        @Nullable String cfg,
-        @Nullable String script) {
-        assert HOST != null;
-
-        Collection<Map<String, Object>> maps = new ArrayList<>(hosts.size());
-
-        for (String host : hosts) {
-            Map<String, Object> params = new HashMap<>();
-
-            params.put(GridNodeStartUtils.HOST, host);
-            params.put(UNAME, uname);
-            params.put(PASSWD, passwd);
-            params.put(KEY, key);
-            params.put(NODES, nodes);
-            params.put(IGNITE_HOME, ggHome);
-            params.put(CFG, cfg);
-            params.put(SCRIPT, script);
-
-            maps.add(params);
-        }
-
-        return maps;
-    }
-
-    /**
-     * @param name Filename.
-     * @return Whether name belongs to log file.
-     */
-    private boolean isSshNodeLogName(String name) {
-        return name.matches("gridgain.[0-9a-z-]+.log");
-    }
-
-    /**
-     * @param cluster Cluster.
-     * @param hosts Hosts.
-     * @param dflts Default.
-     * @param restart Restart flag.
-     * @param timeout Timeout.
-     * @param maxConn Maximum connections.
-     * @return Results collection.
-     * @throws IgniteCheckedException If failed.
-     */
-    private Collection<GridTuple3<String, Boolean, String>> 
startNodes(IgniteCluster cluster,
-        Collection<Map<String, Object>> hosts,
-        @Nullable Map<String, Object> dflts,
-        boolean restart,
-        int timeout,
-        int maxConn) throws IgniteCheckedException {
-        cluster = cluster.withAsync();
-
-        assertNull(cluster.startNodes(hosts, dflts, restart, timeout, 
maxConn));
-
-        return cluster.<Collection<GridTuple3<String, Boolean, 
String>>>future().get(WAIT_TIMEOUT);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteNodeStartUtilsSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteNodeStartUtilsSelfTest.java
 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteNodeStartUtilsSelfTest.java
new file mode 100644
index 0000000..46a64c5
--- /dev/null
+++ 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteNodeStartUtilsSelfTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.apache.ignite.lang.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.io.*;
+import java.util.*;
+
+import static org.apache.ignite.internal.util.nodestart.IgniteNodeStartUtils.*;
+
+/**
+ * Tests for {@link 
org.apache.ignite.internal.util.nodestart.IgniteNodeStartUtils}.
+ */
+public class IgniteNodeStartUtilsSelfTest extends GridCommonAbstractTest {
+    /**
+     * @throws Exception If failed.
+     */
+    public void testParseFile() throws Exception {
+        File file = 
U.resolveGridGainPath("modules/core/src/test/config/start-nodes.ini");
+
+        IgniteBiTuple<Collection<Map<String, Object>>, Map<String, Object>> t 
= parseFile(file);
+
+        assert t != null;
+
+        Collection<Map<String, Object>> hosts = t.get1();
+
+        assert hosts != null;
+        assert hosts.size() == 2;
+
+        for (Map<String, Object> host : hosts) {
+            assert host != null;
+
+            assert "192.168.1.1".equals(host.get(HOST)) || 
"192.168.1.2".equals(host.get(HOST));
+
+            if ("192.168.1.1".equals(host.get(HOST))) {
+                assert (Integer)host.get(PORT) == 1;
+                assert "uname1".equals(host.get(UNAME));
+                assert "passwd1".equals(host.get(PASSWD));
+                assert new File("key1").equals(host.get(KEY));
+                assert (Integer)host.get(NODES) == 1;
+                assert "ggHome1".equals(host.get(IGNITE_HOME));
+                assert "cfg1".equals(host.get(CFG));
+                assert "script1".equals(host.get(SCRIPT));
+            }
+            else if ("192.168.1.2".equals(host.get(HOST))) {
+                assert (Integer)host.get(PORT) == 2;
+                assert "uname2".equals(host.get(UNAME));
+                assert "passwd2".equals(host.get(PASSWD));
+                assert new File("key2").equals(host.get(KEY));
+                assert (Integer)host.get(NODES) == 2;
+                assert "ggHome2".equals(host.get(IGNITE_HOME));
+                assert "cfg2".equals(host.get(CFG));
+                assert "script2".equals(host.get(SCRIPT));
+            }
+        }
+
+        Map<String, Object> dflts = t.get2();
+
+        assert dflts != null;
+
+        assert (Integer)dflts.get(PORT) == 3;
+        assert "uname3".equals(dflts.get(UNAME));
+        assert "passwd3".equals(dflts.get(PASSWD));
+        assert new File("key3").equals(dflts.get(KEY));
+        assert (Integer)dflts.get(NODES) == 3;
+        assert "ggHome3".equals(dflts.get(IGNITE_HOME));
+        assert "cfg3".equals(dflts.get(CFG));
+        assert "script3".equals(dflts.get(SCRIPT));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteProjectionStartStopRestartSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteProjectionStartStopRestartSelfTest.java
 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteProjectionStartStopRestartSelfTest.java
new file mode 100644
index 0000000..0e9ee8a
--- /dev/null
+++ 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteProjectionStartStopRestartSelfTest.java
@@ -0,0 +1,1032 @@
+/*
+ * 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;
+
+import org.apache.ignite.*;
+import org.apache.ignite.cluster.*;
+import org.apache.ignite.events.*;
+import org.apache.ignite.internal.util.lang.*;
+import org.apache.ignite.internal.util.nodestart.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.apache.ignite.lang.*;
+import org.apache.ignite.testframework.junits.common.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+import static java.util.concurrent.TimeUnit.*;
+import static org.apache.ignite.events.IgniteEventType.*;
+import static org.apache.ignite.internal.util.nodestart.IgniteNodeStartUtils.*;
+
+/**
+ * Tests for {@code startNodes(..)}, {@code stopNodes(..)}
+ * and {@code restartNodes(..)} methods.
+ * <p>
+ * {@code tests.properties} file must specify username ({@code ssh.username} 
property)
+ * and one (and only one) of password ({@code ssh.password} property) or
+ * private key path ({@code ssh.key} property).
+ */
+@SuppressWarnings("ConstantConditions")
+public class IgniteProjectionStartStopRestartSelfTest extends 
GridCommonAbstractTest {
+    /** */
+    private static final String SSH_UNAME = System.getenv("test.ssh.username");
+
+    /** */
+    private static final String SSH_PWD = System.getenv("test.ssh.password");
+
+    /** */
+    private static final String SSH_KEY = System.getenv("ssh.key");
+
+    /** */
+    private static final String CUSTOM_SCRIPT_WIN = 
"modules/core/src/test/bin/start-nodes-custom.bat";
+
+    /** */
+    private static final String CUSTOM_SCRIPT_LINUX = 
"modules/core/src/test/bin/start-nodes-custom.sh";
+
+    /** */
+    private static final String CFG_NO_ATTR = 
"modules/core/src/test/config/spring-start-nodes.xml";
+
+    /** */
+    private static final String CFG_ATTR = 
"modules/core/src/test/config/spring-start-nodes-attr.xml";
+
+    /** */
+    private static final String CUSTOM_CFG_ATTR_KEY = "grid.node.ssh.started";
+
+    /** */
+    private static final String CUSTOM_CFG_ATTR_VAL = "true";
+
+    /** */
+    private static final long WAIT_TIMEOUT = 40 * 1000;
+
+    /** */
+    private String pwd;
+
+    /** */
+    private File key;
+
+    /** */
+    private Ignite ignite;
+
+    /** */
+    private static final String HOST = "127.0.0.1";
+
+    /** */
+    private final AtomicInteger joinedCnt = new AtomicInteger();
+
+    /** */
+    private final AtomicInteger leftCnt = new AtomicInteger();
+
+    /** */
+    private volatile CountDownLatch joinedLatch;
+
+    /** */
+    private volatile CountDownLatch leftLatch;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        if (SSH_KEY != null) {
+            key = new File(SSH_KEY);
+
+            assert key.exists() : "Private key doesn't exist: " + 
key.getAbsolutePath();
+            assert key.isFile() : "Private key is not a file: " + 
key.getAbsolutePath();
+        }
+        else
+            pwd = SSH_PWD;
+
+        log.info("Username: " + SSH_UNAME);
+        log.info("Password: " + pwd);
+        log.info("Key path: " + key);
+
+        G.setDaemon(true);
+
+        ignite = G.start(CFG_NO_ATTR);
+
+        G.setDaemon(false);
+
+        ignite.events().localListen(new IgnitePredicate<IgniteEvent>() {
+            @Override public boolean apply(IgniteEvent evt) {
+                info("Received event: " + evt.shortDisplay());
+
+                if (evt.type() == EVT_NODE_JOINED) {
+                    joinedCnt.incrementAndGet();
+
+                    if (joinedLatch != null)
+                        joinedLatch.countDown();
+                } else if (evt.type() == EVT_NODE_LEFT) {
+                    leftCnt.incrementAndGet();
+
+                    if (leftLatch != null)
+                        leftLatch.countDown();
+                }
+
+                return true;
+            }
+        }, EVT_NODE_JOINED, EVT_NODE_LEFT);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (!ignite.cluster().nodes().isEmpty()) {
+            leftLatch = new CountDownLatch(ignite.cluster().nodes().size());
+
+            ignite.cluster().stopNodes();
+
+            assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        }
+
+        boolean wasEmpty = ignite.cluster().nodes().isEmpty();
+
+        G.stop(true);
+
+        joinedCnt.set(0);
+        leftCnt.set(0);
+
+        joinedLatch = null;
+        leftLatch = null;
+
+        assert wasEmpty : "grid.isEmpty() returned false after all nodes were 
stopped [nodes=" + ignite.cluster().nodes() + ']';
+    }
+
+    /** {@inheritDoc} */
+    @Override protected long getTestTimeout() {
+        return 90 * 1000;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartOneNode() throws Exception {
+        joinedLatch = new CountDownLatch(1);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 1;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 1;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartThreeNodes() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, DFLT_TIMEOUT, 1);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartThreeNodesAndDoEmptyCall() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, false, 0, 16);
+
+        assert res.isEmpty();
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartThreeNodesAndTryToStartOneNode() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, false, 0, 16);
+
+        assert res.isEmpty();
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartFiveNodesInTwoCalls() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(2);
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 5, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, false, 0, 16);
+
+        assert res.size() == 2;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 5;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 5;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartFiveWithTwoSpecs() throws Exception {
+        joinedLatch = new CountDownLatch(5);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                F.asList(map(HOST, SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                    map(HOST, SSH_UNAME, pwd, key, 3, U.getGridGainHome(), 
CFG_NO_ATTR, null)),
+                null, false, 0, 16);
+
+        assert res.size() == 5;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 5;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 5;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStartThreeNodesAndRestart() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 3;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(3);
+        leftLatch = new CountDownLatch(3);
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, true, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 6;
+        assert leftCnt.get() == 3;
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCustomScript() throws Exception {
+        joinedLatch = new CountDownLatch(1);
+
+        String script = U.isWindows() ? CUSTOM_SCRIPT_WIN : 
CUSTOM_SCRIPT_LINUX;
+
+        script = 
Paths.get(U.getGridGainHome()).relativize(U.resolveGridGainPath(script).toPath()).toString();
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 1, 
U.getGridGainHome(), null, script),
+                null, false, 0, 16);
+
+        assert res.size() == 1;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert joinedCnt.get() == 1;
+        assert leftCnt.get() == 0;
+
+        assert ignite.cluster().nodes().size() == 1;
+
+        assert 
CUSTOM_CFG_ATTR_VAL.equals(F.first(ignite.cluster().nodes()).<String>attribute(CUSTOM_CFG_ATTR_KEY));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStopNodes() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, null, 3, 
U.getGridGainHome(), CFG_NO_ATTR,
+                null), null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        leftLatch = new CountDownLatch(3);
+
+        ignite.cluster().stopNodes();
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().isEmpty();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStopNodesFiltered() throws Exception {
+        joinedLatch = new CountDownLatch(2);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 2;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        joinedLatch = new CountDownLatch(1);
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, false, 0, 16);
+
+        assert res.size() == 1;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        leftLatch = new CountDownLatch(2);
+
+        Collection<UUID> ids = 
F.transform(ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes(),
+            new IgniteClosure<ClusterNode, UUID>() {
+            @Override public UUID apply(ClusterNode node) {
+                return node.id();
+            }
+        });
+
+        ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes();
+
+        ignite.cluster().stopNodes(ids);
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStopNodeById() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        leftLatch = new CountDownLatch(1);
+
+        
ignite.cluster().stopNodes(Collections.singleton(F.first(ignite.cluster().forRemotes().nodes()).id()));
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 2;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStopNodesByIds() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        leftLatch = new CountDownLatch(2);
+
+        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
+
+        Collection<UUID> ids = new HashSet<>();
+
+        ids.add(it.next().id());
+        ids.add(it.next().id());
+
+        ignite.cluster().stopNodes(ids);
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStopNodesByIdsC() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        leftLatch = new CountDownLatch(2);
+
+        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
+
+        ignite.cluster().stopNodes(F.asList(it.next().id(), it.next().id()));
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRestartNodes() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(3);
+        leftLatch = new CountDownLatch(3);
+
+        ignite.cluster().restartNodes();
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRestartNodesFiltered() throws Exception {
+        joinedLatch = new CountDownLatch(2);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 2, 
U.getGridGainHome(), CFG_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 2;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        joinedLatch = new CountDownLatch(1);
+
+        res = startNodes(ignite.cluster(),
+            maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+            null, false, 0, 16);
+
+        assert res.size() == 1;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(2);
+        leftLatch = new CountDownLatch(2);
+
+        X.println("Restarting nodes with " + CUSTOM_CFG_ATTR_KEY);
+
+        Collection<UUID> ids = 
F.transform(ignite.cluster().forAttribute(CUSTOM_CFG_ATTR_KEY, 
CUSTOM_CFG_ATTR_VAL).nodes(),
+            new IgniteClosure<ClusterNode, UUID>() {
+                @Override public UUID apply(ClusterNode node) {
+                    return node.id();
+                }
+            }
+        );
+
+        ignite.cluster().restartNodes(ids);
+
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRestartNodeById() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(1);
+        leftLatch = new CountDownLatch(1);
+
+        
ignite.cluster().restartNodes(Collections.singleton(F.first(ignite.cluster().forRemotes().nodes()).id()));
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRestartNodesByIds() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(2);
+        leftLatch = new CountDownLatch(2);
+
+        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
+
+        ignite.cluster().restartNodes(F.asList(it.next().id(), 
it.next().id()));
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRestartNodesByIdsC() throws Exception {
+        joinedLatch = new CountDownLatch(3);
+
+        Collection<GridTuple3<String, Boolean, String>> res =
+            startNodes(ignite.cluster(),
+                maps(Collections.singleton(HOST), SSH_UNAME, pwd, key, 3, 
U.getGridGainHome(), CFG_NO_ATTR, null),
+                null, false, 0, 16);
+
+        assert res.size() == 3;
+
+        F.forEach(res, new CI1<GridTuple3<String, Boolean, String>>() {
+            @Override public void apply(GridTuple3<String, Boolean, String> t) 
{
+                assert t.get1().equals(HOST);
+
+                if (!t.get2())
+                    throw new IgniteException(t.get3());
+            }
+        });
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+
+        joinedLatch = new CountDownLatch(2);
+        leftLatch = new CountDownLatch(2);
+
+        Iterator<ClusterNode> it = ignite.cluster().nodes().iterator();
+
+        ignite.cluster().restartNodes(F.asList(it.next().id(), 
it.next().id()));
+
+        assert joinedLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+        assert leftLatch.await(WAIT_TIMEOUT, MILLISECONDS);
+
+        assert ignite.cluster().nodes().size() == 3;
+    }
+
+    /**
+     * @param host Hostname.
+     * @param uname Username.
+     * @param passwd Password.
+     * @param key Private key file.
+     * @param nodes Number of nodes.
+     * @param igniteHome GridGain home.
+     * @param cfg Configuration file path.
+     * @param script Startup script path.
+     * @return Parameters map.
+     */
+    private Map<String, Object> map(
+        String host,
+        @Nullable String uname,
+        @Nullable String passwd,
+        @Nullable File key,
+        @Nullable Integer nodes,
+        @Nullable String igniteHome,
+        @Nullable String cfg,
+        @Nullable String script) {
+        assert host != null;
+
+        Map<String, Object> params = new HashMap<>();
+
+        params.put(IgniteNodeStartUtils.HOST, host);
+        params.put(UNAME, uname);
+        params.put(PASSWD, passwd);
+        params.put(KEY, key);
+        params.put(NODES, nodes);
+        params.put(IGNITE_HOME, igniteHome);
+        params.put(CFG, cfg);
+        params.put(SCRIPT, script);
+
+        return params;
+    }
+
+    /**
+     * @param hosts Hostnames.
+     * @param uname Username.
+     * @param passwd Password.
+     * @param key Private key file.
+     * @param nodes Number of nodes.
+     * @param igniteHome GridGain home.
+     * @param cfg Configuration file path.
+     * @param script Startup script path.
+     * @return Parameters map.
+     */
+    private Collection<Map<String, Object>> maps(
+        Collection<String> hosts,
+        @Nullable String uname,
+        @Nullable String passwd,
+        @Nullable File key,
+        @Nullable Integer nodes,
+        @Nullable String igniteHome,
+        @Nullable String cfg,
+        @Nullable String script) {
+        assert HOST != null;
+
+        Collection<Map<String, Object>> maps = new ArrayList<>(hosts.size());
+
+        for (String host : hosts) {
+            Map<String, Object> params = new HashMap<>();
+
+            params.put(IgniteNodeStartUtils.HOST, host);
+            params.put(UNAME, uname);
+            params.put(PASSWD, passwd);
+            params.put(KEY, key);
+            params.put(NODES, nodes);
+            params.put(IGNITE_HOME, igniteHome);
+            params.put(CFG, cfg);
+            params.put(SCRIPT, script);
+
+            maps.add(params);
+        }
+
+        return maps;
+    }
+
+    /**
+     * @param name Filename.
+     * @return Whether name belongs to log file.
+     */
+    private boolean isSshNodeLogName(String name) {
+        return name.matches("gridgain.[0-9a-z-]+.log");
+    }
+
+    /**
+     * @param cluster Cluster.
+     * @param hosts Hosts.
+     * @param dflts Default.
+     * @param restart Restart flag.
+     * @param timeout Timeout.
+     * @param maxConn Maximum connections.
+     * @return Results collection.
+     * @throws IgniteCheckedException If failed.
+     */
+    private Collection<GridTuple3<String, Boolean, String>> 
startNodes(IgniteCluster cluster,
+        Collection<Map<String, Object>> hosts,
+        @Nullable Map<String, Object> dflts,
+        boolean restart,
+        int timeout,
+        int maxConn) throws IgniteCheckedException {
+        cluster = cluster.withAsync();
+
+        assertNull(cluster.startNodes(hosts, dflts, restart, timeout, 
maxConn));
+
+        return cluster.<Collection<GridTuple3<String, Boolean, 
String>>>future().get(WAIT_TIMEOUT);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteStartStopRestartTestSuite.java
----------------------------------------------------------------------
diff --git 
a/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteStartStopRestartTestSuite.java
 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteStartStopRestartTestSuite.java
index a46a62b..65fcb44 100644
--- 
a/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteStartStopRestartTestSuite.java
+++ 
b/modules/ssh/src/test/java/org/apache/ignite/internal/IgniteStartStopRestartTestSuite.java
@@ -30,9 +30,9 @@ public class IgniteStartStopRestartTestSuite {
     public static TestSuite suite() throws Exception {
         TestSuite suite = new TestSuite("Start Nodes Test Suite");
 
-        suite.addTestSuite(GridNodeStartUtilsSelfTest.class);
+        suite.addTestSuite(IgniteNodeStartUtilsSelfTest.class);
 
-        suite.addTestSuite(GridProjectionStartStopRestartSelfTest.class);
+        suite.addTestSuite(IgniteProjectionStartStopRestartSelfTest.class);
 
         return suite;
     }

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/start/VisorStartCommand.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/start/VisorStartCommand.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/start/VisorStartCommand.scala
index 69d68fd..f853f95 100644
--- 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/start/VisorStartCommand.scala
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/start/VisorStartCommand.scala
@@ -176,7 +176,7 @@ class VisorStartCommand {
             val passwdOpt = argValue("pw", argLst)
             val keyOpt = argValue("k", argLst)
             val nodesOpt = argValue("n", argLst)
-            val ggHomeOpt = argValue("g", argLst)
+            val igniteHomeOpt = argValue("g", argLst)
             val cfgOpt = argValue("c", argLst)
             val scriptOpt = argValue("s", argLst)
             val maxConnOpt = argValue("m", argLst)
@@ -279,7 +279,7 @@ class VisorStartCommand {
                     "passwd" -> passwdOpt.orNull,
                     "key" -> keyFile,
                     "nodes" -> nodes,
-                    "ggHome" -> ggHomeOpt.orNull,
+                    "igniteHome" -> igniteHomeOpt.orNull,
                     "cfg" -> cfgOpt.orNull,
                     "script" -> scriptOpt.orNull
                 )

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/winservice/.gitignore
----------------------------------------------------------------------
diff --git a/modules/winservice/.gitignore b/modules/winservice/.gitignore
new file mode 100644
index 0000000..2dadad2
--- /dev/null
+++ b/modules/winservice/.gitignore
@@ -0,0 +1,2 @@
+/*/bin/
+/*/obj/

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/winservice/IgniteService.sln
----------------------------------------------------------------------
diff --git a/modules/winservice/IgniteService.sln 
b/modules/winservice/IgniteService.sln
new file mode 100644
index 0000000..f6cf22e
--- /dev/null
+++ b/modules/winservice/IgniteService.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IgniteService", 
"IgniteService\IgniteService.csproj", "{86BE9E54-4293-482A-8956-BDCF20BA53A8}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|x86 = Debug|x86
+               Release|x86 = Release|x86
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {86BE9E54-4293-482A-8956-BDCF20BA53A8}.Debug|x86.ActiveCfg = 
Debug|x86
+               {86BE9E54-4293-482A-8956-BDCF20BA53A8}.Debug|x86.Build.0 = 
Debug|x86
+               {86BE9E54-4293-482A-8956-BDCF20BA53A8}.Release|x86.ActiveCfg = 
Release|x86
+               {86BE9E54-4293-482A-8956-BDCF20BA53A8}.Release|x86.Build.0 = 
Release|x86
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+EndGlobal

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/67376020/modules/winservice/IgniteService/IgniteService.cs
----------------------------------------------------------------------
diff --git a/modules/winservice/IgniteService/IgniteService.cs 
b/modules/winservice/IgniteService/IgniteService.cs
new file mode 100644
index 0000000..63d1183
--- /dev/null
+++ b/modules/winservice/IgniteService/IgniteService.cs
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+namespace Ignite
+{
+    using System;
+    using System.ServiceProcess;
+    using System.Diagnostics;
+    using System.Threading;
+    using System.IO;
+
+    /** <summary>Service implementation.</summary> */
+    class IgniteService : ServiceBase
+    {
+        /** <summary>Service name.</summary> */
+        private String name;
+
+        /** <summary>External process.</summary> */
+        private Process proc;
+
+        /**
+         * <summary>Creates new service.</summary>
+         */
+        public IgniteService()
+        {
+            this.ServiceName = "Ignite Service";
+        }
+
+        /**
+         * <summary>Runs service.</summary>
+         */
+        public static void Main()
+        {
+            System.ServiceProcess.ServiceBase.Run(new IgniteService());
+        }
+
+        /** <inheritdoc /> */
+        protected override void OnStart(string[] args)
+        {
+            base.OnStart(args);
+
+            try
+            {
+                if (args.Length != 3)
+                    throw new Exception("Following arguments must be provided: 
service name, script path, log path.");
+
+                name = args[0];
+
+                String script = args[1];
+
+                int spaceIdx = script.IndexOf(' ');
+
+                String scriptPath = spaceIdx != -1 ? script.Substring(0, 
spaceIdx) : script;
+                String scriptArgs = spaceIdx != -1 ? script.Substring(spaceIdx 
+ 1) : String.Empty;
+
+                String log = args[2];
+
+                proc = new Process();
+
+                proc.StartInfo.FileName = scriptPath;
+                proc.StartInfo.Arguments = scriptArgs + " > " + log + " 2>&1";
+                proc.StartInfo.CreateNoWindow = true;
+
+                proc.Start();
+
+                Thread waitThread = new Thread(new 
ThreadStart(WaitStopAndDelete));
+
+                waitThread.Start();
+            }
+            catch (Exception e)
+            {
+                EventLog.WriteEntry(e.Message);
+
+                try
+                {
+                    Kill();
+                }
+                finally
+                {
+                    StopAndDelete();
+                }
+            }
+        }
+
+        /** <inheritdoc /> */
+        protected override void OnStop()
+        {
+            base.OnStop();
+
+            Kill();
+        }
+
+        /**
+         * <summary>Waits for external process to complete, stops and deletes 
service.</summary>
+         */
+        private void WaitStopAndDelete()
+        {
+            try
+            {
+                proc.WaitForExit();
+            }
+            finally
+            {
+                StopAndDelete();
+            }
+        }
+
+        /**
+         * <summary>Stops and deletes service.</summary>
+         */
+        private void StopAndDelete()
+        {
+            try
+            {
+                Stop();
+            }
+            finally
+            {
+                Delete();
+            }
+        }
+
+        /**
+         * <summary>Kills the process.</summary>
+         */
+        private void Kill()
+        {
+            Execute("taskkill /f /t /pid " + proc.Id);
+        }
+
+        /**
+         * <summary>Deletes service.</summary>
+         */
+        private void Delete()
+        {
+            Execute("sc delete " + name);
+        }
+
+        /**
+         * <summary>Executes command.</summary>
+         * <param name="cmd">Command.</name>
+         */
+        private void Execute(String cmd)
+        {
+            Process cmdProc = new Process();
+
+            cmdProc.StartInfo.FileName = "cmd.exe";
+            cmdProc.StartInfo.Arguments = "/c " + cmd;
+            cmdProc.StartInfo.CreateNoWindow = true;
+
+            cmdProc.Start();
+
+            cmdProc.WaitForExit();
+        }
+    }
+}

Reply via email to