提交 80bc832c 编写于 作者: S stephenconnolly

Fixing a number of issues, and ensuring that the schedule is processed a bit more correctly

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@14463 71c3de6d-444a-0410-be80-ed276b4c234a
上级 3cd3317e
...@@ -4,7 +4,7 @@ import java.util.logging.Logger; ...@@ -4,7 +4,7 @@ import java.util.logging.Logger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Date; import java.util.concurrent.ExecutionException;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectStreamException; import java.io.ObjectStreamException;
import java.io.InvalidObjectException; import java.io.InvalidObjectException;
...@@ -12,10 +12,10 @@ import java.io.InvalidObjectException; ...@@ -12,10 +12,10 @@ import java.io.InvalidObjectException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import hudson.model.Descriptor; import hudson.model.Descriptor;
import hudson.model.Computer;
import hudson.scheduler.CronTabList; import hudson.scheduler.CronTabList;
import hudson.util.FormFieldValidator; import hudson.util.FormFieldValidator;
import static hudson.Util.fixNull; import static hudson.Util.fixNull;
import hudson.Util;
import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.StaplerResponse;
...@@ -34,22 +34,101 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom ...@@ -34,22 +34,101 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
private final String startTimeSpec; private final String startTimeSpec;
private transient CronTabList tabs; private transient CronTabList tabs;
private transient Calendar lastChecked; private transient Calendar lastChecked;
private final long upTimeMins; private transient Calendar lastStarted = null;
private transient long nextStop = Long.MIN_VALUE;
private transient long nextStart = Long.MIN_VALUE;
private transient long lastStop = Long.MAX_VALUE;
private transient long lastStart = Long.MAX_VALUE;
private final int upTimeMins;
private final boolean keepUpWhenActive;
@DataBoundConstructor @DataBoundConstructor
public SimpleScheduledRetentionStrategy(String startTimeSpec, long upTimeMins) throws ANTLRException { public SimpleScheduledRetentionStrategy(String startTimeSpec, int upTimeMins, boolean keepUpWhenActive)
throws ANTLRException {
this.startTimeSpec = startTimeSpec; this.startTimeSpec = startTimeSpec;
this.keepUpWhenActive = keepUpWhenActive;
this.tabs = CronTabList.create(startTimeSpec); this.tabs = CronTabList.create(startTimeSpec);
this.lastChecked = new GregorianCalendar(); this.lastChecked = new GregorianCalendar();
this.upTimeMins = upTimeMins; this.upTimeMins = Math.max(1, upTimeMins);
this.lastChecked.add(Calendar.MINUTE, -1); this.lastChecked.add(Calendar.MINUTE, -1);
} }
protected Object readResolve() throws ObjectStreamException { public int getUpTimeMins() {
return upTimeMins;
}
public boolean isKeepUpWhenActive() {
return keepUpWhenActive;
}
public String getStartTimeSpec() {
return startTimeSpec;
}
private synchronized void updateStartStopWindow() {
if (lastStart == Long.MAX_VALUE && lastStop == Long.MAX_VALUE) {
// we need to initialize
// get some useful default values for the lastStart and lastStop... they should be in the past and at least
// less than any useful real last start/stop
// so default lastStart = now - upTime * 3, and lastStop = now - upTime * 2
Calendar time = new GregorianCalendar();
time.add(Calendar.MINUTE, -upTimeMins);
time.add(Calendar.MINUTE, -upTimeMins);
time.add(Calendar.MINUTE, -upTimeMins);
lastStart = time.getTimeInMillis();
time.add(Calendar.MINUTE, upTimeMins);
lastStop = time.getTimeInMillis();
// we're only interested in the last start if it is less than the upTimeMins ago
// any older and last Start is not relevant as the node should be stopped
time = new GregorianCalendar();
time.add(Calendar.MINUTE, -upTimeMins);
time.add(Calendar.MINUTE, -1);
while (System.currentTimeMillis() + 1000 > time.getTimeInMillis()) {
if (tabs.check(time)) {
lastStart = time.getTimeInMillis();
time.add(Calendar.MINUTE, upTimeMins);
lastStop = time.getTimeInMillis();
break;
}
time.add(Calendar.MINUTE, 1);
}
nextStart = lastStart;
nextStop = lastStop;
}
if (nextStop < System.currentTimeMillis()) {
// next stop is in the past
lastStart = nextStart;
lastStop = nextStop;
// we don't want to look too far into the future
Calendar time = new GregorianCalendar();
time.add(Calendar.MINUTE, Math.min(15, upTimeMins));
long stopLooking = time.getTimeInMillis();
time.setTimeInMillis(nextStop);
while (stopLooking > time.getTimeInMillis()) {
if (tabs.check(time)) {
nextStart = time.getTimeInMillis();
time.add(Calendar.MINUTE, upTimeMins);
nextStop = time.getTimeInMillis();
break;
}
time.add(Calendar.MINUTE, 1);
}
}
}
protected synchronized Object readResolve() throws ObjectStreamException {
try { try {
tabs = CronTabList.create(startTimeSpec); tabs = CronTabList.create(startTimeSpec);
lastChecked = new GregorianCalendar(); lastChecked = new GregorianCalendar();
this.lastChecked.add(Calendar.MINUTE, -1); this.lastChecked.add(Calendar.MINUTE, -1);
nextStop = Long.MIN_VALUE;
nextStart = Long.MIN_VALUE;
lastStop = Long.MAX_VALUE;
lastStart = Long.MAX_VALUE;
} catch (ANTLRException e) { } catch (ANTLRException e) {
InvalidObjectException x = new InvalidObjectException(e.getMessage()); InvalidObjectException x = new InvalidObjectException(e.getMessage());
x.initCause(e); x.initCause(e);
...@@ -58,37 +137,64 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom ...@@ -58,37 +137,64 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
return this; return this;
} }
public long check(SlaveComputer c) { public synchronized long check(final SlaveComputer c) {
if (c.isOffline()) { if (lastStarted == null && c.isOnline()) {
Calendar startUp = null; lastStarted = new GregorianCalendar();
while (new Date().getTime() - lastChecked.getTimeInMillis() > 1000) { } else if (lastStarted != null && c.isOffline()) {
LOGGER.log(Level.FINE, "scheduled start checking {0}", lastChecked); lastStarted = null;
if (tabs.check(lastChecked)) { }
// need to ensure that we don't fire up after the upTime has been passed updateStartStopWindow();
// if we have an aggressive upTime, or a slow check long now = System.currentTimeMillis();
startUp = (Calendar) lastChecked.clone(); boolean shouldBeOnline = (lastStart < now && lastStop > now) || (nextStart < now && nextStop > now);
LOGGER.log(Level.INFO, "Checking computer {0} against schedule. online = {1}, shouldBeOnline = {2}",
new Object[]{c.getName(), c.isOnline(), shouldBeOnline});
if (shouldBeOnline && c.isOffline()) {
LOGGER.log(Level.INFO, "Trying to launch computer {0} as schedule says it should be on-line at "
+ "this point in time", new Object[]{c.getName()});
if (c.isLaunchSupported()) {
Computer.threadPoolForRemoting.submit(new Runnable() {
public void run() {
try {
c.connect(true).get();
if (c.isOnline()) {
LOGGER.log(Level.INFO, "Launched computer {0} per schedule", new Object[]{c.getName()});
} }
lastChecked.add(Calendar.MINUTE, 1); if (keepUpWhenActive && c.isOnline() && !c.isAcceptingTasks()) {
LOGGER.log(Level.INFO,
"Enabling new jobs for computer {0} as it has started its scheduled uptime",
new Object[]{c.getName()});
c.setAcceptingTasks(true);
} }
if (startUp != null) { } catch (InterruptedException e) {
if (new Date().getTime() - startUp.getTimeInMillis() > 1000 * 60L * upTimeMins) { } catch (ExecutionException e) {
LOGGER.log(Level.INFO, "Missed scheduled startup");
} else {
LOGGER.log(Level.INFO, "Launching computer {0} per schedule", new Object[]{c.getName()});
if (c.isLaunchSupported()) {
c.connect(true);
} }
} }
});
}
} else if (!shouldBeOnline && c.isOnline()) {
if (keepUpWhenActive) {
if (!c.isIdle() && c.isAcceptingTasks()) {
c.setAcceptingTasks(false);
LOGGER.log(Level.INFO,
"Disabling new jobs for computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
return 1;
} else if (c.isIdle() && c.isAcceptingTasks()) {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect();
} else if (c.isIdle() && !c.isAcceptingTasks()) {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect();
} }
return upTimeMins;
} else { } else {
if (new Date().getTime() - lastChecked.getTimeInMillis() > 1000 * 60L * upTimeMins) {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished its scheduled uptime", LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()}); new Object[]{c.getName()});
c.disconnect(); c.disconnect();
} }
return 1;
} }
return 1;
} }
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
...@@ -97,7 +203,7 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom ...@@ -97,7 +203,7 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
return DESCRIPTOR; return DESCRIPTOR;
} }
private static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> { public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
/** /**
* Constructs a new DescriptorImpl. * Constructs a new DescriptorImpl.
......
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" <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"> xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Startup Schedule}" help="/help/project-config/timer-format.html"> <f:entry title="${%Startup Schedule}" help="/help/project-config/timer-format.html">
<f:textarea name="slave.startTimeSpec" checkUrl="'${rootURL}/slaves/SimpleScheduledRetentionStrategy/check?value='+escape(this.value)" <f:textarea name="retentionStrategy.startTimeSpec" checkUrl="'${rootURL}/retentionStrategy/SimpleScheduledRetentionStrategy/check?value='+escape(this.value)"
value="${s.startTimeSpec}"/> value="${instance.startTimeSpec}"/>
</f:entry> </f:entry>
<f:entry title="${%Scheduled Uptime (mins)}"> <f:entry title="${%Scheduled Uptime (mins)}">
<f:textarea name="slave.upTimeMins" value="${s.upTimeMins}"/> <input class="setting-input number validated" name="retentionStrategy.upTimeMins"
type="text" value="${instance.upTimeMins}"
checkUrl="'${rootURL}/fieldCheck?errorText='+escape('${%Scheduled Uptime is mandatory.}')+'&amp;value='+escape(this.value)"/>
</f:entry>
<f:entry title="${%Keep on-line while jobs are running}">
<f:checkbox name="retentionStrategy.keepUpWhenActive" checked="${h.defaultToTrue(instance.keepUpWhenActive)}"/>
</f:entry> </f:entry>
</j:jelly> </j:jelly>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册