/**
   * Change topology.
   *
   * @param parent Grid to execute tasks on.
   * @param add New nodes count.
   * @param rmv Remove nodes count.
   * @param type Type of nodes to manipulate.
   */
  private static void changeTopology(Ignite parent, int add, int rmv, String type) {
    Collection<ComputeTaskFuture<?>> tasks = new ArrayList<>();

    IgniteCompute comp = parent.compute().withAsync();

    // Start nodes in parallel.
    while (add-- > 0) {
      comp.execute(ClientStartNodeTask.class, type);

      tasks.add(comp.future());
    }

    for (ComputeTaskFuture<?> task : tasks) task.get();

    // Stop nodes in sequence.
    while (rmv-- > 0) parent.compute().execute(ClientStopNodeTask.class, type);

    // Wait for node stops.
    // U.sleep(1000);

    Collection<String> gridNames = new ArrayList<>();

    for (Ignite g : G.allGrids()) gridNames.add(g.name());

    parent.log().info(">>> Available grids: " + gridNames);
  }
  /** @throws Exception If failed. */
  @SuppressWarnings({"AssignmentToCatchBlockParameter"})
  public void testCancel() throws Exception {
    Ignite ignite = G.ignite(getTestGridName());

    ignite
        .compute()
        .localDeployTask(GridCancelTestTask.class, GridCancelTestTask.class.getClassLoader());

    ComputeTaskFuture<?> res0 =
        executeAsync(
            ignite.compute().withTimeout(maxJobExecTime * 2),
            GridCancelTestTask.class.getName(),
            null);

    try {
      Object res = res0.get();

      info("Cancel test result: " + res);

      synchronized (mux) {
        // Every execute must be called.
        assert execCnt <= SPLIT_COUNT : "Invalid execute count: " + execCnt;

        // Job returns 1 if was cancelled.
        assert (Integer) res <= SPLIT_COUNT : "Invalid task result: " + res;

        // Should be exactly the same as Jobs number.
        assert cancelCnt <= SPLIT_COUNT : "Invalid cancel count: " + cancelCnt;

        // One per start and one per stop and some that come with heartbeats.
        assert colResolutionCnt > SPLIT_COUNT + 1
            : "Invalid collision resolution count: " + colResolutionCnt;
      }
    } catch (ComputeTaskTimeoutException e) {
      error("Task execution got timed out.", e);
    } catch (Exception e) {
      assert e.getCause() != null;

      if (e.getCause() instanceof IgniteCheckedException) e = (Exception) e.getCause();

      if (e.getCause() instanceof IOException) e = (Exception) e.getCause();

      assert e.getCause() instanceof InterruptedException
          : "Invalid exception cause: " + e.getCause();
    }
  }
  /**
   * Checks that GridTaskListener is only called once per task.
   *
   * @throws Exception If failed.
   */
  @SuppressWarnings({"BusyWait", "unchecked"})
  public void testGridTaskListener() throws Exception {
    final AtomicInteger cnt = new AtomicInteger(0);

    IgniteInClosure<IgniteFuture<?>> lsnr =
        new CI1<IgniteFuture<?>>() {
          @Override
          public void apply(IgniteFuture<?> fut) {
            assert fut != null;

            cnt.incrementAndGet();
          }
        };

    Ignite ignite = G.ignite(getTestGridName());

    assert ignite != null;

    ignite.compute().localDeployTask(TestTask.class, TestTask.class.getClassLoader());

    ComputeTaskFuture<?> fut = executeAsync(ignite.compute(), TestTask.class.getName(), null);

    fut.listen(lsnr);

    fut.get();

    while (cnt.get() == 0) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        error("Got interrupted while sleep.", e);

        break;
      }
    }

    assert cnt.get() == 1
        : "Unexpected GridTaskListener apply count [count=" + cnt.get() + ", expected=1]";
  }
  /**
   * @param gridName Grid name.
   * @throws Exception If test failed.
   */
  protected void doMultiNodeTest(String gridName) throws Exception {
    startLatch = new CountDownLatch(3);

    read1Latch = new CountDownLatch(1);
    read1FinishedLatch = new CountDownLatch(2);

    read2Latch = new CountDownLatch(1);
    read2FinishedLatch = new CountDownLatch(2);

    read3Latch = new CountDownLatch(1);
    read3FinishedLatch = new CountDownLatch(2);

    rmvLatch = new CountDownLatch(1);

    try {
      startGrid(gridName + 1);

      Ignite ignite = startGrid(gridName);

      ComputeTaskFuture fut =
          executeAsync(ignite.compute(), new GridMultiNodeGlobalConsumerTask(), null);

      executeAsync(ignite.compute(), GridMultiNodeTestCheckPointTask.class, null)
          .get(2 * 60 * 1000);

      fut.get();

      for (Ignite g : G.allGrids()) {
        assert checkCheckpointManager(g)
            : "Session IDs got stuck after task completion [grid="
                + g.name()
                + ", sesIds="
                + checkpoints(g).sessionIds()
                + ']';
      }
    } finally {
      stopAllGrids();
    }
  }