Node.java 15.6 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4 5
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
 * Seiji Sogabe, Stephen Connolly
K
kohsuke 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
K
kohsuke 已提交
25 26
package hudson.model;

27
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
N
Nathan Parry 已提交
28
import hudson.Extension;
K
kohsuke 已提交
29
import hudson.ExtensionPoint;
30
import hudson.FilePath;
31
import hudson.FileSystemProvisioner;
32
import hudson.Launcher;
33
import hudson.model.Descriptor.FormException;
34
import hudson.model.Queue.Task;
35
import hudson.model.labels.LabelAtom;
36
import hudson.model.queue.CauseOfBlockage;
37
import hudson.node_monitors.NodeMonitor;
38
import hudson.remoting.VirtualChannel;
39
import hudson.security.ACL;
K
kohsuke 已提交
40
import hudson.security.AccessControlled;
41
import hudson.security.Permission;
N
Nathan Parry 已提交
42
import hudson.slaves.ComputerListener;
K
kohsuke 已提交
43
import hudson.slaves.NodeDescriptor;
44 45
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
N
Nathan Parry 已提交
46
import hudson.slaves.OfflineCause;
47
import hudson.util.ClockDifference;
48
import hudson.util.DescribableList;
K
kohsuke 已提交
49
import hudson.util.EnumConverter;
50 51
import hudson.util.TagCloud;
import hudson.util.TagCloud.WeightFunction;
K
kohsuke 已提交
52

K
kohsuke 已提交
53
import java.io.IOException;
54
import java.lang.reflect.Type;
55 56
import java.util.Collections;
import java.util.HashSet;
K
kohsuke 已提交
57
import java.util.Set;
K
kohsuke 已提交
58
import java.util.List;
N
Nathan Parry 已提交
59
import java.util.logging.Logger;
60

61
import jenkins.model.Jenkins;
62
import net.sf.json.JSONObject;
63
import org.kohsuke.stapler.BindInterceptor;
64
import org.kohsuke.stapler.Stapler;
65
import org.kohsuke.stapler.StaplerRequest;
K
kohsuke 已提交
66 67
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
68

K
kohsuke 已提交
69
/**
K
kohsuke 已提交
70 71 72
 * Base type of Hudson slaves (although in practice, you probably extend {@link Slave} to define a new slave type.)
 *
 * <p>
73
 * As a special case, {@link Jenkins} extends from here.
K
kohsuke 已提交
74 75
 *
 * @author Kohsuke Kawaguchi
K
kohsuke 已提交
76
 * @see NodeMonitor
K
kohsuke 已提交
77
 * @see NodeDescriptor
K
kohsuke 已提交
78
 */
K
kohsuke 已提交
79
@ExportedBean
80
public abstract class Node extends AbstractModelObject implements ReconfigurableDescribable<Node>, ExtensionPoint, AccessControlled {
N
Nathan Parry 已提交
81 82 83

    private static final Logger LOGGER = Logger.getLogger(Node.class.getName());

84 85 86 87 88
    /**
     * Newly copied slaves get this flag set, so that Hudson doesn't try to start this node until its configuration
     * is saved once.
     */
    protected volatile transient boolean holdOffLaunchUntilSave;
89

90 91 92 93 94 95 96 97
    public String getDisplayName() {
        return getNodeName(); // default implementation
    }

    public String getSearchUrl() {
        return "computer/"+getNodeName();
    }

98 99 100 101
    public boolean isHoldOffLaunchUntilSave() {
        return holdOffLaunchUntilSave;
    }

K
kohsuke 已提交
102
    /**
K
kohsuke 已提交
103
     * Name of this node.
K
kohsuke 已提交
104 105 106 107
     *
     * @return
     *      "" if this is master
     */
K
kohsuke 已提交
108
    @Exported(visibility=999)
109
    public abstract String getNodeName();
K
kohsuke 已提交
110

K
kohsuke 已提交
111 112 113 114 115 116 117 118 119 120
    /**
     * When the user clones a {@link Node}, Hudson uses this method to change the node name right after
     * the cloned {@link Node} object is instantiated.
     *
     * <p>
     * This method is never used for any other purpose, and as such for all practical intents and purposes,
     * the node name should be treated like immutable.
     *
     * @deprecated to indicate that this method isn't really meant to be called by random code.
     */
121
    public abstract void setNodeName(String name);
K
kohsuke 已提交
122

K
kohsuke 已提交
123 124 125
    /**
     * Human-readable description of this node.
     */
K
kohsuke 已提交
126
    @Exported
127
    public abstract String getNodeDescription();
K
kohsuke 已提交
128 129 130

