001/* 002 * Copyright (c) 2004-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: welker $' 006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 007 * '$Revision: 24234 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.actor.ssh; 031 032import java.io.ByteArrayOutputStream; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.kepler.ssh.ExecException; 037import org.kepler.ssh.ExecFactory; 038import org.kepler.ssh.ExecInterface; 039 040import ptolemy.actor.TypedAtomicActor; 041import ptolemy.actor.TypedIOPort; 042import ptolemy.actor.parameters.PortParameter; 043import ptolemy.data.BooleanToken; 044import ptolemy.data.IntToken; 045import ptolemy.data.StringToken; 046import ptolemy.data.expr.Parameter; 047import ptolemy.data.expr.SingletonParameter; 048import ptolemy.data.type.BaseType; 049import ptolemy.kernel.CompositeEntity; 050import ptolemy.kernel.util.Attribute; 051import ptolemy.kernel.util.IllegalActionException; 052import ptolemy.kernel.util.NameDuplicationException; 053 054////////////////////////////////////////////////////////////////////////// 055//// ExecuteCmd 056/** 057 * <p> 058 * Connects to a remote host using Ssh protocol (or does nothing for the local 059 * host) and executes a command. It returns the stdout, stderr and exit code 060 * after the command terminates. 061 * 062 * </p> 063 * <p> 064 * This actor uses the org.kepler.ssh package to have longlasting connections. 065 * 066 * </p> 067 * <p> 068 * If the <i>target</i> is empty string or equals <b>local</b>, the Java Runtime 069 * will be used for local execution instead of ssh. It behaves similarly to 070 * other local command-line exec actors of Kepler but you do not need to change 071 * your workflow for remote/local executions by using this actor. 072 * 073 * </p> 074 * <p> 075 * If the <i>timeoutSeconds</i> is set greater than zero, the command will be 076 * timeouted after the specified amount of time (in seconds). 077 * 078 * </p> 079 * <p> 080 * In case there is an ssh connection related error (or timeout) the 081 * <i>exitcode</i> will be -32767, <i>errors</i> will contain the error message, 082 * <i>stdout</i> and <i>stderr</i> will be empty string. 083 * 084 * </p> 085 * <p> 086 * To ensure fixed rate of token production for SDF, the actor emits an empty 087 * string on <i>errors</i> if the command is executed without ssh related 088 * errors. 089 * 090 * </p> 091 * <p> 092 * If <i>cleanupAfterError</i> is set, the remote process and its children will 093 * be killed (provided, we have the connection still alive). Very useful in case 094 * of timeout because that leaves remote processes running. Use only when 095 * connecting to a unix machine. In case of <i>local</i>, this flag is not used. 096 * 097 * </p> 098 * <p> 099 * Streaming of output during the command execution is not supported by this 100 * actor. 101 * 102 * </p> 103 * <p> 104 * <b>Third party operation</b><br> 105 * If the remote command is expected to ask for a password (or passphrase when 106 * connecting to a remote host with public-key authentication) set the expert 107 * parameter <i>thirdParty</i> for the user@host:port of that third party (it 108 * can be the same as <i>target</i> if a sudo command is executed). 109 * 110 * </p> 111 * <p> 112 * The authentication to the third party should be the same from the target host 113 * and from Kepler's local host. Kepler authenticates (by opening a channel) to 114 * the third party and then it provides the password/passphrase used for the 115 * authentication to the command on the target host. Therefore, this actor 116 * cannot be used to reach a remote host through a proxy machine and execute a 117 * command there. 118 * 119 * </p> 120 * <p> 121 * The third party execution can be used e.g. to execute and ssh/scp command 122 * that connects to another host, also reachable from Kepler's host, to execute 123 * external data transfer commands (bbcp, GridFTP, SRM-Lite etc) or sudo 124 * commands. 125 * 126 * </p> 127 * <p> 128 * The actor will first authenticate Kepler to the third party host (if not yet 129 * done by other actors, e.g. SshSession). During the execution of the command, 130 * it looks for the appearance of the string 'password' or 'passphrase' in the 131 * stdout/stderr streams (case-insensitively). If such string is found, it 132 * writes the authentication code stored within Kepler used for the 133 * authentication. Therefore, the command must read the password on the standard 134 * input, not directly from the terminal device. This process is performed only 135 * once! 136 * 137 * </p> 138 * <p> 139 * The underlying java code does not have pseudo-terminal emulation, so if you 140 * cannot force the command to read passwords from the stdin (e.g. scp command), 141 * you have to use an external tool to execute the command through a 142 * pseudo-terminal. <b>ptyexec</b> is provided in the org.kepler.ssh package, a 143 * C program, that should be compiled and put into the path on the target 144 * machine. Then you can execute <i>"ptyexec scp ..."</i>. 145 * </p> 146 * 147 * @author Norbert Podhorszki 148 * @version $Revision: 24234 $ 149 * @category.name remote 150 * @category.name connection 151 * @category.name external execution 152 */ 153 154public class ExecuteCmd extends TypedAtomicActor { 155 156 /** 157 * Construct an ExecuteCmd actor with the given container and name. Create 158 * the parameters, initialize their values. 159 * 160 * @param container 161 * The container. 162 * @param name 163 * The name of this actor. 164 * @exception IllegalActionException 165 * If the entity cannot be contained by the proposed 166 * container. 167 * @exception NameDuplicationException 168 * If the container already has an actor with this name. 169 */ 170 public ExecuteCmd(CompositeEntity container, String name) 171 throws NameDuplicationException, IllegalActionException { 172 super(container, name); 173 174 // target selects the machine where to connect to 175 target = new PortParameter(this, "target", new StringToken( 176 "[user@]host[:port]")); 177 new Parameter(target.getPort(), "_showName", BooleanToken.TRUE); 178 179 command = new TypedIOPort(this, "command", true, false); 180 command.setTypeEquals(BaseType.STRING); 181 new Parameter(command, "_showName", BooleanToken.TRUE); 182 183 stdout = new TypedIOPort(this, "stdout", false, true); 184 stdout.setTypeEquals(BaseType.STRING); 185 new Parameter(stdout, "_showName", BooleanToken.TRUE); 186 187 stderr = new TypedIOPort(this, "stderr", false, true); 188 stderr.setTypeEquals(BaseType.STRING); 189 new Parameter(stderr, "_showName", BooleanToken.TRUE); 190 191 exitcode = new TypedIOPort(this, "exitcode", false, true); 192 exitcode.setTypeEquals(BaseType.INT); 193 new Parameter(exitcode, "_showName", BooleanToken.TRUE); 194 195 errors = new TypedIOPort(this, "errors", false, true); 196 errors.setTypeEquals(BaseType.STRING); 197 new Parameter(errors, "_showName", BooleanToken.TRUE); 198 199 timeoutSeconds = new Parameter(this, "timeoutSeconds", new IntToken(0)); 200 timeoutSeconds.setTypeEquals(BaseType.INT); 201 202 cleanupAfterError = new Parameter(this, "cleanupAfterError", 203 new BooleanToken(false)); 204 cleanupAfterError.setTypeEquals(BaseType.BOOLEAN); 205 206 /* 207 * isThirdPartyOperation = new Parameter(this, "isThirdPartyOperation", 208 * new BooleanToken(false)); 209 * isThirdPartyOperation.setTypeEquals(BaseType.BOOLEAN); 210 */ 211 212 /* 213 * Hidden, expert PortParameter, directed to SOUTH by default to not to 214 * disturb the port layout of the actor 215 */ 216 thirdParty = new PortParameter(this, "thirdParty", new StringToken("")); 217 /* 218 * shownameTP = new SingletonParameter(thirdParty.getPort(), 219 * "_showname"); shownameTP.setToken(BooleanToken.FALSE); hideTP = new 220 * SingletonParameter(thirdParty.getPort(), "_hide"); 221 * hideTP.setToken(BooleanToken.TRUE); 222 */ 223 new Parameter(thirdParty.getPort(), "_showName", BooleanToken.FALSE); 224 new Parameter(thirdParty.getPort(), "_hide", BooleanToken.TRUE); 225 // DOES NOT WORK: new Parameter(thirdParty.getPort(), "_cardinal", new 226 // StringToken("SOUTH")); 227 228 /* 229 * streamingMode = new Parameter(this, "streaming mode", new 230 * BooleanToken(false)); streamingMode.setTypeEquals(BaseType.BOOLEAN); 231 */ 232 233 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " 234 + "width=\"75\" height=\"50\" style=\"fill:blue\"/>\n" 235 + "<text x=\"5\" y=\"30\"" 236 + "style=\"font-size:14; fill:yellow; font-family:SansSerif\">" 237 + "ExecCmd</text>\n" + "</svg>\n"); 238 } 239 240 // //////////////// Public ports and parameters /////////////////////// 241 242 /** 243 * Target in user@host:port format. If user is not provided, the local 244 * username will be used. If port is not provided, the default port 22 will 245 * be applied. If target is "local" or empty string, the command will be 246 * executed locally, using Java Runtime. 247 */ 248 public PortParameter target; 249 250 /** 251 * The command to be executed on the remote host. It needs to be provided as 252 * a string. 253 */ 254 public TypedIOPort command; 255 256 /** 257 * Third party target in user@host:port format. If user is not provided, the 258 * local username will be used. If port is not provided, the default port 22 259 * will be applied. 260 */ 261 public PortParameter thirdParty; 262 263 /** _hide parameter of thirdParty. */ 264 public SingletonParameter hideTP; 265 266 /** _showname parameter of thirdParty. */ 267 public SingletonParameter shownameTP; 268 269 /** 270 * Output of the command as it would output to the standard shell output. 271 */ 272 public TypedIOPort stdout; 273 274 /** 275 * The error that were reported by the remote execution or while connecting. 276 */ 277 public TypedIOPort stderr; 278 279 /** 280 * The exit code of the command. 281 */ 282 public TypedIOPort exitcode; 283 284 /** 285 * The string representation of all the errors that happened during the 286 * execution of the actor, if there are any. 287 */ 288 public TypedIOPort errors; 289 290 /** 291 * Timeout in seconds for the command to be executed. 0 means waiting 292 * indefinitely for command termination. 293 */ 294 public Parameter timeoutSeconds; 295 296 /** 297 * Enforce killing remote process(es) after an error or timeout. Unix 298 * specific solution is used, therefore you should not set this flag if 299 * connecting to other servers. But it is very useful for unix as timeout 300 * leaves processes living there, and sometimes errors too. All processes 301 * belonging to the same group as the remote command (i.e. its children) 302 * will be killed. 303 */ 304 public Parameter cleanupAfterError; 305 306 /** 307 * Specifying whether the output should be sent in a streaming mode. 308 * Streaming is not implemented yet. 309 */ 310 public Parameter streamingMode; 311 312 /** 313 * Specifying whether third party is to be defined. If false, the 314 * portparameter thirdParty is hidden, otherwise it is shown. 315 */ 316 public Parameter isThirdPartyOperation; 317 318 // ///////////////////////////////////////////////////////////////// 319 // // public methods //// 320 321 /** 322 * If the specified attribute is <i>showTriggerPort</i>, then get the value 323 * of it and re-render the trigger port. If it is true, show the trigger 324 * port; if it is false, hide the trigger port. 325 * 326 * @param attribute 327 * The attribute that has changed. 328 * @exception IllegalActionException. 329 */ 330 public void attributeChanged(Attribute attribute) 331 throws IllegalActionException { 332 if (attribute == isThirdPartyOperation) { 333 BooleanToken useTP = (BooleanToken) isThirdPartyOperation 334 .getToken(); 335 log.debug("flag isThirdPartyOperation has changed to " 336 + useTP.booleanValue()); 337 try { 338 if (useTP.booleanValue()) { 339 thirdParty.setContainer(this); 340 thirdParty.getPort().setContainer(this); 341 } else { 342 thirdParty.getPort().setContainer(null); 343 thirdParty.setContainer(null); 344 } 345 // hideTP.setToken(useTP.not()); 346 // shownameTP.setToken(useTP); 347 } catch (NameDuplicationException ndex) { 348 log.error("Trouble with thirdParty portparameter: " 349 + ndex.getMessage()); 350 } 351 } 352 } 353 354 /** 355 * Send the token in the <i>value</i> parameter to the output. 356 * 357 * @exception IllegalActionException 358 * If it is thrown by the send() method sending out the 359 * token. 360 */ 361 public void fire() throws IllegalActionException { 362 super.fire(); 363 364 // process inputs 365 target.update(); 366 StringToken tg = (StringToken) target.getToken(); 367 String strTarget = tg.stringValue(); 368 String strCommand = ((StringToken) command.get(0)).stringValue(); 369 int timeout = ((IntToken) timeoutSeconds.getToken()).intValue(); 370 boolean cleanup = ((BooleanToken) cleanupAfterError.getToken()) 371 .booleanValue(); 372 /* 373 * boolean streaming = ((BooleanToken) 374 * streamingMode.getToken()).booleanValue(); 375 */ 376 377 // third party target 378 thirdParty.update(); 379 String strThirdParty = ((StringToken) thirdParty.getToken()) 380 .stringValue(); 381 382 int exitCode = 0; 383 ByteArrayOutputStream cmdStdout = new ByteArrayOutputStream(); 384 ByteArrayOutputStream cmdStderr = new ByteArrayOutputStream(); 385 386 // execute command 387 try { 388 // get the execution object 389 log.info("Get exec object for " + strTarget); 390 ExecInterface execObj = ExecFactory.getExecObject(strTarget); 391 392 execObj.setTimeout(timeout, false, false); 393 execObj.setForcedCleanUp(cleanup); 394 395 log.info("Exec cmd: " + strCommand); 396 exitCode = execObj.executeCmd(strCommand, cmdStdout, cmdStderr, 397 strThirdParty); 398 399 } catch (ExecException e) { 400 String errText = new String("ExecuteCmd error:\n" + e.getMessage()); 401 402 if (isDebugging) 403 log.debug(errText); 404 405 stdout.send(0, new StringToken("")); 406 stderr.send(0, new StringToken("")); 407 exitcode.send(0, new IntToken(-32767)); 408 errors.send(0, new StringToken(errText)); 409 return; 410 } 411 412 if (isDebugging) 413 log.debug("exit code = " + exitCode); 414 415 // send stdout, stderr and empty string as internal errors 416 exitcode.send(0, new IntToken(exitCode)); 417 stdout.send(0, new StringToken(cmdStdout.toString())); 418 stderr.send(0, new StringToken(cmdStderr.toString())); 419 errors.send(0, new StringToken("")); 420 421 } // end-method fire() 422 423 private static final Log log = LogFactory 424 .getLog(ExecuteCmd.class.getName()); 425 private static final boolean isDebugging = log.isDebugEnabled(); 426} 427 428// vim: sw=4 ts=4 et