浏览代码

https://issues.apache.org/jira/browse/AMBARI-13450. AMBARI-13450 Bootstrap Cluster via different SSH Port Number (Selim Ozcan via aonishuk)

Andrew Onishuk 9 年之前
父节点
当前提交
39c04ac9b4

+ 18 - 10
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/BSRunner.java

@@ -41,6 +41,7 @@ class BSRunner extends Thread {
   private static Log LOG = LogFactory.getLog(BSRunner.class);
 
   private static final String DEFAULT_USER = "root";
+  private static final String DEFAULT_SSHPORT = "22";
 
   private  boolean finished = false;
   private SshHostInfo sshHostInfo;
@@ -164,7 +165,13 @@ class BSRunner extends Thread {
     if (user == null || user.isEmpty()) {
       user = DEFAULT_USER;
     }
-    String command[] = new String[12];
+
+    String sshPort = sshHostInfo.getSshPort();
+    if(sshPort == null || sshPort.isEmpty()){
+       sshPort = DEFAULT_SSHPORT;
+    }
+
+    String command[] = new String[13];
     BSStat stat = BSStat.RUNNING;
     String scriptlog = "";
     try {
@@ -194,14 +201,15 @@ class BSRunner extends Thread {
       command[1] = hostString;
       command[2] = this.requestIdDir.toString();
       command[3] = user;
-      command[4] = this.sshKeyFile.toString();
-      command[5] = this.agentSetupScript.toString();
-      command[6] = this.ambariHostname;
-      command[7] = this.clusterOsFamily;
-      command[8] = this.projectVersion;
-      command[9] = this.serverPort+"";
-      command[10] = userRunAs;
-      command[11] = (this.passwordFile==null) ? "null" : this.passwordFile.toString();
+      command[4] = sshPort;
+      command[5] = this.sshKeyFile.toString();
+      command[6] = this.agentSetupScript.toString();
+      command[7] = this.ambariHostname;
+      command[8] = this.clusterOsFamily;
+      command[9] = this.projectVersion;
+      command[10] = this.serverPort+"";
+      command[11] = userRunAs;
+      command[12] = (this.passwordFile==null) ? "null" : this.passwordFile.toString();
 
       Map<String, String> envVariables = new HashMap<String, String>();
 
@@ -218,7 +226,7 @@ class BSRunner extends Thread {
       }
 
       LOG.info("Host= " + hostString + " bs=" + this.bsScript + " requestDir=" +
-          requestIdDir + " user=" + user + " keyfile=" + this.sshKeyFile +
+          requestIdDir + " user=" + user + " sshPort=" + sshPort + " keyfile=" + this.sshKeyFile +
           " passwordFile " + this.passwordFile + " server=" + this.ambariHostname +
           " version=" + projectVersion + " serverPort=" + this.serverPort + " userRunAs=" + userRunAs);
 

+ 7 - 0
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/SshHostInfo.java

@@ -48,6 +48,9 @@ public class SshHostInfo {
   @XmlElement
   private String user;
 
+  @XmlElement
+  private String sshPort;
+
   @XmlElement
   private String password;
   
@@ -86,6 +89,10 @@ public class SshHostInfo {
     this.user = user;
   }
 
+  public String getSshPort(){ return sshPort; }
+
+  public void setSshPort(String sshPort){ this.sshPort = sshPort; }
+
   public String getPassword() {
     return password;
   }

+ 35 - 31
ambari-server/src/main/python/bootstrap.py

@@ -74,8 +74,9 @@ class HostLog:
 class SCP:
   """ SCP implementation that is thread based. The status can be returned using
    status val """
-  def __init__(self, user, sshkey_file, host, inputFile, remote, bootdir, host_log):
+  def __init__(self, user, sshPort, sshkey_file, host, inputFile, remote, bootdir, host_log):
     self.user = user
+    self.sshPort = sshPort
     self.sshkey_file = sshkey_file
     self.host = host
     self.inputFile = inputFile
@@ -90,7 +91,7 @@ class SCP:
                   "-r",
                   "-o", "ConnectTimeout=60",
                   "-o", "BatchMode=yes",
-                  "-o", "StrictHostKeyChecking=no",
+                  "-o", "StrictHostKeyChecking=no", "-P", self.sshPort,
                   "-i", self.sshkey_file, self.inputFile, self.user + "@" +
                                                          self.host + ":" + self.remote]
     if DEBUG:
@@ -111,8 +112,9 @@ class SCP:
 
 class SSH:
   """ Ssh implementation of this """
-  def __init__(self, user, sshkey_file, host, command, bootdir, host_log, errorMessage = None):
+  def __init__(self, user, sshPort, sshkey_file, host, command, bootdir, host_log, errorMessage = None):
     self.user = user
+    self.sshPort = sshPort
     self.sshkey_file = sshkey_file
     self.host = host
     self.command = command
@@ -128,7 +130,7 @@ class SSH:
                   "-o", "StrictHostKeyChecking=no",
                   "-o", "BatchMode=yes",
                   "-tt", # Should prevent "tput: No value for $TERM and no -T specified" warning
-                  "-i", self.sshkey_file,
+                  "-i", self.sshkey_file, "-p", self.sshPort,
                   self.user + "@" + self.host, self.command]
     if DEBUG:
       self.host_log.write("Running ssh command " + ' '.join(sshcommand))
@@ -454,7 +456,7 @@ class BootstrapDefault(Bootstrap):
     params = self.shared_state
     self.host_log.write("==========================\n")
     self.host_log.write("Copying OS type check script...")
-    scp = SCP(params.user, params.sshkey_file, self.host, fileToCopy,
+    scp = SCP(params.user, params.sshPort, params.sshkey_file, self.host, fileToCopy,
               target, params.bootdir, self.host_log)
     result = scp.run()
     self.host_log.write("\n")
@@ -467,7 +469,7 @@ class BootstrapDefault(Bootstrap):
     params = self.shared_state
     self.host_log.write("==========================\n")
     self.host_log.write("Copying common functions script...")
-    scp = SCP(params.user, params.sshkey_file, self.host, fileToCopy,
+    scp = SCP(params.user, params.sshPort, params.sshkey_file, self.host, fileToCopy,
               target, params.bootdir, self.host_log)
     result = scp.run()
     self.host_log.write("\n")
@@ -507,7 +509,7 @@ class BootstrapDefault(Bootstrap):
     if (os.path.exists(fileToCopy)):
       self.host_log.write("==========================\n")
       self.host_log.write("Copying repo file to 'tmp' folder...")
-      scp = SCP(params.user, params.sshkey_file, self.host, fileToCopy,
+      scp = SCP(params.user, params.sshPort, params.sshkey_file, self.host, fileToCopy,
                 target, params.bootdir, self.host_log)
       retcode1 = scp.run()
       self.host_log.write("\n")
@@ -517,7 +519,7 @@ class BootstrapDefault(Bootstrap):
       self.host_log.write("Moving file to repo dir...")
       targetDir = self.getRepoDir()
       command = self.getMoveRepoFileCommand(targetDir)
-      ssh = SSH(params.user, params.sshkey_file, self.host, command,
+      ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
                 params.bootdir, self.host_log)
       retcode2 = ssh.run()
       self.host_log.write("\n")
@@ -526,7 +528,7 @@ class BootstrapDefault(Bootstrap):
       self.host_log.write("==========================\n")
       self.host_log.write("Changing permissions for ambari.repo...")
       command = self.getRepoFileChmodCommand()
-      ssh = SSH(params.user, params.sshkey_file, self.host, command,
+      ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
                 params.bootdir, self.host_log)
       retcode4 = ssh.run()
       self.host_log.write("\n")
@@ -536,7 +538,7 @@ class BootstrapDefault(Bootstrap):
         self.host_log.write("==========================\n")
         self.host_log.write("Update apt cache of repository...")
         command = self.getAptUpdateCommand()
-        ssh = SSH(params.user, params.sshkey_file, self.host, command,
+        ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
                   params.bootdir, self.host_log)
         retcode2 = ssh.run()
         self.host_log.write("\n")
@@ -554,7 +556,7 @@ class BootstrapDefault(Bootstrap):
     self.host_log.write("Copying setup script file...")
     fileToCopy = params.setup_agent_file
     target = self.getRemoteName(self.SETUP_SCRIPT_FILENAME)
-    scp = SCP(params.user, params.sshkey_file, self.host, fileToCopy,
+    scp = SCP(params.user, params.sshPort, params.sshkey_file, self.host, fileToCopy,
               target, params.bootdir, self.host_log)
     retcode3 = scp.run()
     self.host_log.write("\n")
@@ -600,7 +602,7 @@ class BootstrapDefault(Bootstrap):
               (self.getOsCheckScriptRemoteLocation(),
                PYTHON_ENV, self.getOsCheckScriptRemoteLocation(), params.cluster_os_type)
 
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode = ssh.run()
     self.host_log.write("\n")
@@ -615,7 +617,7 @@ class BootstrapDefault(Bootstrap):
       command = "dpkg --get-selections|grep -e '^sudo\s*install'"
     else:
       command = "rpm -qa | grep -e '^sudo\-'"
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log,
               errorMessage="Error: Sudo command is not available. "
                            "Please install the sudo command.")
@@ -627,7 +629,7 @@ class BootstrapDefault(Bootstrap):
     # Copy the password file
     self.host_log.write("Copying password file to 'tmp' folder...")
     params = self.shared_state
-    scp = SCP(params.user, params.sshkey_file, self.host, params.password_file,
+    scp = SCP(params.user, params.sshPort, params.sshkey_file, self.host, params.password_file,
               self.getPasswordFile(), params.bootdir, self.host_log)
     retcode1 = scp.run()
 
@@ -636,7 +638,7 @@ class BootstrapDefault(Bootstrap):
     # Change password file mode to 600
     self.host_log.write("Changing password file mode...")
     command = "chmod 600 " + self.getPasswordFile()
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode2 = ssh.run()
 
@@ -648,7 +650,7 @@ class BootstrapDefault(Bootstrap):
     self.host_log.write("Changing password file mode...")
     params = self.shared_state
     command = "chmod 600 " + self.getPasswordFile()
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode = ssh.run()
     self.host_log.write("Change password file mode on host finished")
@@ -659,7 +661,7 @@ class BootstrapDefault(Bootstrap):
     self.host_log.write("Deleting password file...")
     params = self.shared_state
     command = "rm " + self.getPasswordFile()
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode = ssh.run()
     self.host_log.write("Deleting password file finished")
@@ -675,7 +677,7 @@ class BootstrapDefault(Bootstrap):
     command = "sudo mkdir -p {0} ; sudo chown -R {1} {0} ; sudo chmod 755 {3} ; sudo chmod 755 {2} ; sudo chmod 777 {0}".format(
       self.TEMP_FOLDER, quote_bash_args(params.user), DEFAULT_AGENT_DATA_FOLDER, DEFAULT_AGENT_LIB_FOLDER)
 
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode = ssh.run()
     self.host_log.write("\n")
@@ -692,7 +694,7 @@ class BootstrapDefault(Bootstrap):
     self.host_log.write("==========================\n")
     self.host_log.write("Running setup agent script...")
     command = self.getRunSetupCommand(self.host)
-    ssh = SSH(params.user, params.sshkey_file, self.host, command,
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, self.host, command,
               params.bootdir, self.host_log)
     retcode = ssh.run()
     self.host_log.write("\n")
@@ -791,11 +793,12 @@ class PBootstrap:
 
 
 class SharedState:
-  def __init__(self, user, sshkey_file, script_dir, boottmpdir, setup_agent_file,
+  def __init__(self, user, sshPort, sshkey_file, script_dir, boottmpdir, setup_agent_file,
                ambari_server, cluster_os_type, ambari_version, server_port,
                user_run_as, password_file = None):
     self.hostlist_to_remove_password_file = None
     self.user = user
+    self.sshPort = sshPort
     self.sshkey_file = sshkey_file
     self.bootdir = boottmpdir
     self.script_dir = script_dir
@@ -817,7 +820,7 @@ def main(argv=None):
   onlyargs = argv[1:]
   if len(onlyargs) < 3:
     sys.stderr.write("Usage: <comma separated hosts> "
-                     "<tmpdir for storage> <user> <sshkey_file> <agent setup script>"
+                     "<tmpdir for storage> <user> <sshPort> <sshkey_file> <agent setup script>"
                      " <ambari-server name> <cluster os type> <ambari version> <ambari port> <user_run_as> <passwordFile>\n")
     sys.exit(2)
     pass
@@ -827,14 +830,15 @@ def main(argv=None):
   hostList = onlyargs[0].split(",")
   bootdir =  onlyargs[1]
   user = onlyargs[2]
-  sshkey_file = onlyargs[3]
-  setupAgentFile = onlyargs[4]
-  ambariServer = onlyargs[5]
-  cluster_os_type = onlyargs[6]
-  ambariVersion = onlyargs[7]
-  server_port = onlyargs[8]
-  user_run_as = onlyargs[9]
-  passwordFile = onlyargs[10]
+  sshPort = onlyargs[3]
+  sshkey_file = onlyargs[4]
+  setupAgentFile = onlyargs[5]
+  ambariServer = onlyargs[6]
+  cluster_os_type = onlyargs[7]
+  ambariVersion = onlyargs[8]
+  server_port = onlyargs[9]
+  user_run_as = onlyargs[10]
+  passwordFile = onlyargs[11]
 
   if not OSCheck.is_windows_family():
     # ssh doesn't like open files
@@ -845,10 +849,10 @@ def main(argv=None):
 
   logging.info("BootStrapping hosts " + pprint.pformat(hostList) +
                " using " + scriptDir + " cluster primary OS: " + cluster_os_type +
-               " with user '" + user + "' sshKey File " + sshkey_file + " password File " + passwordFile +\
+               " with user '" + user + "'with ssh Port '" + sshPort + "' sshKey File " + sshkey_file + " password File " + passwordFile +\
                " using tmp dir " + bootdir + " ambari: " + ambariServer +"; server_port: " + server_port +\
                "; ambari version: " + ambariVersion+"; user_run_as: " + user_run_as)
-  sharedState = SharedState(user, sshkey_file, scriptDir, bootdir, setupAgentFile,
+  sharedState = SharedState(user, sshPort, sshkey_file, scriptDir, bootdir, setupAgentFile,
                        ambariServer, cluster_os_type, ambariVersion,
                        server_port, user_run_as, passwordFile)
   pbootstrap = PBootstrap(hostList, sharedState)

+ 51 - 51
ambari-server/src/test/python/TestBootstrap.py

@@ -43,7 +43,7 @@ class TestBootstrap(TestCase):
 
 
   def test_getRemoteName(self):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                       "setupAgentFile", "ambariServer", "centos6", None, "8440", "root")
     res = bootstrap_obj = Bootstrap("hostname", shared_state)
     utime1 = 1234
@@ -65,7 +65,7 @@ class TestBootstrap(TestCase):
   # TODO: test_return_error_message_for_missing_sudo_package
 
   def test_getAmbariPort(self):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -82,11 +82,11 @@ class TestBootstrap(TestCase):
   @patch("os.path.dirname")
   @patch("os.path.realpath")
   def test_bootstrap_main(self, dirname_mock, realpath_mock, run_mock, exit_mock, stderr_mock, subprocess_Popen_mock):
-    bootstrap.main(["bootstrap.py", "hostname,hostname2", "/tmp/bootstrap", "root", "sshkey_file", "setupAgent.py", "ambariServer", \
+    bootstrap.main(["bootstrap.py", "hostname,hostname2", "/tmp/bootstrap", "root", "123", "sshkey_file", "setupAgent.py", "ambariServer", \
                     "centos6", "1.1.1", "8440", "root", "passwordfile"])
     self.assertTrue(run_mock.called)
     run_mock.reset_mock()
-    bootstrap.main(["bootstrap.py", "hostname,hostname2", "/tmp/bootstrap", "root", "sshkey_file", "setupAgent.py", "ambariServer", \
+    bootstrap.main(["bootstrap.py", "hostname,hostname2", "/tmp/bootstrap", "root", "123", "sshkey_file", "setupAgent.py", "ambariServer", \
                     "centos6", "1.1.1", "8440", "root", None])
     self.assertTrue(run_mock.called)
     run_mock.reset_mock()
@@ -104,7 +104,7 @@ class TestBootstrap(TestCase):
 
   @patch("os.environ")
   def test_getRunSetupWithPasswordCommand(self, environ_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     environ_mock.__getitem__.return_value = "TEST_PASSPHRASE"
@@ -118,7 +118,7 @@ class TestBootstrap(TestCase):
 
 
   def test_generateRandomFileName(self):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -129,7 +129,7 @@ class TestBootstrap(TestCase):
   @patch.object(OSCheck, "is_redhat_family")
   @patch.object(OSCheck, "is_suse_family")
   def test_getRepoDir(self, is_suse_family, is_redhat_family):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -145,7 +145,7 @@ class TestBootstrap(TestCase):
     self.assertEquals(res, "/etc/yum.repos.d")
 
   def test_getSetupScript(self):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -155,7 +155,7 @@ class TestBootstrap(TestCase):
   def test_run_setup_agent_command_ends_with_project_version(self):
     os.environ[AMBARI_PASSPHRASE_VAR_NAME] = ""
     version = "1.1.1"
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                version, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -166,7 +166,7 @@ class TestBootstrap(TestCase):
   def test_agent_setup_command_without_project_version(self):
     os.environ[AMBARI_PASSPHRASE_VAR_NAME] = ""
     version = None
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                version, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -207,7 +207,7 @@ class TestBootstrap(TestCase):
 
   @patch("subprocess.Popen")
   def test_SCP(self, popenMock):
-    params = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    params = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                   "setupAgentFile", "ambariServer", "centos6",
                                   "1.2.1", "8440", "root")
     host_log_mock = MagicMock()
@@ -216,7 +216,7 @@ class TestBootstrap(TestCase):
       log['text'] = log['text'] + text
 
     host_log_mock.write.side_effect = write_side_effect
-    scp = SCP(params.user, params.sshkey_file, "dummy-host", "src/file",
+    scp = SCP(params.user, params.sshPort, params.sshkey_file, "dummy-host", "src/file",
               "dst/file", params.bootdir, host_log_mock)
     log_sample = "log_sample"
     error_sample = "error_sample"
@@ -233,7 +233,7 @@ class TestBootstrap(TestCase):
     self.assertTrue(error_sample in log['text'])
     command_str = str(popenMock.call_args[0][0])
     self.assertEquals(command_str, "['scp', '-r', '-o', 'ConnectTimeout=60', '-o', "
-        "'BatchMode=yes', '-o', 'StrictHostKeyChecking=no', '-i', 'sshkey_file',"
+        "'BatchMode=yes', '-o', 'StrictHostKeyChecking=no', '-P', '123', '-i', 'sshkey_file',"
         " 'src/file', 'root@dummy-host:dst/file']")
     self.assertEqual(retcode["exitstatus"], 0)
 
@@ -250,7 +250,7 @@ class TestBootstrap(TestCase):
 
   @patch("subprocess.Popen")
   def test_SSH(self, popenMock):
-    params = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    params = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                   "setupAgentFile", "ambariServer", "centos6",
                                   "1.2.1", "8440", "root")
     host_log_mock = MagicMock()
@@ -259,7 +259,7 @@ class TestBootstrap(TestCase):
       log['text'] = log['text'] + text
 
     host_log_mock.write.side_effect = write_side_effect
-    ssh = SSH(params.user, params.sshkey_file, "dummy-host", "dummy-command",
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, "dummy-host", "dummy-command",
               params.bootdir, host_log_mock)
     log_sample = "log_sample"
     error_sample = "error_sample"
@@ -277,7 +277,7 @@ class TestBootstrap(TestCase):
     command_str = str(popenMock.call_args[0][0])
     self.assertEquals(command_str, "['ssh', '-o', 'ConnectTimeOut=60', '-o', "
             "'StrictHostKeyChecking=no', '-o', 'BatchMode=yes', '-tt', '-i', "
-            "'sshkey_file', 'root@dummy-host', 'dummy-command']")
+            "'sshkey_file', '-p', '123', 'root@dummy-host', 'dummy-command']")
     self.assertEqual(retcode["exitstatus"], 0)
 
     log['text'] = ""
@@ -295,7 +295,7 @@ class TestBootstrap(TestCase):
     process.returncode = 1
 
     dummy_error_message = "dummy_error_message"
-    ssh = SSH(params.user, params.sshkey_file, "dummy-host", "dummy-command",
+    ssh = SSH(params.user, params.sshPort, params.sshkey_file, "dummy-host", "dummy-command",
               params.bootdir, host_log_mock, errorMessage= dummy_error_message)
     retcode = ssh.run()
 
@@ -306,7 +306,7 @@ class TestBootstrap(TestCase):
 
 
   def test_getOsCheckScript(self):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -316,7 +316,7 @@ class TestBootstrap(TestCase):
 
   @patch.object(BootstrapDefault, "getRemoteName")
   def test_getOsCheckScriptRemoteLocation(self, getRemoteName_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -328,7 +328,7 @@ class TestBootstrap(TestCase):
 
   @patch.object(BootstrapDefault, "is_suse")
   def test_getRepoFile(self, is_suse_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -342,7 +342,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_createTargetDir(self, write_mock, run_mock,
                             init_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -351,7 +351,7 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.createTargetDir()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command,
                      "sudo mkdir -p /var/lib/ambari-agent/tmp ; "
                      "sudo chown -R root /var/lib/ambari-agent/tmp ; "
@@ -366,7 +366,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_copyOsCheckScript(self, write_mock, run_mock, init_mock,
                     getOsCheckScriptRemoteLocation_mock, getOsCheckScript_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -377,8 +377,8 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.copyOsCheckScript()
     self.assertEquals(res, expected)
-    input_file = str(init_mock.call_args[0][3])
-    remote_file = str(init_mock.call_args[0][4])
+    input_file = str(init_mock.call_args[0][4])
+    remote_file = str(init_mock.call_args[0][5])
     self.assertEqual(input_file, "OsCheckScript")
     self.assertEqual(remote_file, "OsCheckScriptRemoteLocation")
 
@@ -389,7 +389,7 @@ class TestBootstrap(TestCase):
   @patch.object(OSCheck, "is_ubuntu_family")
   @patch.object(OSCheck, "is_redhat_family")
   def test_getRepoFile(self, is_redhat_family, is_ubuntu_family, is_suse_family, hasPassword_mock, getRemoteName_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     is_redhat_family.return_value = True
@@ -437,7 +437,7 @@ class TestBootstrap(TestCase):
     os_path_exists_mock.side_effect = os_path_exists_side_effect
     os_path_exists_mock.return_value = None
 
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     is_redhat_family.return_value = True
@@ -459,11 +459,11 @@ class TestBootstrap(TestCase):
     ssh_run_mock.side_effect = [expected2, expected4]
     res = bootstrap_obj.copyNeededFiles()
     self.assertEquals(res, expected1["exitstatus"])
-    input_file = str(scp_init_mock.call_args[0][3])
-    remote_file = str(scp_init_mock.call_args[0][4])
+    input_file = str(scp_init_mock.call_args[0][4])
+    remote_file = str(scp_init_mock.call_args[0][5])
     self.assertEqual(input_file, "setupAgentFile")
     self.assertEqual(remote_file, "RemoteName")
-    command = str(ssh_init_mock.call_args[0][3])
+    command = str(ssh_init_mock.call_args[0][4])
     self.assertEqual(command, "sudo chmod 644 RepoFile")
     # Another order
     expected1 = {"exitstatus": 0, "log": "log0", "errormsg": "errorMsg"}
@@ -507,7 +507,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_runOsCheckScript(self, write_mock, run_mock,
                             init_mock, getOsCheckScriptRemoteLocation_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -517,7 +517,7 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.runOsCheckScript()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command,
                      "chmod a+x OsCheckScriptRemoteLocation && "
                      "env PYTHONPATH=$PYTHONPATH:/var/lib/ambari-agent/tmp OsCheckScriptRemoteLocation centos6")
@@ -529,7 +529,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_runSetupAgent(self, write_mock, run_mock,
                          getRunSetupCommand_mock, init_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -539,7 +539,7 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.runSetupAgent()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command, "RunSetupCommand")
 
 
@@ -549,7 +549,7 @@ class TestBootstrap(TestCase):
   def test_getRunSetupCommand(self, getRunSetupWithoutPasswordCommand_mock,
                               getRunSetupWithPasswordCommand_mock,
                               hasPassword_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -568,7 +568,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_createDoneFile(self, write_mock):
     tmp_dir = tempfile.gettempdir()
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", tmp_dir,
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", tmp_dir,
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -587,7 +587,7 @@ class TestBootstrap(TestCase):
   @patch.object(SSH, "run")
   @patch.object(HostLog, "write")
   def test_checkSudoPackage(self, write_mock, run_mock, init_mock, is_redhat_family, is_ubuntu_family, is_suse_family):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -599,7 +599,7 @@ class TestBootstrap(TestCase):
     is_suse_family.return_value = False
     res = bootstrap_obj.checkSudoPackage()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command, "rpm -qa | grep -e '^sudo\-'")
 
   @patch.object(OSCheck, "is_suse_family")
@@ -610,7 +610,7 @@ class TestBootstrap(TestCase):
   @patch.object(HostLog, "write")
   def test_checkSudoPackageUbuntu(self, write_mock, run_mock, init_mock,
                                   is_redhat_family, is_ubuntu_family, is_suse_family):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "ubuntu12",
                                None, "8440", "root")
     is_redhat_family.return_value = False
@@ -622,7 +622,7 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.checkSudoPackage()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command, "dpkg --get-selections|grep -e '^sudo\s*install'")
 
 
@@ -632,7 +632,7 @@ class TestBootstrap(TestCase):
   @patch.object(BootstrapDefault, "getPasswordFile")
   def test_deletePasswordFile(self, getPasswordFile_mock, write_mock, run_mock,
                               init_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -642,7 +642,7 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.deletePasswordFile()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command, "rm PasswordFile")
 
 
@@ -655,7 +655,7 @@ class TestBootstrap(TestCase):
   def test_copyPasswordFile(self, write_mock, ssh_run_mock,
                             ssh_init_mock, scp_run_mock,
                             scp_init_mock, getPasswordFile_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root", password_file="PasswordFile")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -669,11 +669,11 @@ class TestBootstrap(TestCase):
     ssh_run_mock.return_value = expected2
     res = bootstrap_obj.copyPasswordFile()
     self.assertEquals(res, expected1["exitstatus"])
-    input_file = str(scp_init_mock.call_args[0][3])
+    input_file = str(scp_init_mock.call_args[0][4])
     remote_file = str(scp_init_mock.call_args[0][4])
     self.assertEqual(input_file, "PasswordFile")
     self.assertEqual(remote_file, "PasswordFile")
-    command = str(ssh_init_mock.call_args[0][3])
+    command = str(ssh_init_mock.call_args[0][4])
     self.assertEqual(command, "chmod 600 PasswordFile")
     # Another order
     expected1 = {"exitstatus": 0, "log": "log0", "errormsg": "errorMsg"}
@@ -688,7 +688,7 @@ class TestBootstrap(TestCase):
   @patch.object(BootstrapDefault, "getPasswordFile")
   def test_changePasswordFileModeOnHost(self, getPasswordFile_mock, write_mock,
                                         run_mock, init_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -698,14 +698,14 @@ class TestBootstrap(TestCase):
     run_mock.return_value = expected
     res = bootstrap_obj.changePasswordFileModeOnHost()
     self.assertEquals(res, expected)
-    command = str(init_mock.call_args[0][3])
+    command = str(init_mock.call_args[0][4])
     self.assertEqual(command, "chmod 600 PasswordFile")
 
 
   @patch.object(HostLog, "write")
   def test_try_to_execute(self, write_mock):
     expected = 43
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -738,7 +738,7 @@ class TestBootstrap(TestCase):
   @patch("logging.error")
   def test_run(self, error_mock, warn_mock, write_mock, createDoneFile_mock,
                hasPassword_mock, try_to_execute_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -810,7 +810,7 @@ class TestBootstrap(TestCase):
   @patch.object(BootstrapDefault, "createDoneFile")
   @patch.object(HostLog, "write")
   def test_interruptBootstrap(self, write_mock, createDoneFile_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     bootstrap_obj = Bootstrap("hostname", shared_state)
@@ -827,7 +827,7 @@ class TestBootstrap(TestCase):
   @patch.object(BootstrapDefault, "getStatus")
   def test_PBootstrap(self, getStatus_mock, interruptBootstrap_mock, start_mock,
                       info_mock, warn_mock, time_mock, sleep_mock):
-    shared_state = SharedState("root", "sshkey_file", "scriptDir", "bootdir",
+    shared_state = SharedState("root", "123", "sshkey_file", "scriptDir", "bootdir",
                                "setupAgentFile", "ambariServer", "centos6",
                                None, "8440", "root")
     n = 180

+ 2 - 0
ambari-web/app/controllers/wizard.js

@@ -616,6 +616,7 @@ App.WizardController = Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
     sshKey: "", //string
     bootRequestId: null, //string
     sshUser: "root", //string
+    sshPort: "22",
     agentUser: "root" //string
   },
 
@@ -628,6 +629,7 @@ App.WizardController = Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
     sshKey: "", //string
     bootRequestId: null, //string
     sshUser: "", //string
+    sshPort: "22",
     agentUser: "" //string
   },
 

+ 22 - 3
ambari-web/app/controllers/wizard/step2_controller.js

@@ -93,6 +93,14 @@ App.WizardStep2Controller = Em.Controller.extend({
     return this.get('content.installOptions.sshUser');
   }.property('content.installOptions.sshUser'),
 
+  /**
+   * "Shortcut" to <code>content.installOptions.sshPort</code>
+   * @type {string}
+   */
+  sshPort: function () {
+    return this.get('content.installOptions.sshPort');
+  }.property('content.installOptions.sshPort'),
+
   /**
    * "Shortcut" to <code>content.installOptions.agentUser</code>
    * @type {string}
@@ -147,6 +155,17 @@ App.WizardStep2Controller = Em.Controller.extend({
     return null;
   }.property('sshUser', 'useSSH', 'hasSubmitted', 'manualInstall'),
 
+  /**
+   * Error-message if <code>sshPort</code> is empty, null otherwise
+   * @type {string|null}
+   */
+  sshPortError: function () {
+    if (this.get('manualInstall') === false && this.get('useSSH') && Em.isEmpty(this.get('sshPort').trim() ))  {
+      return Em.I18n.t('installer.step2.sshPort.required');
+    }
+    return null;
+  }.property('sshPort', 'useSSH', 'hasSubmitted', 'manualInstall'),
+
   /**
    * Error-message if <code>agentUser</code> is empty, null otherwise
    * @type {string|null}
@@ -163,8 +182,8 @@ App.WizardStep2Controller = Em.Controller.extend({
    * @type {bool}
    */
   isSubmitDisabled: function () {
-    return (this.get('hostsError') || this.get('sshKeyError') || this.get('sshUserError') || this.get('agentUserError'));
-  }.property('hostsError', 'sshKeyError', 'sshUserError', 'agentUserError'),
+    return (this.get('hostsError') || this.get('sshKeyError') || this.get('sshUserError') || this.get('sshPortError') || this.get('agentUserError'));
+  }.property('hostsError', 'sshKeyError', 'sshUserError', 'sshPortError', 'agentUserError'),
 
   installedHostNames: function () {
     var installedHostsName = [];
@@ -292,7 +311,7 @@ App.WizardStep2Controller = Em.Controller.extend({
       this.set('hostsError', Em.I18n.t('installer.step2.hostName.error.already_installed'));
     }
 
-    if (this.get('hostsError') || this.get('sshUserError') || this.get('agentUserError') || this.get('sshKeyError')) {
+    if (this.get('hostsError') || this.get('sshUserError') || this.get('sshPortError') || this.get('agentUserError') || this.get('sshKeyError')) {
       return false;
     }
 

+ 2 - 0
ambari-web/app/controllers/wizard/step3_controller.js

@@ -278,6 +278,7 @@ App.WizardStep3Controller = Em.Controller.extend(App.ReloadPopupMixin, {
         'sshKey': this.get('content.installOptions.sshKey'),
         'hosts': this.getBootstrapHosts(),
         'user': this.get('content.installOptions.sshUser'),
+        'sshPort': this.get('content.installOptions.sshPort'),
         'userRunAs': App.get('supports.customizeAgentUserAccount') ? this.get('content.installOptions.agentUser') : 'root'
     });
     App.router.get(this.get('content.controllerName')).launchBootstrap(bootStrapData, function (requestId) {
@@ -461,6 +462,7 @@ App.WizardStep3Controller = Em.Controller.extend(App.ReloadPopupMixin, {
         'sshKey': this.get('content.installOptions.sshKey'),
         'hosts': hosts.mapProperty('name'),
         'user': this.get('content.installOptions.sshUser'),
+        'sshPort': this.get('content.installOptions.sshPort'),
         'userRunAs': App.get('supports.customizeAgentUserAccount') ? this.get('content.installOptions.agentUser') : 'root'
       });
     this.set('numPolls', 0);

+ 3 - 0
ambari-web/app/messages.js

@@ -586,6 +586,9 @@ Em.I18n.translations = {
   'installer.step2.sshUser.toolTip':'The user account used to install the Ambari Agent on the target host(s) via SSH. This user must be set up with passwordless SSH and sudo access on all the target host(s)',
   'installer.step2.sshUser.placeholder':'Enter user name',
   'installer.step2.sshUser.required':'User name is required',
+  'installer.step2.sshPort':'SSH Port Number',
+  'installer.step2.sshPort.toolTip':'SSH Port Number',
+  'installer.step2.sshPort.required':'SSH Port Number is required.',
   'installer.step2.agentUser':'Ambari Agent User Account',
   'installer.step2.agentUser.toolTip':'The user account used to run the Ambari Agent daemon on the target host(s). This user must be set up with passwordless sudo access on all the target host(s)',
   'installer.step2.bootStrap.error':'Errors were encountered while setting up Ambari Agents on the hosts.',

+ 4 - 0
ambari-web/app/styles/application.less

@@ -719,6 +719,10 @@ h1 {
       margin-right: 10px;
       padding-top: 5px;
     }
+    .ssh-port {
+      margin-right: 10px;
+      padding-top: 5px;
+    }
     #targetHosts {
       .target-hosts-input {
         padding-left: 18px;

+ 12 - 0
ambari-web/app/templates/wizard/step2.hbs

@@ -90,6 +90,18 @@
               {{/if}}
             </div>
           </div>
+           <div class="row-fluid">
+                <label rel="tooltip" {{translateAttr title="installer.step2.sshPort.toolTip"}} class="ssh-port pull-left span4">
+                    {{t installer.step2.sshPort}}
+                </label>
+
+                <div {{bindAttr class="sshPortError:error :control-group"}}>
+                    {{view view.textFieldView valueBinding="content.installOptions.sshPort" isEnabledBinding="content.installOptions.useSsh" }}
+                    {{#if sshPortError}}
+                        <span class="help-inline">{{sshPortError}}</span>
+                    {{/if}}
+                </div>
+           </div>
           {{#if App.supports.customizeAgentUserAccount}}
             <div class="row-fluid">
               <label rel="tooltip" {{translateAttr title="installer.step2.agentUser.toolTip"}} class="ssh-user pull-left span4">

+ 45 - 0
ambari-web/test/controllers/wizard/step2_test.js

@@ -29,21 +29,25 @@ describe('App.WizardStep2Controller', function () {
     {
       manualInstall: false,
       user: '',
+      sshPort:'',
       e: ''
     },
     {
       manualInstall: true,
       user: '',
+      sshPort:'',
       e: null
     },
     {
       manualInstall: true,
       user: 'nobody',
+      sshPort:'123',
       e: null
     },
     {
       manualInstall: false,
       user: 'nobody',
+      sshPort:'123',
       e: null
     }
   ]);
@@ -100,6 +104,15 @@ describe('App.WizardStep2Controller', function () {
     });
   });
 
+  describe('#sshPort', function() {
+      it('should be equal to content.installOptions.sshPort', function() {
+          var controller = App.WizardStep2Controller.create({content: {installOptions: {sshPort: '123'}}});
+          expect(controller.get('sshPort')).to.equal('123');
+          controller.set('content.installOptions.sshPort', '321');
+          expect(controller.get('sshPort')).to.equal('321');
+      });
+  });
+
   describe('#agentUser', function() {
     it('should be equal to content.installOptions.agentUser', function() {
       var controller = App.WizardStep2Controller.create({content: {installOptions: {agentUser: '123'}}});
@@ -285,6 +298,22 @@ describe('App.WizardStep2Controller', function () {
 
   });
 
+  describe('#sshPortError', function () {
+
+      userErrorTests.forEach(function(test) {
+          it('', function() {
+              var controller = App.WizardStep2Controller.create({content: {installOptions: {manualInstall: test.manualInstall, sshPort: test.sshPort}}});
+              if(Em.isNone(test.e)) {
+                  expect(controller.get('sshPortError')).to.equal(null);
+              }
+              else {
+                  expect(controller.get('sshPortError').length).to.be.above(2);
+              }
+          });
+      });
+
+  });
+
   describe('#agentUserError', function () {
 
     afterEach(function () {
@@ -379,6 +408,15 @@ describe('App.WizardStep2Controller', function () {
       expect(controller.evaluateStep()).to.equal(false);
     });
 
+    it('should return false if sshPortError is not empty', function () {
+        var controller = App.WizardStep2Controller.create({
+            hostNames: 'apache.ambari',
+            parseHostNamesAsPatternExpression: Em.K
+        });
+        controller.reopen({sshPortError: 'error'});
+        expect(controller.evaluateStep()).to.equal(false);
+    });
+
     it('should return false if agentUserError is not empty', function () {
       var controller = App.WizardStep2Controller.create({
         hostNames: 'apache.ambari',
@@ -487,6 +525,7 @@ describe('App.WizardStep2Controller', function () {
       hostsError: '',
       sshKeyError: '',
       sshUserError: '',
+      sshPortError: '',
       agentUserError: ''
     });
 
@@ -512,6 +551,12 @@ describe('App.WizardStep2Controller', function () {
       controller.set('sshUserError', '');
       expect(controller.get('isSubmitDisabled').length).to.above(0);
     });
+
+    it('should return value if sshPortError is not empty', function () {
+        controller.set('sshPortError', 'error');
+        controller.set('agentUserError', '');
+        expect(controller.get('isSubmitDisabled').length).to.above(0);
+    });
   });
 
   describe('#installedHostsPopup', function() {

+ 4 - 0
ambari-web/test/controllers/wizard/step3_test.js

@@ -558,6 +558,7 @@ describe('App.WizardStep3Controller', function () {
             installOptions: {
               sshKey: 'key',
               sshUser: 'root',
+              sshPort: '123',
               agentUser: 'user'
             },
             hosts: { "host0": { "name": "host0" }, "host1": { "name": "host1" } }
@@ -571,6 +572,7 @@ describe('App.WizardStep3Controller', function () {
           sshKey: 'key',
           hosts: ['host0', 'host1'],
           user: 'root',
+          sshPort: '123',
           userRunAs: item.userRunAs
         }));
       });
@@ -2410,6 +2412,7 @@ describe('App.WizardStep3Controller', function () {
           installOptions: {
             sshKey: 'key',
             sshUser: 'root',
+            sshPort: '123',
             agentUser: 'user'
           },
           hosts: { "host0": { "name": "host0" }, "host1": { "name": "host1" } },
@@ -2436,6 +2439,7 @@ describe('App.WizardStep3Controller', function () {
           sshKey: 'key',
           hosts: ['host0', 'host1'],
           user: 'root',
+          sshPort: '123',
           userRunAs: item.userRunAs
         }));
       });