    /**
     * Returns a {@link Launcher} for executing programs on this node.
K
kohsuke 已提交
131 132 133
     *
     * <p>
     * The callee must call {@link Launcher#decorateFor(Node)} before returning to complete the decoration. 
K
kohsuke 已提交
134
     */
135
    public abstract Launcher createLauncher(TaskListener listener);
K
kohsuke 已提交
136 137 138 139 140 141 142

    /**
     * Returns the number of {@link Executor}s.
     *
     * This may be different from <code>getExecutors().size()</code>
     * because it takes time to adjust the number of executors.
     */
K
kohsuke 已提交
143
    @Exported
144
    public abstract int getNumExecutors();
K
kohsuke 已提交
145 146

    /**
147
     * Returns {@link Mode#EXCLUSIVE} if this node is only available
K
kohsuke 已提交
148 149 150
     * for those jobs that exclusively specifies this node
     * as the assigned node.
     */
K
kohsuke 已提交
151
    @Exported
152
    public abstract Mode getMode();
K
kohsuke 已提交
153

154 155 156 157
    /**
     * Gets the corresponding {@link Computer} object.
     *
     * @return
K
kohsuke 已提交
158 159
     *      this method can return null if there's no {@link Computer} object for this node,
     *      such as when this node has no executors at all.
160
     */
161
    public final Computer toComputer() {
162 163
        AbstractCIBase ciBase = Jenkins.getInstance();
        return ciBase.getComputer(this);
164
    }
165

K
kohsuke 已提交
166 167 168 169 170 171 172 173 174 175
    /**
     * Gets the current channel, if the node is connected and online, or null.
     *
     * This is just a convenience method for {@link Computer#getChannel()} with null check. 
     */
    public final VirtualChannel getChannel() {
        Computer c = toComputer();
        return c==null ? null : c.getChannel();
    }

K
kohsuke 已提交
176 177
    /**
     * Creates a new {@link Computer} object that acts as the UI peer of this {@link Node}.
178
     * Nobody but {@link Jenkins#updateComputerList()} should call this method.
K
kohsuke 已提交
179
     */
180
    protected abstract Computer createComputer();
K
kohsuke 已提交
181

N
Nathan Parry 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    /**
     * Let Nodes be aware of the lifecycle of their own {@link Computer}.
     */
    @Extension
    public static class InternalComputerListener extends ComputerListener {
        @Override
        public void onOnline(Computer c, TaskListener listener) {
            Node node = c.getNode();

            // At startup, we need to restore any previously in-effect temp offline cause.
            // We wait until the computer is started rather than getting the data to it sooner
            // so that the normal computer start up processing works as expected.
            if (node.temporaryOfflineCause != null && node.temporaryOfflineCause != c.getOfflineCause()) {
                c.setTemporarilyOffline(true, node.temporaryOfflineCause);
            }
        }
    }

    private OfflineCause temporaryOfflineCause;

