mirror of https://github.com/jenkinsci/jenkins.git
rolling back my 9366 to bring Stephen's slave related changes to the main line
git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@9381 71c3de6d-444a-0410-be80-ed276b4c234a
This commit is contained in:
parent
cf70da21f1
commit
36c60b320b
|
|
@ -1,5 +1,5 @@
|
|||
target
|
||||
.svn
|
||||
pom.ipr
|
||||
pom.iml
|
||||
pom.iml
|
||||
pom.iws
|
||||
|
|
|
|||
|
|
@ -1,21 +1,7 @@
|
|||
package hudson;
|
||||
|
||||
import hudson.maven.ExecutedMojo;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Action;
|
||||
import hudson.model.Descriptor;
|
||||
import hudson.model.Hudson;
|
||||
import hudson.model.Item;
|
||||
import hudson.model.ItemGroup;
|
||||
import hudson.model.Items;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.JobPropertyDescriptor;
|
||||
import hudson.model.ModelObject;
|
||||
import hudson.model.Node;
|
||||
import hudson.model.Project;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.TopLevelItem;
|
||||
import hudson.model.View;
|
||||
import hudson.model.*;
|
||||
import hudson.search.SearchableModelObject;
|
||||
import hudson.tasks.BuildStep;
|
||||
import hudson.tasks.BuildStepDescriptor;
|
||||
|
|
@ -507,6 +493,14 @@ public class Functions {
|
|||
return BuildStepDescriptor.filter(BuildStep.PUBLISHERS, project.getClass());
|
||||
}
|
||||
|
||||
public static List<Descriptor<SlaveStartMethod>> getSlaveStartMethodDescriptors() {
|
||||
return SlaveStartMethod.LIST;
|
||||
}
|
||||
|
||||
public static List<Descriptor<SlaveAvailabilityStrategy>> getSlaveAvailabilityStrategyDescriptors() {
|
||||
return SlaveAvailabilityStrategy.LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the path to the icon of the given action
|
||||
* from the context path.
|
||||
|
|
|
|||
|
|
@ -125,12 +125,22 @@ public abstract class Computer extends AbstractModelObject {
|
|||
|
||||
/**
|
||||
* Returns true if this computer is supposed to be launched via JNLP.
|
||||
* @deprecated see {@linkplain #isStartSupported()} and {@linkplain hudson.model.SlaveStartMethod}
|
||||
*/
|
||||
@Exported
|
||||
@Deprecated
|
||||
public boolean isJnlpAgent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this computer can be launched by Hudson.
|
||||
*/
|
||||
@Exported
|
||||
public boolean isStartSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this node is marked temporarily offline by the user.
|
||||
*
|
||||
|
|
@ -181,7 +191,7 @@ public abstract class Computer extends AbstractModelObject {
|
|||
public List<AbstractProject> getTiedJobs() {
|
||||
return getNode().getSelfLabel().getTiedJobs();
|
||||
}
|
||||
|
||||
|
||||
public RunList getBuilds() {
|
||||
return new RunList(Hudson.getInstance().getAllItems(Job.class)).node(getNode());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import org.kohsuke.stapler.export.Exported;
|
|||
* Serves as the top of {@link Computer}s in the URL hierarchy.
|
||||
* <p>
|
||||
* Getter methods are prefixed with '_' to avoid collision with computer names.
|
||||
*
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
@ExportedBean
|
||||
|
|
@ -43,13 +43,13 @@ public final class ComputerSet implements ModelObject {
|
|||
|
||||
public void do_launchAll(StaplerRequest req, StaplerResponse rsp) throws IOException {
|
||||
for(Computer c : get_all()) {
|
||||
if(c.isJnlpAgent())
|
||||
if(c.isStartSupported())
|
||||
continue;
|
||||
c.launch();
|
||||
}
|
||||
rsp.sendRedirect(".");
|
||||
}
|
||||
|
||||
|
||||
public Api getApi() {
|
||||
return new Api(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ import hudson.util.TextFile;
|
|||
import hudson.util.XStream2;
|
||||
import hudson.widgets.Widget;
|
||||
import net.sf.json.JSONObject;
|
||||
import net.sf.json.JSONArray;
|
||||
import org.acegisecurity.AccessDeniedException;
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.GrantedAuthority;
|
||||
|
|
@ -178,7 +179,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
* <p>
|
||||
* Intuitively, this corresponds to the user database.
|
||||
*
|
||||
* See {@link HudsonFilter} for the concrete authentication protocol.
|
||||
* See {@link HudsonFilter} for the concrete authentication protocol.
|
||||
*
|
||||
* Never null. Always use {@link #setSecurityRealm(SecurityRealm)} to
|
||||
* update this field.
|
||||
|
|
@ -394,7 +395,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
/**
|
||||
* Returns a secret key that survives across container start/stop.
|
||||
* <p>
|
||||
* This value is useful for implementing some of the security features.
|
||||
* This value is useful for implementing some of the security features.
|
||||
*/
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
|
|
@ -1314,7 +1315,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
authorizationStrategy = AuthorizationStrategy.UNSECURED;
|
||||
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
|
||||
}
|
||||
|
||||
|
||||
|
||||
LOGGER.info(String.format("Took %s ms to load",System.currentTimeMillis()-startTime));
|
||||
}
|
||||
|
|
@ -1421,30 +1422,10 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
}
|
||||
}
|
||||
|
||||
numExecutors = Integer.parseInt(req.getParameter("numExecutors"));
|
||||
quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
|
||||
|
||||
systemMessage = Util.nullify(req.getParameter("system_message"));
|
||||
|
||||
{// update slave list
|
||||
List<Slave> newSlaves = new ArrayList<Slave>();
|
||||
String[] names = req.getParameterValues("slave.name");
|
||||
if(names!=null) {
|
||||
for(int i=0;i< names.length;i++) {
|
||||
newSlaves.add(req.bindParameters(Slave.class,"slave.",i));
|
||||
}
|
||||
}
|
||||
this.slaves = newSlaves;
|
||||
updateComputerList();
|
||||
|
||||
// label trim off
|
||||
for (Label l : labels.values()) {
|
||||
l.reset();
|
||||
if(l.getNodes().isEmpty())
|
||||
labels.remove(l);
|
||||
}
|
||||
}
|
||||
|
||||
{// update JDK installations
|
||||
jdks.clear();
|
||||
String[] names = req.getParameterValues("jdk_name");
|
||||
|
|
@ -1487,6 +1468,83 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts submission from the configuration page.
|
||||
*/
|
||||
public synchronized void doConfigExecutorsSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
try {
|
||||
checkPermission(ADMINISTER);
|
||||
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
|
||||
JSONObject json = StructuredForm.get(req);
|
||||
|
||||
numExecutors = Integer.parseInt(req.getParameter("numExecutors"));
|
||||
|
||||
{
|
||||
// update slave list
|
||||
Object src = json.get("slaves");
|
||||
ArrayList<Slave> r = new ArrayList<Slave>();
|
||||
if (src instanceof JSONObject) {
|
||||
r.add(newSlave(req, (JSONObject) src));
|
||||
}
|
||||
if (src instanceof JSONArray) {
|
||||
JSONArray a = (JSONArray) src;
|
||||
for (Object o : a) {
|
||||
if (o instanceof JSONObject) {
|
||||
r.add(newSlave(req, (JSONObject) o));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.slaves = r;
|
||||
updateComputerList();
|
||||
|
||||
// label trim off
|
||||
for (Label l : labels.values()) {
|
||||
l.reset();
|
||||
if(l.getNodes().isEmpty())
|
||||
labels.remove(l);
|
||||
}
|
||||
}
|
||||
|
||||
boolean result = true;
|
||||
|
||||
save();
|
||||
|
||||
if(result)
|
||||
rsp.sendRedirect(req.getContextPath()+'/'); // go to the top page
|
||||
else
|
||||
rsp.sendRedirect("configure"); // back to config
|
||||
} catch (FormException e) {
|
||||
sendError(e,req,rsp);
|
||||
}
|
||||
}
|
||||
|
||||
private Slave newSlave(StaplerRequest req, JSONObject j) throws FormException {
|
||||
final SlaveStartMethod startMethod = newDescribedChild(req, j, "startMethod", SlaveStartMethod.LIST);
|
||||
final SlaveAvailabilityStrategy availabilityStrategy =
|
||||
newDescribedChild(req, j, "availabilityStrategy", SlaveAvailabilityStrategy.LIST);
|
||||
final Slave slave = req.bindJSON(Slave.class, j);
|
||||
slave.setStartMethod(startMethod);
|
||||
slave.setAvailabilityStrategy(availabilityStrategy);
|
||||
return slave;
|
||||
}
|
||||
|
||||
private <T extends Describable<T>> T newDescribedChild(StaplerRequest req, JSONObject j,
|
||||
String name, List<Descriptor<T>> descriptors)
|
||||
throws FormException {
|
||||
final String clazz = j.get(name + "Class").toString();
|
||||
final JSONObject data = j.getJSONObject(name);
|
||||
j.remove(name + "Class");
|
||||
j.remove(name);
|
||||
for (Descriptor<T> d: descriptors) {
|
||||
if (d.getClass().getName().equals(clazz)) {
|
||||
return d.newInstance(req, data);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the new description.
|
||||
*/
|
||||
|
|
@ -1924,7 +1982,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
PrintWriter w = rsp.getWriter();
|
||||
w.println("Shutting down");
|
||||
w.close();
|
||||
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
|
|
@ -2388,7 +2446,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
|
|||
/**
|
||||
* Prefix to static resources like images and javascripts in the war file.
|
||||
* Either "" or strings like "/static/VERSION", which avoids Hudson to pick up
|
||||
* stale cache when the user upgrades to a different version.
|
||||
* stale cache when the user upgrades to a different version.
|
||||
*/
|
||||
public static String RESOURCE_PATH;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import hudson.node_monitors.NodeMonitor;
|
|||
import hudson.util.EnumConverter;
|
||||
import hudson.util.ClockDifference;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.acegisecurity.ui.AbstractProcessingFilter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.io.IOException;
|
||||
|
|
@ -136,7 +140,8 @@ public interface Node {
|
|||
}
|
||||
|
||||
static {
|
||||
Stapler.CONVERT_UTILS.register(new EnumConverter(),Mode.class);
|
||||
Stapler.CONVERT_UTILS.register(new EnumConverter(), Mode.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import hudson.util.StreamCopyThread;
|
|||
import hudson.util.StreamTaskListener;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
|
@ -36,16 +37,13 @@ import java.io.PrintWriter;
|
|||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Information about a Hudson slave node.
|
||||
*
|
||||
|
|
@ -79,10 +77,14 @@ public final class Slave implements Node, Serializable {
|
|||
private Mode mode;
|
||||
|
||||
/**
|
||||
* Command line to launch the agent, like
|
||||
* "ssh myslave java -jar /path/to/hudson-remoting.jar"
|
||||
* Slave availablility strategy.
|
||||
*/
|
||||
private String agentCommand;
|
||||
private SlaveAvailabilityStrategy availabilityStrategy;
|
||||
|
||||
/**
|
||||
* The starter that will startup this slave.
|
||||
*/
|
||||
private SlaveStartMethod startMethod;
|
||||
|
||||
/**
|
||||
* Whitespace-separated labels.
|
||||
|
|
@ -100,13 +102,12 @@ public final class Slave implements Node, Serializable {
|
|||
/**
|
||||
* @stapler-constructor
|
||||
*/
|
||||
public Slave(String name, String description, String command, String remoteFS, String numExecutors, Mode mode,
|
||||
String label) throws FormException {
|
||||
public Slave(String name, String description, String remoteFS, String numExecutors,
|
||||
Mode mode, String label) throws FormException {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.numExecutors = Util.tryParseNumber(numExecutors, 1).intValue();
|
||||
this.mode = mode;
|
||||
this.agentCommand = command;
|
||||
this.remoteFS = remoteFS;
|
||||
this.label = Util.fixNull(label).trim();
|
||||
getAssignedLabels(); // compute labels now
|
||||
|
|
@ -126,8 +127,12 @@ public final class Slave implements Node, Serializable {
|
|||
throw new FormException(Messages.Slave_InvalidConfig_Executors(name), null);
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return agentCommand;
|
||||
public SlaveStartMethod getStartMethod() {
|
||||
return startMethod == null ? new JNLPStartMethod() : startMethod;
|
||||
}
|
||||
|
||||
public void setStartMethod(SlaveStartMethod startMethod) {
|
||||
this.startMethod = startMethod;
|
||||
}
|
||||
|
||||
public String getRemoteFS() {
|
||||
|
|
@ -150,6 +155,14 @@ public final class Slave implements Node, Serializable {
|
|||
return mode;
|
||||
}
|
||||
|
||||
public SlaveAvailabilityStrategy getAvailabilityStrategy() {
|
||||
return availabilityStrategy == null ? new SlaveAvailabilityStrategy.Always() : availabilityStrategy;
|
||||
}
|
||||
|
||||
public void setAvailabilityStrategy(SlaveAvailabilityStrategy availabilityStrategy) {
|
||||
this.availabilityStrategy = availabilityStrategy;
|
||||
}
|
||||
|
||||
public String getLabelString() {
|
||||
return Util.fixNull(label).trim();
|
||||
}
|
||||
|
|
@ -303,15 +316,14 @@ public final class Slave implements Node, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean isJnlpAgent() {
|
||||
return getNode().getCommand().length()==0;
|
||||
return getNode().getStartMethod() instanceof JNLPStartMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted current time stamp.
|
||||
*/
|
||||
private static String getTimestamp() {
|
||||
return String.format("[%1$tD %1$tT]",new Date());
|
||||
@Override
|
||||
public boolean isStartSupported() {
|
||||
return getNode().getStartMethod().isStartSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -321,53 +333,7 @@ public final class Slave implements Node, Serializable {
|
|||
closeChannel();
|
||||
|
||||
final OutputStream launchLog = openLogFile();
|
||||
|
||||
if(slave.agentCommand.length()>0) {
|
||||
// launch the slave agent asynchronously
|
||||
threadPoolForRemoting.execute(new Runnable() {
|
||||
// TODO: do this only for nodes that are so configured.
|
||||
// TODO: support passive connection via JNLP
|
||||
public void run() {
|
||||
final StreamTaskListener listener = new StreamTaskListener(launchLog);
|
||||
try {
|
||||
listener.getLogger().println(Messages.Slave_Launching(getTimestamp()));
|
||||
listener.getLogger().println("$ "+slave.agentCommand);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(Util.tokenize(slave.agentCommand));
|
||||
final EnvVars cookie = ProcessTreeKiller.createCookie();
|
||||
pb.environment().putAll(cookie);
|
||||
final Process proc = pb.start();
|
||||
|
||||
// capture error information from stderr. this will terminate itself
|
||||
// when the process is killed.
|
||||
new StreamCopyThread("stderr copier for remote agent on "+slave.getNodeName(),
|
||||
proc.getErrorStream(), launchLog).start();
|
||||
|
||||
setChannel(proc.getInputStream(),proc.getOutputStream(),launchLog,new Listener() {
|
||||
public void onClosed(Channel channel, IOException cause) {
|
||||
if(cause!=null)
|
||||
cause.printStackTrace(listener.error(Messages.Slave_Terminated(getTimestamp())));
|
||||
ProcessTreeKiller.get().kill(proc,cookie);
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("slave agent launched for "+slave.getNodeName());
|
||||
numRetryAttempt=0;
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace(listener.error("aborted"));
|
||||
} catch (IOException e) {
|
||||
Util.displayIOException(e,listener);
|
||||
|
||||
String msg = Util.getWin32ErrorMessage(e);
|
||||
if(msg==null) msg="";
|
||||
else msg=" : "+msg;
|
||||
msg = Messages.Slave_UnableToLaunch(slave.getNodeName(),msg);
|
||||
logger.log(Level.SEVERE,msg,e);
|
||||
e.printStackTrace(listener.error(msg));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
slave.startMethod.start(this, slave, launchLog, logger);
|
||||
}
|
||||
|
||||
public OutputStream openLogFile() {
|
||||
|
|
@ -391,7 +357,7 @@ public final class Slave implements Node, Serializable {
|
|||
if(this.channel!=null)
|
||||
throw new IllegalStateException("Already connected");
|
||||
|
||||
Channel channel = new Channel(nodeName,threadPoolForRemoting, Channel.Mode.NEGOTIATE,
|
||||
Channel channel = new Channel(nodeName,threadPoolForRemoting, Channel.Mode.NEGOTIATE,
|
||||
in,out, launchLog);
|
||||
channel.addListener(new Listener() {
|
||||
public void onClosed(Channel c,IOException cause) {
|
||||
|
|
@ -592,6 +558,11 @@ public final class Slave implements Node, Serializable {
|
|||
if(command.length()>0) command += ' ';
|
||||
agentCommand = command+"java -jar ~/bin/slave.jar";
|
||||
}
|
||||
if (startMethod == null) {
|
||||
startMethod = (agentCommand == null || agentCommand.trim().length() == 0)
|
||||
? new JNLPStartMethod()
|
||||
: new CommandStartMethod(agentCommand);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -611,6 +582,126 @@ public final class Slave implements Node, Serializable {
|
|||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
||||
public static class JNLPStartMethod extends SlaveStartMethod {
|
||||
|
||||
@Override
|
||||
public boolean isStartSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void start(ComputerImpl computer, Slave slave, OutputStream launchLog, Logger logger) {
|
||||
// do nothing as we cannot self start
|
||||
}
|
||||
|
||||
//@DataBoundConstructor
|
||||
public JNLPStartMethod() {
|
||||
}
|
||||
|
||||
public Descriptor<SlaveStartMethod> getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
public static final Descriptor<SlaveStartMethod> DESCRIPTOR = new Descriptor<SlaveStartMethod>(JNLPStartMethod.class) {
|
||||
public String getDisplayName() {
|
||||
return "Launch slave agents via JNLP";
|
||||
}
|
||||
|
||||
public SlaveStartMethod newInstance(StaplerRequest req, JSONObject formData) throws FormException {
|
||||
return new JNLPStartMethod();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class CommandStartMethod extends SlaveStartMethod {
|
||||
|
||||
/**
|
||||
* Command line to launch the agent, like
|
||||
* "ssh myslave java -jar /path/to/hudson-remoting.jar"
|
||||
*/
|
||||
private String agentCommand;
|
||||
|
||||
@DataBoundConstructor
|
||||
public CommandStartMethod(String command) {
|
||||
this.agentCommand = command;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return agentCommand;
|
||||
}
|
||||
|
||||
public Descriptor<SlaveStartMethod> getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
public static final Descriptor<SlaveStartMethod> DESCRIPTOR = new Descriptor<SlaveStartMethod>(CommandStartMethod.class) {
|
||||
public String getDisplayName() {
|
||||
return "Launch slave via execution of command on the Master";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the formatted current time stamp.
|
||||
*/
|
||||
private static String getTimestamp() {
|
||||
return String.format("[%1$tD %1$tT]", new Date());
|
||||
}
|
||||
|
||||
public void start(final ComputerImpl computer, final Slave slave, final OutputStream launchLog,
|
||||
final Logger logger) {
|
||||
final CommandStartMethod method = (CommandStartMethod) slave.startMethod;
|
||||
// launch the slave agent asynchronously
|
||||
Computer.threadPoolForRemoting.execute(new Runnable() {
|
||||
// TODO: do this only for nodes that are so configured.
|
||||
// TODO: support passive connection via JNLP
|
||||
public void run() {
|
||||
final StreamTaskListener listener = new StreamTaskListener(launchLog);
|
||||
try {
|
||||
listener.getLogger().println(Messages.Slave_Launching(getTimestamp()));
|
||||
listener.getLogger().println("$ " + method.getCommand());
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(Util.tokenize(method.getCommand()));
|
||||
final EnvVars cookie = ProcessTreeKiller.createCookie();
|
||||
pb.environment().putAll(cookie);
|
||||
final Process proc = pb.start();
|
||||
|
||||
// capture error information from stderr. this will terminate itself
|
||||
// when the process is killed.
|
||||
new StreamCopyThread("stderr copier for remote agent on " + slave.getNodeName(),
|
||||
proc.getErrorStream(), launchLog).start();
|
||||
|
||||
computer.setChannel(proc.getInputStream(), proc.getOutputStream(), launchLog, new Listener() {
|
||||
public void onClosed(Channel channel, IOException cause) {
|
||||
if (cause != null) {
|
||||
cause.printStackTrace(
|
||||
listener.error(Messages.Slave_Terminated(getTimestamp())));
|
||||
}
|
||||
ProcessTreeKiller.get().kill(proc, cookie);
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("slave agent launched for " + slave.getNodeName());
|
||||
computer.numRetryAttempt = 0;
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace(listener.error("aborted"));
|
||||
} catch (IOException e) {
|
||||
Util.displayIOException(e, listener);
|
||||
|
||||
String msg = Util.getWin32ErrorMessage(e);
|
||||
if (msg == null) {
|
||||
msg = "";
|
||||
} else {
|
||||
msg = " : " + msg;
|
||||
}
|
||||
msg = Messages.Slave_UnableToLaunch(slave.getNodeName(), msg);
|
||||
logger.log(Level.SEVERE, msg, e);
|
||||
e.printStackTrace(listener.error(msg));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// backwrad compatibility
|
||||
//
|
||||
|
|
@ -630,4 +721,28 @@ public final class Slave implements Node, Serializable {
|
|||
* @deprecated
|
||||
*/
|
||||
private transient String command;
|
||||
/**
|
||||
* Command line to launch the agent, like
|
||||
* "ssh myslave java -jar /path/to/hudson-remoting.jar"
|
||||
*/
|
||||
private transient String agentCommand;
|
||||
|
||||
static {
|
||||
SlaveStartMethod.LIST.add(Slave.JNLPStartMethod.DESCRIPTOR);
|
||||
SlaveStartMethod.LIST.add(Slave.CommandStartMethod.DESCRIPTOR);
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,38 @@
|
|||
package hudson.model;
|
||||
|
||||
import hudson.model.Slave.ComputerImpl;
|
||||
import static hudson.model.SlaveAvailabilityStrategy.*;
|
||||
import hudson.triggers.SafeTimerTask;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Periodically checks the slaves and try to reconnect dead slaves.
|
||||
*
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class SlaveReconnectionWork extends SafeTimerTask {
|
||||
protected void doRun() {
|
||||
for(Slave s : Hudson.getInstance().getSlaves()) {
|
||||
ComputerImpl c = s.getComputer();
|
||||
if(c==null) // shouldn't happen, but let's be defensive
|
||||
continue;
|
||||
if(c.isOffline() && !c.isJnlpAgent())
|
||||
c.tryReconnect();
|
||||
// use a weak hashmap
|
||||
Map<Slave, Long> nextCheck = new WeakHashMap<Slave, Long>();
|
||||
for (Slave s : Hudson.getInstance().getSlaves()) {
|
||||
if (!nextCheck.containsKey(s) || System.currentTimeMillis() > nextCheck.get(s)) {
|
||||
final Queue queue = Hudson.getInstance().getQueue();
|
||||
boolean hasJob = false;
|
||||
for (Executor exec: s.getComputer().getExecutors()) {
|
||||
if (!exec.isIdle()) {
|
||||
hasJob = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO get only the items from the queue that can apply to this slave
|
||||
State state = new State(queue.getItems().length > 0, hasJob);
|
||||
// at the moment I don't trust strategies to wait more than 60 minutes
|
||||
// strategies need to wait at least one minute
|
||||
final long waitInMins = Math.min(1, Math.max(60, s.getAvailabilityStrategy().check(s, state)));
|
||||
nextCheck.put(s, System.currentTimeMillis() + 60 * 1000 * waitInMins);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package hudson.model;
|
||||
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.security.SecurityRealm;
|
||||
import hudson.util.DescriptorList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -24,5 +26,8 @@ public abstract class SlaveStartMethod implements Describable<SlaveStartMethod>,
|
|||
|
||||
public abstract void start(Slave.ComputerImpl computer, Slave slave, OutputStream launchLog, Logger logger);
|
||||
|
||||
public static final List<Descriptor<SlaveStartMethod>> LIST = new ArrayList<Descriptor<SlaveStartMethod>>();
|
||||
/**
|
||||
* All registered {@link SlaveStartMethod} implementations.
|
||||
*/
|
||||
public static final DescriptorList<SlaveStartMethod> LIST = new DescriptorList<SlaveStartMethod>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,17 +206,18 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
|
|||
public static final Timer timer = new Timer("Hudson cron thread");
|
||||
|
||||
public static void init() {
|
||||
timer.scheduleAtFixedRate(new Cron(), 1000*60, 1000*60/*every minute*/);
|
||||
long MIN = 1000*60;
|
||||
long HOUR =60*MIN;
|
||||
long DAY = 24*HOUR;
|
||||
|
||||
timer.scheduleAtFixedRate(new Cron(), MIN, MIN);
|
||||
|
||||
new DoubleLaunchChecker().schedule();
|
||||
|
||||
// clean up fingerprint once a day
|
||||
long MIN = 1000*60;
|
||||
long HOUR =60*MIN;
|
||||
long DAY = 24*HOUR;
|
||||
timer.scheduleAtFixedRate(new FingerprintCleanupThread(),DAY,DAY);
|
||||
timer.scheduleAtFixedRate(new WorkspaceCleanupThread(),DAY+4*HOUR,DAY);
|
||||
timer.scheduleAtFixedRate(new SlaveReconnectionWork(),15*MIN,5*MIN);
|
||||
timer.scheduleAtFixedRate(new SlaveReconnectionWork(),15*MIN,1*MIN);
|
||||
|
||||
// start monitoring nodes, although there's no hurry.
|
||||
timer.schedule(new SafeTimerTask() {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<j:when test="${app.slaveAgentPort==-1}">
|
||||
<img src="${imagesURL}/32x32/error.gif"/>
|
||||
<div class="error">
|
||||
TCP port for JNLP slave agentsis is disabled.
|
||||
TCP port for JNLP slave agents is disabled.
|
||||
<a href="${rootURL}/configure">Go to system config screen and change it</a>.
|
||||
</div>
|
||||
</j:when>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@
|
|||
<s:entry title="${%System Message}" help="/help/system-config/systemMessage.html">
|
||||
<s:textarea name="system_message" value="${it.systemMessage}" />
|
||||
</s:entry>
|
||||
<s:entry title="${%# of executors}" help="/help/system-config/numExecutors.html">
|
||||
<input type="text" name="numExecutors" class="setting-input number"
|
||||
value="${it.numExecutors}"/>
|
||||
</s:entry>
|
||||
<s:entry title="${%Quiet period}" help="/help/project-config/quietPeriod.html">
|
||||
<input class="setting-input number" name="quiet_period"
|
||||
type="text" value="${it.quietPeriod}"/>
|
||||
|
|
@ -56,55 +52,6 @@
|
|||
</s:entry>
|
||||
</s:optionalBlock>
|
||||
|
||||
|
||||
<s:section title="${%Master/Slave Support}">
|
||||
<s:entry title="${%Slaves}"
|
||||
description="${%slaves.description}">
|
||||
<s:repeatable var="s" items="${it.slaves}" name="slaves">
|
||||
<table width="100%">
|
||||
<s:entry title="${%name}" help="/help/system-config/master-slave/name.html">
|
||||
<s:textbox name="slave.name" value="${s.nodeName}"
|
||||
checkUrl="'fieldCheck?errorText='+escape('${%Name is mandatory}')+'&value='+escape(this.value)"/>
|
||||
</s:entry>
|
||||
|
||||
<s:entry title="${%launch command}" help="/help/system-config/master-slave/command.html">
|
||||
<s:textbox name="slave.command" value="${s.command}"/>
|
||||
</s:entry>
|
||||
|
||||
<s:entry title="${%description}" help="/help/system-config/master-slave/description.html">
|
||||
<s:textbox name="slave.description" value="${s.nodeDescription}" />
|
||||
</s:entry>
|
||||
<s:entry title="${%# of executors}" help="/help/system-config/master-slave/numExecutors.html">
|
||||
<s:textbox name="slave.numExecutors" value="${s.numExecutors}"
|
||||
checkUrl="'fieldCheck?errorText='+escape('${%Number of executors is mandatory.}')+'&type=number-positive&value='+escape(this.value)"/>
|
||||
</s:entry>
|
||||
<s:entry title="${%remote FS root}" help="/help/system-config/master-slave/remoteFS.html">
|
||||
<s:textbox name="slave.remoteFS" value="${s.remoteFS}"
|
||||
checkUrl="'fieldCheck?errorText='+escape('${%Remote directory is mandatory.}')+'&value='+escape(this.value)"/>
|
||||
</s:entry>
|
||||
|
||||
<s:entry title="${%usage}" help="/help/system-config/master-slave/usage.html">
|
||||
<select class="setting-input" name="slave.mode">
|
||||
<j:forEach var="m" items="${h.getNodeModes()}">
|
||||
<s:option value="${m.name}" selected="${m==s.mode}">${m.description}</s:option>
|
||||
</j:forEach>
|
||||
</select>
|
||||
</s:entry>
|
||||
|
||||
<s:entry title="${%labels}" help="/help/system-config/master-slave/label.html">
|
||||
<s:textbox name="slave.label" value="${s.labelString}" />
|
||||
</s:entry>
|
||||
|
||||
<s:entry title="">
|
||||
<div align="right">
|
||||
<s:repeatableDeleteButton />
|
||||
</div>
|
||||
</s:entry>
|
||||
</table>
|
||||
</s:repeatable>
|
||||
</s:entry>
|
||||
</s:section>
|
||||
|
||||
<s:section title="${%JDKs}">
|
||||
<s:entry title="${%JDK installations}"
|
||||
description="${%List of JDK installations on this system}">
|
||||
|
|
|
|||
|
|
@ -80,25 +80,29 @@
|
|||
</table>
|
||||
</div>
|
||||
</j:if>
|
||||
<p>${d}, ${d.class.name}, ${d == null}</p>
|
||||
</s:nested>
|
||||
</s:dropdownListBlock>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
</s:dropdownList>
|
||||
|
||||
<s:dropdownList name="slave.onlineAvailability" title="${%Availability}"
|
||||
<s:dropdownList name="slave.availabilityStrategyClass" title="${%Availability}"
|
||||
help="/help/system-config/master-slave/availability.html">
|
||||
<j:forEach var="a" items="${h.getNodeAvailabilities()}">
|
||||
<s:dropdownListBlock value="${a}" name="${a.name}"
|
||||
selected="${a==s.onlineAvailability}"
|
||||
title="${a.description}">
|
||||
<s:nested>
|
||||
<table width="100%">
|
||||
<st:include from="${a}" page="${a.name}.jelly"/>
|
||||
</table>
|
||||
</s:nested>
|
||||
</s:dropdownListBlock>
|
||||
<j:forEach var="d" items="${h.getSlaveAvailabilityStrategyDescriptors()}">
|
||||
<j:if test="${d != null}">
|
||||
<s:dropdownListBlock value="${d.class.name}" name="${d.displayName}"
|
||||
selected="${s.availabilityStrategy.descriptor==d}"
|
||||
title="${d.displayName}">
|
||||
<s:nested>
|
||||
<j:if test='${d.configPage != ""}'>
|
||||
<j:set var="descriptor" value="${d}"/>
|
||||
<j:set var="instance"
|
||||
value="${h.ifThenElse(s.availabilityStrategy.descriptor==d,s.availabilityStrategy,null)}"/>
|
||||
<st:include from="${d}" page="${d.configPage}" optional="true"/>
|
||||
</j:if>
|
||||
</s:nested>
|
||||
</s:dropdownListBlock>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
</s:dropdownList>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
<l:main-panel>
|
||||
<h1>${%Manage Hudson}</h1>
|
||||
<table style="padding-left: 2em;">
|
||||
<local:feature icon="setting.gif" href="configure" title="${%System Configuration}"/>
|
||||
<local:feature icon="setting.gif" href="configure" title="${%Configure System}"/>
|
||||
<local:feature icon="setting.gif" href="configureExecutors" title="${%Configure Executors}"/>
|
||||
<local:feature icon="refresh.gif" href="reload" title="${%Reload Configuration from Disk}">
|
||||
${%Discard all the loaded data in memory and reload everything from file system.}
|
||||
${%Useful when you modified config files directly on disk.}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
<f:entry title="${%launch command}" help="/help/system-config/master-slave/command.html">
|
||||
<f:textbox name="startMethod.command" value="${instance.command}"/>
|
||||
</f:entry>
|
||||
|
||||
</j:jelly>
|
||||
|
|
@ -0,0 +1 @@
|
|||
launch\ command=\u8d77\u52d5\u30b3\u30de\u30f3\u30c9
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
</j:jelly>
|
||||
|
|
@ -4,30 +4,51 @@
|
|||
@name (mandatory)
|
||||
name of the drop-down list.
|
||||
-->
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
|
||||
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
|
||||
<j:set var="id" value="${h.generateId()}"/>
|
||||
<f:entry title="${title}">
|
||||
<!-- create drop-down list -->
|
||||
<select name="${name}" id="${id}" onchange="updateDropDownList(this)">
|
||||
<j:set var="dropdownListMode" value="createSelectField" />
|
||||
<d:invokeBody />
|
||||
</select>
|
||||
<j:set var="id" value="${h.generateId()}"/>
|
||||
<tr>
|
||||
<td class="setting-leftspace"><st:nbsp/></td>
|
||||
<td class="setting-name">
|
||||
${attrs.title}
|
||||
</td>
|
||||
<td class="setting-main">
|
||||
<!-- create drop-down list -->
|
||||
<select name="${name}" id="${id}" onchange="updateDropDownList(this)">
|
||||
<j:set var="dropdownListMode" value="createSelectField"/>
|
||||
<d:invokeBody/>
|
||||
</select>
|
||||
<script>
|
||||
$$('${id}').subForms = [];
|
||||
</script>
|
||||
</td>
|
||||
<j:if test="${attrs.help!=null}">
|
||||
<td class="setting-help">
|
||||
<a href="#" class="help-button" helpURL="${rootURL}${attrs.help}"><img src="${imagesURL}/16x16/help.gif"
|
||||
alt="Help for feature: ${title}"/></a>
|
||||
</td>
|
||||
</j:if>
|
||||
</tr>
|
||||
|
||||
<!-- generate the actual form entries -->
|
||||
<f:nested>
|
||||
<table width="100%">
|
||||
<j:set var="dropdownListMode" value="generateEntries"/>
|
||||
<d:invokeBody/>
|
||||
</table>
|
||||
</f:nested>
|
||||
|
||||
<!-- set the initial visibility -->
|
||||
<script>
|
||||
$$('${id}').subForms = [];
|
||||
updateDropDownList($$('${id}'));
|
||||
</script>
|
||||
</f:entry>
|
||||
|
||||
<!-- generate the actual form entries -->
|
||||
<f:nested>
|
||||
<table width="100%">
|
||||
<j:set var="dropdownListMode" value="generateEntries" />
|
||||
<d:invokeBody />
|
||||
</table>
|
||||
</f:nested>
|
||||
|
||||
<!-- set the initial visibility -->
|
||||
<script>
|
||||
updateDropDownList($$('${id}'));
|
||||
</script>
|
||||
<j:if test="${!empty(attrs.description)}">
|
||||
<f:description>
|
||||
${description}
|
||||
</f:description>
|
||||
</j:if>
|
||||
<j:if test="${help!=null}">
|
||||
<f:helpArea/>
|
||||
</j:if>
|
||||
</j:jelly>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ install: build
|
|||
# Add here commands to install the package into debian/hudson.
|
||||
mkdir -p $(CURDIR)/debian/hudson/usr/local/hudson
|
||||
cp hudson.war $(CURDIR)/debian/hudson/usr/local/hudson/hudson.war
|
||||
cp debian/hudson.bin $(CURDIR)/debian/hudson/usr/sbin/hudson
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue