/**
   * Determines whether a vaild session exists for the contact of remote machine.
   *
   * @param sshContact ID of SSH Contact
   * @return <tt>true</tt> if the session is connected <tt>false</tt> otherwise
   */
  public boolean isSessionValid(ContactSSH sshContact) {
    Session sshSession = sshContact.getSSHSession();
    if (sshSession != null) if (sshSession.isConnected()) return true;

    // remove reference to an unconnected SSH Session, if any
    sshContact.setSSHSession(null);
    return false;
  }
  /**
   * Creates a SSH Session with a remote machine and tries to login according to the details
   * specified by Contact An appropriate message is shown to the end user in case the login fails
   *
   * @param sshContact ID of SSH Contact
   * @throws JSchException if a JSch is unable to create a SSH Session with the remote machine
   * @throws InterruptedException if the thread is interrupted before session connected or is timed
   *     out
   * @throws OperationFailedException if not of above reasons :-)
   */
  public void createSSHSessionAndLogin(ContactSSH sshContact)
      throws JSchException, OperationFailedException, InterruptedException {
    logger.info("Creating a new SSH Session to " + sshContact.getHostName());

    // creating a new JSch Stack identifier for contact
    JSch jsch = new JSch();

    String knownHosts = (String) accountID.getAccountProperties().get("KNOWN_HOSTS_FILE");

    if (!knownHosts.equals("Optional")) jsch.setKnownHosts(knownHosts);

    String identitiyKey = (String) accountID.getAccountProperties().get("IDENTITY_FILE");

    String userName = sshContact.getUserName();

    // use the name of system user if the contact has not supplied SSH
    // details
    if (userName.equals("")) userName = System.getProperty("user.name");

    if (!identitiyKey.equals("Optional")) jsch.addIdentity(identitiyKey);

    // creating a new session for the contact
    Session session =
        jsch.getSession(
            userName, sshContact.getHostName(), sshContact.getSSHConfigurationForm().getPort());

    /**
     * Creating and associating User Info with the session User Info passes authentication from
     * sshContact to SSH Stack
     */
    SSHUserInfo sshUserInfo = new SSHUserInfo(sshContact);

    session.setUserInfo(sshUserInfo);

    /** initializing the session */
    session.connect(connectionTimeout);

    int count = 0;

    // wait for session to get connected
    while (!session.isConnected() && count <= 30000) {
      Thread.sleep(1000);
      count += 1000;
      logger.trace("SSH:" + sshContact.getHostName() + ": Sleep zzz .. ");
    }

    // if timeout have exceeded
    if (count > 30000) {
      sshContact.setSSHSession(null);
      JOptionPane.showMessageDialog(
          null, "SSH Connection attempt to " + sshContact.getHostName() + " timed out");

      // error codes are not defined yet
      throw new OperationFailedException(
          "SSH Connection attempt to " + sshContact.getHostName() + " timed out", 2);
    }

    sshContact.setJSch(jsch);
    sshContact.setSSHSession(session);

    logger.info("A new SSH Session to " + sshContact.getHostName() + " Created");
  }
  /** {@inheritDoc} */
  @Override
  public ClusterStartNodeResult call() {
    JSch ssh = new JSch();

    Session ses = null;

    try {
      if (spec.key() != null) ssh.addIdentity(spec.key().getAbsolutePath());

      ses = ssh.getSession(spec.username(), spec.host(), spec.port());

      if (spec.password() != null) ses.setPassword(spec.password());

      ses.setConfig("StrictHostKeyChecking", "no");

      ses.connect(timeout);

      boolean win = isWindows(ses);

      char separator = win ? '\\' : '/';

      spec.fixPaths(separator);

      String igniteHome = spec.igniteHome();

      if (igniteHome == null) igniteHome = win ? DFLT_IGNITE_HOME_WIN : DFLT_IGNITE_HOME_LINUX;

      String script = spec.script();

      if (script == null) script = DFLT_SCRIPT_LINUX;

      String cfg = spec.configuration();

      if (cfg == null) cfg = "";

      String startNodeCmd;
      String scriptOutputFileName =
          FILE_NAME_DATE_FORMAT.format(new Date())
              + '-'
              + UUID.randomUUID().toString().substring(0, 8)
              + ".log";

      if (win)
        throw new UnsupportedOperationException(
            "Apache Ignite cannot be auto-started on Windows from IgniteCluster.startNodes(…) API.");
      else { // Assume Unix.
        int spaceIdx = script.indexOf(' ');

        String scriptPath = spaceIdx > -1 ? script.substring(0, spaceIdx) : script;
        String scriptArgs = spaceIdx > -1 ? script.substring(spaceIdx + 1) : "";
        String rmtLogArgs = buildRemoteLogArguments(spec.username(), spec.host());
        String tmpDir = env(ses, "$TMPDIR", "/tmp/");
        String scriptOutputDir = tmpDir + "ignite-startNodes";

        shell(ses, "mkdir " + scriptOutputDir);

        // Mac os don't support ~ in double quotes. Trying get home path from remote system.
        if (igniteHome.startsWith("~")) {
          String homeDir = env(ses, "$HOME", "~");

          igniteHome = igniteHome.replaceFirst("~", homeDir);
        }

        startNodeCmd =
            new SB()
                .
                // Console output is consumed, started nodes must use Ignite file appenders for log.
                a("nohup ")
                .a("\"")
                .a(igniteHome)
                .a('/')
                .a(scriptPath)
                .a("\"")
                .a(" ")
                .a(scriptArgs)
                .a(!cfg.isEmpty() ? " \"" : "")
                .a(cfg)
                .a(!cfg.isEmpty() ? "\"" : "")
                .a(rmtLogArgs)
                .a(" > ")
                .a(scriptOutputDir)
                .a("/")
                .a(scriptOutputFileName)
                .a(" 2>& 1 &")
                .toString();
      }

      info("Starting remote node with SSH command: " + startNodeCmd, spec.logger(), log);

      shell(ses, startNodeCmd);

      return new ClusterStartNodeResultImpl(spec.host(), true, null);
    } catch (IgniteInterruptedCheckedException e) {
      return new ClusterStartNodeResultImpl(spec.host(), false, e.getMessage());
    } catch (Exception e) {
      return new ClusterStartNodeResultImpl(spec.host(), false, X.getFullStackTrace(e));
    } finally {
      if (ses != null && ses.isConnected()) ses.disconnect();
    }
  }