提交 36c60b32 编写于 作者: K kohsuke

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
上级 cf70da21
target
.svn
pom.ipr
pom.iml
pom.iml
pom.iws
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 SlaveAvailabilityStrategy availabilityStrategy;
/**
* The starter that will startup this slave.
*/
private String agentCommand;
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);
// }
}
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);
}
}
}
}
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}')+'&amp;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.}')+'&amp;type=number-positive&amp;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.}')+'&amp;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.}
......
<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>
\ No newline at end of file
launch\ command=\u8d77\u52d5\u30b3\u30de\u30f3\u30c9
\ No newline at end of file
<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>
\ No newline at end of file
......@@ -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>
<script>
$$('${id}').subForms = [];
</script>
</f:entry>
<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>
<!-- 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>
<!-- 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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册