Author: chatellier Date: 2008-11-28 17:04:51 +0000 (Fri, 28 Nov 2008) New Revision: 1637 Added: isis-fish/trunk/src/main/java/fr/ifremer/isisfish/simulator/launcher/SshSimulatorLauncher.java Modified: isis-fish/trunk/src/main/java/fr/ifremer/isisfish/IsisConfig.java Log: Add ssh simulation launcher Modified: isis-fish/trunk/src/main/java/fr/ifremer/isisfish/IsisConfig.java =================================================================== --- isis-fish/trunk/src/main/java/fr/ifremer/isisfish/IsisConfig.java 2008-11-28 17:04:21 UTC (rev 1636) +++ isis-fish/trunk/src/main/java/fr/ifremer/isisfish/IsisConfig.java 2008-11-28 17:04:51 UTC (rev 1637) @@ -21,16 +21,6 @@ import static org.codelutin.i18n.I18n._; -import fr.ifremer.isisfish.actions.ExportAction; -import fr.ifremer.isisfish.actions.OtherAction; -import fr.ifremer.isisfish.actions.ImportAction; -import fr.ifremer.isisfish.actions.SimulationAction; -import fr.ifremer.isisfish.actions.VCSAction; -import fr.ifremer.isisfish.simulator.SimulationContext; -import fr.ifremer.isisfish.simulator.launcher.InProcessSimulatorLauncher; -import fr.ifremer.isisfish.simulator.launcher.SimulationService; -import fr.ifremer.isisfish.simulator.launcher.SubProcessSimulationLauncher; -import fr.ifremer.isisfish.vcs.VCS; import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -42,6 +32,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; + import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.lang.time.DurationFormatUtils; import org.apache.commons.logging.Log; @@ -51,6 +42,18 @@ import org.codelutin.util.StringUtil; import org.codelutin.util.VersionNumber; +import fr.ifremer.isisfish.actions.ExportAction; +import fr.ifremer.isisfish.actions.ImportAction; +import fr.ifremer.isisfish.actions.OtherAction; +import fr.ifremer.isisfish.actions.SimulationAction; +import fr.ifremer.isisfish.actions.VCSAction; +import fr.ifremer.isisfish.simulator.SimulationContext; +import fr.ifremer.isisfish.simulator.launcher.InProcessSimulatorLauncher; +import fr.ifremer.isisfish.simulator.launcher.SimulationService; +import fr.ifremer.isisfish.simulator.launcher.SshSimulatorLauncher; +import fr.ifremer.isisfish.simulator.launcher.SubProcessSimulationLauncher; +import fr.ifremer.isisfish.vcs.VCS; + /** * * @author poussin @@ -212,7 +215,7 @@ /** * Retourne l'url du serveur de simulation - * @return + * @return simulator server */ public String getSimulatorServer() { String result = getOption(Option.SIMULATOR_SERVER.key); @@ -221,7 +224,7 @@ /** * Retourne le login pour acceder au serveur de simulation - * @return + * @return simulator username */ public String getSimulatorUsername() { String result = getOption(Option.SIMULATOR_USER_NAME.key); @@ -230,7 +233,7 @@ /** * Retourne le mot de passe pour acceder au serveur de simulation - * @return + * @return simulator password */ public String getSimulatorPassword() { String result = getOption(Option.SIMULATOR_PASSWORD.key); @@ -247,6 +250,46 @@ } /** + * Retourne l'url du serveur de simulation accessible via SSH. + * + * @return simulator server + */ + public String getSimulatorSshServer() { + String result = getOption(Option.SIMULATOR_SSH_SERVER.key); + return result; + } + + /** + * Retourne le login pour acceder au serveur de simulation accessible via SSH. + * + * @return simulator username + */ + public String getSimulatorSshUsername() { + String result = getOption(Option.SIMULATOR_SSH_USER_NAME.key); + return result; + } + + /** + * Retourne le mot de passe pour acceder au serveur de simulation accessible via SSH. + * + * @return simulator password + */ + public String getSimulatorSshPassword() { + String result = getOption(Option.SIMULATOR_SSH_PASSWORD.key); + return result; + } + + /** + * Retourne la clé privée pour acceder au serveur de simulation accessible via SSH. + * + * @return simulator password + */ + public String getSimulatorSshPrivateKey() { + String result = getOption(Option.SIMULATOR_SSH_PRIVATEKEY.key); + return result; + } + + /** * Le type de simulation par defaut a utiliser (local, remote, ...) * @return */ @@ -382,7 +425,7 @@ /** * @return le dictionnaire des tags par defaut d'une simulation a partir * de la propriete {@link Option#DEFAULT_TAG_VALUE} - * @see Option#DEFAULT_TAG_VALUE_PROPERTY_KEY + * @see Option#DEFAULT_TAG_VALUE */ public Map<String, String> getDefaultTagValueAsMap() { Map<String, String> result = new HashMap<String, String>(); @@ -570,12 +613,22 @@ SIMULATOR_CLASSFILE("simulator.classfile", _("isisfish.config.main.defaultSimulator.description"), "DefaultSimulator.java"), /** prevu pour l'architecture de lancement en plugin: local, isis-server, caparmor, ... */ SIMULATOR_LAUNCHER(SimulationService.SIMULATION_LAUNCHER + ".localDefault", _("isisfish.config.main.localSimulator.description"), InProcessSimulatorLauncher.class.getName()), - SIMULATOR_LAUNCHER2(SimulationService.SIMULATION_LAUNCHER + ".localSubDefault", _("isisfish.config.main.localSubSimulator.description"), SubProcessSimulationLauncher.class.getName()), + SIMULATOR_LAUNCHER_SUB(SimulationService.SIMULATION_LAUNCHER + ".subDefault", _("isisfish.config.main.subSimulator.description"), SubProcessSimulationLauncher.class.getName()), + SIMULATOR_LAUNCHER_REMOTE(SimulationService.SIMULATION_LAUNCHER + ".remoteDefault", _("isisfish.config.main.remoteSimulator.description"), SshSimulatorLauncher.class.getName()), SIMULATOR_SERVER("simulation.server", _("isisfish.config.main.simulationServer.description"), "http://simulateur.ifremer.fr:9090"), /** le login a utiliser pour les launcher distant, le type du launcher est ajouter a la cle (car 1 login par launcher) */ SIMULATOR_USER_NAME("simulator.username", _("isisfish.config.main.login.description"), "anonymous"), SIMULATOR_PASSWORD("simulator.password", _("isisfish.config.main.password.description"), "guest"), + + /** Serveur accessible par ssh : address */ + SIMULATOR_SSH_SERVER("simulation.ssh.server", _("isisfish.config.main.simulation.ssh.server.description"), "caparmor.ifremer.fr:22"), + /** Serveur accessible par ssh : login */ + SIMULATOR_SSH_USER_NAME("simulation.ssh.username", _("isisfish.config.main.simulation.ssh.login.description"), "isisfish"), + /** Serveur accessible par ssh : password */ + SIMULATOR_SSH_PASSWORD("simulation.ssh.password", _("isisfish.config.main.simulation.ssh.password.description"), ""), + /** Serveur accessible par ssh : cle privee */ + SIMULATOR_SSH_PRIVATEKEY("simulation.ssh.privatekey", _("isisfish.config.main.simulation.ssh.privatekey.description"), getUserHome() + File.separator + ".ssh" + File.separator + "id_rsa"), LOCALE("locale", _("isisfish.config.main.locale.description"), "fr_FR"), // REGION_MAP("regionMap", _("isisfish.config.main.regionMap.description"), "maps"), Added: isis-fish/trunk/src/main/java/fr/ifremer/isisfish/simulator/launcher/SshSimulatorLauncher.java =================================================================== --- isis-fish/trunk/src/main/java/fr/ifremer/isisfish/simulator/launcher/SshSimulatorLauncher.java (rev 0) +++ isis-fish/trunk/src/main/java/fr/ifremer/isisfish/simulator/launcher/SshSimulatorLauncher.java 2008-11-28 17:04:51 UTC (rev 1637) @@ -0,0 +1,499 @@ +/* *##% + * Copyright (C) 2008 Code Lutin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +package fr.ifremer.isisfish.simulator.launcher; + +import static org.codelutin.i18n.I18n._; + +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.rmi.RemoteException; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UIKeyboardInteractive; +import com.jcraft.jsch.UserInfo; + +import fr.ifremer.isisfish.IsisFish; +import fr.ifremer.isisfish.datastore.SimulationStorage; +import fr.ifremer.isisfish.simulator.SimulationControl; +import fr.ifremer.isisfish.simulator.SimulationException; + +/** + * Use a remote simulation server. + * + * Upload zip simulation file on server and launch + * simulation on that file. + * + * Isis-Fish must be installed on remote server. + * + * Scp code using jsch is taken from : + * http://www.jcraft.com/jsch/examples/ScpTo.java + * + * @author chatellier + * @version $Revision: 1.0 $ + * + * Last update : $Date: 18 nov. 2008 $ + * By : $Author: chatellier $ + */ +public class SshSimulatorLauncher implements SimulatorLauncher { + + /** Class logger */ + protected static Log log = LogFactory.getLog(SshSimulatorLauncher.class); + + protected class MyUserInfo implements UserInfo, UIKeyboardInteractive { + + protected String passphrase; + protected JTextField passphraseField = new JPasswordField(20); + protected String passwd; + protected JTextField passwordField = new JPasswordField(20); + + /** + * Constructor. + */ + public MyUserInfo() { + this(null); + } + + /** + * Constructor with password. + * + * @param passwd password + */ + public MyUserInfo(String passwd) { + this.passwd = passwd; + } + + public boolean promptYesNo(String str) { + log.debug("promptYesNo"); + Object[] options = { "yes", "no" }; + int foo = JOptionPane.showOptionDialog(null, str, "Warning", + JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, + null, options, options[0]); + return foo == 0; + } + + public String getPassphrase() { + log.debug("getPassphrase"); + return passphrase; + } + + public boolean promptPassphrase(String message) { + log.debug("promptPassphrase"); + Object[] ob = { passphraseField }; + int result = JOptionPane.showConfirmDialog(null, ob, message, + JOptionPane.OK_CANCEL_OPTION); + boolean bResult = false; + if (result == JOptionPane.OK_OPTION) { + passphrase = passphraseField.getText(); + bResult = true; + } + return bResult; + } + + public String getPassword() { + log.debug("getPassword"); + return passwd; + } + + public boolean promptPassword(String message) { + log.debug("promptPassword"); + Object[] ob = { passwordField }; + int result = JOptionPane.showConfirmDialog(null, ob, message, + JOptionPane.OK_CANCEL_OPTION); + boolean bResult = false; + if (result == JOptionPane.OK_OPTION) { + passwd = passwordField.getText(); + bResult = true; + } + return bResult; + } + + public void showMessage(String message) { + JOptionPane.showMessageDialog(null, message); + } + + public String[] promptKeyboardInteractive(String destination, + String name, String instruction, String[] prompt, boolean[] echo) { + log.debug("promptKeyboardInteractive"); + final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + Container panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + gbc.weightx = 1.0; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + panel.add(new JLabel(instruction), gbc); + gbc.gridy++; + + gbc.gridwidth = GridBagConstraints.RELATIVE; + + JTextField[] texts = new JTextField[prompt.length]; + for (int i = 0; i < prompt.length; i++) { + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 0; + gbc.weightx = 1; + panel.add(new JLabel(prompt[i]), gbc); + + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weighty = 1; + if (echo[i]) { + texts[i] = new JTextField(20); + } else { + texts[i] = new JPasswordField(20); + } + panel.add(texts[i], gbc); + gbc.gridy++; + } + + String[] response = null; + if (JOptionPane.showConfirmDialog(null, panel, destination + ": " + + name, JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) { + response = new String[prompt.length]; + for (int i = 0; i < prompt.length; i++) { + response[i] = texts[i].getText(); + } + } + // else = cancel + return response; + } + + } + + /* + * @see fr.ifremer.isisfish.simulator.launcher.SimulatorLauncher#maxSimulationThread() + */ + @Override + public int maxSimulationThread() { + return 1; + } + + /* + * @see fr.ifremer.isisfish.simulator.launcher.SimulatorLauncher#simulate(fr.ifremer.isisfish.simulator.launcher.SimulationService, fr.ifremer.isisfish.simulator.SimulationControl, java.io.File) + */ + @Override + public SimulationStorage simulate(SimulationService simulationService, + SimulationControl control, File simulationZip) + throws RemoteException { + + // start ssh session + try { + Session sshSession = openSSHSession(); + + // upload simulation on server + String remotePath = uploadSimulation(sshSession, simulationZip); + + // si l'upload a fonctionné + if(remotePath != null) { + launchSimulation(sshSession, simulationService, control, remotePath); + } + } catch (JSchException e) { + if(log.isErrorEnabled()) { + log.error(_("isisfish.error.simulation.remote.global")); + } + } + + + + return null; + } + + /** + * Connect to remote server throw SSH, and return session. + * + * @return valid opened session + * + * @throws JSchException + */ + protected Session openSSHSession() throws JSchException { + + JSch jsch = new JSch(); + + // add ssh key + File sshKey = new File(IsisFish.config.getSimulatorSshPrivateKey()); + if(sshKey.canRead()) { + jsch.addIdentity(sshKey.getAbsolutePath()); + } + + // extract connection infos + String host = IsisFish.config.getSimulatorSshServer(); + String username = IsisFish.config.getSimulatorSshUsername(); + String password = IsisFish.config.getSimulatorSshPassword(); + int port = 22; // by default, 22 + + if(host.indexOf(':') > 0) { + String sPort = host.substring(host.indexOf(':') + 1); + try { + port = Integer.parseInt(sPort); + } + catch(NumberFormatException e) { + if(log.isWarnEnabled()) { + log.warn(_("isisfish.error.simulation.remote.wrongportvalue", sPort)); + } + } + host = host.substring(0, host.indexOf(':')); + } + + Session session = jsch.getSession(username, host, port); + + // username and password will be given via UserInfo interface. + UserInfo ui = new MyUserInfo(password); + session.setUserInfo(ui); + session.connect(60000); + + return session; + } + + /** + * Close ssh session. + * + * @param session + */ + protected void closeSSHSession(Session session) { + session.disconnect(); + } + + /** + * Upload simulation file to server. + * + * @param session already open valid ssh session + * @param simulationFile simulation file to upload + * + * @return remote file path or <tt>null</tt> if errors + */ + protected String uploadSimulation(Session session, File simulationFile) { + + // return flag + String remotePath = null; + + // file info + String filePath = simulationFile.getAbsolutePath(); + + FileInputStream fis = null; + try { + + // exec 'scp -t rfile' remotely + String command = "scp -p -t " + filePath; + Channel channel = session.openChannel("exec"); + ((ChannelExec) channel).setCommand(command); + + // get I/O streams for remote scp + OutputStream out = channel.getOutputStream(); + InputStream in = channel.getInputStream(); + + channel.connect(); + + // Check input stream validity + if (checkAck(in) == 0) { + + if (log.isDebugEnabled()) { + log.debug("Uploading " + filePath); + } + + // send "C0644 filesize filename", where filename should not include '/' + long filesize = (new File(filePath)).length(); + command = "C0644 " + filesize + " "; + if (filePath.lastIndexOf('/') > 0) { + command += filePath + .substring(filePath.lastIndexOf('/') + 1); + } else { + command += filePath; + } + command += "\n"; + out.write(command.getBytes()); + out.flush(); + + if (checkAck(in) == 0) { + + // send a content of lfile + fis = new FileInputStream(filePath); + byte[] buf = new byte[1024]; + while (true) { + int len = fis.read(buf, 0, buf.length); + if (len <= 0) { + break; + } + out.write(buf, 0, len); //out.flush(); + } + fis.close(); + fis = null; + // send '\0' + buf[0] = 0; + out.write(buf, 0, 1); + out.flush(); + if (checkAck(in) == 0) { + remotePath = filePath; + } + out.close(); + + channel.disconnect(); + } + } + + } catch (JSchException e) { + if (log.isErrorEnabled()) { + log.error(_("isisfish.error.simulation.remote.upload", + filePath)); + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error(_("isisfish.error.simulation.remote.upload", + filePath)); + } + } + finally { + try{ + if(fis!=null) { + fis.close(); + } + }catch(IOException e) { + // exception ignoree + } + } + + return remotePath; + } + + /** + * Check input stream validity. + * + * @param in input stream + */ + protected int checkAck(InputStream in) throws IOException { + int b = in.read(); + // b may be 0 for success, + // 1 for error, + // 2 for fatal error, + // -1 + if (b == 0) { + return b; + } + if (b == -1) { + return b; + } + + if (b == 1 || b == 2) { + StringBuffer sb = new StringBuffer(); + int c; + do { + c = in.read(); + sb.append((char) c); + } while (c != '\n'); + if (b == 1) { // error + if(log.isErrorEnabled()) { + log.error(_("isisfish.error.simulation.remote.upload.stream",sb.toString())); + } + } + if (b == 2) { // fatal error + if(log.isFatalEnabled()) { + log.fatal(_("isisfish.error.simulation.remote.upload.stream",sb.toString())); + } + } + } + return b; + } + + /** + * Launch simulation on remote server. + * + * @param session opened ssh session + * @param simulationService + * @param control + * @param remotePath + * @throws JSchException + */ + protected void launchSimulation(Session session, SimulationService simulationService, SimulationControl control, String remotePath) throws JSchException { + + String simulationId = control.getId(); + + // remote launch command + String command = "java -jar isis-fish/isis-fish.jar"; + // no ui + command += " --option launch.ui false"; + // start simulation + command += " --simulateWithSimulation " + simulationId + " \"" + remotePath + "\""; + + if(log.isDebugEnabled()) { + log.debug("Launch : " + command); + } + + Channel channel = session.openChannel("exec"); + ((ChannelExec) channel).setCommand(command); + + channel.setInputStream(null); + + try { + InputStream in = channel.getInputStream(); + + channel.connect(); + + byte[] tmp = new byte[1024]; + while (true) { + while (in.available() > 0) { + int i = in.read(tmp, 0, 1024); + if (i < 0) + break; + if(log.isInfoEnabled()) { + log.info(new String(tmp, 0, i)); + } + } + if (channel.isClosed()) { + if(log.isInfoEnabled()) { + log.info("exit-status: " + channel.getExitStatus()); + } + break; + } + try { + Thread.sleep(1000); + } catch (Exception ee) { + } + } + channel.disconnect(); + } catch (IOException e) { + throw new SimulationException(_("isisfish.error.simulation.remote.launch"), e); + } + } + + @Override + public String toString() { + return _("isisfish.simulator.launcher.remote"); + } +}
participants (1)
-
chatellier@users.labs.libre-entreprise.org