Slave.java 13.3 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;
K
kohsuke 已提交
7 8
import hudson.security.ACL;
import hudson.security.Permission;
9
import hudson.slaves.ComputerLauncher;
10
import hudson.slaves.RetentionStrategy;
11 12
import hudson.slaves.CommandLauncher;
import hudson.slaves.JNLPLauncher;
13
import hudson.slaves.SlaveComputer;
K
kohsuke 已提交
14
import hudson.slaves.DumbSlave;
K
kohsuke 已提交
15
import hudson.model.Descriptor.FormException;
K
kohsuke 已提交
16 17
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
18 19
import hudson.tasks.DynamicLabeler;
import hudson.tasks.LabelFinder;
K
kohsuke 已提交
20
import hudson.util.ClockDifference;
21 22
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
23
import org.kohsuke.stapler.DataBoundConstructor;
K
kohsuke 已提交
24
import org.apache.commons.io.IOUtils;
K
kohsuke 已提交
25

26
import javax.servlet.ServletException;
K
kohsuke 已提交
27 28 29 30
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
31 32
import java.net.URL;
import java.net.URLConnection;
K
kohsuke 已提交
33
import java.net.MalformedURLException;
34
import java.util.*;
K
kohsuke 已提交
35

K
kohsuke 已提交
36 37 38
/**
 * Information about a Hudson slave node.
 *
K
noted  
kohsuke 已提交
39 40 41 42
 * <p>
 * Ideally this would have been in the <tt>hudson.slaves</tt> package,
 * but for compatibility reasons, it can't.
 *
K
kohsuke 已提交
43 44 45
 * <p>
 * TODO: move out more stuff to {@link DumbSlave}.
 *
K
kohsuke 已提交
46 47
 * @author Kohsuke Kawaguchi
 */
K
kohsuke 已提交
48
public abstract class Slave implements Node, Serializable {
K
kohsuke 已提交
49
    /**
K
kohsuke 已提交
50
     * Name of this slave node.
K
kohsuke 已提交
51
     */
K
kohsuke 已提交
52
    protected String name;
K
kohsuke 已提交
53 54 55 56 57 58 59 60

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

    /**
     * Path to the root of the workspace
K
kohsuke 已提交
61
     * from the view point of this node, such as "/hudson"
K
kohsuke 已提交
62
     */
K
kohsuke 已提交
63
    protected final String remoteFS;
K
kohsuke 已提交
64 65 66 67 68 69 70 71 72 73 74

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

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

K
kohsuke 已提交
75
    /**
76 77
     * Slave availablility strategy.
     */
K
kohsuke 已提交
78
    private RetentionStrategy retentionStrategy;
79 80 81

