/******************************************************************************* * * Copyright (c) 2004-2012 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Winston Prakash, Seiji Sogabe, Stephen Connolly * *******************************************************************************/ package hudson.model; import hudson.Extension; import hudson.ExtensionPoint; import hudson.FilePath; import hudson.FileSystemProvisioner; import hudson.Launcher; import hudson.model.Queue.Task; import hudson.model.labels.LabelAtom; import hudson.model.queue.CauseOfBlockage; import hudson.node_monitors.NodeMonitor; import hudson.remoting.VirtualChannel; import hudson.security.ACL; import hudson.security.AccessControlled; import hudson.security.Permission; import hudson.slaves.ComputerListener; import hudson.slaves.NodeDescriptor; import hudson.slaves.NodeProperty; import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.OfflineCause; import hudson.util.ClockDifference; import hudson.util.DescribableList; import hudson.util.EnumConverter; import hudson.util.TagCloud; import hudson.util.TagCloud.WeightFunction; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * Base type of Hudson slaves (although in practice, you probably extend * {@link Slave} to define a new slave type.) * * <p> As a special case, {@link Hudson} extends from here. * * @author Kohsuke Kawaguchi * @see NodeMonitor * @see NodeDescriptor */ @ExportedBean public abstract class Node extends AbstractModelObject implements Describable<Node>, ExtensionPoint, AccessControlled { private static final Logger LOGGER = Logger.getLogger(Node.class.getName()); /** * 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; /** * Contains info about reason the node is offline. */ private OfflineCause offlineCause; /** * Listener for set offline cause for node when computer get started. */ @Extension public static class NodeListener extends ComputerListener { @Override public void onOnline(Computer c, TaskListener listener) { Node node = c.getNode(); if (null != node.offlineCause && node.offlineCause != c.getOfflineCause()) { c.setTemporarilyOffline(true, node.offlineCause); } } } /** * Sets the reason about why the node is offline and save configuration. * * @param cause the offline cause */ public void setOfflineCause(OfflineCause cause) { try { if (cause != offlineCause) { offlineCause = cause; Hudson.getInstance().save(); } } catch (IOException e) { LOGGER.error("Unable to complete save: " + e.getMessage()); } } public String getDisplayName() { return getNodeName(); // default implementation } public String getSearchUrl() { return "computer/" + getNodeName(); } public boolean isHoldOffLaunchUntilSave() { return holdOffLaunchUntilSave; } /** * Name of this node. * * @return "" if this is master */ @Exported(visibility = 999) public abstract String getNodeName(); /** * 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. */ public abstract void setNodeName(String name); /** * Human-readable description of this node. */ @Exported public abstract String getNodeDescription(); /** * Returns a {@link Launcher} for executing programs on this node. * * <p> The callee must call {@link Launcher#decorateFor(Node)} before * returning to complete the decoration. */ public abstract Launcher createLauncher(TaskListener listener); /** * 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. */ @Exported public abstract int getNumExecutors(); /** * Returns {@link Mode#EXCLUSIVE} if this node is only available for those * jobs that exclusively specifies this node as the assigned node. */ @Exported public abstract Mode getMode(); /** * Gets the corresponding {@link Computer} object. * * @return 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. */ public final Computer toComputer() { return Hudson.getInstance().getComputer(this); } /** * 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(); } /** * Creates a new {@link Computer} object that acts as the UI peer of this * {@link Node}. Nobody but {@link Hudson#updateComputerList()} should call * this method. */ protected abstract Computer createComputer(); /** * Return the possibly empty tag cloud for the labels of this node. */ public TagCloud<LabelAtom> getLabelCloud() { return new TagCloud<LabelAtom>(getAssignedLabels(), new WeightFunction<LabelAtom>() { public float weight(LabelAtom item) { return item.getTiedJobs().size(); } }); } /** * Returns the possibly empty set of labels that are assigned to this node, * 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. */ @Exported public Set<LabelAtom> getAssignedLabels() { Set<LabelAtom> r = Label.parse(getLabelString()); r.add(getSelfLabel()); r.addAll(getDynamicLabels()); return Collections.unmodifiableSet(r); } /** * 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>. */ private HashSet<LabelAtom> getDynamicLabels() { HashSet<LabelAtom> result = new HashSet<LabelAtom>(); for (LabelFinder labeler : LabelFinder.all()) { // Filter out any bad(null) results from plugins // for compatibility reasons, findLabels may return LabelExpression and not atom. for (Label label : labeler.findLabels(this)) { if (label instanceof LabelAtom) { result.add((LabelAtom) label); } } } return result; } /** * 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. */ public abstract String getLabelString(); /** * Gets the special label that represents this node itself. */ public LabelAtom getSelfLabel() { return LabelAtom.get(getNodeName()); } /** * @deprecated as of 3.1.1 * Use {@link #canTake(Queue.BuildableItem)} */ public CauseOfBlockage canTake(Task task) { return null; } public CauseOfBlockage canTake(Queue.BuildableItem item) { Label l = item.getAssignedLabel(); 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()) { CauseOfBlockage c = prop.canTake(item); if (c!=null) return c; } // Looks like we can take the task return null; } /** * 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. * * @return null if this node is not connected hence the path is not * available */ // TODO: should this be modified now that getWorkspace is moved from AbstractProject to AbstractBuild? public abstract FilePath getWorkspaceFor(TopLevelItem item); /** * 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. */ public abstract FilePath getRootPath(); /** * Gets the {@link FilePath} on this node. */ public FilePath createPath(String absolutePath) { VirtualChannel ch = getChannel(); if (ch == null) { return null; // offline } return new FilePath(ch, absolutePath); } public FileSystemProvisioner getFileSystemProvisioner() { // TODO: make this configurable or auto-detectable or something else return FileSystemProvisioner.DEFAULT; } /** * Gets the {@link NodeProperty} instances configured for this {@link Node}. */ public abstract DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties(); // used in the Jelly script to expose descriptors public List<NodePropertyDescriptor> getNodePropertyDescriptors() { return NodeProperty.for_(this); } public ACL getACL() { return HudsonSecurityEntitiesHolder.getHudsonSecurityManager().getAuthorizationStrategy().getACL(this); } public final void checkPermission(Permission permission) { getACL().checkPermission(permission); } public final boolean hasPermission(Permission permission) { return getACL().hasPermission(permission); } public abstract NodeDescriptor getDescriptor(); /** * Estimates the clock difference with this slave. * * @return always non-null. * @throws InterruptedException if the operation is aborted. */ public abstract ClockDifference getClockDifference() throws IOException, InterruptedException; /** * Constants that control how Hudson allocates jobs to slaves. */ public enum Mode { NORMAL(Messages.Node_Mode_NORMAL()), EXCLUSIVE(Messages.Node_Mode_EXCLUSIVE()); private final String description; public String getDescription() { return description; } public String getName() { return name(); } Mode(String description) { this.description = description; } static { Stapler.CONVERT_UTILS.register(new EnumConverter(), Mode.class); } } }