Slave.java 11.6 KB
Newer Older
K
kohsuke 已提交
1 2 3 4
package hudson.model;

import hudson.FilePath;
import hudson.Launcher;
5
import hudson.Launcher.RemoteLauncher;
K
kohsuke 已提交
6
import hudson.Util;
7
import hudson.slaves.ComputerLauncher;
8
import hudson.slaves.RetentionStrategy;
9 10
import hudson.slaves.CommandLauncher;
import hudson.slaves.JNLPLauncher;
11
import hudson.slaves.SlaveComputer;
K
kohsuke 已提交
12
import hudson.model.Descriptor.FormException;
K
kohsuke 已提交
13 14
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
15 16
import hudson.tasks.DynamicLabeler;
import hudson.tasks.LabelFinder;
K
kohsuke 已提交
17
import hudson.util.ClockDifference;
18 19
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
kohsuke 已提交
20

21
import javax.servlet.ServletException;
K
kohsuke 已提交
22 23 24 25
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
26 27
import java.net.URL;
import java.net.URLConnection;
28
import java.util.*;
K
kohsuke 已提交
29

K
kohsuke 已提交
30 31 32
/**
 * Information about a Hudson slave node.
 *
K
noted  
kohsuke 已提交
33 34 35 36
 * <p>
 * Ideally this would have been in the <tt>hudson.slaves</tt> package,
 * but for compatibility reasons, it can't.
 *
K
kohsuke 已提交
37 38
 * @author Kohsuke Kawaguchi
 */
K
kohsuke 已提交
39
public final class Slave implements Node, Serializable {
K
kohsuke 已提交
40
    /**
K
kohsuke 已提交
41
     * Name of this slave node.
K
kohsuke 已提交
42
     */
K
kohsuke 已提交
43
    protected final String name;
K
kohsuke 已提交
44 45 46 47 48 49 50 51

    /**
     * Description of this node.
     */
    private final String description;

    /**
     * Path to the root of the workspace
K
kohsuke 已提交
52
     * from the view point of this node, such as "/hudson"
K
kohsuke 已提交
53
     */
K
kohsuke 已提交
54
    protected final String remoteFS;
K
kohsuke 已提交
55 56 57 58 59 60 61 62 63 64 65

    /**
     * Number of executors of this node.
     */
    private int numExecutors = 2;

    /**
     * Job allocation strategy.
     */
    private Mode mode;

K
kohsuke 已提交
66
    /**
67 68
     * Slave availablility strategy.
     */
K
kohsuke 已提交
69
    private RetentionStrategy retentionStrategy;
70 71 72

    /**
     * The starter that will startup this slave.
73
     */
74
    private ComputerLauncher launcher;
K
kohsuke 已提交
75

76 77 78 79 80 81 82 83 84 85
    /**
     * Whitespace-separated labels.
     */
    private String label="";

    /**
     * Lazily computed set of labels from {@link #label}.
     */
    private transient volatile Set<Label> labels;

86 87 88
    private transient volatile Set<Label> dynamicLabels;
    private transient volatile int dynamicLabelsInstanceHash;

K
kohsuke 已提交
89 90 91
    /**
     * @stapler-constructor
     */
92 93
    public Slave(String name, String description, String remoteFS, String numExecutors,
                 Mode mode, String label) throws FormException {
K
kohsuke 已提交
94 95
        this.name = name;
        this.description = description;
96
        this.numExecutors = Util.tryParseNumber(numExecutors, 1).intValue();
K
kohsuke 已提交
97
        this.mode = mode;
K
kohsuke 已提交
98
        this.remoteFS = remoteFS;
99 100
        this.label = Util.fixNull(label).trim();
        getAssignedLabels();    // compute labels now
K
kohsuke 已提交
101

K
d'oh!  
kohsuke 已提交
102
        if (name.equals(""))
K
i18n  
kohsuke 已提交
103
            throw new FormException(Messages.Slave_InvalidConfig_NoName(), null);
K
kohsuke 已提交
104

105 106 107 108 109
        // this prevents the config from being saved when slaves are offline.
        // on a large deployment with a lot of slaves, some slaves are bound to be offline,
        // so this check is harmful.
        //if (!localFS.exists())
        //    throw new FormException("Invalid slave configuration for " + name + ". No such directory exists: " + localFS, null);
K
kohsuke 已提交
110
        if (remoteFS.equals(""))
K
i18n  
kohsuke 已提交
111
            throw new FormException(Messages.Slave_InvalidConfig_NoRemoteDir(name), null);
112

113
        if (this.numExecutors<=0)
K
i18n  
kohsuke 已提交
114
            throw new FormException(Messages.Slave_InvalidConfig_Executors(name), null);
K
kohsuke 已提交
115 116
    }

117
    public ComputerLauncher getLauncher() {
118
        return launcher == null ? new JNLPLauncher() : launcher;
119 120
    }

121
    public void setLauncher(ComputerLauncher launcher) {
122
        this.launcher = launcher;
K
kohsuke 已提交
123 124 125 126 127 128
    }