    /**
     * The starter that will startup this slave.
82
     */
83
    private ComputerLauncher launcher;
K
kohsuke 已提交
84

85 86 87 88 89 90 91 92 93 94
    /**
     * Whitespace-separated labels.
     */
    private String label="";

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

95 96 97
    private transient volatile Set<Label> dynamicLabels;
    private transient volatile int dynamicLabelsInstanceHash;

98
    @DataBoundConstructor
99
    public Slave(String name, String description, String remoteFS, String numExecutors,
100
                 Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException {
K
kohsuke 已提交
101 102 103 104 105
        this(name,description,remoteFS,Util.tryParseNumber(numExecutors, 1).intValue(),mode,label,launcher,retentionStrategy);
    }

    public Slave(String name, String description, String remoteFS, int numExecutors,
                 Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException {
K
kohsuke 已提交
106 107
        this.name = name;
        this.description = description;
K
kohsuke 已提交
108
        this.numExecutors = numExecutors;
K
kohsuke 已提交
109
        this.mode = mode;
K
kohsuke 已提交
110
        this.remoteFS = remoteFS;
111
        this.label = Util.fixNull(label).trim();
112 113
        this.launcher = launcher;
        this.retentionStrategy = retentionStrategy;
114
        getAssignedLabels();    // compute labels now
K
kohsuke 已提交
115

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

119 120 121 122 123
        // 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 已提交
124 125 126

//        if (remoteFS.equals(""))
//            throw new FormException(Messages.Slave_InvalidConfig_NoRemoteDir(name), null);
127

128
        if (this.numExecutors<=0)
K
i18n  
kohsuke 已提交
129
            throw new FormException(Messages.Slave_InvalidConfig_Executors(name), null);
K
kohsuke 已提交
130 131
    }

132
    public ComputerLauncher getLauncher() {
133
        return launcher == null ? new JNLPLauncher() : launcher;
134 135
    }

136
    public void setLauncher(ComputerLauncher launcher) {
137
        this.launcher = launcher;
K
kohsuke 已提交
138 139 140 141 142 143
    }

    public String getRemoteFS() {
        return remoteFS;
    }

K
kohsuke 已提交
144 145
    public String getNodeName() {
        return name;
K
kohsuke 已提交
146 147
    }

K
kohsuke 已提交
148 149 150 151
    public void setNodeName(String name) {
        this.name = name; 
    }

K
kohsuke 已提交
152 153 154 155 156 157 158 159 160 161 162 163
    public String getNodeDescription() {
        return description;
    }

    public int getNumExecutors() {
        return numExecutors;
    }

    public Mode getMode() {
        return mode;
    }

K
kohsuke 已提交
164 165
    public RetentionStrategy getRetentionStrategy() {
        return retentionStrategy == null ? RetentionStrategy.Always.INSTANCE : retentionStrategy;
166 167
    }

K
kohsuke 已提交
168 169
    public void setRetentionStrategy(RetentionStrategy availabilityStrategy) {
        this.retentionStrategy = availabilityStrategy;
170 171
    }

172 173 174
    public String getLabelString() {
        return Util.fixNull(label).trim();
    }
175

176
    public Set<Label> getAssignedLabels() {
177 178
        // todo refactor to make dynamic labels a bit less hacky
        if(labels==null || isChangedDynamicLabels()) {
179 180 181 182 183 184 185 186
            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());
187
            r.addAll(getDynamicLabels());
188 189 190 191 192
            this.labels = Collections.unmodifiableSet(r);
        }
        return labels;
    }

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
    /**
     * 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
216 217 218
     *
     * @return
     *      never null.
219 220
     */
    public Set<Label> getDynamicLabels() {
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        // 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));
238 239
                    }
                }
240 241
            } else {
                dynamicLabelsInstanceHash = 0;
242
            }
243 244

            return labels;
245 246
        }
    }
247 248 249 250 251

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

252
    public ClockDifference getClockDifference() throws IOException, InterruptedException {
K
kohsuke 已提交
253
        VirtualChannel channel = getComputer().getChannel();
K
kohsuke 已提交
254 255
        if(channel==null)
            throw new IOException(getNodeName()+" is offline");
K
kohsuke 已提交
256

K
kohsuke 已提交
257
        long startTime = System.currentTimeMillis();
K
kohsuke 已提交
258
        long slaveTime = channel.call(new GetSystemTime());
K
kohsuke 已提交
259
        long endTime = System.currentTimeMillis();
K
kohsuke 已提交
260

261
        return new ClockDifference((startTime+endTime)/2 - slaveTime);
K
kohsuke 已提交
262 263
    }

K
kohsuke 已提交
264 265 266 267 268 269 270 271 272 273 274 275
    public ACL getACL() {
        return Hudson.getInstance().getAuthorizationStrategy().getACL(this);
    }

    public final void checkPermission(Permission permission) {
        getACL().checkPermission(permission);
    }

    public final boolean hasPermission(Permission permission) {
        return getACL().hasPermission(permission);
    }

K
kohsuke 已提交
276
    public Computer createComputer() {
277
        return new SlaveComputer(this);
K
kohsuke 已提交
278 279
    }

