PageRenderTime 37ms CodeModel.GetById 7ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

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

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