Skip to content

Commit

Permalink
Extract interfaces for objects to be used through the executors widget (
Browse files Browse the repository at this point in the history
#9749)

* Extract interfaces for objects to be used through the executors widget

This is required for CloudBees CI HA support: we provide alternate implementations of these interfaces to represent computers and related objects that exist in different physical replicas of the same logical instance. This allows to be build an aggregated view of computers and executors, some local, some remote.

* Fix Deprecated annotation

* Missing @OverRide annotations

* Missing @SInCE TODO

* No longer true according to AbstractSubTask javadoc

* Restore old signature for compatibility

* Fix reviews

* Javadoc

* Typo

* Fix inconsistency between hasOfflineCause and getOfflineCauseReason

* Remove default implementation of ITask#getUrl

* Provide default impl in SubTask for compatibility

* Mark as CheckForNull

* Spotbugs

* ComputerSet#getComputers returns a collection that is sorted by name

* Missing Override

* Move permission declaration to the right place, closer to usage
  • Loading branch information
Vlatombe authored Oct 2, 2024
1 parent a6f723c commit 39e6622
Show file tree
Hide file tree
Showing 16 changed files with 726 additions and 253 deletions.
174 changes: 35 additions & 139 deletions core/src/main/java/hudson/model/Computer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import hudson.console.AnnotatedLargeText;
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.Queue.FlyweightTask;
import hudson.model.labels.LabelAtom;
import hudson.model.queue.WorkUnit;
import hudson.node_monitors.AbstractDiskSpaceMonitor;
Expand Down Expand Up @@ -106,6 +105,9 @@
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jenkins.model.DisplayExecutor;
import jenkins.model.IComputer;
import jenkins.model.IDisplayExecutor;
import jenkins.model.Jenkins;
import jenkins.security.ImpersonatingExecutorService;
import jenkins.security.MasterToSlaveCallable;
Expand All @@ -116,8 +118,6 @@
import jenkins.util.SystemProperties;
import jenkins.widgets.HasWidgets;
import net.jcip.annotations.GuardedBy;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand Down Expand Up @@ -150,7 +150,7 @@
* if a {@link Node} is configured (probably temporarily) with 0 executors,
* you won't have a {@link Computer} object for it (except for the built-in node,
* which always gets its {@link Computer} in case we have no static executors and
* we need to run a {@link FlyweightTask} - see JENKINS-7291 for more discussion.)
* we need to run a {@link Queue.FlyweightTask} - see JENKINS-7291 for more discussion.)
*
* Also, even if you remove a {@link Node}, it takes time for the corresponding
* {@link Computer} to be removed, if some builds are already in progress on that
Expand All @@ -164,7 +164,7 @@
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener, DescriptorByNameOwner, StaplerProxy, HasWidgets {
public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, IComputer, ExecutorListener, DescriptorByNameOwner, StaplerProxy, HasWidgets {

private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<>();
// TODO:
Expand Down Expand Up @@ -351,12 +351,6 @@ public AnnotatedLargeText<Computer> getLogText() {
return new AnnotatedLargeText<>(getLogFile(), Charset.defaultCharset(), false, this);
}

@NonNull
@Override
public ACL getACL() {
return Jenkins.get().getAuthorizationStrategy().getACL(this);
}

/**
* If the computer was offline (either temporarily or not),
* this method will return the cause.
Expand All @@ -369,14 +363,13 @@ public OfflineCause getOfflineCause() {
return offlineCause;
}

/**
* If the computer was offline (either temporarily or not),
* this method will return the cause as a string (without user info).
*
* @return
* empty string if the system was put offline without given a cause.
*/
@Override
public boolean hasOfflineCause() {
return offlineCause != null;
}

@Exported
@Override
public String getOfflineCauseReason() {
if (offlineCause == null) {
return "";
Expand Down Expand Up @@ -581,9 +574,6 @@ public int getNumExecutors() {
return numExecutors;
}

/**
* Returns {@link Node#getNodeName() the name of the node}.
*/
public @NonNull String getName() {
return nodeName != null ? nodeName : "";
}
Expand Down Expand Up @@ -628,6 +618,7 @@ public BuildTimelineWidget getTimeline() {
}

@Exported
@Override
public boolean isOffline() {
return temporarilyOffline || getChannel() == null;
}
Expand All @@ -645,12 +636,6 @@ public boolean isManualLaunchAllowed() {
return getRetentionStrategy().isManualLaunchAllowed(this);
}


/**
* Is a {@link #connect(boolean)} operation in progress?
*/
public abstract boolean isConnecting();

/**
* Returns true if this computer is supposed to be launched via inbound protocol.
* @deprecated since 2008-05-18.
Expand All @@ -662,14 +647,8 @@ public boolean isJnlpAgent() {
return false;
}

/**
* Returns true if this computer can be launched by Hudson proactively and automatically.
*
* <p>
* For example, inbound agents return {@code false} from this, because the launch process
* needs to be initiated from the agent side.
*/
@Exported
@Override
public boolean isLaunchSupported() {
return true;
}
Expand Down Expand Up @@ -727,14 +706,8 @@ public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause
}
}

/**
* Returns the icon for this computer.
*
* It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
*
* @see #getIconClassName()
*/
@Exported
@Override
public String getIcon() {
// The machine was taken offline by someone
if (isTemporarilyOffline() && getOfflineCause() instanceof OfflineCause.UserCause) return "symbol-computer-disconnected";
Expand All @@ -748,19 +721,15 @@ public String getIcon() {
}

/**
* Returns the class name that will be used to lookup the icon.
* {@inheritDoc}
*
* This class name will be added as a class tag to the html img tags where the icon should
* show up followed by a size specifier given by {@link Icon#toNormalizedIconSizeClass(String)}
* The conversion of class tag to src tag is registered through {@link IconSet#addIcon(Icon)}
*
* It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
*
* @see #getIcon()
* <p>
* It is both the recommended and default implementation to serve different icons based on {@link #isOffline}.
*/
@Exported
@Override
public String getIconClassName() {
return getIcon();
return IComputer.super.getIconClassName();
}

public String getIconAltText() {
Expand All @@ -780,6 +749,8 @@ public String getCaption() {
return Messages.Computer_Caption(nodeName);
}

@Override
@NonNull
public String getUrl() {
return "computer/" + Util.fullEncode(getName()) + "/";
}
Expand Down Expand Up @@ -947,19 +918,18 @@ public int countIdle() {
return n;
}

/**
* Returns the number of {@link Executor}s that are doing some work right now.
*/
@Override
public final int countBusy() {
return countExecutors() - countIdle();
}

/**
* Returns the current size of the executor pool for this computer.
* {@inheritDoc}
* This number may temporarily differ from {@link #getNumExecutors()} if there
* are busy tasks when the configured size is decreased. OneOffExecutors are
* not included in this count.
*/
@Override
public final int countExecutors() {
return executors.size();
}
Expand Down Expand Up @@ -996,14 +966,14 @@ public List<Executor> getAllExecutors() {
}

/**
* Used to render the list of executors.
* @return a snapshot of the executor display information
* {@inheritDoc}
* @since 1.607
*/
@Restricted(NoExternalUse.class)
public List<DisplayExecutor> getDisplayExecutors() {
@Override
@NonNull
public List<IDisplayExecutor> getDisplayExecutors() {
// The size may change while we are populating, but let's start with a reasonable guess to minimize resizing
List<DisplayExecutor> result = new ArrayList<>(executors.size() + oneOffExecutors.size());
List<IDisplayExecutor> result = new ArrayList<>(executors.size() + oneOffExecutors.size());
int index = 0;
for (Executor e : executors) {
if (e.isDisplayCell()) {
Expand Down Expand Up @@ -1659,15 +1629,8 @@ public Object getTarget() {
return e != null ? e.getOwner() : null;
}

/**
* Returns {@code true} if the computer is accepting tasks. Needed to allow agents programmatic suspension of task
* scheduling that does not overlap with being offline.
*
* @return {@code true} if the computer is accepting tasks
* @see hudson.slaves.RetentionStrategy#isAcceptingTasks(Computer)
* @see hudson.model.Node#isAcceptingTasks()
*/
@OverrideMustInvoke
@Override
public boolean isAcceptingTasks() {
final Node node = getNode();
return getRetentionStrategy().isAcceptingTasks(this) && (node == null || node.isAcceptingTasks());
Expand Down Expand Up @@ -1727,79 +1690,12 @@ public static void relocateOldLogs() {
}
}

/**
* A value class to provide a consistent snapshot view of the state of an executor to avoid race conditions
* during rendering of the executors list.
*
* @since 1.607
*/
@Restricted(NoExternalUse.class)
public static class DisplayExecutor implements ModelObject {

@NonNull
private final String displayName;
@NonNull
private final String url;
@NonNull
private final Executor executor;

public DisplayExecutor(@NonNull String displayName, @NonNull String url, @NonNull Executor executor) {
this.displayName = displayName;
this.url = url;
this.executor = executor;
}

@Override
@NonNull
public String getDisplayName() {
return displayName;
}

@NonNull
public String getUrl() {
return url;
}

@NonNull
public Executor getExecutor() {
return executor;
}

@Override
public String toString() {
String sb = "DisplayExecutor{" + "displayName='" + displayName + '\'' +
", url='" + url + '\'' +
", executor=" + executor +
'}';
return sb;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

DisplayExecutor that = (DisplayExecutor) o;

return executor.equals(that.executor);
}

@Extension(ordinal = Double.MAX_VALUE)
@Restricted(DoNotUse.class)
public static class InternalComputerListener extends ComputerListener {
@Override
public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException {
c.cachedEnvironment = null;
}
}

@Extension(ordinal = Double.MAX_VALUE)
@Restricted(DoNotUse.class)
public static class InternalComputerListener extends ComputerListener {
@Override
public int hashCode() {
return executor.hashCode();
public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException {
c.cachedEnvironment = null;
}
}

Expand Down
Loading

0 comments on commit 39e6622

Please sign in to comment.