Slave.java 11.8 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 8
import hudson.slaves.SlaveStartMethod;
import hudson.slaves.SlaveAvailabilityStrategy;
9 10
import hudson.slaves.CommandStartMethod;
import hudson.slaves.JNLPStartMethod;
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 18
import hudson.util.ClockDifference;
import hudson.util.RingBufferLogHandler;
19 20
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
kohsuke 已提交
21

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

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

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

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

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

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

K
kohsuke 已提交
68
    /**
69 70 71 72 73 74
     * Slave availablility strategy.
     */
    private SlaveAvailabilityStrategy availabilityStrategy;

    /**
     * The starter that will startup this slave.
75
     */
76
    private SlaveStartMethod startMethod;
K
kohsuke 已提交
77

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

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

88 89 90
    private transient volatile Set<Label> dynamicLabels;
    private transient volatile int dynamicLabelsInstanceHash;

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

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

107 108 109 110 111
        // 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 已提交
112
        if (remoteFS.equals(""))
K
i18n  
kohsuke 已提交
113
            throw new FormException(Messages.Slave_InvalidConfig_NoRemoteDir(name), null);
114

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

119 120 121 122 123 124
    public SlaveStartMethod getStartMethod() {
        return startMethod == null ? new JNLPStartMethod() : startMethod;
    }

    public void setStartMethod(SlaveStartMethod startMethod) {
        this.startMethod = startMethod;
K
kohsuke 已提交
125 126 127 128 129 130
    }

    public String getRemoteFS() {
        return remoteFS;
    }

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

    public String getNodeDescription() {
        return description;
    }

    public int getNumExecutors() {
        return numExecutors;
    }

    public Mode getMode() {
        return mode;
    }

147 148 149 150 151 152 153 154
    public SlaveAvailabilityStrategy getAvailabilityStrategy() {
        return availabilityStrategy == null ? new SlaveAvailabilityStrategy.Always() : availabilityStrategy;
    }

    public void setAvailabilityStrategy(SlaveAvailabilityStrategy availabilityStrategy) {
        this.availabilityStrategy = availabilityStrategy;
    }

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

159
    public Set<Label> getAssignedLabels() {
160 161
        // todo refactor to make dynamic labels a bit less hacky
        if(labels==null || isChangedDynamicLabels()) {
162 163 164 165 166 167 168 169
            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());
170
            r.addAll(getDynamicLabels());
171 172 173 174 175
            this.labels = Collections.unmodifiableSet(r);
        }
        return labels;
    }

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    /**
     * 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
199 200 201
     *
     * @return
     *      never null.
202 203
     */
    public Set<Label> getDynamicLabels() {
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        // 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));
221 222
                    }
                }
223 224
            } else {
                dynamicLabelsInstanceHash = 0;
225
            }
226 227

            return labels;
228 229
        }
    }
230 231 232 233 234

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

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

K
kohsuke 已提交
240 241 242 243 244 245 246
        long startTime = System.currentTimeMillis();
        long slaveTime = channel.call(new Callable<Long,RuntimeException>() {
            public Long call() {
                return System.currentTimeMillis();
            }
        });
        long endTime = System.currentTimeMillis();
K
kohsuke 已提交
247

248
        return new ClockDifference((startTime+endTime)/2 - slaveTime);
K
kohsuke 已提交
249 250
    }

K
kohsuke 已提交
251
    public Computer createComputer() {
252
        return new SlaveComputer(this);
K
kohsuke 已提交
253 254
    }

255
    public FilePath getWorkspaceFor(TopLevelItem item) {
K
kohsuke 已提交
256 257 258
        FilePath r = getWorkspaceRoot();
        if(r==null)     return null;    // offline
        return r.child(item.getName());
259 260
    }

K
kohsuke 已提交
261
    public FilePath getRootPath() {
262 263 264 265
        return createPath(remoteFS);
    }

    public FilePath createPath(String absolutePath) {
K
kohsuke 已提交
266 267
        VirtualChannel ch = getComputer().getChannel();
        if(ch==null)    return null;    // offline
268
        return new FilePath(ch,absolutePath);
K
kohsuke 已提交
269 270
    }

K
kohsuke 已提交
271 272
    /**
     * Root directory on this slave where all the job workspaces are laid out.
K
kohsuke 已提交
273 274
     * @return
     *      null if not connected.
K
kohsuke 已提交
275 276
     */
    public FilePath getWorkspaceRoot() {
K
kohsuke 已提交
277 278 279
        FilePath r = getRootPath();
        if(r==null) return null;
        return r.child("workspace");
K
kohsuke 已提交
280
    }
K
kohsuke 已提交
281

282 283 284 285
    /**
     * Web-bound object used to serve jar files for JNLP.
     */
    public static final class JnlpJar {
K
kohsuke 已提交
286
        private final String fileName;
287

K
kohsuke 已提交
288 289
        public JnlpJar(String fileName) {
            this.fileName = fileName;
290 291 292
        }

        public void doIndex( StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
K
kohsuke 已提交
293 294 295 296
            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);
297 298
            }

K
kohsuke 已提交
299
            URLConnection con = res.openConnection();
300 301 302 303 304 305 306
            InputStream in = con.getInputStream();
            rsp.serveFile(req, in, con.getLastModified(), con.getContentLength(), "*.jar" );
            in.close();
        }

    }

K
kohsuke 已提交
307
    public Launcher createLauncher(TaskListener listener) {
308
        SlaveComputer c = getComputer();
309
        return new RemoteLauncher(listener, c.getChannel(), c.isUnix);
K
kohsuke 已提交
310 311
    }

K
kohsuke 已提交
312
    /**
313
     * Gets the corresponding computer object.
K
kohsuke 已提交
314
     */
315 316
    public SlaveComputer getComputer() {
        return (SlaveComputer)Hudson.getInstance().getComputer(this);
K
kohsuke 已提交
317 318
    }

319 320 321 322
    public Computer toComputer() {
        return getComputer();
    }

K
kohsuke 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335
    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 已提交
336 337 338 339 340 341 342 343 344
    /**
     * 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";
        }
345 346 347 348 349
        if (startMethod == null) {
            startMethod = (agentCommand == null || agentCommand.trim().length() == 0)
                    ? new JNLPStartMethod()
                    : new CommandStartMethod(agentCommand);
        }
K
kohsuke 已提交
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        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;
372 373 374 375 376 377 378
    /**
     * Command line to launch the agent, like
     * "ssh myslave java -jar /path/to/hudson-remoting.jar"
     */
    private transient String agentCommand;

    static {
379 380
        SlaveStartMethod.LIST.add(JNLPStartMethod.DESCRIPTOR);
        SlaveStartMethod.LIST.add(CommandStartMethod.DESCRIPTOR);
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
    }


//    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.
//            }
//        }, SlaveStartMethod.class);
//    }
K
kohsuke 已提交
396
}