/hudson-core/src/main/java/hudson/slaves/CommandLauncher.java

http://github.com/hudson/hudson · Java · 184 lines · 118 code · 24 blank · 42 comment · 10 complexity · a6758eca02f8ee82f2b93298ab294df4 MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson.slaves;
  25. import hudson.EnvVars;
  26. import hudson.Util;
  27. import hudson.Extension;
  28. import hudson.model.Descriptor;
  29. import hudson.model.Hudson;
  30. import hudson.model.TaskListener;
  31. import hudson.remoting.Channel;
  32. import hudson.util.StreamCopyThread;
  33. import hudson.util.FormValidation;
  34. import hudson.util.ProcessTree;
  35. import java.io.IOException;
  36. import java.util.Date;
  37. import java.util.logging.Level;
  38. import java.util.logging.Logger;
  39. import org.kohsuke.stapler.DataBoundConstructor;
  40. import org.kohsuke.stapler.QueryParameter;
  41. /**
  42. * {@link ComputerLauncher} through a remote login mechanism like ssh/rsh.
  43. *
  44. * @author Stephen Connolly
  45. * @author Kohsuke Kawaguchi
  46. */
  47. public class CommandLauncher extends ComputerLauncher {
  48. /**
  49. * Command line to launch the agent, like
  50. * "ssh myslave java -jar /path/to/hudson-remoting.jar"
  51. */
  52. private final String agentCommand;
  53. /**
  54. * Optional environment variables to add to the current environment. Can be null.
  55. */
  56. private final EnvVars env;
  57. @DataBoundConstructor
  58. public CommandLauncher(String command) {
  59. this(command, null);
  60. }
  61. public CommandLauncher(String command, EnvVars env) {
  62. this.agentCommand = command;
  63. this.env = env;
  64. }
  65. public String getCommand() {
  66. return agentCommand;
  67. }
  68. /**
  69. * Gets the formatted current time stamp.
  70. */
  71. private static String getTimestamp() {
  72. return String.format("[%1$tD %1$tT]", new Date());
  73. }
  74. @Override
  75. public void launch(SlaveComputer computer, final TaskListener listener) {
  76. EnvVars _cookie = null;
  77. Process _proc = null;
  78. try {
  79. listener.getLogger().println(hudson.model.Messages.Slave_Launching(getTimestamp()));
  80. if(getCommand().trim().length()==0) {
  81. listener.getLogger().println(Messages.CommandLauncher_NoLaunchCommand());
  82. return;
  83. }
  84. listener.getLogger().println("$ " + getCommand());
  85. ProcessBuilder pb = new ProcessBuilder(Util.tokenize(getCommand()));
  86. final EnvVars cookie = _cookie = EnvVars.createCookie();
  87. pb.environment().putAll(cookie);
  88. {// system defined variables
  89. String rootUrl = Hudson.getInstance().getRootUrl();
  90. if (rootUrl!=null) {
  91. pb.environment().put("HUDSON_URL", rootUrl);
  92. pb.environment().put("SLAVEJAR_URL", rootUrl+"/jnlpJars/slave.jar");
  93. }
  94. }
  95. if (env != null) {
  96. pb.environment().putAll(env);
  97. }
  98. final Process proc = _proc = pb.start();
  99. // capture error information from stderr. this will terminate itself
  100. // when the process is killed.
  101. new StreamCopyThread("stderr copier for remote agent on " + computer.getDisplayName(),
  102. proc.getErrorStream(), listener.getLogger()).start();
  103. computer.setChannel(proc.getInputStream(), proc.getOutputStream(), listener.getLogger(), new Channel.Listener() {
  104. @Override
  105. public void onClosed(Channel channel, IOException cause) {
  106. try {
  107. int exitCode = proc.exitValue();
  108. if (exitCode!=0) {
  109. listener.error("Process terminated with exit code "+exitCode);
  110. }
  111. } catch (IllegalThreadStateException e) {
  112. // hasn't terminated yet
  113. }
  114. try {
  115. ProcessTree.get().killAll(proc, cookie);
  116. } catch (InterruptedException e) {
  117. LOGGER.log(Level.INFO, "interrupted", e);
  118. }
  119. }
  120. });
  121. LOGGER.info("slave agent launched for " + computer.getDisplayName());
  122. } catch (InterruptedException e) {
  123. e.printStackTrace(listener.error(Messages.ComputerLauncher_abortedLaunch()));
  124. } catch (RuntimeException e) {
  125. e.printStackTrace(listener.error(Messages.ComputerLauncher_unexpectedError()));
  126. } catch (Error e) {
  127. e.printStackTrace(listener.error(Messages.ComputerLauncher_unexpectedError()));
  128. } catch (IOException e) {
  129. Util.displayIOException(e, listener);
  130. String msg = Util.getWin32ErrorMessage(e);
  131. if (msg == null) {
  132. msg = "";
  133. } else {
  134. msg = " : " + msg;
  135. }
  136. msg = hudson.model.Messages.Slave_UnableToLaunch(computer.getDisplayName(), msg);
  137. LOGGER.log(Level.SEVERE, msg, e);
  138. e.printStackTrace(listener.error(msg));
  139. if(_proc!=null)
  140. try {
  141. ProcessTree.get().killAll(_proc, _cookie);
  142. } catch (InterruptedException x) {
  143. x.printStackTrace(listener.error(Messages.ComputerLauncher_abortedLaunch()));
  144. }
  145. }
  146. }
  147. private static final Logger LOGGER = Logger.getLogger(CommandLauncher.class.getName());
  148. @Extension
  149. public static class DescriptorImpl extends Descriptor<ComputerLauncher> {
  150. public String getDisplayName() {
  151. return Messages.CommandLauncher_displayName();
  152. }
  153. public FormValidation doCheckCommand(@QueryParameter String value) {
  154. if(Util.fixEmptyAndTrim(value)==null)
  155. return FormValidation.error("Command is empty");
  156. else
  157. return FormValidation.ok();
  158. }
  159. }
  160. }