From 73b16084f93e5037a4f14f0d51fdfe465eff8efe Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Mar 2018 14:52:34 -0500 Subject: [PATCH] [JENKINS-49994] Warn about Remoting serialization of anonymous inner classes and their ilk. --- cli/src/main/java/hudson/cli/Connection.java | 3 +- core/src/main/java/hudson/FilePath.java | 295 +++++++++++------- .../hudson/console/AnnotatedLargeText.java | 3 +- .../main/java/hudson/console/ConsoleNote.java | 3 +- .../main/java/hudson/tasks/Fingerprinter.java | 92 +++--- .../main/java/hudson/util/ProcessTree.java | 12 +- .../jenkins/security/ClassFilterImpl.java | 13 +- .../JnlpSlaveRestarterInstaller.java | 112 ++++--- pom.xml | 2 +- 9 files changed, 306 insertions(+), 229 deletions(-) diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java index 017051a64a..eacc567a7e 100644 --- a/cli/src/main/java/hudson/cli/Connection.java +++ b/cli/src/main/java/hudson/cli/Connection.java @@ -55,6 +55,7 @@ import java.security.Signature; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Used by Jenkins core only in deprecated Remoting-based CLI. @@ -102,7 +103,7 @@ public class Connection { * Sends a serializable object. */ public void writeObject(Object o) throws IOException { - ObjectOutputStream oos = new ObjectOutputStream(out); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(out); oos.writeObject(o); // don't close oss, which will close the underlying stream // no need to flush either, given the way oos is implemented diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 8f09e2ebaa..f387df93e6 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -88,7 +88,6 @@ import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.Enumeration; @@ -1105,23 +1104,28 @@ public final class FilePath implements Serializable { * @since 1.522 */ public Callable asCallableWith(final FileCallable task) { - return new Callable() { - @Override - public V call() throws IOException { - try { - return act(task); - } catch (InterruptedException e) { - throw (IOException)new InterruptedIOException().initCause(e); - } + return new CallableWith<>(task); + } + private class CallableWith implements Callable { + private final FileCallable task; + CallableWith(FileCallable task) { + this.task = task; + } + @Override + public V call() throws IOException { + try { + return act(task); + } catch (InterruptedException e) { + throw (IOException)new InterruptedIOException().initCause(e); } + } - @Override - public void checkRoles(RoleChecker checker) throws SecurityException { - task.checkRoles(checker); - } + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + task.checkRoles(checker); + } - private static final long serialVersionUID = 1L; - }; + private static final long serialVersionUID = 1L; } /** @@ -1189,13 +1193,15 @@ public final class FilePath implements Serializable { * Deletes this directory, including all its contents recursively. */ public void deleteRecursive() throws IOException, InterruptedException { - act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Void invoke(File f, VirtualChannel channel) throws IOException { - deleteRecursive(deleting(f)); - return null; - } - }); + act(new DeleteRecursive()); + } + private class DeleteRecursive extends SecureFileCallable { + private static final long serialVersionUID = 1L; + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException { + deleteRecursive(deleting(f)); + return null; + } } /** @@ -1375,32 +1381,44 @@ public final class FilePath implements Serializable { */ public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException { try { - return new FilePath(channel,act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public String invoke(File dir, VirtualChannel channel) throws IOException { - if(!inThisDirectory) - dir = new File(System.getProperty("java.io.tmpdir")); - else - mkdirs(dir); - - File f; - try { - f = creating(File.createTempFile(prefix, suffix, dir)); - } catch (IOException e) { - throw new IOException("Failed to create a temporary directory in "+dir,e); - } - - try (Writer w = new FileWriter(writing(f))) { - w.write(contents); - } - - return f.getAbsolutePath(); - } - })); + return new FilePath(channel, act(new CreateTextTempFile(inThisDirectory, prefix, suffix, contents))); } catch (IOException e) { throw new IOException("Failed to create a temp file on "+remote,e); } } + private final class CreateTextTempFile extends SecureFileCallable { + private static final long serialVersionUID = 1L; + private final boolean inThisDirectory; + private final String prefix; + private final String suffix; + private final String contents; + CreateTextTempFile(boolean inThisDirectory, String prefix, String suffix, String contents) { + this.inThisDirectory = inThisDirectory; + this.prefix = prefix; + this.suffix = suffix; + this.contents = contents; + } + @Override + public String invoke(File dir, VirtualChannel channel) throws IOException { + if(!inThisDirectory) + dir = new File(System.getProperty("java.io.tmpdir")); + else + mkdirs(dir); + + File f; + try { + f = creating(File.createTempFile(prefix, suffix, dir)); + } catch (IOException e) { + throw new IOException("Failed to create a temporary directory in "+dir,e); + } + + try (Writer w = new FileWriter(writing(f))) { + w.write(contents); + } + + return f.getAbsolutePath(); + } + } /** * Creates a temporary directory inside the directory represented by 'this' @@ -1456,15 +1474,17 @@ public final class FilePath implements Serializable { * @return true, for a modicum of compatibility */ public boolean delete() throws IOException, InterruptedException { - act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Void invoke(File f, VirtualChannel channel) throws IOException { - Util.deleteFile(deleting(f)); - return null; - } - }); + act(new Delete()); return true; } + private class Delete extends SecureFileCallable { + private static final long serialVersionUID = 1L; + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException { + Util.deleteFile(deleting(f)); + return null; + } + } /** * Checks if the file exists. @@ -1486,12 +1506,14 @@ public final class FilePath implements Serializable { * @see #touch(long) */ public long lastModified() throws IOException, InterruptedException { - return act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Long invoke(File f, VirtualChannel channel) throws IOException { - return stating(f).lastModified(); - } - }); + return act(new LastModified()); + } + private class LastModified extends SecureFileCallable { + private static final long serialVersionUID = 1L; + @Override + public Long invoke(File f, VirtualChannel channel) throws IOException { + return stating(f).lastModified(); + } } /** @@ -1543,12 +1565,14 @@ public final class FilePath implements Serializable { * Checks if the file is a directory. */ public boolean isDirectory() throws IOException, InterruptedException { - return act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Boolean invoke(File f, VirtualChannel channel) throws IOException { - return stating(f).isDirectory(); - } - }); + return act(new IsDirectory()); + } + private final class IsDirectory extends SecureFileCallable { + private static final long serialVersionUID = 1L; + @Override + public Boolean invoke(File f, VirtualChannel channel) throws IOException { + return stating(f).isDirectory(); + } } /** @@ -1626,14 +1650,20 @@ public final class FilePath implements Serializable { */ public void chmod(final int mask) throws IOException, InterruptedException { if(!isUnix() || mask==-1) return; - act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Void invoke(File f, VirtualChannel channel) throws IOException { - _chmod(writing(f), mask); + act(new Chmod(mask)); + } + private class Chmod extends SecureFileCallable { + private static final long serialVersionUID = 1L; + private final int mask; + Chmod(int mask) { + this.mask = mask; + } + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException { + _chmod(writing(f), mask); - return null; - } - }); + return null; + } } /** @@ -1815,25 +1845,29 @@ public final class FilePath implements Serializable { } final Pipe p = Pipe.createRemoteToLocal(); - actAsync(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - - @Override - public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - try (InputStream fis = Files.newInputStream(reading(f).toPath()); - OutputStream out = p.getOut()) { - org.apache.commons.io.IOUtils.copy(fis, out); - } catch (InvalidPathException e) { - p.error(new IOException(e)); - } catch (Exception x) { - p.error(x); - } - return null; - } - }); + actAsync(new Read(p)); return p.getIn(); } + private class Read extends SecureFileCallable { + private static final long serialVersionUID = 1L; + private final Pipe p; + Read(Pipe p) { + this.p = p; + } + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { + try (InputStream fis = Files.newInputStream(reading(f).toPath()); + OutputStream out = p.getOut()) { + org.apache.commons.io.IOUtils.copy(fis, out); + } catch (InvalidPathException e) { + p.error(new IOException(e)); + } catch (Exception x) { + p.error(x); + } + return null; + } + } /** * Reads this file from the specific offset. @@ -1955,19 +1989,27 @@ public final class FilePath implements Serializable { * @since 1.105 */ public void write(final String content, final String encoding) throws IOException, InterruptedException { - act(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Void invoke(File f, VirtualChannel channel) throws IOException { - mkdirs(f.getParentFile()); - try (OutputStream fos = Files.newOutputStream(writing(f).toPath()); - Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) { - w.write(content); - } catch (InvalidPathException e) { - throw new IOException(e); - } - return null; + act(new Write(encoding, content)); + } + private class Write extends SecureFileCallable { + private static final long serialVersionUID = 1L; + private final String encoding; + private final String content; + Write(String encoding, String content) { + this.encoding = encoding; + this.content = content; + } + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException { + mkdirs(f.getParentFile()); + try (OutputStream fos = Files.newOutputStream(writing(f).toPath()); + Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) { + w.write(content); + } catch (InvalidPathException e) { + throw new IOException(e); } - }); + return null; + } } /** @@ -2076,24 +2118,30 @@ public final class FilePath implements Serializable { public void copyTo(OutputStream os) throws IOException, InterruptedException { final OutputStream out = new RemoteOutputStream(os); - act(new SecureFileCallable() { - private static final long serialVersionUID = 4088559042349254141L; - public Void invoke(File f, VirtualChannel channel) throws IOException { - try (InputStream fis = Files.newInputStream(reading(f).toPath())) { - org.apache.commons.io.IOUtils.copy(fis, out); - return null; - } catch (InvalidPathException e) { - throw new IOException(e); - } finally { - out.close(); - } - } - }); + act(new CopyTo(out)); // make sure the writes fully got delivered to 'os' before we return. // this is needed because I/O operation is asynchronous syncIO(); } + private class CopyTo extends SecureFileCallable { + private static final long serialVersionUID = 4088559042349254141L; + private final OutputStream out; + CopyTo(OutputStream out) { + this.out = out; + } + @Override + public Void invoke(File f, VirtualChannel channel) throws IOException { + try (InputStream fis = Files.newInputStream(reading(f).toPath())) { + org.apache.commons.io.IOUtils.copy(fis, out); + return null; + } catch (InvalidPathException e) { + throw new IOException(e); + } finally { + out.close(); + } + } + } /** * With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary. @@ -2265,14 +2313,7 @@ public final class FilePath implements Serializable { // remote -> local copy final Pipe pipe = Pipe.createRemoteToLocal(); - Future future = actAsync(new SecureFileCallable() { - private static final long serialVersionUID = 1L; - public Integer invoke(File f, VirtualChannel channel) throws IOException { - try (OutputStream out = pipe.getOut()) { - return writeToTar(f, scanner, TarCompression.GZIP.compress(out)); - } - } - }); + Future future = actAsync(new CopyRecursiveRemoteToLocal(pipe, scanner)); try { readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn())); } catch (IOException e) {// BuildException or IOException @@ -2300,7 +2341,21 @@ public final class FilePath implements Serializable { } } } - + private class CopyRecursiveRemoteToLocal extends SecureFileCallable { + private static final long serialVersionUID = 1L; + private final Pipe pipe; + private final DirScanner scanner; + CopyRecursiveRemoteToLocal(Pipe pipe, DirScanner scanner) { + this.pipe = pipe; + this.scanner = scanner; + } + @Override + public Integer invoke(File f, VirtualChannel channel) throws IOException { + try (OutputStream out = pipe.getOut()) { + return writeToTar(f, scanner, TarCompression.GZIP.compress(out)); + } + } + } /** * Writes files in 'this' directory to a tar stream. diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java index 897661329c..edfab781f2 100644 --- a/core/src/main/java/hudson/console/AnnotatedLargeText.java +++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java @@ -52,6 +52,7 @@ import com.jcraft.jzlib.GZIPInputStream; import com.jcraft.jzlib.GZIPOutputStream; import static java.lang.Math.abs; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Extension to {@link LargeText} that handles annotations by {@link ConsoleAnnotator}. @@ -169,7 +170,7 @@ public class AnnotatedLargeText extends LargeText { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Cipher sym = PASSING_ANNOTATOR.encrypt(); - ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new CipherOutputStream(baos,sym))); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(new GZIPOutputStream(new CipherOutputStream(baos,sym))); oos.writeLong(System.currentTimeMillis()); // send timestamp to prevent a replay attack oos.writeObject(caw.getConsoleAnnotator()); oos.close(); diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index c0b281c6ca..165e6d9ae7 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -54,6 +54,7 @@ import com.jcraft.jzlib.GZIPOutputStream; import hudson.remoting.ClassFilter; import jenkins.security.HMACConfidentialKey; import jenkins.util.SystemProperties; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Data that hangs off from a console output. @@ -180,7 +181,7 @@ public abstract class ConsoleNote implements Serializable, Describable build, FilePath ws, TaskListener listener, Map record, final String targets) throws IOException, InterruptedException { - final class Record implements Serializable { - final boolean produced; - final String relativePath; - final String fileName; - final String md5sum; + private static final class Record implements Serializable { - public Record(boolean produced, String relativePath, String fileName, String md5sum) { - this.produced = produced; - this.relativePath = relativePath; - this.fileName = fileName; - this.md5sum = md5sum; - } + final boolean produced; + final String relativePath; + final String fileName; + final String md5sum; - Fingerprint addRecord(Run build) throws IOException { - FingerprintMap map = Jenkins.getInstance().getFingerprintMap(); - return map.getOrCreate(produced?build:null, fileName, md5sum); - } - - private static final long serialVersionUID = 1L; + public Record(boolean produced, String relativePath, String fileName, String md5sum) { + this.produced = produced; + this.relativePath = relativePath; + this.fileName = fileName; + this.md5sum = md5sum; } - final long buildTimestamp = build.getTimeInMillis(); + Fingerprint addRecord(Run build) throws IOException { + FingerprintMap map = Jenkins.getInstance().getFingerprintMap(); + return map.getOrCreate(produced?build:null, fileName, md5sum); + } - List records = ws.act(new MasterToSlaveFileCallable>() { - public List invoke(File baseDir, VirtualChannel channel) throws IOException { - List results = new ArrayList(); + private static final long serialVersionUID = 1L; + } - FileSet src = Util.createFileSet(baseDir,targets); + private static final class FindRecords extends MasterToSlaveFileCallable> { - DirectoryScanner ds = src.getDirectoryScanner(); - for( String f : ds.getIncludedFiles() ) { - File file = new File(baseDir,f); + private final String targets; + private final long buildTimestamp; - // consider the file to be produced by this build only if the timestamp - // is newer than when the build has started. - // 2000ms is an error margin since since VFAT only retains timestamp at 2sec precision - boolean produced = buildTimestamp <= file.lastModified()+2000; + FindRecords(String targets, long buildTimestamp) { + this.targets = targets; + this.buildTimestamp = buildTimestamp; + } - try { - results.add(new Record(produced,f,file.getName(),new FilePath(file).digest())); - } catch (IOException e) { - throw new IOException(Messages.Fingerprinter_DigestFailed(file),e); - } catch (InterruptedException e) { - throw new IOException(Messages.Fingerprinter_Aborted(),e); - } + @Override + public List invoke(File baseDir, VirtualChannel channel) throws IOException { + List results = new ArrayList(); + + FileSet src = Util.createFileSet(baseDir,targets); + + DirectoryScanner ds = src.getDirectoryScanner(); + for( String f : ds.getIncludedFiles() ) { + File file = new File(baseDir,f); + + // consider the file to be produced by this build only if the timestamp + // is newer than when the build has started. + // 2000ms is an error margin since since VFAT only retains timestamp at 2sec precision + boolean produced = buildTimestamp <= file.lastModified()+2000; + + try { + results.add(new Record(produced,f,file.getName(),new FilePath(file).digest())); + } catch (IOException e) { + throw new IOException(Messages.Fingerprinter_DigestFailed(file),e); + } catch (InterruptedException e) { + throw new IOException(Messages.Fingerprinter_Aborted(),e); } - - return results; } - }); - for (Record r : records) { + return results; + } + + } + + private void record(Run build, FilePath ws, TaskListener listener, Map record, final String targets) throws IOException, InterruptedException { + for (Record r : ws.act(new FindRecords(targets, build.getTimeInMillis()))) { Fingerprint fp = r.addRecord(build); if(fp==null) { listener.error(Messages.Fingerprinter_FailedFor(r.relativePath)); diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java index 95fe237fc3..f1503dd7ff 100644 --- a/core/src/main/java/hudson/util/ProcessTree.java +++ b/core/src/main/java/hudson/util/ProcessTree.java @@ -156,11 +156,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree, try { VirtualChannel channelToMaster = SlaveComputer.getChannelToMaster(); if (channelToMaster!=null) { - killers = channelToMaster.call(new SlaveToMasterCallable, IOException>() { - public List call() throws IOException { - return new ArrayList(ProcessKiller.all()); - } - }); + killers = channelToMaster.call(new ListAll()); } else { // used in an environment that doesn't support talk-back to the master. // let's do with what we have. @@ -172,6 +168,12 @@ public abstract class ProcessTree implements Iterable, IProcessTree, } return killers; } + private static class ListAll extends SlaveToMasterCallable, IOException> { + @Override + public List call() throws IOException { + return new ArrayList<>(ProcessKiller.all()); + } + } /** * Represents a process. diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 9bf2377e4b..5fb8dccf40 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -134,14 +134,14 @@ public class ClassFilterImpl extends ClassFilter { } } return cache.computeIfAbsent(_c, c -> { - if (ClassFilter.STANDARD.isBlacklisted(c)) { // currently never true: only the name overload is overridden - return true; - } String name = c.getName(); if (Main.isUnitTest && (name.contains("$$EnhancerByMockitoWithCGLIB$$") || name.contains("$$FastClassByMockitoWithCGLIB$$") || name.startsWith("org.mockito."))) { mockOff(); return false; } + if (ClassFilter.STANDARD.isBlacklisted(c)) { // currently never true, but may issue diagnostics + return true; + } if (c.isArray()) { LOGGER.log(Level.FINE, "permitting {0} since it is an array", name); return false; @@ -156,13 +156,6 @@ public class ClassFilterImpl extends ClassFilter { } String location = codeSource(c); if (location != null) { - if (c.isAnonymousClass()) { // e.g., pkg.Outer$1 - LOGGER.warning("JENKINS-49795: attempt to (de-)serialize anonymous " + c + " in " + location); - } else if (c.isLocalClass()) { // e.g., pkg.Outer$1Local - LOGGER.warning("JENKINS-49795: attempt to (de-)serialize local " + c + " in " + location); - } else if (c.isSynthetic()) { // e.g., pkg.Outer$$Lambda$1/12345678 - LOGGER.warning("JENKINS-49795: attempt to (de-)serialize synthetic " + c + " in " + location); - } if (isLocationWhitelisted(location)) { LOGGER.log(Level.FINE, "permitting {0} due to its location in {1}", new Object[] {name, location}); return false; diff --git a/core/src/main/java/jenkins/slaves/restarter/JnlpSlaveRestarterInstaller.java b/core/src/main/java/jenkins/slaves/restarter/JnlpSlaveRestarterInstaller.java index 1eb68dcc6f..e47cc39925 100644 --- a/core/src/main/java/jenkins/slaves/restarter/JnlpSlaveRestarterInstaller.java +++ b/core/src/main/java/jenkins/slaves/restarter/JnlpSlaveRestarterInstaller.java @@ -16,6 +16,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Callable; import java.util.logging.Logger; import static java.util.logging.Level.*; @@ -34,69 +35,82 @@ import jenkins.security.MasterToSlaveCallable; public class JnlpSlaveRestarterInstaller extends ComputerListener implements Serializable { @Override public void onOnline(final Computer c, final TaskListener listener) throws IOException, InterruptedException { - MasterComputer.threadPoolForRemoting.submit(new java.util.concurrent.Callable() { - @Override - public Void call() throws Exception { - install(c, listener); - return null; - } - }); + MasterComputer.threadPoolForRemoting.submit(new Install(c, listener)); + } + private static class Install implements Callable { + private final Computer c; + private final TaskListener listener; + Install(Computer c, TaskListener listener) { + this.c = c; + this.listener = listener; + } + @Override + public Void call() throws Exception { + install(c, listener); + return null; + } } - private void install(Computer c, TaskListener listener) { + private static void install(Computer c, TaskListener listener) { try { final List restarters = new ArrayList(SlaveRestarter.all()); VirtualChannel ch = c.getChannel(); if (ch==null) return; // defensive check - List effective = ch.call(new MasterToSlaveCallable, IOException>() { - public List 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 itr = restarters.iterator(); itr.hasNext(); ) { - SlaveRestarter r = itr.next(); - if (!r.canWork()) - itr.remove(); - } - - e.addListener(new EngineListenerAdapter() { - @Override - public void onReconnect() { - try { - for (SlaveRestarter r : restarters) { - try { - LOGGER.info("Restarting agent via "+r); - r.restart(); - } catch (Exception x) { - LOGGER.log(SEVERE, "Failed to restart agent with "+r, x); - } - } - } finally { - // if we move on to the reconnection without restart, - // don't let the current implementations kick in when the agent loses connection again - restarters.clear(); - } - } - }); - - return restarters; - } - }); + List effective = ch.call(new FindEffectiveRestarters(restarters)); LOGGER.log(FINE, "Effective SlaveRestarter on {0}: {1}", new Object[] {c.getName(), effective}); } catch (Throwable e) { Functions.printStackTrace(e, listener.error("Failed to install restarter")); } } + private static class FindEffectiveRestarters extends MasterToSlaveCallable, IOException> { + private final List restarters; + FindEffectiveRestarters(List restarters) { + this.restarters = restarters; + } + @Override + public List 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 itr = restarters.iterator(); itr.hasNext(); ) { + SlaveRestarter r = itr.next(); + if (!r.canWork()) + itr.remove(); + } + + e.addListener(new EngineListenerAdapter() { + @Override + public void onReconnect() { + try { + for (SlaveRestarter r : restarters) { + try { + LOGGER.info("Restarting agent via "+r); + r.restart(); + } catch (Exception x) { + LOGGER.log(SEVERE, "Failed to restart agent with "+r, x); + } + } + } finally { + // if we move on to the reconnection without restart, + // don't let the current implementations kick in when the agent loses connection again + restarters.clear(); + } + } + }); + + return restarters; + } + } private static final Logger LOGGER = Logger.getLogger(JnlpSlaveRestarterInstaller.class.getName()); diff --git a/pom.xml b/pom.xml index 6ca04fee1b..fd82cdb4ae 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ THE SOFTWARE. 3.0.0 - 3.17 + 3.18-20180307.194815-1 2.60