    /**
     * Enable a {@link Computer} to inform its node when it is taken
     * temporarily offline.
     */
    void setTemporaryOfflineCause(OfflineCause cause) {
        try {
            if (temporaryOfflineCause != cause) {
                temporaryOfflineCause = cause;
210
                Jenkins.getInstance().save(); // Gotta be a better way to do this
N
Nathan Parry 已提交
211 212 213 214 215 216
            }
        } catch (java.io.IOException e) {
            LOGGER.warning("Unable to complete save, temporary offline status will not be persisted: " + e.getMessage());
        }
    }

217 218 219
    /**
     * Return the possibly empty tag cloud for the labels of this node.
     */
220 221 222
    public TagCloud<LabelAtom> getLabelCloud() {
        return new TagCloud<LabelAtom>(getAssignedLabels(),new WeightFunction<LabelAtom>() {
            public float weight(LabelAtom item) {
223 224 225 226
                return item.getTiedJobs().size();
            }
        });
    }
227 228
    /**
     * Returns the possibly empty set of labels that are assigned to this node,
229 230 231 232 233 234 235
     * including the automatic {@link #getSelfLabel() self label}, manually
     * assigned labels and dynamically assigned labels via the
     * {@link LabelFinder} extension point.
     *
     * This method has a side effect of updating the hudson-wide set of labels
     * and should be called after events that will change that - e.g. a slave
     * connecting.
236
     */
K
kohsuke 已提交
237
    @Exported
238 239
    public Set<LabelAtom> getAssignedLabels() {
        Set<LabelAtom> r = Label.parse(getLabelString());
240 241 242 243
        r.add(getSelfLabel());
        r.addAll(getDynamicLabels());
        return Collections.unmodifiableSet(r);
    }
244

K
kohsuke 已提交
245
    /**
246 247 248 249
     * Return all the labels assigned dynamically to this node.
     * This calls all the LabelFinder implementations with the node converts
     * the results into Labels.
     * @return HashSet<Label>.
K
kohsuke 已提交
250
     */
251 252 253
    private HashSet<LabelAtom> getDynamicLabels() {
        HashSet<LabelAtom> result = new HashSet<LabelAtom>();
        for (LabelFinder labeler : LabelFinder.all()) {
254
            // Filter out any bad(null) results from plugins
255
            // for compatibility reasons, findLabels may return LabelExpression and not atom.
256
            for (Label label : labeler.findLabels(this))
257 258
                if (label instanceof LabelAtom) result.add((LabelAtom)label);
        }
259 260 261
        return result;
    }

K
kohsuke 已提交
262

263 264 265 266 267 268 269
    /**
     * Returns the manually configured label for a node. The list of assigned
     * and dynamically determined labels is available via 
     * {@link #getAssignedLabels()} and includes all labels that have been
     * manually configured.
     * 
     * Mainly for form binding.
270
     */
271
    public abstract String getLabelString();
272

273 274 275
    /**
     * Gets the special label that represents this node itself.
     */
276 277 278
    @WithBridgeMethods(Label.class)
    public LabelAtom getSelfLabel() {
        return LabelAtom.get(getNodeName());
279
    }
280

281 282 283 284 285 286 287 288 289
    /**
     * Called by the {@link Queue} to determine whether or not this node can
     * take the given task. The default checks include whether or not this node
     * is part of the task's assigned label, whether this node is in
     * {@link Mode#EXCLUSIVE} mode if it is not in the task's assigned label,
     * and whether or not any of this node's {@link NodeProperty}s say that the
     * task cannot be run.
     *
     * @since 1.360
290 291
     * @deprecated as of 1.413
     *      Use {@link #canTake(Queue.BuildableItem)}
292 293
     */
    public CauseOfBlockage canTake(Task task) {
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
        return null;
    }

