Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding extension point so that the master can install code that restarts JNLP slaves.
- Loading branch information
Showing
4 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
core/src/main/java/jenkins/slaves/restarter/JnlpSlaveRestarterInstaller.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package jenkins.slaves.restarter; | ||
|
||
import hudson.Extension; | ||
import hudson.model.Computer; | ||
import hudson.model.TaskListener; | ||
import hudson.remoting.Callable; | ||
import hudson.remoting.Engine; | ||
import hudson.remoting.EngineListener; | ||
import hudson.remoting.EngineListenerAdapter; | ||
import hudson.remoting.VirtualChannel; | ||
import hudson.slaves.ComputerListener; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.logging.Logger; | ||
|
||
import static java.util.logging.Level.*; | ||
|
||
/** | ||
* Actual slave restart logic. | ||
* | ||
* <p> | ||
* Use {@link ComputerListener} to install {@link EngineListener}, which in turn gets executed when | ||
* the slave gets disconnected. | ||
* | ||
* @author Kohsuke Kawaguchi | ||
*/ | ||
@Extension | ||
public class JnlpSlaveRestarterInstaller extends ComputerListener { | ||
@Override | ||
public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException { | ||
final List<SlaveRestarter> restarters = new ArrayList<SlaveRestarter>(SlaveRestarter.all()); | ||
|
||
VirtualChannel ch = c.getChannel(); | ||
if (ch==null) return; // defensive check | ||
|
||
List<SlaveRestarter> effective = ch.call(new Callable<List<SlaveRestarter>, IOException>() { | ||
public List<SlaveRestarter> call() throws IOException { | ||
Engine e = Engine.current(); | ||
if (e == null) return null; // not running under Engine | ||
|
||
try { | ||
Engine.class.getMethod("addListener", EngineListener.class); | ||
} catch (NoSuchMethodException _) { | ||
return null; // running with older version of remoting that doesn't support adding listener | ||
} | ||
|
||
// filter out ones that doesn't apply | ||
for (Iterator<SlaveRestarter> itr = restarters.iterator(); itr.hasNext(); ) { | ||
SlaveRestarter r = itr.next(); | ||
if (!r.canWork()) | ||
itr.remove(); | ||
} | ||
|
||
e.addListener(new EngineListenerAdapter() { | ||
@Override | ||
public void onDisconnect() { | ||
try { | ||
for (SlaveRestarter r : restarters) { | ||
try { | ||
r.restart(); | ||
} catch (Exception x) { | ||
LOGGER.log(SEVERE, "Failed to restart slave with "+r, x); | ||
} | ||
} | ||
} finally { | ||
// if we move on to the reconnection without restart, | ||
// don't let the current implementations kick in when the slave loses connection again | ||
restarters.clear(); | ||
} | ||
} | ||
}); | ||
|
||
return restarters; | ||
} | ||
}); | ||
|
||
// TODO: report this to GUI | ||
LOGGER.fine("Effective SlaveRestarter on "+c.getDisplayName()+": "+effective); | ||
} | ||
|
||
private static final Logger LOGGER = Logger.getLogger(JnlpSlaveRestarterInstaller.class.getName()); | ||
} |
49 changes: 49 additions & 0 deletions
49
core/src/main/java/jenkins/slaves/restarter/SlaveRestarter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package jenkins.slaves.restarter; | ||
|
||
import hudson.ExtensionList; | ||
import hudson.ExtensionPoint; | ||
import jenkins.model.Jenkins; | ||
|
||
import java.io.Serializable; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Extension point to control how to restart JNLP slave when it loses the connection with the master. | ||
* | ||
* <p> | ||
* Objects are instantiated on the master, then transfered to a slave via serialization. | ||
* | ||
* @author Kohsuke Kawaguchi | ||
*/ | ||
This comment has been minimized.
Sorry, something went wrong. |
||
public abstract class SlaveRestarter implements ExtensionPoint, Serializable { | ||
/** | ||
* Called on the slave to see if this restarter can work on this slave. | ||
*/ | ||
public abstract boolean canWork(); | ||
|
||
/** | ||
* If {@link #canWork()} method returns true, this method is called later when | ||
* the connection is lost to restart the slave. | ||
* | ||
* <p> | ||
* Note that by the time this method is called, classloader is no longer capable of | ||
* loading any additional classes. Therefore {@link #canWork()} method must have | ||
* exercised enough of the actual restart process so that this call can proceed | ||
* without trying to load additional classes nor resources. | ||
* | ||
* <p> | ||
* This method is not expected to return, and the JVM should terminate before this call returns. | ||
* If the method returns normally, the JNLP slave will move on to the reconnection without restart. | ||
* If an exception is thrown, it is reported as an error and then the JNLP slave will move on to the | ||
* reconnection without restart. | ||
*/ | ||
public abstract void restart() throws Exception; | ||
|
||
public static ExtensionList<SlaveRestarter> all() { | ||
return Jenkins.getInstance().getExtensionList(SlaveRestarter.class); | ||
} | ||
|
||
private static final Logger LOGGER = Logger.getLogger(SlaveRestarter.class.getName()); | ||
|
||
private static final long serialVersionUID = 1L; | ||
} |
70 changes: 70 additions & 0 deletions
70
core/src/main/java/jenkins/slaves/restarter/UnixSlaveRestarter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package jenkins.slaves.restarter; | ||
|
||
import com.sun.akuma.Daemon; | ||
import com.sun.akuma.JavaVMArguments; | ||
import com.sun.jna.Native; | ||
import com.sun.jna.StringArray; | ||
import hudson.Extension; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.logging.Logger; | ||
|
||
import static hudson.util.jna.GNUCLibrary.*; | ||
import static java.util.logging.Level.*; | ||
|
||
/** | ||
* On Unix, restart via exec-ing to itself. | ||
*/ | ||
@Extension | ||
public class UnixSlaveRestarter extends SlaveRestarter { | ||
private transient JavaVMArguments args; | ||
|
||
@Override | ||
public boolean canWork() { | ||
try { | ||
if (File.pathSeparatorChar!=':') | ||
return false; // quick test to reject non-Unix without loading all the rest of the classes | ||
|
||
args = JavaVMArguments.current(); | ||
|
||
// go through the whole motion to make sure all the relevant classes are loaded now | ||
LIBC.getdtablesize(); | ||
int v = LIBC.fcntl(99999, F_GETFD); | ||
LIBC.fcntl(99999, F_SETFD, v); | ||
|
||
Daemon.getCurrentExecutable(); | ||
LIBC.execv("positively/no/such/executable", new StringArray(new String[]{"a","b","c"})); | ||
|
||
return true; | ||
} catch (UnsupportedOperationException e) { | ||
LOGGER.log(FINE, getClass()+" unsuitable", e); | ||
return false; | ||
} catch (LinkageError e) { | ||
LOGGER.log(FINE, getClass()+" unsuitable", e); | ||
return false; | ||
} catch (IOException e) { | ||
LOGGER.log(FINE, getClass()+" unsuitable", e); | ||
return false; | ||
} | ||
} | ||
|
||
public void restart() throws Exception { | ||
// close all files upon exec, except stdin, stdout, and stderr | ||
int sz = LIBC.getdtablesize(); | ||
for (int i = 3; i < sz; i++) { | ||
int flags = LIBC.fcntl(i, F_GETFD); | ||
if (flags < 0) continue; | ||
LIBC.fcntl(i, F_SETFD, flags | FD_CLOEXEC); | ||
} | ||
|
||
// exec to self | ||
String exe = Daemon.getCurrentExecutable(); | ||
LIBC.execv(exe, new StringArray(args.toArray(new String[args.size()]))); | ||
throw new IOException("Failed to exec '" + exe + "' " + LIBC.strerror(Native.getLastError())); | ||
} | ||
|
||
private static final Logger LOGGER = Logger.getLogger(UnixSlaveRestarter.class.getName()); | ||
|
||
private static final long serialVersionUID = 1L; | ||
} |
52 changes: 52 additions & 0 deletions
52
core/src/main/java/jenkins/slaves/restarter/WinswSlaveRestarter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package jenkins.slaves.restarter; | ||
|
||
import hudson.Extension; | ||
|
||
import java.io.IOException; | ||
import java.util.logging.Logger; | ||
|
||
import static java.util.logging.Level.*; | ||
import static org.apache.commons.io.IOUtils.*; | ||
|
||
/** | ||
* With winsw, restart via winsw | ||
*/ | ||
@Extension | ||
public class WinswSlaveRestarter extends SlaveRestarter { | ||
private transient String exe; | ||
|
||
@Override | ||
public boolean canWork() { | ||
try { | ||
exe = System.getenv("WINSW_EXECUTABLE"); | ||
if (exe==null) | ||
return false; // not under winsw | ||
|
||
return exec("status") ==0; | ||
} catch (InterruptedException e) { | ||
LOGGER.log(FINE, getClass()+" unsuitable", e); | ||
return false; | ||
} catch (IOException e) { | ||
LOGGER.log(FINE, getClass()+" unsuitable", e); | ||
return false; | ||
} | ||
} | ||
|
||
private int exec(String cmd) throws InterruptedException, IOException { | ||
ProcessBuilder pb = new ProcessBuilder(exe, cmd); | ||
pb.redirectErrorStream(true); | ||
Process p = pb.start(); | ||
p.getOutputStream().close(); | ||
copy(p.getInputStream(), System.out); | ||
return p.waitFor(); | ||
} | ||
|
||
public void restart() throws Exception { | ||
int r = exec("restart"); | ||
throw new IOException("Restart failure. '"+exe+" restart' completed with "+r+" but I'm still alive"); | ||
} | ||
|
||
private static final Logger LOGGER = Logger.getLogger(WinswSlaveRestarter.class.getName()); | ||
|
||
private static final long serialVersionUID = 1L; | ||
} |
@since