    public String getRemoteFS() {
        return remoteFS;
    }

K
kohsuke 已提交
129 130
    public String getNodeName() {
        return name;
K
kohsuke 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144
    }

    public String getNodeDescription() {
        return description;
    }

    public int getNumExecutors() {
        return numExecutors;
    }

    public Mode getMode() {
        return mode;
    }

K
kohsuke 已提交
145 146
    public RetentionStrategy getRetentionStrategy() {
        return retentionStrategy == null ? RetentionStrategy.Always.INSTANCE : retentionStrategy;
147 148
    }

K
kohsuke 已提交
149 150
    public void setRetentionStrategy(RetentionStrategy availabilityStrategy) {
        this.retentionStrategy = availabilityStrategy;
151 152
    }

153 154 155
    public String getLabelString() {
        return Util.fixNull(label).trim();
    }
156

157
    public Set<Label> getAssignedLabels() {
158 159
        // todo refactor to make dynamic labels a bit less hacky
        if(labels==null || isChangedDynamicLabels()) {
160 161 162 163 164 165 166 167
            Set<Label> r = new HashSet<Label>();
            String ls = getLabelString();
            if(ls.length()>0) {
                for( String l : ls.split(" +")) {
                    r.add(Hudson.getInstance().getLabel(l));
                }
            }
            r.add(getSelfLabel());
168
            r.addAll(getDynamicLabels());
169 170 171 172 173
            this.labels = Collections.unmodifiableSet(r);
        }
        return labels;
    }

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    /**
     * Check if we should rebuild the list of dynamic labels.
     * @todo make less hacky
     * @return
     */
    private boolean isChangedDynamicLabels() {
        Computer comp = getComputer();
        if (comp == null) {
            return dynamicLabelsInstanceHash != 0;
        } else {
            if (dynamicLabelsInstanceHash == comp.hashCode()) {
                return false;
            }
            dynamicLabels = null; // force a re-calc
            return true;
        }
    }

    /**
     * Returns the possibly empty set of labels that it has been determined as supported by this node.
     *
     * @todo make less hacky
     * @see hudson.tasks.LabelFinder
197 198 199
     *
     * @return
     *      never null.
200 201
     */
    public Set<Label> getDynamicLabels() {
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
        // another thread may preempt and set dynamicLabels field to null,
        // so a care needs to be taken to avoid race conditions under all circumstances.
        Set<Label> labels = dynamicLabels;
        if (labels != null)     return labels;

        synchronized (this) {
            labels = dynamicLabels;
            if (labels != null)     return labels;

            dynamicLabels = labels = new HashSet<Label>();
            Computer computer = getComputer();
            VirtualChannel channel;
            if (computer != null && (channel = computer.getChannel()) != null) {
                dynamicLabelsInstanceHash = computer.hashCode();
                for (DynamicLabeler labeler : LabelFinder.LABELERS) {
                    for (String label : labeler.findLabels(channel)) {
                        labels.add(Hudson.getInstance().getLabel(label));
219 220
                    }
                }
221 222
            } else {
                dynamicLabelsInstanceHash = 0;
223
            }
224 225

            return labels;
226 227
        }
    }
228 229 230 231 232

    public Label getSelfLabel() {
        return Hudson.getInstance().getLabel(name);
    }

233
    public ClockDifference getClockDifference() throws IOException, InterruptedException {
K
kohsuke 已提交
234
        VirtualChannel channel = getComputer().getChannel();
K
kohsuke 已提交
235 236
        if(channel==null)
            throw new IOException(getNodeName()+" is offline");
K
kohsuke 已提交
237

K
kohsuke 已提交
238
        long startTime = System.currentTimeMillis();
K
kohsuke 已提交
239
        long slaveTime = channel.call(new GetSystemTime());
K
kohsuke 已提交
240
        long endTime = System.currentTimeMillis();
K
kohsuke 已提交
241

242
        return new ClockDifference((startTime+endTime)/2 - slaveTime);
K
kohsuke 已提交
243 244
    }

K
kohsuke 已提交
245
    public Computer createComputer() {
246
        return new SlaveComputer(this);
K
kohsuke 已提交
247 248
    }

249
    public FilePath getWorkspaceFor(TopLevelItem item) {
K
kohsuke 已提交
250 251 252
        FilePath r = getWorkspaceRoot();
        if(r==null)     return null;    // offline
        return r.child(item.getName());
253 254
    }

K
kohsuke 已提交
255
    public FilePath getRootPath() {
256 257 258 259
        return createPath(remoteFS);
    }