    /**
     * Called by the {@link Queue} to determine whether or not this node can
     * take the given task. The default checks include whether or not this node
     * is part of the task's assigned label, whether this node is in
     * {@link Mode#EXCLUSIVE} mode if it is not in the task's assigned label,
     * and whether or not any of this node's {@link NodeProperty}s say that the
     * task cannot be run.
     *
     * @since 1.413
     */
    public CauseOfBlockage canTake(Queue.BuildableItem item) {
        Label l = item.task.getAssignedLabel();
309 310 311 312 313 314 315 316 317
        if(l!=null && !l.contains(this))
            return CauseOfBlockage.fromMessage(Messages._Node_LabelMissing(getNodeName(),l));   // the task needs to be executed on label that this node doesn't have.

        if(l==null && getMode()== Mode.EXCLUSIVE)
            return CauseOfBlockage.fromMessage(Messages._Node_BecauseNodeIsReserved(getNodeName()));   // this node is reserved for tasks that are tied to it

        // Check each NodeProperty to see whether they object to this node
        // taking the task
        for (NodeProperty prop: getNodeProperties()) {
318
            CauseOfBlockage c = prop.canTake(item);
319 320 321 322 323 324 325
            if (c!=null)    return c;
        }

        // Looks like we can take the task
        return null;
    }

326 327 328 329 330 331
    /**
     * Returns a "workspace" directory for the given {@link TopLevelItem}.
     *
     * <p>
     * Workspace directory is usually used for keeping out the checked out
     * source code, but it can be used for anything.
K
kohsuke 已提交
332 333 334
     *
     * @return
     *      null if this node is not connected hence the path is not available
335
     */
K
kohsuke 已提交
336
    // TODO: should this be modified now that getWorkspace is moved from AbstractProject to AbstractBuild?
337
    public abstract FilePath getWorkspaceFor(TopLevelItem item);
338

K
kohsuke 已提交
339 340 341 342 343 344 345 346 347 348 349
    /**
     * Gets the root directory of this node.
     *
     * <p>
     * Hudson always owns a directory on every node. This method
     * returns that.
     *
     * @return
     *      null if the node is offline and hence the {@link FilePath}
     *      object is not available.
     */
350
    public abstract FilePath getRootPath();
K
kohsuke 已提交
351

352 353 354
    /**
     * Gets the {@link FilePath} on this node.
     */
355
    public FilePath createPath(String absolutePath) {
K
kohsuke 已提交
356
        VirtualChannel ch = getChannel();
357 358 359 360
        if(ch==null)    return null;    // offline
        return new FilePath(ch,absolutePath);
    }

361 362 363 364 365
    public FileSystemProvisioner getFileSystemProvisioner() {
        // TODO: make this configurable or auto-detectable or something else
        return FileSystemProvisioner.DEFAULT;
    }

366 367 368
    /**
     * Gets the {@link NodeProperty} instances configured for this {@link Node}.
     */
369
    public abstract DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties();
370

K
kohsuke 已提交
371 372 373 374
    // used in the Jelly script to expose descriptors
    public List<NodePropertyDescriptor> getNodePropertyDescriptors() {
        return NodeProperty.for_(this);
    }
375
    
376
    public ACL getACL() {
377
        return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
378 379 380 381 382 383 384 385 386
    }
    
    public final void checkPermission(Permission permission) {
        getACL().checkPermission(permission);
    }

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

S
Seiji Sogabe 已提交
388
    public Node reconfigure(final StaplerRequest req, JSONObject form) throws FormException {
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
        if (form==null)     return null;

        final JSONObject jsonForProperties = form.optJSONObject("nodeProperties");
        BindInterceptor old = req.setBindListener(new BindInterceptor() {
            @Override
            public Object onConvert(Type targetType, Class targetTypeErasure, Object jsonSource) {
                if (jsonForProperties!=jsonSource)  return DEFAULT;

                try {
                    DescribableList<NodeProperty<?>, NodePropertyDescriptor> tmp = new DescribableList<NodeProperty<?>, NodePropertyDescriptor>(Saveable.NOOP,getNodeProperties().toList());
                    tmp.rebuild(req, jsonForProperties, NodeProperty.all());
                    return tmp.toList();
                } catch (FormException e) {
                    throw new IllegalArgumentException(e);
                } catch (IOException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        });

        try {
            return getDescriptor().newInstance(req, form);
        } finally {
            req.setBindListener(old);
        }
414 415
    }

416
    public abstract NodeDescriptor getDescriptor();
K
kohsuke 已提交
417

K
kohsuke 已提交
418 419 420 421
    /**
     * Estimates the clock difference with this slave.
     *
     * @return
422
     *      always non-null.
K
kohsuke 已提交
423 424 425
     * @throws InterruptedException
     *      if the operation is aborted.
     */
426
    public abstract ClockDifference getClockDifference() throws IOException, InterruptedException;
K
kohsuke 已提交
427

K
kohsuke 已提交
428 429 430
    /**
     * Constants that control how Hudson allocates jobs to slaves.
     */
K
kohsuke 已提交
431
    public enum Mode {
S
sogabe 已提交
432 433
        NORMAL(Messages.Node_Mode_NORMAL()),
        EXCLUSIVE(Messages.Node_Mode_EXCLUSIVE());
K
kohsuke 已提交
434 435 436 437 438 439 440 441 442 443 444 445 446 447

        private final String description;

        public String getDescription() {
            return description;
        }

        public String getName() {
            return name();
        }

        Mode(String description) {
            this.description = description;
        }
K
kohsuke 已提交
448 449

        static {
450
            Stapler.CONVERT_UTILS.register(new EnumConverter(), Mode.class);
451 452
        }
    }
453

K
kohsuke 已提交
454
}