/hudson-core/src/main/java/hudson/model/Slave.java

http://github.com/hudson/hudson · Java · 422 lines · 238 code · 68 blank · 116 comment · 29 complexity · c5feb1256b1052d236e754d8b4721549 MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Martin Eigenbrodt, Stephen Connolly, Tom Huybrechts
  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.model;
  25. import hudson.FilePath;
  26. import hudson.Launcher;
  27. import hudson.Util;
  28. import hudson.Launcher.RemoteLauncher;
  29. import hudson.diagnosis.OldDataMonitor;
  30. import hudson.model.Descriptor.FormException;
  31. import hudson.remoting.Callable;
  32. import hudson.remoting.VirtualChannel;
  33. import hudson.slaves.CommandLauncher;
  34. import hudson.slaves.ComputerLauncher;
  35. import hudson.slaves.DumbSlave;
  36. import hudson.slaves.JNLPLauncher;
  37. import hudson.slaves.NodeDescriptor;
  38. import hudson.slaves.NodeProperty;
  39. import hudson.slaves.NodePropertyDescriptor;
  40. import hudson.slaves.RetentionStrategy;
  41. import hudson.slaves.SlaveComputer;
  42. import hudson.util.ClockDifference;
  43. import hudson.util.DescribableList;
  44. import hudson.util.FormValidation;
  45. import java.io.File;
  46. import java.io.IOException;
  47. import java.io.InputStream;
  48. import java.io.Serializable;
  49. import java.net.MalformedURLException;
  50. import java.net.URL;
  51. import java.net.URLConnection;
  52. import java.util.ArrayList;
  53. import java.util.List;
  54. import java.util.Set;
  55. import javax.servlet.ServletException;
  56. import org.apache.commons.io.IOUtils;
  57. import org.kohsuke.stapler.DataBoundConstructor;
  58. import org.kohsuke.stapler.HttpResponse;
  59. import org.kohsuke.stapler.QueryParameter;
  60. import org.kohsuke.stapler.StaplerRequest;
  61. import org.kohsuke.stapler.StaplerResponse;
  62. /**
  63. * Information about a Hudson slave node.
  64. *
  65. * <p>
  66. * Ideally this would have been in the <tt>hudson.slaves</tt> package,
  67. * but for compatibility reasons, it can't.
  68. *
  69. * <p>
  70. * TODO: move out more stuff to {@link DumbSlave}.
  71. *
  72. * @author Kohsuke Kawaguchi
  73. */
  74. public abstract class Slave extends Node implements Serializable {
  75. /**
  76. * Name of this slave node.
  77. */
  78. protected String name;
  79. /**
  80. * Description of this node.
  81. */
  82. private final String description;
  83. /**
  84. * Path to the root of the workspace
  85. * from the view point of this node, such as "/hudson"
  86. */
  87. protected final String remoteFS;
  88. /**
  89. * Number of executors of this node.
  90. */
  91. private int numExecutors = 2;
  92. /**
  93. * Job allocation strategy.
  94. */
  95. private Mode mode;
  96. /**
  97. * Slave availablility strategy.
  98. */
  99. private RetentionStrategy retentionStrategy;
  100. /**
  101. * The starter that will startup this slave.
  102. */
  103. private ComputerLauncher launcher;
  104. /**
  105. * Whitespace-separated labels.
  106. */
  107. private String label="";
  108. private /*almost final*/ DescribableList<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(Hudson.getInstance());
  109. /**
  110. * Lazily computed set of labels from {@link #label}.
  111. */
  112. private transient volatile Set<Label> labels;
  113. @DataBoundConstructor
  114. public Slave(String name, String nodeDescription, String remoteFS, String numExecutors,
  115. Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException {
  116. this(name,nodeDescription,remoteFS,Util.tryParseNumber(numExecutors, 1).intValue(),mode,labelString,launcher,retentionStrategy, nodeProperties);
  117. }
  118. /**
  119. * @deprecated since 2009-02-20.
  120. */
  121. @Deprecated
  122. public Slave(String name, String nodeDescription, String remoteFS, int numExecutors,
  123. Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException, IOException {
  124. this(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, new ArrayList());
  125. }
  126. public Slave(String name, String nodeDescription, String remoteFS, int numExecutors,
  127. Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException {
  128. this.name = name;
  129. this.description = nodeDescription;
  130. this.numExecutors = numExecutors;
  131. this.mode = mode;
  132. this.remoteFS = Util.fixNull(remoteFS).trim();
  133. this.label = Util.fixNull(labelString).trim();
  134. this.launcher = launcher;
  135. this.retentionStrategy = retentionStrategy;
  136. getAssignedLabels(); // compute labels now
  137. this.nodeProperties.replaceBy(nodeProperties);
  138. if (name.equals(""))
  139. throw new FormException(Messages.Slave_InvalidConfig_NoName(), null);
  140. // if (remoteFS.equals(""))
  141. // throw new FormException(Messages.Slave_InvalidConfig_NoRemoteDir(name), null);
  142. if (this.numExecutors<=0)
  143. throw new FormException(Messages.Slave_InvalidConfig_Executors(name), null);
  144. }
  145. public ComputerLauncher getLauncher() {
  146. return launcher == null ? new JNLPLauncher() : launcher;
  147. }
  148. public void setLauncher(ComputerLauncher launcher) {
  149. this.launcher = launcher;
  150. }
  151. public String getRemoteFS() {
  152. return remoteFS;
  153. }
  154. public String getNodeName() {
  155. return name;
  156. }
  157. public void setNodeName(String name) {
  158. this.name = name;
  159. }
  160. public String getNodeDescription() {
  161. return description;
  162. }
  163. public int getNumExecutors() {
  164. return numExecutors;
  165. }
  166. public Mode getMode() {
  167. return mode;
  168. }
  169. public void setMode(Mode mode) {
  170. this.mode = mode;
  171. }
  172. public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
  173. return nodeProperties;
  174. }
  175. public RetentionStrategy getRetentionStrategy() {
  176. return retentionStrategy == null ? RetentionStrategy.Always.INSTANCE : retentionStrategy;
  177. }
  178. public void setRetentionStrategy(RetentionStrategy availabilityStrategy) {
  179. this.retentionStrategy = availabilityStrategy;
  180. }
  181. public String getLabelString() {
  182. return Util.fixNull(label).trim();
  183. }
  184. public ClockDifference getClockDifference() throws IOException, InterruptedException {
  185. VirtualChannel channel = getChannel();
  186. if(channel==null)
  187. throw new IOException(getNodeName()+" is offline");
  188. long startTime = System.currentTimeMillis();
  189. long slaveTime = channel.call(new GetSystemTime());
  190. long endTime = System.currentTimeMillis();
  191. return new ClockDifference((startTime+endTime)/2 - slaveTime);
  192. }
  193. public Computer createComputer() {
  194. return new SlaveComputer(this);
  195. }
  196. public FilePath getWorkspaceFor(TopLevelItem item) {
  197. FilePath r = getWorkspaceRoot();
  198. if(r==null) return null; // offline
  199. return r.child(item.getName());
  200. }
  201. public FilePath getRootPath() {
  202. return createPath(remoteFS);
  203. }
  204. /**
  205. * Root directory on this slave where all the job workspaces are laid out.
  206. * @return
  207. * null if not connected.
  208. */
  209. public FilePath getWorkspaceRoot() {
  210. FilePath r = getRootPath();
  211. if(r==null) return null;
  212. return r.child(WORKSPACE_ROOT);
  213. }
  214. /**
  215. * Web-bound object used to serve jar files for JNLP.
  216. */
  217. public static final class JnlpJar implements HttpResponse {
  218. private final String fileName;
  219. public JnlpJar(String fileName) {
  220. this.fileName = fileName;
  221. }
  222. public void doIndex( StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
  223. URLConnection con = connect();
  224. // since we end up redirecting users to jnlpJars/foo.jar/, set the content disposition
  225. // so that browsers can download them in the right file name.
  226. // see http://support.microsoft.com/kb/260519 and http://www.boutell.com/newfaq/creating/forcedownload.html
  227. rsp.setHeader("Content-Disposition", "attachment; filename=" + fileName);
  228. InputStream in = con.getInputStream();
  229. rsp.serveFile(req, in, con.getLastModified(), con.getContentLength(), "*.jar" );
  230. in.close();
  231. }
  232. public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
  233. doIndex(req,rsp);
  234. }
  235. private URLConnection connect() throws IOException {
  236. URL res = getURL();
  237. return res.openConnection();
  238. }
  239. public URL getURL() throws MalformedURLException {
  240. URL res = Hudson.getInstance().servletContext.getResource("/WEB-INF/" + fileName);
  241. if(res==null) {
  242. // during the development this path doesn't have the files.
  243. res = new URL(new File(".").getAbsoluteFile().toURI().toURL(),"target/generated-resources/WEB-INF/"+fileName);
  244. }
  245. return res;
  246. }
  247. public byte[] readFully() throws IOException {
  248. InputStream in = connect().getInputStream();
  249. try {
  250. return IOUtils.toByteArray(in);
  251. } finally {
  252. in.close();
  253. }
  254. }
  255. }
  256. public Launcher createLauncher(TaskListener listener) {
  257. SlaveComputer c = getComputer();
  258. return new RemoteLauncher(listener, c.getChannel(), c.isUnix()).decorateFor(this);
  259. }
  260. /**
  261. * Gets the corresponding computer object.
  262. */
  263. public SlaveComputer getComputer() {
  264. return (SlaveComputer)toComputer();
  265. }
  266. @Override
  267. public boolean equals(Object o) {
  268. if (this == o) return true;
  269. if (o == null || getClass() != o.getClass()) return false;
  270. final Slave that = (Slave) o;
  271. return name.equals(that.name);
  272. }
  273. @Override
  274. public int hashCode() {
  275. return name.hashCode();
  276. }
  277. /**
  278. * Invoked by XStream when this object is read into memory.
  279. */
  280. private Object readResolve() {
  281. // convert the old format to the new one
  282. if(command!=null && agentCommand==null) {
  283. if(command.length()>0) command += ' ';
  284. agentCommand = command+"java -jar ~/bin/slave.jar";
  285. }
  286. if (command!=null || localFS!=null)
  287. OldDataMonitor.report(Hudson.getInstance(), "1.69");
  288. if (launcher == null) {
  289. launcher = (agentCommand == null || agentCommand.trim().length() == 0)
  290. ? new JNLPLauncher()
  291. : new CommandLauncher(agentCommand);
  292. }
  293. if(nodeProperties==null)
  294. nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(Hudson.getInstance());
  295. return this;
  296. }
  297. public SlaveDescriptor getDescriptor() {
  298. Descriptor d = Hudson.getInstance().getDescriptorOrDie(getClass());
  299. if (d instanceof SlaveDescriptor)
  300. return (SlaveDescriptor) d;
  301. throw new IllegalStateException(d.getClass()+" needs to extend from SlaveDescriptor");
  302. }
  303. public static abstract class SlaveDescriptor extends NodeDescriptor {
  304. public FormValidation doCheckNumExecutors(@QueryParameter String value) {
  305. return FormValidation.validatePositiveInteger(value);
  306. }
  307. /**
  308. * Performs syntactical check on the remote FS for slaves.
  309. */
  310. public FormValidation doCheckRemoteFS(@QueryParameter String value) throws IOException, ServletException {
  311. if(Util.fixEmptyAndTrim(value)==null)
  312. return FormValidation.error(Messages.Slave_Remote_Director_Mandatory());
  313. if(value.startsWith("\\\\") || value.startsWith("/net/"))
  314. return FormValidation.warning(Messages.Slave_Network_Mounted_File_System_Warning());
  315. return FormValidation.ok();
  316. }
  317. }
  318. //
  319. // backward compatibility
  320. //
  321. /**
  322. * In Hudson < 1.69 this was used to store the local file path
  323. * to the remote workspace. No longer in use.
  324. *
  325. * @deprecated
  326. * ... but still in use during the transition.
  327. */
  328. private File localFS;
  329. /**
  330. * In Hudson < 1.69 this was used to store the command
  331. * to connect to the remote machine, like "ssh myslave".
  332. *
  333. * @deprecated
  334. */
  335. private transient String command;
  336. /**
  337. * Command line to launch the agent, like
  338. * "ssh myslave java -jar /path/to/hudson-remoting.jar"
  339. */
  340. private transient String agentCommand;
  341. /**
  342. * Obtains the system clock.
  343. */
  344. private static final class GetSystemTime implements Callable<Long,RuntimeException> {
  345. public Long call() {
  346. return System.currentTimeMillis();
  347. }
  348. private static final long serialVersionUID = 1L;
  349. }
  350. /**
  351. * Determines the workspace root file name for those who really really need the shortest possible path name.
  352. */
  353. private static final String WORKSPACE_ROOT = System.getProperty(Slave.class.getName()+".workspaceRoot","workspace");
  354. }