    public FilePath createPath(String absolutePath) {
K
kohsuke 已提交
260 261
        VirtualChannel ch = getComputer().getChannel();
        if(ch==null)    return null;    // offline
262
        return new FilePath(ch,absolutePath);
K
kohsuke 已提交
263 264
    }

K
kohsuke 已提交
265 266
    /**
     * Root directory on this slave where all the job workspaces are laid out.
K
kohsuke 已提交
267 268
     * @return
     *      null if not connected.
K
kohsuke 已提交
269 270
     */
    public FilePath getWorkspaceRoot() {
K
kohsuke 已提交
271 272 273
        FilePath r = getRootPath();
        if(r==null) return null;
        return r.child("workspace");
K
kohsuke 已提交
274
    }
K
kohsuke 已提交
275

276 277 278 279
    /**
     * Web-bound object used to serve jar files for JNLP.
     */
    public static final class JnlpJar {
K
kohsuke 已提交
280
        private final String fileName;
281

K
kohsuke 已提交
282 283
        public JnlpJar(String fileName) {
            this.fileName = fileName;
284 285 286
        }

        public void doIndex( StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
K
kohsuke 已提交
287 288 289 290
            URL res = req.getServletContext().getResource("/WEB-INF/" + fileName);
            if(res==null) {
                // during the development this path doesn't have the files.
                res = new URL(new File(".").getAbsoluteFile().toURL(),"target/generated-resources/WEB-INF/"+fileName);
291 292
            }

K
kohsuke 已提交
293
            URLConnection con = res.openConnection();
294 295 296 297 298 299 300
            InputStream in = con.getInputStream();
            rsp.serveFile(req, in, con.getLastModified(), con.getContentLength(), "*.jar" );
            in.close();
        }

    }

K
kohsuke 已提交
301
    public Launcher createLauncher(TaskListener listener) {
302
        SlaveComputer c = getComputer();
303
        return new RemoteLauncher(listener, c.getChannel(), c.isUnix());
K
kohsuke 已提交
304 305
    }

K
kohsuke 已提交
306
    /**
307
     * Gets the corresponding computer object.
K
kohsuke 已提交
308
     */
309 310
    public SlaveComputer getComputer() {
        return (SlaveComputer)Hudson.getInstance().getComputer(this);
K
kohsuke 已提交
311 312
    }

313 314 315 316
    public Computer toComputer() {
        return getComputer();
    }

K
kohsuke 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final Slave that = (Slave) o;

        return name.equals(that.name);
    }

    public int hashCode() {
        return name.hashCode();
    }

K
kohsuke 已提交
330 331 332 333 334 335 336 337 338
    /**
     * Invoked by XStream when this object is read into memory.
     */
    private Object readResolve() {
        // convert the old format to the new one
        if(command!=null && agentCommand==null) {
            if(command.length()>0)  command += ' ';
            agentCommand = command+"java -jar ~/bin/slave.jar";
        }
339 340 341 342
        if (launcher == null) {
            launcher = (agentCommand == null || agentCommand.trim().length() == 0)
                    ? new JNLPLauncher()
                    : new CommandLauncher(agentCommand);
343
        }
K
kohsuke 已提交
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
        return this;
    }

//
// backwrad compatibility
//
    /**
     * In Hudson < 1.69 this was used to store the local file path
     * to the remote workspace. No longer in use.
     *
     * @deprecated
     *      ... but still in use during the transition.
     */
    private File localFS;

    /**
     * In Hudson < 1.69 this was used to store the command
     * to connect to the remote machine, like "ssh myslave".
     *
     * @deprecated
     */
    private transient String command;
366 367 368 369 370 371
    /**
     * Command line to launch the agent, like
     * "ssh myslave java -jar /path/to/hudson-remoting.jar"
     */
    private transient String agentCommand;

K
kohsuke 已提交
372 373 374 375 376 377 378 379 380 381
    /**
     * Obtains the system clock.
     */
    private static final class GetSystemTime implements Callable<Long,RuntimeException> {
        public Long call() {
            return System.currentTimeMillis();
        }

        private static final long serialVersionUID = 1L;
    }
382 383 384 385 386 387 388 389 390 391 392

//    static {
//        ConvertUtils.register(new Converter(){
//            public Object convert(Class type, Object value) {
//                if (value != null) {
//                System.out.println("CVT: " + type + " from (" + value.getClass() + ") " + value);
//                } else {
//                    System.out.println("CVT: " + type + " from " + value);
//                }
//                return null;  //To change body of implemented methods use File | Settings | File Templates.
//            }
393
//        }, ComputerLauncher.class);
394
//    }
K
kohsuke 已提交
395
}