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

http://github.com/hudson/hudson · Java · 214 lines · 98 code · 18 blank · 98 comment · 1 complexity · 74c780c92cd6ed570ab6b0a5dd5be53b MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2009, Sun Microsystems, Inc.
  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.Launcher.LocalLauncher;
  26. import hudson.Proc;
  27. import hudson.FilePath;
  28. import hudson.model.Computer;
  29. import hudson.model.TaskListener;
  30. import hudson.remoting.Channel;
  31. import hudson.remoting.Launcher;
  32. import hudson.remoting.SocketInputStream;
  33. import hudson.remoting.SocketOutputStream;
  34. import hudson.util.ClasspathBuilder;
  35. import hudson.util.JVMBuilder;
  36. import hudson.util.StreamCopyThread;
  37. import java.io.BufferedInputStream;
  38. import java.io.BufferedOutputStream;
  39. import java.io.IOException;
  40. import java.io.InputStream;
  41. import java.io.OutputStream;
  42. import java.net.InetSocketAddress;
  43. import java.net.ServerSocket;
  44. import java.net.Socket;
  45. import java.util.Map;
  46. import java.util.concurrent.ExecutorService;
  47. import java.util.logging.Level;
  48. import java.util.logging.Logger;
  49. /**
  50. * Various convenient subtype of {@link Channel}s.
  51. *
  52. * @author Kohsuke Kawaguchi
  53. */
  54. public class Channels {
  55. /**
  56. * @deprecated since 2009-04-13.
  57. * Use {@link #forProcess(String, ExecutorService, InputStream, OutputStream, OutputStream, Proc)}
  58. */
  59. public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, Proc proc) throws IOException {
  60. return forProcess(name,execService,in,out,null,proc);
  61. }
  62. /**
  63. * Creates a channel that wraps a remote process, so that when we shut down the connection
  64. * we kill the process.
  65. */
  66. public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, OutputStream header, final Proc proc) throws IOException {
  67. return new Channel(name, execService, in, out, header) {
  68. /**
  69. * Kill the process when the channel is severed.
  70. */
  71. @Override
  72. protected synchronized void terminate(IOException e) {
  73. super.terminate(e);
  74. try {
  75. proc.kill();
  76. } catch (IOException x) {
  77. // we are already in the error recovery mode, so just record it and move on
  78. LOGGER.log(Level.INFO, "Failed to terminate the severed connection",x);
  79. } catch (InterruptedException x) {
  80. // process the interrupt later
  81. Thread.currentThread().interrupt();
  82. }
  83. }
  84. @Override
  85. public synchronized void close() throws IOException {
  86. super.close();
  87. // wait for the child process to complete
  88. try {
  89. proc.join();
  90. } catch (InterruptedException e) {
  91. // process the interrupt later
  92. Thread.currentThread().interrupt();
  93. }
  94. }
  95. };
  96. }
  97. public static Channel forProcess(String name, ExecutorService execService, final Process proc, OutputStream header) throws IOException {
  98. final Thread thread = new StreamCopyThread(name + " stderr", proc.getErrorStream(), header);
  99. thread.start();
  100. return new Channel(name, execService, proc.getInputStream(), proc.getOutputStream(), header) {
  101. /**
  102. * Kill the process when the channel is severed.
  103. */
  104. @Override
  105. protected synchronized void terminate(IOException e) {
  106. super.terminate(e);
  107. proc.destroy();
  108. // the stderr copier should exit by itself
  109. }
  110. @Override
  111. public synchronized void close() throws IOException {
  112. super.close();
  113. // wait for Maven to complete
  114. try {
  115. proc.waitFor();
  116. thread.join();
  117. } catch (InterruptedException e) {
  118. // process the interrupt later
  119. Thread.currentThread().interrupt();
  120. }
  121. }
  122. };
  123. }
  124. /**
  125. * Launches a new JVM with the given classpath and system properties, establish a communication channel,
  126. * and return a {@link Channel} to it.
  127. *
  128. * @param displayName
  129. * Human readable name of what this JVM represents. For example "Selenium grid" or "Hadoop".
  130. * This token is used for messages to {@code listener}.
  131. * @param listener
  132. * The progress of the launcher and the failure information will be sent here. Must not be null.
  133. * @param workDir
  134. * If non-null, the new JVM will have this directory as the working directory. This must be a local path.
  135. * @param classpath
  136. * The classpath of the new JVM. Can be null if you just need {@code slave.jar} (and everything else
  137. * can be sent over the channel.) But if you have jars that are known to be necessary by the new JVM,
  138. * setting it here will improve the classloading performance (by avoiding remote class file transfer.)
  139. * Classes in this classpath will also take precedence over any other classes that's sent via the channel
  140. * later, so it's also useful for making sure you get the version of the classes you want.
  141. * @param systemProperties
  142. * If the new JVM should have a certain system properties set. Can be null.
  143. *
  144. * @return
  145. * never null
  146. * @since 1.300
  147. */
  148. public static Channel newJVM(String displayName, TaskListener listener, FilePath workDir, ClasspathBuilder classpath, Map<String,String> systemProperties) throws IOException {
  149. JVMBuilder vmb = new JVMBuilder();
  150. vmb.systemProperties(systemProperties);
  151. return newJVM(displayName,listener,vmb,workDir,classpath);
  152. }
  153. /**
  154. * Launches a new JVM with the given classpath, establish a communication channel,
  155. * and return a {@link Channel} to it.
  156. *
  157. * @param displayName
  158. * Human readable name of what this JVM represents. For example "Selenium grid" or "Hadoop".
  159. * This token is used for messages to {@code listener}.
  160. * @param listener
  161. * The progress of the launcher and the failure information will be sent here. Must not be null.
  162. * @param workDir
  163. * If non-null, the new JVM will have this directory as the working directory. This must be a local path.
  164. * @param classpath
  165. * The classpath of the new JVM. Can be null if you just need {@code slave.jar} (and everything else
  166. * can be sent over the channel.) But if you have jars that are known to be necessary by the new JVM,
  167. * setting it here will improve the classloading performance (by avoiding remote class file transfer.)
  168. * Classes in this classpath will also take precedence over any other classes that's sent via the channel
  169. * later, so it's also useful for making sure you get the version of the classes you want.
  170. * @param vmb
  171. * A partially configured {@link JVMBuilder} that allows the caller to fine-tune the launch parameter.
  172. *
  173. * @return
  174. * never null
  175. * @since 1.361
  176. */
  177. public static Channel newJVM(String displayName, TaskListener listener, JVMBuilder vmb, FilePath workDir, ClasspathBuilder classpath) throws IOException {
  178. ServerSocket serverSocket = new ServerSocket();
  179. serverSocket.bind(new InetSocketAddress("localhost",0));
  180. serverSocket.setSoTimeout(10*1000);
  181. // use -cp + FQCN instead of -jar since remoting.jar can be rebundled (like in the case of the swarm plugin.)
  182. vmb.classpath().addJarOf(Channel.class);
  183. vmb.mainClass(Launcher.class);
  184. if(classpath!=null)
  185. vmb.args().add("-cp").add(classpath);
  186. vmb.args().add("-connectTo","localhost:"+serverSocket.getLocalPort());
  187. listener.getLogger().println("Starting "+displayName);
  188. Proc p = vmb.launch(new LocalLauncher(listener)).stdout(listener).pwd(workDir).start();
  189. Socket s = serverSocket.accept();
  190. serverSocket.close();
  191. return forProcess("Channel to "+displayName, Computer.threadPoolForRemoting,
  192. new BufferedInputStream(new SocketInputStream(s)),
  193. new BufferedOutputStream(new SocketOutputStream(s)),null,p);
  194. }
  195. private static final Logger LOGGER = Logger.getLogger(Channels.class.getName());
  196. }