280
    public FilePath getWorkspaceFor(TopLevelItem item) {
K
kohsuke 已提交
281 282 283
        FilePath r = getWorkspaceRoot();
        if(r==null)     return null;    // offline
        return r.child(item.getName());
284 285
    }

K
kohsuke 已提交
286
    public FilePath getRootPath() {
287 288 289 290
        return createPath(remoteFS);
    }

    public FilePath createPath(String absolutePath) {
K
kohsuke 已提交
291 292
        VirtualChannel ch = getComputer().getChannel();
        if(ch==null)    return null;    // offline
293
        return new FilePath(ch,absolutePath);
K
kohsuke 已提交
294 295
    }

K
kohsuke 已提交
296 297
    /**
     * Root directory on this slave where all the job workspaces are laid out.
K
kohsuke 已提交
298 299
     * @return
     *      null if not connected.
K
kohsuke 已提交
300 301
     */
    public FilePath getWorkspaceRoot() {
K
kohsuke 已提交
302 303 304
        FilePath r = getRootPath();
        if(r==null) return null;
        return r.child("workspace");
K
kohsuke 已提交
305
    }
K
kohsuke 已提交
306

307 308 309 310
    /**
     * Web-bound object used to serve jar files for JNLP.
     */
    public static final class JnlpJar {
K
kohsuke 已提交
311
        private final String fileName;
312

K
kohsuke 已提交
313 314
        public JnlpJar(String fileName) {
            this.fileName = fileName;
315 316 317
        }

        public void doIndex( StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
K
kohsuke 已提交
318 319 320 321 322 323 324 325 326 327 328 329 330
            URLConnection con = connect();
            InputStream in = con.getInputStream();
            rsp.serveFile(req, in, con.getLastModified(), con.getContentLength(), "*.jar" );
            in.close();
        }

        private URLConnection connect() throws IOException {
            URL res = getURL();
            return res.openConnection();
        }

        public URL getURL() throws MalformedURLException {
            URL res = Hudson.getInstance().servletContext.getResource("/WEB-INF/" + fileName);
K
kohsuke 已提交
331 332 333
            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);
334
            }
K
kohsuke 已提交
335 336
            return res;
        }
337

K
kohsuke 已提交
338 339 340 341 342 343 344
        public byte[] readFully() throws IOException {
            InputStream in = connect().getInputStream();
            try {
                return IOUtils.toByteArray(in);
            } finally {
                in.close();
            }
345 346 347 348
        }

    }

K
kohsuke 已提交
349
    public Launcher createLauncher(TaskListener listener) {
350
        SlaveComputer c = getComputer();
351
        return new RemoteLauncher(listener, c.getChannel(), c.isUnix());
K
kohsuke 已提交
352 353
    }

K
kohsuke 已提交
354
    /**
355
     * Gets the corresponding computer object.
K
kohsuke 已提交
356
     */
357 358
    public SlaveComputer getComputer() {
        return (SlaveComputer)Hudson.getInstance().getComputer(this);
K
kohsuke 已提交
359 360
    }

361 362 363 364
    public Computer toComputer() {
        return getComputer();
    }

K
kohsuke 已提交
365 366 367 368 369 370 371 372 373 374 375 376 377
    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 已提交
378 379 380 381 382 383 384 385 386
    /**
     * 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";
        }
387 388 389 390
        if (launcher == null) {
            launcher = (agentCommand == null || agentCommand.trim().length() == 0)
                    ? new JNLPLauncher()
                    : new CommandLauncher(agentCommand);
391
        }
K
kohsuke 已提交
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
        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;
414 415 416 417 418 419
    /**
     * Command line to launch the agent, like
     * "ssh myslave java -jar /path/to/hudson-remoting.jar"
     */
    private transient String agentCommand;

K
kohsuke 已提交
420 421 422 423 424 425 426 427 428 429
    /**
     * 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;
    }
430 431 432 433 434 435 436 437 438 439 440

//    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.
//            }
441
//        }, ComputerLauncher.class);
442
//    }
K
kohsuke 已提交
443
}