From ef7e916883f1818023a11776ff4d2057eaf39fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Gond=C5=BEa?= Date: Fri, 11 Aug 2017 12:45:59 +0200 Subject: [PATCH 001/243] [FIXED JENKINS-46140] Improve presentation of remote exception --- src/main/java/hudson/remoting/Channel.java | 18 +++++----- .../java/hudson/remoting/ChannelTest.java | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 4f440aec6..d9f560fec 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -1644,16 +1644,16 @@ public void checkRoles(RoleChecker checker) throws SecurityException { * Decorates the stack elements of the given {@link Throwable} by adding the call site information. */ /*package*/ void attachCallSiteStackTrace(Throwable t) { - Exception e = new Exception(); - StackTraceElement[] callSite = e.getStackTrace(); - StackTraceElement[] original = t.getStackTrace(); - - StackTraceElement[] combined = new StackTraceElement[original.length+1+callSite.length]; - System.arraycopy(original,0,combined,0,original.length); // original stack trace at the top - combined[original.length] = new StackTraceElement(".....","remote call to "+name,null,-2); - System.arraycopy(callSite,0,combined,original.length+1,callSite.length); + t.addSuppressed(new CallSiteStackTrace(name)); + } - t.setStackTrace(combined); + /** + * Dummy exception indicating the stacktrace on calling side of channel for remote exception for ease of debugging. + */ + private static final class CallSiteStackTrace extends Exception { + public CallSiteStackTrace(String message) { + super("Remote call to " + message); + } } public String getName() { diff --git a/src/test/java/hudson/remoting/ChannelTest.java b/src/test/java/hudson/remoting/ChannelTest.java index 4eb53febf..37a9b0b5a 100644 --- a/src/test/java/hudson/remoting/ChannelTest.java +++ b/src/test/java/hudson/remoting/ChannelTest.java @@ -17,6 +17,8 @@ import javax.annotation.Nonnull; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + import org.jenkinsci.remoting.RoleChecker; /** @@ -183,6 +185,39 @@ public void testDiagnostics() throws Exception { assertTrue(sw.toString().contains("Commands sent=0")); assertTrue(sw.toString().contains("Commands received=0")); } + + public void testCallSiteStacktrace() throws Exception { + try { + failRemotelyToBeWrappedLocally(); + fail(); + } catch (Exception e) { + assertEquals("Local Nested", e.getMessage()); + assertEquals(Exception.class, e.getClass()); + Throwable cause = e.getCause(); + assertEquals("Node Nested", cause.getMessage()); + assertEquals(IOException.class, cause.getClass()); + Throwable rootCause = cause.getCause(); + assertEquals("Node says hello!", rootCause.getMessage()); + assertEquals(RuntimeException.class, rootCause.getClass()); + Throwable callSite = cause.getSuppressed()[0]; + assertEquals("Remote call to north", callSite.getMessage()); + assertEquals("hudson.remoting.Channel$CallSiteStackTrace", callSite.getClass().getName()); + } + } + + private void failRemotelyToBeWrappedLocally() throws Exception { + try { + channel.call(new ThrowingCallable()); + } catch (IOException e) { + throw new Exception("Local Nested", e); + } + } + + private static class ThrowingCallable extends CallableBase { + @Override public Void call() throws IOException { + throw new IOException("Node Nested", new RuntimeException("Node says hello!")); + } + } /** * Checks if {@link UserRequest}s can be executed during the pending close operation. From da8eb2edfcac72b5fa4fe10b1ac94af64032250e Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 29 Aug 2017 07:58:45 -0700 Subject: [PATCH 002/243] Changelog: noting the 2.76 release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16283aaa9..5fdd16aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.11 -Release date: Coming Soon +Release date: Aug 18, 2017 => [Jenkins 2.76](https://jenkins.io/changelog/#v2.76) :exclamation: **Warning!** Starting from this release, Jenkins Remoting requires Java 8 to run. In edge cases it may require manual actions during the upgrade. From c9d858a50ceac2b562f2856c75266d736b6ea5f0 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sat, 9 Sep 2017 18:20:11 +0200 Subject: [PATCH 003/243] Bump to latest jenkinsci/pom (#195) * Bump to latest jenkinsci/pom * Add to try and fix build failure ``` + mvn --batch-mode clean install -Dmaven.test.failure.ignore=true [INFO] Scanning for projects... [ERROR] [ERROR] Some problems were encountered while processing the POMs: [FATAL] Non-resolvable parent POM for org.jenkins-ci.main:remoting:3.12-SNAPSHOT: Could not find artifact org.jenkins-ci:jenkins:pom:1.39-20170902.001419-2 and 'parent.relativePath' points at wrong local POM @ line 28, column 11 @ [ERROR] The build could not read 1 project -> [Help 1] [ERROR] [ERROR] The project org.jenkins-ci.main:remoting:3.12-SNAPSHOT (/home/jenkins/workspace/Core_remoting_PR-195-MOTRB3KGWGBYYHB7QXC5FRNNUMPSCLDV4EGXUKHQVEWPTDZIGLOA/pom.xml) has 1 error [ERROR] Non-resolvable parent POM for org.jenkins-ci.main:remoting:3.12-SNAPSHOT: Could not find artifact org.jenkins-ci:jenkins:pom:1.39-20170902.001419-2 and 'parent.relativePath' points at wrong local POM @ line 28, column 11 -> [Help 2] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException [ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException script returned exit code 1 ``` * Bump to 1.39 release --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 20ce4ba59..f021a70f3 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,8 @@ THE SOFTWARE. org.jenkins-ci jenkins - 1.38 + 1.39 + org.jenkins-ci.main From 096128e0f7fde812e817d68398a9d73f951397e7 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 9 Sep 2017 18:06:03 +0100 Subject: [PATCH 004/243] Javadoc: Cleanup some Javadoc warnings --- src/main/java/hudson/remoting/Capability.java | 2 +- src/main/java/hudson/remoting/Channel.java | 6 +++--- src/main/java/hudson/remoting/ExportTable.java | 2 +- .../java/hudson/remoting/InterceptingExecutorService.java | 4 ++++ src/main/java/hudson/remoting/PipeWindow.java | 1 + src/main/java/hudson/remoting/package-info.java | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/hudson/remoting/Capability.java b/src/main/java/hudson/remoting/Capability.java index ce4294329..064031a65 100644 --- a/src/main/java/hudson/remoting/Capability.java +++ b/src/main/java/hudson/remoting/Capability.java @@ -208,7 +208,7 @@ protected Class resolveClass(ObjectStreamClass desc) throws IOException, Clas /** * Supports chunked encoding. * - * @sine 2.38 + * @since 2.38 */ private static final long MASK_CHUNKED_ENCODING = 1L << 7; diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index d9f560fec..7ec90bea7 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -743,7 +743,7 @@ public T export(Class type, T instance) { * Unexports object. * @param id Object ID * @param cause Stacktrace pf the object creation call - * @param severeErrorIfMissing Consider missing object as {@link #SEVERE} error. {@link #FINE} otherwise + * @param severeErrorIfMissing Consider missing object as {@code SEVERE} error. {@code FINE} otherwise * @since TODO */ /*package*/ void unexport(int id, Throwable cause, boolean severeErrorIfMissing) { @@ -1809,7 +1809,7 @@ protected int[] initialValue() { /** * A reference for the {@link Channel} that can be cleared out on {@link #close()}/{@link #terminate(IOException)}. - * Could probably be replaced with {@link AtomicReference} but then we would not retain the only change being + * Could probably be replaced with {@link java.util.concurrent.atomic.AtomicReference} but then we would not retain the only change being * from valid channel to {@code null} channel semantics of this class. * @since 2.52 * @see #reference @@ -1818,7 +1818,7 @@ protected int[] initialValue() { /** * Cached name of the channel. - * @see {@link Channel#getName()} + * @see Channel#getName() */ @Nonnull private final String name; diff --git a/src/main/java/hudson/remoting/ExportTable.java b/src/main/java/hudson/remoting/ExportTable.java index 45747895e..f99eaa0fe 100644 --- a/src/main/java/hudson/remoting/ExportTable.java +++ b/src/main/java/hudson/remoting/ExportTable.java @@ -500,7 +500,7 @@ void unexportByOid(Integer oid, Throwable callSite) { * Removes the exported object for the specified oid from the table. * @param oid Object ID. If {@code null} the method will do nothing. * @param callSite Unexport command caller - * @param severeErrorIfMissing Consider missing object as {@link #SEVERE} error. {@link #FINE} otherwise + * @param severeErrorIfMissing Consider missing object as {@code SEVERE} error. {@code FINE} otherwise * @since TODO */ synchronized void unexportByOid(@CheckForNull Integer oid, @CheckForNull Throwable callSite, boolean severeErrorIfMissing) { diff --git a/src/main/java/hudson/remoting/InterceptingExecutorService.java b/src/main/java/hudson/remoting/InterceptingExecutorService.java index d22a9c2ae..913f33100 100644 --- a/src/main/java/hudson/remoting/InterceptingExecutorService.java +++ b/src/main/java/hudson/remoting/InterceptingExecutorService.java @@ -1,5 +1,7 @@ package hudson.remoting; +import org.jenkinsci.remoting.CallableDecorator; + import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -13,6 +15,8 @@ /** * {@link ExecutorService} that runs all the tasks in a given set of {@link CallableDecorator}s. * @author Kohsuke Kawaguchi + * @see CallableDecorator + * @see CallableDecoratorList */ class InterceptingExecutorService extends DelegatingExecutorService { private final CallableDecoratorList decorators; diff --git a/src/main/java/hudson/remoting/PipeWindow.java b/src/main/java/hudson/remoting/PipeWindow.java index 65a499da8..35f6897fd 100644 --- a/src/main/java/hudson/remoting/PipeWindow.java +++ b/src/main/java/hudson/remoting/PipeWindow.java @@ -83,6 +83,7 @@ abstract class PipeWindow { * @return * The available window size >= min. * @param min + * Minimum size of the window to retrieve */ abstract int get(int min) throws InterruptedException, IOException; diff --git a/src/main/java/hudson/remoting/package-info.java b/src/main/java/hudson/remoting/package-info.java index b467948b9..fba40a24c 100644 --- a/src/main/java/hudson/remoting/package-info.java +++ b/src/main/java/hudson/remoting/package-info.java @@ -26,6 +26,6 @@ * *

* Code in this package is used for running a part of a program in slaves. - * If you are new to this package, start from {@link Channel}. + * If you are new to this package, start from {@link hudson.remoting.Channel}. */ package hudson.remoting; From a599bc7ec7b48311459d7ae079b146495a17db8d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 9 Sep 2017 18:46:46 +0100 Subject: [PATCH 005/243] Javadoc: Cleanup the most of `@since TODO` references --- src/main/java/hudson/remoting/Channel.java | 15 ++++++------ .../java/hudson/remoting/ChannelBuilder.java | 20 ++++++++-------- .../remoting/ChannelClosedException.java | 2 +- .../java/hudson/remoting/ClassFilter.java | 4 ++-- src/main/java/hudson/remoting/Engine.java | 24 +++++++++---------- .../java/hudson/remoting/EngineListener.java | 2 +- .../java/hudson/remoting/ExportTable.java | 1 - src/main/java/hudson/remoting/Launcher.java | 8 +++---- src/main/java/hudson/remoting/Request.java | 2 +- src/main/java/hudson/remoting/jnlp/Main.java | 10 ++++---- .../org/jenkinsci/remoting/RoleChecker.java | 2 +- .../org/jenkinsci/remoting/RoleSensitive.java | 2 +- .../remoting/engine/JnlpAgentEndpoint.java | 3 +-- .../engine/JnlpAgentEndpointResolver.java | 1 - .../remoting/engine/WorkDirManager.java | 2 +- .../jenkinsci/remoting/nio/FifoBuffer.java | 4 ++-- 16 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 7ec90bea7..5b9a5a764 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -504,7 +504,7 @@ public Channel(String name, ExecutorService exec, CommandTransport transport, bo } /** - * @since TODO + * @since 2.38 */ protected Channel(@Nonnull ChannelBuilder settings, @Nonnull CommandTransport transport) throws IOException { this.name = settings.getName(); @@ -605,7 +605,7 @@ public boolean isOutClosed() { * Get why the sender side of the channel has been closed. * @return Close cause or {@code null} if the sender side is active. * {@code null} result does not guarantee that the channel is actually operational. - * @since TODO + * @since 3.11 */ @CheckForNull public final Throwable getSenderCloseCause() { @@ -631,7 +631,7 @@ public boolean isClosingOrClosed() { * @return {@link #outClosed} if not {@code null}, value of the transient cache * {@link #closeRequestCause} otherwise. * The latter one may show random cause in the case of race conditions. - * @since TODO + * @since 3.11 */ @CheckForNull public Throwable getCloseRequestCause() { @@ -744,7 +744,6 @@ public T export(Class type, T instance) { * @param id Object ID * @param cause Stacktrace pf the object creation call * @param severeErrorIfMissing Consider missing object as {@code SEVERE} error. {@code FINE} otherwise - * @since TODO */ /*package*/ void unexport(int id, Throwable cause, boolean severeErrorIfMissing) { exportedObjects.unexportByOid(id, cause, severeErrorIfMissing); @@ -1115,7 +1114,7 @@ public void setRestricted(boolean b) { } /** - * @since TODO + * @since 2.47 */ public boolean isRemoteClassLoadingAllowed() { return remoteClassLoadingAllowed; @@ -1124,14 +1123,14 @@ public boolean isRemoteClassLoadingAllowed() { /** * Controls whether or not this channel is willing to load classes from the other side. * The default is on. - * @since TODO + * @since 2.47 */ public void setRemoteClassLoadingAllowed(boolean b) { this.remoteClassLoadingAllowed = b; } /** - * @since TODO + * @since 2.47 */ public boolean isArbitraryCallableAllowed() { return arbitraryCallableAllowed; @@ -1139,7 +1138,7 @@ public boolean isArbitraryCallableAllowed() { /** * @see ChannelBuilder#withArbitraryCallableAllowed(boolean) - * @since TODO + * @since 2.47 */ public void setArbitraryCallableAllowed(boolean b) { this.arbitraryCallableAllowed = b; diff --git a/src/main/java/hudson/remoting/ChannelBuilder.java b/src/main/java/hudson/remoting/ChannelBuilder.java index 242e784b7..8295ff63c 100644 --- a/src/main/java/hudson/remoting/ChannelBuilder.java +++ b/src/main/java/hudson/remoting/ChannelBuilder.java @@ -159,7 +159,7 @@ public boolean isRestricted() { * but not {@link Channel#call(Callable)} (and its family of methods.) * * The default is {@code true}. - * @since TODO + * @since 2.47 */ public ChannelBuilder withArbitraryCallableAllowed(boolean b) { this.arbitraryCallableAllowed = b; @@ -167,7 +167,7 @@ public ChannelBuilder withArbitraryCallableAllowed(boolean b) { } /** - * @since TODO + * @since 2.47 */ public boolean isArbitraryCallableAllowed() { return arbitraryCallableAllowed; @@ -176,7 +176,7 @@ public boolean isArbitraryCallableAllowed() { /** * Controls whether or not this channel is willing to load classes from the other side. * The default is {@code true}. - * @since TODO + * @since 2.47 */ public ChannelBuilder withRemoteClassLoadingAllowed(boolean b) { this.remoteClassLoadingAllowed = b; @@ -184,7 +184,7 @@ public ChannelBuilder withRemoteClassLoadingAllowed(boolean b) { } /** - * @since TODO + * @since 2.47 */ public boolean isRemoteClassLoadingAllowed() { return remoteClassLoadingAllowed; @@ -221,7 +221,7 @@ public List getDecorators() { /** * Convenience method to install {@link RoleChecker} that verifies against the fixed set of roles. - * @since TODO + * @since 2.47 */ public ChannelBuilder withRoles(Role... roles) { return withRoles(Arrays.asList(roles)); @@ -229,7 +229,7 @@ public ChannelBuilder withRoles(Role... roles) { /** * Convenience method to install {@link RoleChecker} that verifies against the fixed set of roles. - * @since TODO + * @since 2.47 */ public ChannelBuilder withRoles(final Collection actual) { return withRoleChecker(new RoleChecker() { @@ -246,7 +246,7 @@ public void check(RoleSensitive subject, @Nonnull Collection expected) { /** * Installs another {@link RoleChecker}. - * @since TODO + * @since 2.47 */ public ChannelBuilder withRoleChecker(final RoleChecker checker) { return with(new CallableDecorator() { @@ -269,7 +269,7 @@ public Callable userRequest(Callable op, Ca * Properties are modifiable after {@link Channel} is created, but a property set * during channel building is guaranteed to be visible to the other side as soon * as the channel is established. - * @since TODO + * @since 2.47 */ public ChannelBuilder withProperty(Object key, Object value) { properties.put(key,value); @@ -277,14 +277,14 @@ public ChannelBuilder withProperty(Object key, Object value) { } /** - * @since TODO + * @since 2.47 */ public ChannelBuilder withProperty(ChannelProperty key, T value) { return withProperty((Object) key, value); } /** - * @since TODO + * @since 2.47 */ public Map getProperties() { return properties; diff --git a/src/main/java/hudson/remoting/ChannelClosedException.java b/src/main/java/hudson/remoting/ChannelClosedException.java index 448dae76d..ab97f2023 100644 --- a/src/main/java/hudson/remoting/ChannelClosedException.java +++ b/src/main/java/hudson/remoting/ChannelClosedException.java @@ -30,7 +30,7 @@ public ChannelClosedException(Throwable cause) { * @param message Message * @param cause Cause of the channel close/termination. * May be {@code null} if it cannot be determined when the exception is constructed. - * @since TODO + * @since 3.11 */ public ChannelClosedException(@Nonnull String message, @CheckForNull Throwable cause) { super(message, cause); diff --git a/src/main/java/hudson/remoting/ClassFilter.java b/src/main/java/hudson/remoting/ClassFilter.java index 14d86d56f..6689c15f4 100644 --- a/src/main/java/hudson/remoting/ClassFilter.java +++ b/src/main/java/hudson/remoting/ClassFilter.java @@ -105,7 +105,7 @@ public final Class check(Class c) { * @param filter a regular expression for {@link Class#getName} which, if matched according to {@link Matcher#matches}, will blacklist the class * @throws ClassFilterException Filter pattern cannot be applied. * It means either unexpected processing error or rejection by the internal logic. - * @since TODO + * @since 3.11 */ public static void appendDefaultFilter(Pattern filter) throws ClassFilterException { if (System.getProperty(FILE_OVERRIDE_LOCATION_PROPERTY) == null) { @@ -250,7 +250,7 @@ public String toString() { /** * Class for propagating exceptions in {@link ClassFilter}. - * @since TODO + * @since 3.11 */ public static class ClassFilterException extends Exception { diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 474172447..7fa297015 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -150,7 +150,7 @@ public void run() { /** * Determines whether the socket will have {@link Socket#setKeepAlive(boolean)} set or not. * - * @since TODO + * @since 2.62.1 */ private boolean keepAlive = true; @@ -164,14 +164,14 @@ public void run() { * Specifies a destination for the agent log. * If specified, this option overrides the default destination within {@link #workDir}. * If both this options and {@link #workDir} is not set, the log will not be generated. - * @since TODO + * @since 3.8 */ @CheckForNull private Path agentLog; /** * Specified location of the property file with JUL settings. - * @since TODO + * @since 3.8 */ @CheckForNull private Path loggingConfigFilePath = null; @@ -183,7 +183,7 @@ public void run() { * In order to retain compatibility, the option is disabled by default. *

* Jenkins specifics: This working directory is expected to be equal to the agent root specified in Jenkins configuration. - * @since TODO + * @since 3.8 */ @CheckForNull public Path workDir = null; @@ -193,7 +193,7 @@ public void run() { *

* This option is not expected to be used frequently, but it allows remoting users to specify a custom * storage directory if the default {@code remoting} directory is consumed by other stuff. - * @since TODO + * @since 3.8 */ @Nonnull public String internalDir = WorkDirManager.DirType.INTERNAL_DIR.getDefaultLocation(); @@ -202,7 +202,7 @@ public void run() { * Fail the initialization if the workDir or internalDir are missing. * This option presumes that the workspace structure gets initialized previously in order to ensure that we do not start up with a borked instance * (e.g. if a filesystem mount gets disconnected). - * @since TODO + * @since 3.8 */ @Nonnull public boolean failIfWorkDirIsMissing = WorkDirManager.DEFAULT_FAIL_IF_WORKDIR_IS_MISSING; @@ -223,7 +223,7 @@ public Engine(EngineListener listener, List hudsonUrls, String secretKey, S * Starts the engine. * The procedure initializes the working directory and all the required environment * @throws IOException Initialization error - * @since TODO + * @since 3.9 */ public synchronized void startEngine() throws IOException { startEngine(false); @@ -287,7 +287,7 @@ public void setJarCache(@Nonnull JarCache jarCache) { /** * Sets path to the property file with JUL settings. * @param filePath JAR Cache to be used - * @since TODO + * @since 3.8 */ public void setLoggingConfigFile(@Nonnull Path filePath) { this.loggingConfigFilePath = filePath; @@ -323,7 +323,7 @@ public void setNoReconnect(boolean noReconnect) { * Sets the destination for agent logs. * @param agentLog Path to the agent log. * If {@code null}, the engine will pick the default behavior depending on the {@link #workDir} value - * @since TODO + * @since 3.8 */ public void setAgentLog(@CheckForNull Path agentLog) { this.agentLog = agentLog; @@ -333,7 +333,7 @@ public void setAgentLog(@CheckForNull Path agentLog) { * Specified a path to the work directory. * @param workDir Path to the working directory of the remoting instance. * {@code null} Disables the working directory. - * @since TODO + * @since 3.8 */ public void setWorkDir(@CheckForNull Path workDir) { this.workDir = workDir; @@ -342,7 +342,7 @@ public void setWorkDir(@CheckForNull Path workDir) { /** * Specifies name of the internal data directory within {@link #workDir}. * @param internalDir Directory name - * @since TODO + * @since 3.8 */ public void setInternalDir(@Nonnull String internalDir) { this.internalDir = internalDir; @@ -353,7 +353,7 @@ public void setInternalDir(@Nonnull String internalDir) { * This option presumes that the workspace structure gets initialized previously in order to ensure that we do not start up with a borked instance * (e.g. if a filesystem mount gets disconnected). * @param failIfWorkDirIsMissing Flag - * @since TODO + * @since 3.8 */ public void setFailIfWorkDirIsMissing(boolean failIfWorkDirIsMissing) { this.failIfWorkDirIsMissing = failIfWorkDirIsMissing; } diff --git a/src/main/java/hudson/remoting/EngineListener.java b/src/main/java/hudson/remoting/EngineListener.java index 8e9b87755..ea06d31bd 100644 --- a/src/main/java/hudson/remoting/EngineListener.java +++ b/src/main/java/hudson/remoting/EngineListener.java @@ -61,7 +61,7 @@ public interface EngineListener { /** * Called when a re-connection is about to be attempted. - * @since TODO + * @since 2.0 */ void onReconnect(); } diff --git a/src/main/java/hudson/remoting/ExportTable.java b/src/main/java/hudson/remoting/ExportTable.java index f99eaa0fe..228ca9cdb 100644 --- a/src/main/java/hudson/remoting/ExportTable.java +++ b/src/main/java/hudson/remoting/ExportTable.java @@ -398,7 +398,6 @@ synchronized Object get(int id) throws ExecutionException { * Retrieves object by id. * @param oid Object ID * @return Object or {@code null} if the ID is missing in the {@link ExportTable}. - * @since TODO */ @CheckForNull synchronized Object getOrNull(int oid) { diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index 71abe0b16..ba7826649 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -179,7 +179,7 @@ public void addClasspath(String pathList) throws Exception { /** * Specified location of the property file with JUL settings. - * @since TODO + * @since 3.8 */ @CheckForNull @Option(name="-loggingConfig",usage="Path to the property file with java.util.logging settings") @@ -237,7 +237,7 @@ public boolean verify(String s, SSLSession sslSession) { * In order to retain compatibility, the option is disabled by default. *

* Jenkins specifics: This working directory is expected to be equal to the agent root specified in Jenkins configuration. - * @since TODO + * @since 3.8 */ @Option(name = "-workDir", usage = "Declares the working directory of the remoting instance (stores cache and logs by default)") @@ -249,7 +249,7 @@ public boolean verify(String s, SSLSession sslSession) { *

* This option is not expected to be used frequently, but it allows remoting users to specify a custom * storage directory if the default {@code remoting} directory is consumed by other stuff. - * @since TODO + * @since 3.8 */ @Option(name = "-internalDir", usage = "Specifies a name of the internal files within a working directory ('remoting' by default)", @@ -261,7 +261,7 @@ public boolean verify(String s, SSLSession sslSession) { * Fail the initialization if the workDir or internalDir are missing. * This option presumes that the workspace structure gets initialized previously in order to ensure that we do not start up with a borked instance * (e.g. if a filesystem mount gets disconnected). - * @since TODO + * @since 3.8 */ @Option(name = "-failIfWorkDirIsMissing", usage = "Fails the initialization if the requested workDir or internalDir are missing ('false' by default)", diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 22ce7c2fb..70575933d 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -109,7 +109,7 @@ protected Request() { * * @param channel Channel * @throws IOException Error with explanation if the request cannot be executed. - * @since TODO + * @since 3.11 */ public void checkIfCanBeExecutedOnChannel(@Nonnull Channel channel) throws IOException { final Throwable senderCloseCause = channel.getSenderCloseCause(); diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index d262369bf..747224bf4 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -106,7 +106,7 @@ public class Main { * Specifies a destination for error logs. * If specified, this option overrides the default destination within {@link #workDir}. * If both this options and {@link #workDir} is not set, the log will not be generated. - * @since TODO + * @since 3.8 */ @Option(name="-agentLog", usage="Local agent error log destination (overrides workDir)") @CheckForNull @@ -114,7 +114,7 @@ public class Main { /** * Specified location of the property file with JUL settings. - * @since TODO + * @since 3.8 */ @CheckForNull @Option(name="-loggingConfig",usage="Path to the property file with java.util.logging settings") @@ -127,7 +127,7 @@ public class Main { * In order to retain compatibility, the option is disabled by default. *

* Jenkins specifics: This working directory is expected to be equal to the agent root specified in Jenkins configuration. - * @since TODO + * @since 3.8 */ @Option(name = "-workDir", usage = "Declares the working directory of the remoting instance (stores cache and logs by default)") @@ -139,7 +139,7 @@ public class Main { *

* This option is not expected to be used frequently, but it allows remoting users to specify a custom * storage directory if the default {@code remoting} directory is consumed by other stuff. - * @since TODO + * @since 3.8 */ @Option(name = "-internalDir", usage = "Specifies a name of the internal files within a working directory ('remoting' by default)", @@ -151,7 +151,7 @@ public class Main { * Fail the initialization if the workDir or internalDir are missing. * This option presumes that the workspace structure gets initialized previously in order to ensure that we do not start up with a borked instance * (e.g. if a filesystem mount gets disconnected). - * @since TODO + * @since 3.8 */ @Option(name = "-failIfWorkDirIsMissing", usage = "Fails the initialization if the requested workDir or internalDir are missing ('false' by default)", diff --git a/src/main/java/org/jenkinsci/remoting/RoleChecker.java b/src/main/java/org/jenkinsci/remoting/RoleChecker.java index 5ab49e9d5..d67c058b6 100644 --- a/src/main/java/org/jenkinsci/remoting/RoleChecker.java +++ b/src/main/java/org/jenkinsci/remoting/RoleChecker.java @@ -13,7 +13,7 @@ * * @author Kohsuke Kawaguchi * @see ChannelBuilder#withRoleChecker(RoleChecker) - * @since TODO + * @since 2.47 */ public abstract class RoleChecker { /** diff --git a/src/main/java/org/jenkinsci/remoting/RoleSensitive.java b/src/main/java/org/jenkinsci/remoting/RoleSensitive.java index 10dd983a7..63abe9425 100644 --- a/src/main/java/org/jenkinsci/remoting/RoleSensitive.java +++ b/src/main/java/org/jenkinsci/remoting/RoleSensitive.java @@ -14,7 +14,7 @@ * * @author Kohsuke Kawaguchi * @see RoleChecker - * @since TODO + * @since 2.47 */ public interface RoleSensitive { /** diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpoint.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpoint.java index 7a3f0100b..bb39c1d22 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpoint.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpoint.java @@ -69,7 +69,6 @@ public class JnlpAgentEndpoint { /** * Jenkins URL for the discovered endpoint. - * @since TODO */ @CheckForNull private final URL serviceUrl; @@ -92,7 +91,7 @@ public JnlpAgentEndpoint(@Nonnull String host, int port, @CheckForNull RSAPublic * @param protocols The supported protocols. * @param serviceURL URL of the service hosting the remoting endpoint. * Use {@code null} if it is not a web service or if the URL cannot be determined - * @since TODO + * @since 3.0 */ public JnlpAgentEndpoint(@Nonnull String host, int port, @CheckForNull RSAPublicKey publicKey, @CheckForNull Set protocols, @CheckForNull URL serviceURL) { diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 4b43b5528..3f24d8896 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -82,7 +82,6 @@ public class JnlpAgentEndpointResolver { * The option provides protocol names, but the order of the check is defined internally and cannot be changed. * This option can be also used in order to workaround issues when the headers cannot be delivered * from the server due to whatever reason (e.g. JENKINS-41730). - * @since TODO */ private static String PROTOCOL_NAMES_TO_TRY = System.getProperty(JnlpAgentEndpointResolver.class.getName() + ".protocolNamesToTry"); diff --git a/src/main/java/org/jenkinsci/remoting/engine/WorkDirManager.java b/src/main/java/org/jenkinsci/remoting/engine/WorkDirManager.java index 098a5c2f6..10dbaaf5a 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/WorkDirManager.java +++ b/src/main/java/org/jenkinsci/remoting/engine/WorkDirManager.java @@ -322,7 +322,7 @@ private static ConsoleHandler findConsoleHandler(Logger logger) { /** * Defines components of the Working directory. - * @since TODO + * @since 3.8 */ @Restricted(NoExternalUse.class) public enum DirType { diff --git a/src/main/java/org/jenkinsci/remoting/nio/FifoBuffer.java b/src/main/java/org/jenkinsci/remoting/nio/FifoBuffer.java index 69d6e001d..98dc4eb82 100644 --- a/src/main/java/org/jenkinsci/remoting/nio/FifoBuffer.java +++ b/src/main/java/org/jenkinsci/remoting/nio/FifoBuffer.java @@ -257,7 +257,7 @@ public boolean isClosed() { /** * Returns Exception with stacktrace of the command, which invoked the buffer close. * @return Close cause or {@code null} - * @since TODO + * @since 3.3 */ @CheckForNull public CloseCause getCloseCause() { @@ -618,7 +618,7 @@ public int read(byte[] b, int off, int len) throws IOException { /** * Explains the reason of the buffer close. - * @since TODO + * @since 3.3 */ public static class CloseCause extends Exception { From 9b0b7b3ae57bfeb573c98fb53bec005118ee995d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 13 Sep 2017 10:33:25 +0100 Subject: [PATCH 006/243] [JENKINS-45755] - Make JARCache nullable in Channel --- src/main/java/hudson/remoting/Channel.java | 23 ++++++++---- .../java/hudson/remoting/ChannelBuilder.java | 35 +++++++++++++++++-- src/main/java/hudson/remoting/Engine.java | 11 ++++-- .../hudson/remoting/FileSystemJarCache.java | 3 +- src/main/java/hudson/remoting/JarCache.java | 16 +++++++-- src/main/java/hudson/remoting/Launcher.java | 2 +- .../hudson/remoting/ResourceImageBoth.java | 4 ++- .../hudson/remoting/ResourceImageInJar.java | 5 ++- .../remoting/nio/NioChannelBuilder.java | 5 +++ .../java/hudson/remoting/PrefetchingTest.java | 6 +++- 10 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index d9f560fec..28cbcb71c 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -317,7 +317,12 @@ public class Channel implements VirtualChannel, IChannel, Closeable { */ /*package*/ final ClassLoader baseClassLoader; - @Nonnull + /** + * JAR Resolution Cache. + * Can be {@code null} if caching disabled for this channel. + * In such case some classloading operations may be rejected. + */ + @CheckForNull private JarCache jarCache; /*package*/ final JarLoaderImpl jarLoader; @@ -515,12 +520,10 @@ protected Channel(@Nonnull ChannelBuilder settings, @Nonnull CommandTransport tr this.underlyingOutput = transport.getUnderlyingStream(); // JAR Cache resolution - JarCache effectiveJarCache = settings.getJarCache(); - if (effectiveJarCache == null) { - effectiveJarCache = JarCache.getDefault(); - logger.log(Level.CONFIG, "Using the default JAR Cache: {0}", effectiveJarCache); + this.jarCache = settings.getJarCache(); + if (this.jarCache == null) { + logger.log(Level.CONFIG, "JAR Cache is not defined for channel {0}", name); } - this.jarCache = effectiveJarCache; this.baseClassLoader = settings.getBaseLoader(); this.classFilter = settings.getClassFilter(); @@ -838,9 +841,12 @@ public boolean preloadJar(ClassLoader local, URL... jars) throws IOException, In /** * If this channel is built with jar file caching, return the object that manages this cache. + * @return JAR Cache object. {@code null} if JAR caching is disabled * @since 2.24 + * @since 3.10 JAR Cache is Nonnull + * @since 3.12 JAR Cache made nullable again due to JENKINS-45755 */ - @Nonnull + @CheckForNull public JarCache getJarCache() { return jarCache; } @@ -851,6 +857,9 @@ public JarCache getJarCache() { * * So to best avoid performance loss due to race condition, please set a JarCache in the constructor, * unless your call sequence guarantees that you call this method before remote classes are loaded. + * + * @param jarCache New JAR Cache to be used. + * Cannot be {@code null}, JAR Cache disabling on a running channel is not supported. * @since 2.24 */ public void setJarCache(@Nonnull JarCache jarCache) { diff --git a/src/main/java/hudson/remoting/ChannelBuilder.java b/src/main/java/hudson/remoting/ChannelBuilder.java index 242e784b7..4dca95001 100644 --- a/src/main/java/hudson/remoting/ChannelBuilder.java +++ b/src/main/java/hudson/remoting/ChannelBuilder.java @@ -192,18 +192,47 @@ public boolean isRemoteClassLoadingAllowed() { /** * Sets the JAR cache storage. - * @param jarCache JAR Cache to be used. If {@code null}, a default value will be used by the {@link Channel} + * @param jarCache JAR Cache to be used. If a deprecated {@code null} value is passed, + * the behavior will be determined by the {@link Channel} implementation. * @return {@code this} + * @since 2.38 + * @since 3.12 {@code null} parameter value is deprecated. + * {@link #withoutJarCache()} or {@link #withJarCacheOrDefault(JarCache)} should be used instead. */ - public ChannelBuilder withJarCache(@CheckForNull JarCache jarCache) { + public ChannelBuilder withJarCache(@Nonnull JarCache jarCache) { this.jarCache = jarCache; return this; } + /** + * Sets the JAR cache storage. + * @param jarCache JAR Cache to be used. + * If {@code null}, value of {@link JarCache#getDefault()} will be used. + * @return {@code this} + * @since 3.12 + * @throws IOException Default JAR Cache location cannot be initialized + */ + public ChannelBuilder withJarCacheOrDefault(@CheckForNull JarCache jarCache) throws IOException { + this.jarCache = jarCache != null ? jarCache : JarCache.getDefault(); + return this; + } + + /** + * Resets JAR Cache setting to the default. + * The behavior will be determined by the {@link Channel} implementation. + * + * @since 3.12 + */ + public ChannelBuilder withoutJarCache() { + this.jarCache = null; + return this; + } + /** * Gets the JAR Cache storage. * @return {@code null} if it is not defined. - * {@link Channel} implementation should use a default cache value then. + * {@link Channel} implementation defines the behavior in such case. + * @since 2.38 */ @CheckForNull public JarCache getJarCache() { diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 474172447..23d2ede3d 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -263,7 +263,11 @@ public synchronized void startEngine() throws IOException { throw new IOException("Cannot find the JAR Cache location"); } LOGGER.log(Level.FINE, "Using standard File System JAR Cache. Root Directory is {0}", jarCacheDirectory); - jarCache = new FileSystemJarCache(jarCacheDirectory, true); + try { + jarCache = new FileSystemJarCache(jarCacheDirectory, true); + } catch (IllegalArgumentException ex) { + throw new IOException("Failed to initialize FileSystem JAR Cache in " + jarCacheDirectory, ex); + } } else { LOGGER.log(Level.INFO, "Using custom JAR Cache: {0}", jarCache); } @@ -567,7 +571,10 @@ public void afterProperties(@Nonnull JnlpConnectionState event) { @Override public void beforeChannel(@Nonnull JnlpConnectionState event) { - event.getChannelBuilder().withJarCache(jarCache).withMode(Mode.BINARY); + ChannelBuilder bldr = event.getChannelBuilder().withMode(Mode.BINARY); + if (jarCache != null) { + bldr.withJarCache(jarCache); + } } @Override diff --git a/src/main/java/hudson/remoting/FileSystemJarCache.java b/src/main/java/hudson/remoting/FileSystemJarCache.java index ed18eafdc..eed673e2a 100644 --- a/src/main/java/hudson/remoting/FileSystemJarCache.java +++ b/src/main/java/hudson/remoting/FileSystemJarCache.java @@ -36,6 +36,7 @@ public class FileSystemJarCache extends JarCacheSupport { @GuardedBy("itself") private final Map checksumsByPath = new HashMap<>(); + //TODO: Create new IOException constructor /** * @param rootDir * Root directory. @@ -54,7 +55,7 @@ public FileSystemJarCache(@Nonnull File rootDir, boolean touch) { try { Util.mkdirs(rootDir); } catch (IOException ex) { - throw new RuntimeException("Root directory not writable: " + rootDir); + throw new IllegalArgumentException("Root directory not writable: " + rootDir); } } diff --git a/src/main/java/hudson/remoting/JarCache.java b/src/main/java/hudson/remoting/JarCache.java index 1123f3178..33bc00f3c 100644 --- a/src/main/java/hudson/remoting/JarCache.java +++ b/src/main/java/hudson/remoting/JarCache.java @@ -26,10 +26,20 @@ public abstract class JarCache { */ /*package*/ static final File DEFAULT_NOWS_JAR_CACHE_LOCATION = new File(System.getProperty("user.home"),".jenkins/cache/jars"); - + + //TODO: replace by checked exception + /** + * Gets a default value for {@link FileSystemJarCache} to be initialized on agents. + * @return Created JAR Cache + * @throws IOException Default JAR Cache location cannot be initialized + */ @Nonnull - /*package*/ static JarCache getDefault() { - return new FileSystemJarCache(DEFAULT_NOWS_JAR_CACHE_LOCATION, true); + /*package*/ static JarCache getDefault() throws IOException { + try { + return new FileSystemJarCache(DEFAULT_NOWS_JAR_CACHE_LOCATION, true); + } catch (IllegalArgumentException ex) { + throw new IOException("Failed to initialize the default JAR Cache location", ex); + } } /** diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index 71abe0b16..85aa07542 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -732,7 +732,7 @@ public static void main(InputStream is, OutputStream os, Mode mode, boolean perf ExecutorService executor = Executors.newCachedThreadPool(); ChannelBuilder cb = new ChannelBuilder("channel", executor) .withMode(mode) - .withJarCache(cache); + .withJarCacheOrDefault(cache); // expose StandardOutputStream as a channel property, which is a better way to make this available // to the user of Channel than Channel#getUnderlyingOutput() diff --git a/src/main/java/hudson/remoting/ResourceImageBoth.java b/src/main/java/hudson/remoting/ResourceImageBoth.java index 28846bc80..d5d8bf3a2 100644 --- a/src/main/java/hudson/remoting/ResourceImageBoth.java +++ b/src/main/java/hudson/remoting/ResourceImageBoth.java @@ -42,7 +42,9 @@ Future resolveURL(Channel channel, String resourcePath) throws IOExcepti @Nonnull private Future initiateJarRetrieval(@Nonnull Channel channel) throws IOException, InterruptedException { JarCache c = channel.getJarCache(); - assert c !=null : "we don't advertise jar caching to the other side unless we have a cache with us"; + if (c == null) { + throw new IOException("Failed to initiate retrieval. JAR Cache is disabled for the channel " + channel.getName()); + } try { return c.resolve(channel, sum1, sum2); diff --git a/src/main/java/hudson/remoting/ResourceImageInJar.java b/src/main/java/hudson/remoting/ResourceImageInJar.java index b6d3a372c..831bba459 100644 --- a/src/main/java/hudson/remoting/ResourceImageInJar.java +++ b/src/main/java/hudson/remoting/ResourceImageInJar.java @@ -82,7 +82,10 @@ open jar file (see sun.net.www.protocol.jar.JarURLConnection.JarURLInputStream.c Future _resolveJarURL(Channel channel) throws IOException, InterruptedException { JarCache c = channel.getJarCache(); - assert c !=null : "we don't advertise jar caching to the other side unless we have a cache with us"; + if (c == null) { + throw new IOException(String.format("Failed to resolve a jar %016x%016x. JAR Cache is disabled for the channel %s", + sum1, sum2, channel.getName())); + } return c.resolve(channel, sum1, sum2); // throw (IOException)new IOException(String.format("Failed to resolve a jar %016x%016x",sum1,sum2)).initCause(e); diff --git a/src/main/java/org/jenkinsci/remoting/nio/NioChannelBuilder.java b/src/main/java/org/jenkinsci/remoting/nio/NioChannelBuilder.java index 66b15791c..7ddb9ed1e 100644 --- a/src/main/java/org/jenkinsci/remoting/nio/NioChannelBuilder.java +++ b/src/main/java/org/jenkinsci/remoting/nio/NioChannelBuilder.java @@ -84,6 +84,11 @@ public NioChannelBuilder withJarCache(JarCache jarCache) { return (NioChannelBuilder) super.withJarCache(jarCache); } + @Override + public NioChannelBuilder withoutJarCache() { + return (NioChannelBuilder) super.withoutJarCache(); + } + @Override public NioChannelBuilder withClassFilter(ClassFilter filter) { return (NioChannelBuilder)super.withClassFilter(filter); diff --git a/src/test/java/hudson/remoting/PrefetchingTest.java b/src/test/java/hudson/remoting/PrefetchingTest.java index a8cb21452..f6faacc7e 100644 --- a/src/test/java/hudson/remoting/PrefetchingTest.java +++ b/src/test/java/hudson/remoting/PrefetchingTest.java @@ -215,7 +215,11 @@ private ForceJarLoad(Checksum sum) { public Void call() throws IOException { try { Channel ch = Channel.current(); - ch.getJarCache().resolve(ch,sum1,sum2).get(); + final JarCache jarCache = ch.getJarCache(); + if (jarCache == null) { + throw new IOException("Cannot Force JAR load, JAR cache is disabled"); + } + jarCache.resolve(ch,sum1,sum2).get(); return null; } catch (InterruptedException e) { throw new IOException(e); From 99f01e1d800f0265bf788a60890fa669c94dd78a Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 13 Sep 2017 11:29:59 +0100 Subject: [PATCH 007/243] FileSystemJarCache now propagates cause when the cache directory cannot be created --- src/main/java/hudson/remoting/FileSystemJarCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/FileSystemJarCache.java b/src/main/java/hudson/remoting/FileSystemJarCache.java index eed673e2a..36569d61e 100644 --- a/src/main/java/hudson/remoting/FileSystemJarCache.java +++ b/src/main/java/hudson/remoting/FileSystemJarCache.java @@ -55,7 +55,7 @@ public FileSystemJarCache(@Nonnull File rootDir, boolean touch) { try { Util.mkdirs(rootDir); } catch (IOException ex) { - throw new IllegalArgumentException("Root directory not writable: " + rootDir); + throw new IllegalArgumentException("Root directory not writable: " + rootDir, ex); } } From 92a85fd90af86d27c8bf286232632e89e3a07e74 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 12:29:14 +0200 Subject: [PATCH 008/243] Changelog: Noting 3.12 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fdd16aa4..4f702ec2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.12 + +Release date: Sep 14, 2017 + +* [JENKINS-45755](https://issues.jenkins-ci.org/browse/JENKINS-45755) - +Prevent channel initialization failure when JAR Cache directory is not writable and the channel does not need this cache +(regression in 3.10). + * This issue causes a regression in Jenkins LTS 2.73.1 + See the [upgrade guide](https://jenkins.io/doc/upgrade-guide/2.73/#known-issue-agent-connection-failures-involving-jenkins-masters-with-undefined-or-non-writable-home-directory) for more info. +* [JENKINS-46140](https://issues.jenkins-ci.org/browse/JENKINS-46140) - +Improve representation of remote operation exceptions in logs. + ##### 3.11 Release date: Aug 18, 2017 => [Jenkins 2.76](https://jenkins.io/changelog/#v2.76) From ee8fbcec073bdb88cac821bd2136bbe4cc9f143e Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 12:46:23 +0200 Subject: [PATCH 009/243] Tombstone Remoting 2.x in the changelog --- CHANGELOG-2.x.md | 78 +++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/CHANGELOG-2.x.md b/CHANGELOG-2.x.md index 8cf9d8b4d..ba5797a21 100644 --- a/CHANGELOG-2.x.md +++ b/CHANGELOG-2.x.md @@ -1,21 +1,18 @@ Remoting 2.x Changelog ==== -Below you can changelogs for Remoting 2.x. +:exclamation: Below you can see changelogs for the **obsolete** Remoting 2.x baseline. This version only contains bugfixes and performance improvements. Current mainline is Remoting 3.x, changelogs are available [here](CHANGELOG.md). - -The file also provides links to Jenkins versions, -which bundle the specified remoting version. -See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +There is no plan to release new versions of Remoting 2.x. ##### 2.62.6 -Release date: Can be released on-demand +Release date: Jun 26, 2017 Fixed issues: -* [JENKINS-41852](https://issues.jenkins-ci.org/browse/JENKINS-41852) - +* [JENKINS-41852](https://issues.jenkins-ci.org/browse/JENKINS-41852) - Fix exported object pinning logic to prevent release due to the integer overflow. ([PR #148](https://github.com/jenkinsci/remoting/pull/148)) @@ -25,8 +22,8 @@ Release date: Feb 01, 2017 Fixed issues: -* [SECURITY-383](https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2017-02-01) - -Blacklist classes vulnerable to a remote code execution involving the deserialization of various types in +* [SECURITY-383](https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2017-02-01) - +Blacklist classes vulnerable to a remote code execution involving the deserialization of various types in `javax.imageio.*`, `java.util.ServiceLoader`, and `java.net.URLClassLoader`. ##### 2.62.4 @@ -35,19 +32,19 @@ Release date: Nov 21, 2016 Fixed issues: -* [JENKINS-25218](https://issues.jenkins-ci.org/browse/JENKINS-25218) - +* [JENKINS-25218](https://issues.jenkins-ci.org/browse/JENKINS-25218) - Hardening of FifoBuffer operation logic. The change adds additional minor fixes to the original fix in `remoting-2.54`. ([PR #100](https://github.com/jenkinsci/remoting/pull/100)) Improvements: -* [JENKINS-39150](https://issues.jenkins-ci.org/browse/JENKINS-39150) - +* [JENKINS-39150](https://issues.jenkins-ci.org/browse/JENKINS-39150) - Add logic for dumping diagnostics across all the channels. ([PR #122](https://github.com/jenkinsci/remoting/pull/122), [PR #125](https://github.com/jenkinsci/remoting/pull/125)) -* [JENKINS-39543](https://issues.jenkins-ci.org/browse/JENKINS-39543) - +* [JENKINS-39543](https://issues.jenkins-ci.org/browse/JENKINS-39543) - Improve the caller/callee correlation diagnostics in thread dumps. ([PR #119](https://github.com/jenkinsci/remoting/pull/119)) -* [JENKINS-39290](https://issues.jenkins-ci.org/browse/JENKINS-39290) - +* [JENKINS-39290](https://issues.jenkins-ci.org/browse/JENKINS-39290) - Add the `org.jenkinsci.remoting.nio.NioChannelHub.disabled` flag for disabling NIO (mostly for debugging purposes). ([PR #123](https://github.com/jenkinsci/remoting/pull/123)) @@ -55,7 +52,7 @@ Add the `org.jenkinsci.remoting.nio.NioChannelHub.disabled` flag for disabling N Release date: (Nov 13, 2016) => Jenkins 2.19.3 LTS -* [SECURITY-360](https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-11-16) - +* [SECURITY-360](https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-11-16) - Blacklist serialization of particular classes to close the Remote code execution vulnerability. ([Commit #b7ac85ed4ae41482d9754a881df91d2eb86d047d](https://github.com/jenkinsci/remoting/commit/b7ac85ed4ae41482d9754a881df91d2eb86d047d)) @@ -65,33 +62,33 @@ Release date: (Oct 7, 2016) => Jenkins 2.19.3 LTS Fixed issues: -* [JENKINS-38539](https://issues.jenkins-ci.org/browse/JENKINS-38539) - +* [JENKINS-38539](https://issues.jenkins-ci.org/browse/JENKINS-38539) - Stability: Turn on SO_KEEPALIVE and provide CLI option to turn it off again. (https://github.com/jenkinsci/remoting/pull/110) -* [JENKINS-37539](https://issues.jenkins-ci.org/browse/JENKINS-37539) - +* [JENKINS-37539](https://issues.jenkins-ci.org/browse/JENKINS-37539) - Prevent NullPointerException in Engine#connect() when host or port parameters are null or empty. (https://github.com/jenkinsci/remoting/pull/101) -* [CID-152201] - +* [CID-152201] - Fix resource leak in remoting.jnlp.Main. (https://github.com/jenkinsci/remoting/pull/102) -* [CID-152200,CID-152202] - +* [CID-152200,CID-152202] - Resource leak in Encryption Cipher I/O streams on exceptional paths. (https://github.com/jenkinsci/remoting/pull/104) -##### 2.62 +##### 2.62 Release date: (Aug 14, 2016) => Jenkins 2.17, 2.19.1 LTS Fixed issues: -* [JENKINS-22853](https://issues.jenkins-ci.org/browse/JENKINS-22853) - +* [JENKINS-22853](https://issues.jenkins-ci.org/browse/JENKINS-22853) - Be robust against the delayed EOF command when unexporting input and output streams. (https://github.com/jenkinsci/remoting/pull/97) -* Fixed ~20 minor issues reported by FindBugs. +* Fixed ~20 minor issues reported by FindBugs. More fixes to be delivered in future versions. (https://github.com/jenkinsci/remoting/pull/96) Enhancements: -* [JENKINS-37218](https://issues.jenkins-ci.org/browse/JENKINS-37218) - +* [JENKINS-37218](https://issues.jenkins-ci.org/browse/JENKINS-37218) - Performance: ClassFilter does not use Regular Expressions anymore to match String.startsWith patterns. (https://github.com/jenkinsci/remoting/pull/92) * [JENKINS-37031](https://issues.jenkins-ci.org/browse/JENKINS-37031) @@ -103,7 +100,7 @@ Performance: ClassFilter does not use Regular Expressions anymore t Release date: (Aug 5, 2016) => Jenkins 2.17, 2.19.1 LTS Fixed issues: -* [JENKINS-37140](https://issues.jenkins-ci.org/browse/JENKINS-37140) - +* [JENKINS-37140](https://issues.jenkins-ci.org/browse/JENKINS-37140) - JNLP Slave connection issue with *JNLP3-connect* protocol when the generated encrypted cookie contains a newline symbols. (https://github.com/jenkinsci/remoting/pull/95) * [JENKINS-36991](https://issues.jenkins-ci.org/browse/JENKINS-36991) - @@ -119,24 +116,24 @@ Enhancements: Release date: (June 10, 2016) => Jenkins 2.9, 2.7.2 Fixed issues: -* [JENKINS-22722](https://issues.jenkins-ci.org/browse/JENKINS-22722) - -Make the channel reader tolerant against Socket timeouts. +* [JENKINS-22722](https://issues.jenkins-ci.org/browse/JENKINS-22722) - +Make the channel reader tolerant against Socket timeouts. (https://github.com/jenkinsci/remoting/pull/80) -* [JENKINS-32326](https://issues.jenkins-ci.org/browse/JENKINS-32326) - -Support no_proxy environment variable. +* [JENKINS-32326](https://issues.jenkins-ci.org/browse/JENKINS-32326) - +Support no_proxy environment variable. (https://github.com/jenkinsci/remoting/pull/84) -* [JENKINS-35190](https://issues.jenkins-ci.org/browse/JENKINS-35190) - -Do not invoke PingFailureAnalyzer for agent=>master ping failures. +* [JENKINS-35190](https://issues.jenkins-ci.org/browse/JENKINS-35190) - +Do not invoke PingFailureAnalyzer for agent=>master ping failures. (https://github.com/jenkinsci/remoting/pull/85) -* [JENKINS-31256](https://issues.jenkins-ci.org/browse/JENKINS-31256) - - hudson.Remoting.Engine#waitForServerToBack now uses credentials for connection. +* [JENKINS-31256](https://issues.jenkins-ci.org/browse/JENKINS-31256) - + hudson.Remoting.Engine#waitForServerToBack now uses credentials for connection. (https://github.com/jenkinsci/remoting/pull/87) -* [JENKINS-35494](https://issues.jenkins-ci.org/browse/JENKINS-35494) - -Fix issues in file management in hudson.remoting.Launcher (main executable class). +* [JENKINS-35494](https://issues.jenkins-ci.org/browse/JENKINS-35494) - +Fix issues in file management in hudson.remoting.Launcher (main executable class). (https://github.com/jenkinsci/remoting/pull/88) Enhancements: -* Ensure a message is logged if remoting fails to override the default ClassFilter. +* Ensure a message is logged if remoting fails to override the default ClassFilter. (https://github.com/jenkinsci/remoting/pull/80) ##### 2.59 @@ -144,8 +141,8 @@ Enhancements: Release date: (May 13, 2016) => Jenkins 2.4, 2.7.1 Enhancements: -* [JENKINS-34819](https://issues.jenkins-ci.org/browse/JENKINS-34819) - -Allow disabling the remoting protocols individually. Works around issues like [JENKINS-34121](https://issues.jenkins-ci.org/browse/JENKINS-34121) +* [JENKINS-34819](https://issues.jenkins-ci.org/browse/JENKINS-34819) - +Allow disabling the remoting protocols individually. Works around issues like [JENKINS-34121](https://issues.jenkins-ci.org/browse/JENKINS-34121) (https://github.com/jenkinsci/remoting/pull/83) ##### 2.58 @@ -153,15 +150,14 @@ Allow disabling the remoting protocols individually. Works around issues like [J Release date: (May 11, 2016) => Jenkins 2.4, 2.7.1 Fixes issues: -* [JENKINS-34213](https://issues.jenkins-ci.org/browse/JENKINS-34213) - +* [JENKINS-34213](https://issues.jenkins-ci.org/browse/JENKINS-34213) - Ensure that the unexporter cleans up whatever it can each sweep. (https://github.com/jenkinsci/remoting/pull/81) -* [JENKINS-19445](https://issues.jenkins-ci.org/browse/JENKINS-19445) - +* [JENKINS-19445](https://issues.jenkins-ci.org/browse/JENKINS-19445) - Force class load on UserRequest in order to prevent deadlock on windows nodes when using JNA and Subversion. (https://github.com/jenkinsci/remoting/pull/82) Enhancements: -* [JENKINS-34808](https://issues.jenkins-ci.org/browse/JENKINS-34808) - -Allow user to adjust socket timeout in the channel reader. +* [JENKINS-34808](https://issues.jenkins-ci.org/browse/JENKINS-34808) - +Allow user to adjust socket timeout in the channel reader. (https://github.com/jenkinsci/remoting/pull/68) - From b28d1ece5c67c247f9e23ac5eb8260163b867306 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 12:51:55 +0200 Subject: [PATCH 010/243] [maven-release-plugin] prepare release remoting-3.12 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f021a70f3..9e55e6b02 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.12-SNAPSHOT + 3.12 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.12 From d5ff5c3306ba05014849ac525f574e042d1a9de4 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 12:52:02 +0200 Subject: [PATCH 011/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9e55e6b02..c322e0291 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.12 + 3.13-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.12 + HEAD From 84d6a486c6e63fc6b4b3aa3cb6aabbd661635ee3 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 13:32:18 +0200 Subject: [PATCH 012/243] Build Javadoc only once during the packaging phase. Otherwise Javadoc is being generated twice, and then it is being uploaded twice. Common users have no package replace permissions in artifactory, and the release just fails after that. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c322e0291..cc98e751b 100644 --- a/pom.xml +++ b/pom.xml @@ -283,7 +283,7 @@ THE SOFTWARE. ${hudson.sign.providerClass} ${hudson.sign.providerArg} ${hudson.sign.tsa} - @@ -446,7 +445,6 @@ THE SOFTWARE. org.codehaus.mojo findbugs-maven-plugin - 3.0.4 true Max From 8ee024b49cba9b96eda6738ded5c835153af0edf Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 14:38:50 +0200 Subject: [PATCH 014/243] POM: Do not duplicate Maven Release plugin configuration from Parent POM --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index a728218fe..bb85de2ac 100644 --- a/pom.xml +++ b/pom.xml @@ -416,13 +416,6 @@ THE SOFTWARE. - - maven-release-plugin - 2.5.2 - - release - - org.jacoco jacoco-maven-plugin From a9e0934ff16694c4293faf3debe7f7d2fe500022 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Sep 2017 14:39:18 +0200 Subject: [PATCH 015/243] POM: Maven Surefire plugin should use version from Dependency management of parent POM --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index bb85de2ac..cdbb0ce6b 100644 --- a/pom.xml +++ b/pom.xml @@ -455,7 +455,6 @@ THE SOFTWARE. maven-surefire-plugin - 2.19.1 false 4 From ac60160b459bad1802e250de8749a2dbb21e87ad Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 15 Sep 2017 14:16:36 +0200 Subject: [PATCH 016/243] Docs: Adjust WorkDir description --- docs/workDir.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/workDir.md b/docs/workDir.md index fc8397072..a98772114 100644 --- a/docs/workDir.md +++ b/docs/workDir.md @@ -1,9 +1,9 @@ Remoting Work directory === -In Remoting work directory is a storage +In Remoting work directory is an internal data storage, which may be used by Remoting to store caches, logs and other metadata. -Remoting work directory is available starting from Remoting `3.8`. +Remoting work directory is available starting from Remoting `3.8`, which is available in [Jenkins 2.68](https://jenkins.io/changelog/#v2.68)). Before this version there was no working directory concept in the library itself; all operations were managed by library users (e.g. Jenkins agent workspaces). From a740b6fd0710531e5366a41e3dc2db1526292557 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 25 Sep 2017 13:37:01 +0300 Subject: [PATCH 017/243] Changelog: add Jenkins version for 3.12 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f702ec2f..c6eb17ed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.12 -Release date: Sep 14, 2017 +Release date: Sep 14, 2017 => [Jenkins 2.79](https://jenkins.io/changelog/#v2.79) * [JENKINS-45755](https://issues.jenkins-ci.org/browse/JENKINS-45755) - Prevent channel initialization failure when JAR Cache directory is not writable and the channel does not need this cache From 272aeab6b866f546b7d210b3328dbc015de67277 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Tue, 26 Sep 2017 15:34:21 +0200 Subject: [PATCH 018/243] [FIXED JENKINS-47132] Single quote must be doubled --- .../jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 3f24d8896..09d3df828 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -315,7 +315,7 @@ public void waitForReady() throws InterruptedException { return; } LOGGER.log(Level.INFO, - "Master isn't ready to talk to us on {0}. Will retry again: response code={1}", + "Master isn''t ready to talk to us on {0}. Will retry again: response code={1}", new Object[]{url, con.getResponseCode()}); } catch (IOException e) { // report the failure From f999f5567e696b19db4b4b66cc71d4004344393b Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Thu, 5 Oct 2017 10:44:14 -0400 Subject: [PATCH 019/243] [JENKINS-38711] Add UncaughtExceptionHandlers to the various remoting threads (#187) * [JENKINS-38711] Add UncaughtExceptionHandlers to the various remoting threads * [JENKINS-38711] Add handling to missed threads * [JENKINS-38711] Fix thread restart bug * [JENKINS-38711] Remove test exception * [JENKINS-38711] Move stream closures outside of CopyThread * [JENKINS-38711] Fix log levels * [JENKINS-38711] Fix CopyThread input bug * [JENKINS-38711] Don't close provided OutputStream in ForwarderFactory --- .../hudson/remoting/DaemonThreadFactory.java | 11 +++++-- src/main/java/hudson/remoting/Engine.java | 4 +++ .../remoting/FlightRecorderInputStream.java | 5 +++ src/main/java/hudson/remoting/PingThread.java | 4 +++ .../hudson/remoting/RemoteInputStream.java | 5 +++ .../remoting/SynchronousCommandTransport.java | 4 +++ .../hudson/remoting/forward/CopyThread.java | 31 +++++++++++++------ .../remoting/forward/ForwarderFactory.java | 21 +++++++++++-- .../remoting/forward/PortForwarder.java | 31 ++++++++++++++++--- 9 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/main/java/hudson/remoting/DaemonThreadFactory.java b/src/main/java/hudson/remoting/DaemonThreadFactory.java index bcd41b45c..5e0db36c8 100644 --- a/src/main/java/hudson/remoting/DaemonThreadFactory.java +++ b/src/main/java/hudson/remoting/DaemonThreadFactory.java @@ -1,14 +1,19 @@ package hudson.remoting; import java.util.concurrent.ThreadFactory; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @author Kohsuke Kawaguchi */ public class DaemonThreadFactory implements ThreadFactory { + private static final Logger LOGGER = Logger.getLogger(DaemonThreadFactory.class.getName()); + public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setDaemon(true); - return t; + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setUncaughtExceptionHandler((t, e) -> LOGGER.log(Level.SEVERE, "Unhandled exception in thread " + t, e)); + return thread; } } diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 4dc80d75a..649ac3bde 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -217,6 +217,10 @@ public Engine(EngineListener listener, List hudsonUrls, String secretKey, S this.slaveName = slaveName; if(candidateUrls.isEmpty()) throw new IllegalArgumentException("No URLs given"); + setUncaughtExceptionHandler((t, e) -> { + LOGGER.log(Level.SEVERE, "Uncaught exception in Engine thread " + t, e); + interrupt(); + }); } /** diff --git a/src/main/java/hudson/remoting/FlightRecorderInputStream.java b/src/main/java/hudson/remoting/FlightRecorderInputStream.java index 3d6aa2fc6..171b0c179 100644 --- a/src/main/java/hudson/remoting/FlightRecorderInputStream.java +++ b/src/main/java/hudson/remoting/FlightRecorderInputStream.java @@ -5,6 +5,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Filter input stream that records the content as it's read, so that it can be reported @@ -13,6 +15,7 @@ * @author Kohsuke Kawaguchi */ class FlightRecorderInputStream extends InputStream { + private static final Logger LOGGER = Logger.getLogger(FlightRecorderInputStream.class.getName()); /** * Size (in bytes) of the flight recorder ring buffer used for debugging remoting issues. @@ -62,6 +65,8 @@ public void run() { } } }; + diagnosisThread.setUncaughtExceptionHandler( + (t, e) -> LOGGER.log(Level.SEVERE, "Uncaught exception in diagnosis thread " + t, e)); // wait up to 1 sec to grab as much data as possible diagnosisThread.start(); diff --git a/src/main/java/hudson/remoting/PingThread.java b/src/main/java/hudson/remoting/PingThread.java index 1168579f3..92a2faf8f 100644 --- a/src/main/java/hudson/remoting/PingThread.java +++ b/src/main/java/hudson/remoting/PingThread.java @@ -68,6 +68,10 @@ public PingThread(Channel channel, long timeout, long interval) { this.timeout = timeout; this.interval = interval; setDaemon(true); + setUncaughtExceptionHandler((t, e) -> { + LOGGER.log(Level.SEVERE, "Uncaught exception in PingThread " + t, e); + onDead(e); + }); } public PingThread(Channel channel, long interval) { diff --git a/src/main/java/hudson/remoting/RemoteInputStream.java b/src/main/java/hudson/remoting/RemoteInputStream.java index 7a2807060..ff04155e7 100644 --- a/src/main/java/hudson/remoting/RemoteInputStream.java +++ b/src/main/java/hudson/remoting/RemoteInputStream.java @@ -34,6 +34,8 @@ import java.io.StringWriter; import java.util.EnumSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import static hudson.remoting.RemoteInputStream.Flag.*; @@ -46,6 +48,7 @@ * @author Kohsuke Kawaguchi */ public class RemoteInputStream extends InputStream implements Serializable { + private static final Logger LOGGER = Logger.getLogger(RemoteInputStream.class.getName()); private transient InputStream core; private boolean autoUnexport; private transient Greedy greedyAt; @@ -115,6 +118,8 @@ private void writeObject(ObjectOutputStream oos) throws IOException { new Thread("RemoteInputStream greedy pump thread: " + greedyAt.print()) { { + setUncaughtExceptionHandler( + (t, e) -> LOGGER.log(Level.SEVERE, "Uncaught exception in RemoteInputStream pump thread " + t, e)); setDaemon(true); } diff --git a/src/main/java/hudson/remoting/SynchronousCommandTransport.java b/src/main/java/hudson/remoting/SynchronousCommandTransport.java index f3ec1b39d..6961929ac 100644 --- a/src/main/java/hudson/remoting/SynchronousCommandTransport.java +++ b/src/main/java/hudson/remoting/SynchronousCommandTransport.java @@ -47,6 +47,10 @@ private final class ReaderThread extends Thread { public ReaderThread(CommandReceiver receiver) { super("Channel reader thread: "+channel.getName()); this.receiver = receiver; + setUncaughtExceptionHandler((t, e) -> { + LOGGER.log(Level.SEVERE, "Uncaught exception in SynchronousCommandTransport.ReaderThread " + t, e); + channel.terminate((IOException) new IOException("Unexpected reader termination").initCause(e)); + }); } @Override diff --git a/src/main/java/hudson/remoting/forward/CopyThread.java b/src/main/java/hudson/remoting/forward/CopyThread.java index 3c3f50254..06092db41 100644 --- a/src/main/java/hudson/remoting/forward/CopyThread.java +++ b/src/main/java/hudson/remoting/forward/CopyThread.java @@ -16,23 +16,34 @@ final class CopyThread extends Thread { private final InputStream in; private final OutputStream out; - public CopyThread(String threadName, InputStream in, OutputStream out) { + /** + * Callers are responsible for closing the input and output streams. + */ + public CopyThread(String threadName, InputStream in, OutputStream out, Runnable termination) { + this(threadName, in, out, termination, 5); + } + + private CopyThread(String threadName, InputStream in, OutputStream out, Runnable termination, int remainingTries) { super(threadName); this.in = in; this.out = out; + setUncaughtExceptionHandler((t, e) -> { + if (remainingTries > 0) { + LOGGER.log(Level.WARNING, "Uncaught exception in CopyThread " + t + ", retrying copy", e); + new CopyThread(threadName, in, out, termination, remainingTries - 1).start(); + } else { + LOGGER.log(Level.SEVERE, "Uncaught exception in CopyThread " + t + ", out of retries", e); + termination.run(); + } + }); } public void run() { try { - try { - byte[] buf = new byte[8192]; - int len; - while ((len = in.read(buf)) > 0) - out.write(buf, 0, len); - } finally { - in.close(); - out.close(); - } + byte[] buf = new byte[8192]; + int len; + while ((len = in.read(buf)) > 0) + out.write(buf, 0, len); } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception while copying in thread: " + getName(), e); } diff --git a/src/main/java/hudson/remoting/forward/ForwarderFactory.java b/src/main/java/hudson/remoting/forward/ForwarderFactory.java index 81fb3cd00..77cbefbcc 100644 --- a/src/main/java/hudson/remoting/forward/ForwarderFactory.java +++ b/src/main/java/hudson/remoting/forward/ForwarderFactory.java @@ -32,8 +32,11 @@ import org.jenkinsci.remoting.RoleChecker; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Creates {@link Forwarder}. @@ -41,6 +44,9 @@ * @author Kohsuke Kawaguchi */ public class ForwarderFactory { + + private static final Logger LOGGER = Logger.getLogger(ForwarderFactory.class.getName()); + /** * Creates a connector on the remote side that connects to the speicied host and port. */ @@ -74,8 +80,19 @@ private ForwarderImpl(String remoteHost, int remotePort) { public OutputStream connect(OutputStream out) throws IOException { Socket s = new Socket(remoteHost, remotePort); - new CopyThread(String.format("Copier to %s:%d", remoteHost, remotePort), - SocketChannelStream.in(s), out).start(); + try (InputStream in = SocketChannelStream.in(s)) { + new CopyThread( + String.format("Copier to %s:%d", remoteHost, remotePort), + in, + out, + () -> { + try { + s.close(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Problem closing socket for ForwardingFactory", e); + } + }).start(); + } return new RemoteOutputStream(SocketChannelStream.out(s)); } diff --git a/src/main/java/hudson/remoting/forward/PortForwarder.java b/src/main/java/hudson/remoting/forward/PortForwarder.java index bcb7d9573..33e40d8cf 100644 --- a/src/main/java/hudson/remoting/forward/PortForwarder.java +++ b/src/main/java/hudson/remoting/forward/PortForwarder.java @@ -33,9 +33,11 @@ import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; +import java.util.logging.Level; import java.util.logging.Logger; import static java.util.logging.Level.*; @@ -57,6 +59,14 @@ public PortForwarder(int localPort, Forwarder forwarder) throws IOException { // mark as a daemon thread by default. // the caller can explicitly cancel this by doing "setDaemon(false)" setDaemon(true); + setUncaughtExceptionHandler((t, e) -> { + LOGGER.log(SEVERE, "Uncaught exception in PortForwarder thread " + t, e); + try { + socket.close(); + } catch (IOException e1) { + LOGGER.log(SEVERE, "Could not close socket after uncaught exception"); + } + }); } public int getPort() { @@ -70,11 +80,24 @@ public void run() { while(true) { final Socket s = socket.accept(); new Thread("Port forwarding session from "+s.getRemoteSocketAddress()) { + { + setUncaughtExceptionHandler( + (t, e) -> LOGGER.log(Level.SEVERE, "Unhandled exception in port forwarding session " + t, e)); + } public void run() { - try { - final OutputStream out = forwarder.connect(new RemoteOutputStream(SocketChannelStream.out(s))); - new CopyThread("Copier for "+s.getRemoteSocketAddress(), - SocketChannelStream.in(s), out).start(); + try (InputStream in = SocketChannelStream.in(s); + OutputStream out = forwarder.connect(new RemoteOutputStream(SocketChannelStream.out(s)))) { + new CopyThread( + "Copier for " + s.getRemoteSocketAddress(), + in, + out, + () -> { + try { + s.close(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to close socket", e); + } + }).start(); } catch (IOException e) { // this happens if the socket connection is terminated abruptly. LOGGER.log(FINE,"Port forwarding session was shut down abnormally",e); From d5690d7cfcd7c70431332ccaa1f99ad21516c446 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 5 Oct 2017 18:27:24 +0200 Subject: [PATCH 020/243] [maven-release-plugin] prepare release remoting-3.13 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6d5aa6866..12828afc7 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.13-SNAPSHOT + 3.13 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.13 From c051d65c27c91cd524f0a50ad75406e666072df7 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 5 Oct 2017 18:27:32 +0200 Subject: [PATCH 021/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 12828afc7..2fd755ed3 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.13 + 3.14-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.13 + HEAD From 439732f7c7900adce909d3330f13248539dd07ad Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 5 Oct 2017 19:11:40 +0200 Subject: [PATCH 022/243] Changelog: Noting Remoting 3.10.1 and 3.10.2 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6eb17ed4..40bc802a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,32 @@ Remoting build was failing when user name contained metacharacters. * [PR #190](https://github.com/jenkinsci/remoting/pull/190) - Enforce code signing verification when building Remoting with the `release` profile. +##### 3.10.2 + +:exclamation: This is a backport release for Jenkins 2.73.2, +which integrates changes from 3.11 and 3.12. + +* [JENKINS-45755](https://issues.jenkins-ci.org/browse/JENKINS-45755) - +Prevent channel initialization failure when JAR Cache directory is not writable and the channel does not need this cache +(regression in 3.10). +* [JENKINS-45023](https://issues.jenkins-ci.org/browse/JENKINS-45023) - +Prevent execution of `UserRequest`s when the channel is closed or being closed. +It prevents hanging of the channel in some cases. +* [JENKINS-46259](https://issues.jenkins-ci.org/browse/JENKINS-46259) - + Log all linkage errors when executing `UserRequest`s (generic remote operations started from API). +* [JENKINS-45233](https://issues.jenkins-ci.org/browse/JENKINS-45233) - + Log errors when Response message cannot be delivered due to the closed channel. + +Build Flow: + +* [JENKINS-37567](https://issues.jenkins-ci.org/browse/JENKINS-37567) - +Code signing: [@oleg-nenashev](https://github.com/oleg-nenashev) will be releasing Remoting JARs signed with his certificate +for the next 3.10.x releases. + +##### 3.10.1 + +This release is burned. + ##### 3.10 Release date: (Jun 26, 2017) => Jenkins 2.68 From 0a4dfb7c0d968b39a0e5f146c1f1367a55ed8df4 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 5 Oct 2017 19:25:24 +0200 Subject: [PATCH 023/243] Changelog: Noting 3.13 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bc802a9..622b4724f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.13 + +Release date: Oct 05, 2017 + +Improvements: + +* [JENKINS-38711](https://issues.jenkins-ci.org/browse/JENKINS-38711) - +Add uncaught exception handling logic to remoting threads. +Threads now either have failover or proper termination. + +Fixed issues: + +* [JENKINS-47132](https://issues.jenkins-ci.org/browse/JENKINS-47132) - +When an agent is waiting for master to be ready, +the port was not filled in the `Master isnt ready to talk to us on {0}. Will retry again` log message. + ##### 3.12 Release date: Sep 14, 2017 => [Jenkins 2.79](https://jenkins.io/changelog/#v2.79) @@ -62,6 +78,8 @@ Enforce code signing verification when building Remoting with the `release` prof ##### 3.10.2 +Release date: Oct 05, 2017 + :exclamation: This is a backport release for Jenkins 2.73.2, which integrates changes from 3.11 and 3.12. From a450c9e4d9dbd235cdd91af04f53a500a3a55788 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Mon, 16 Oct 2017 10:35:45 +0200 Subject: [PATCH 024/243] [JENKINS-47425] Do not print a stack trace on connection error (#202) * Do not print a stack trace on connection error It is a possible condition and just pollutes the logs * [JENKINS-47425] Do not print a stack trace on remoting connection error It is a possible condition and just pollutes the logs * retry again -> try again --- .../remoting/engine/JnlpAgentEndpointResolver.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 09d3df828..4dfbef50b 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -25,12 +25,15 @@ import hudson.remoting.Base64; import java.io.IOException; +import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.NoRouteToHostException; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; +import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; import java.net.URLConnection; @@ -315,11 +318,14 @@ public void waitForReady() throws InterruptedException { return; } LOGGER.log(Level.INFO, - "Master isn''t ready to talk to us on {0}. Will retry again: response code={1}", + "Master isn''t ready to talk to us on {0}. Will try again: response code={1}", new Object[]{url, con.getResponseCode()}); + } catch (SocketTimeoutException | ConnectException | NoRouteToHostException e) { + LOGGER.log(INFO, "Failed to connect to the master. Will try again: {0} {1}", + new String[] { e.getClass().getName(), e.getMessage() }); } catch (IOException e) { // report the failure - LOGGER.log(INFO, "Failed to connect to the master. Will retry again", e); + LOGGER.log(INFO, "Failed to connect to the master. Will try again", e); } } } finally { From 833838ad6ae1d5a25c9f22f1ff247719d938e74b Mon Sep 17 00:00:00 2001 From: Matthew Ludlum Date: Fri, 20 Oct 2017 17:26:25 -0500 Subject: [PATCH 025/243] Add insecure flag to allow bypassing SSL hostname verification and cert checks --- src/main/java/hudson/remoting/Engine.java | 29 +++++-- src/main/java/hudson/remoting/jnlp/Main.java | 10 ++- .../engine/JnlpAgentEndpointResolver.java | 85 +++++++++++++++++-- 3 files changed, 110 insertions(+), 14 deletions(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 649ac3bde..dbde54be8 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -23,15 +23,11 @@ */ package hudson.remoting; -import edu.umd.cs.findbugs.annotations.Nullable; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.remoting.Channel.Mode; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.PrintStream; import java.net.Socket; import java.net.URL; import java.nio.file.Path; @@ -134,7 +130,6 @@ public void run() { */ @CheckForNull private URL hudsonUrl; - private final String secretKey; public final String slaveName; private String credentials; @@ -145,6 +140,8 @@ public void run() { */ private String tunnel; + private boolean insecure; + private boolean noReconnect; /** @@ -156,6 +153,23 @@ public void run() { + /** + * Determines if JNLPAgentEndpointResolver will not perform certificate validation + * @return + */ + public boolean isInsecure() { + return insecure; + } + + /** + * Sets if JNLPAgentEndpointResolver will not perform certificate validation + * + * @param insecure + */ + + public void setInsecure(boolean insecure) { + this.insecure = insecure; + } @CheckForNull private JarCache jarCache = null; @@ -327,6 +341,8 @@ public void setNoReconnect(boolean noReconnect) { this.noReconnect = noReconnect; } + + /** * Sets the destination for agent logs. * @param agentLog Path to the agent log. @@ -482,6 +498,7 @@ private void innerRun(IOHub hub, SSLContext context, ExecutorService service) { resolver.setTunnel(tunnel); try { resolver.setSslSocketFactory(getSSLSocketFactory()); + resolver.setInsecure(insecure); } catch (Exception e) { events.error(e); } @@ -809,8 +826,8 @@ private SSLSocketFactory getSSLSocketFactory() trustManagerFactory.init(keyStore); // prepare the SSL context SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, trustManagerFactory.getTrustManagers(), null); // now we have our custom socket factory + ctx.init(null, trustManagerFactory.getTrustManagers(), null); sslSocketFactory = ctx.getSocketFactory(); } return sslSocketFactory; diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index 747224bf4..a04a2b7d7 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -27,14 +27,12 @@ import hudson.remoting.FileSystemJarCache; import java.io.ByteArrayInputStream; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.UnsupportedEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.jenkinsci.remoting.engine.WorkDirManager; -import org.jenkinsci.remoting.util.IOUtils; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Argument; @@ -51,7 +49,6 @@ import hudson.remoting.Engine; import hudson.remoting.EngineListener; -import java.nio.file.Path; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -92,6 +89,10 @@ public class Main { usage="If the connection ends, don't retry and just exit.") public boolean noReconnect = false; + @Option(name="-insecure", + usage="Ignore SSL validation errors - use as a last resort only.") + public boolean insecure = false; + @Option(name="-noKeepAlive", usage="Disable TCP socket keep alive on connection to the master.") public boolean noKeepAlive = false; @@ -242,6 +243,9 @@ public Engine createEngine() { engine.setJarCache(new FileSystemJarCache(jarCache,true)); engine.setNoReconnect(noReconnect); engine.setKeepAlive(!noKeepAlive); + LOGGER.log(INFO, "Insecure Status: {0}", insecure); + engine.setInsecure(insecure); + // TODO: ideally logging should be initialized before the "Setting up slave" entry if (agentLog != null) { diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 4dfbef50b..bad55a8ec 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -38,7 +38,10 @@ import java.net.URL; import java.net.URLConnection; import java.security.KeyFactory; +import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; @@ -58,6 +61,12 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import static java.util.logging.Level.INFO; import static org.jenkinsci.remoting.util.ThrowableUtils.chain; @@ -80,6 +89,8 @@ public class JnlpAgentEndpointResolver { private String tunnel; + private boolean insecure; + /** * If specified, only the protocols from the list will be tried during the connection. * The option provides protocol names, but the order of the check is defined internally and cannot be changed. @@ -137,6 +148,25 @@ public void setTunnel(String tunnel) { this.tunnel = tunnel; } + /** + * Determine if certificate checking should be ignored for JNLP endpoint + * + * @return if insecure, endpoint check is ignored + */ + + public boolean isInsecure() { + return insecure; + } + + /** + * Sets if insecure mode of endpoint should be used. + * + * @param insecure + */ + public void setInsecure(boolean insecure) { + this.insecure = insecure; + } + @CheckForNull public JnlpAgentEndpoint resolve() throws IOException { IOException firstError = null; @@ -154,7 +184,7 @@ public JnlpAgentEndpoint resolve() throws IOException { // find out the TCP port HttpURLConnection con = - (HttpURLConnection) openURLConnection(salURL, credentials, proxyCredentials, sslSocketFactory); + (HttpURLConnection) openURLConnection(salURL, credentials, proxyCredentials, sslSocketFactory, insecure); try { try { con.setConnectTimeout(30000); @@ -310,7 +340,7 @@ public void waitForReady() throws InterruptedException { t.setName(oldName + ": trying " + url + " for " + retries + " times"); HttpURLConnection con = - (HttpURLConnection) openURLConnection(url, credentials, proxyCredentials, sslSocketFactory); + (HttpURLConnection) openURLConnection(url, credentials, proxyCredentials, sslSocketFactory, insecure); con.setConnectTimeout(5000); con.setReadTimeout(5000); con.connect(); @@ -331,7 +361,6 @@ public void waitForReady() throws InterruptedException { } finally { t.setName(oldName); } - } @CheckForNull @@ -378,7 +407,7 @@ static InetSocketAddress getResolvedHttpProxyAddress(@Nonnull String host, int p * Credentials can be passed e.g. to support running Jenkins behind a (reverse) proxy requiring authorization */ static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials, - SSLSocketFactory sslSocketFactory) throws IOException { + SSLSocketFactory sslSocketFactory, boolean insecure) throws IOException { String httpProxy = null; // If http.proxyHost property exists, openConnection() uses it. if (System.getProperty("http.proxyHost") == null) { @@ -407,8 +436,54 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy String encoding = Base64.encode(proxyCredentials.getBytes("UTF-8")); con.setRequestProperty("Proxy-Authorization", "Basic " + encoding); } - if (con instanceof HttpsURLConnection && sslSocketFactory != null) { + + if (insecure && con instanceof HttpsURLConnection) { + System.out.println(String.format("Insecure Status: %s", insecure)); + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + + ctx.init(null, new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, + String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, + String authType) { + } + + }}, new SecureRandom()); + sslSocketFactory = ctx.getSocketFactory(); + + HostnameVerifier allHostsValid = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + ((HttpsURLConnection) con).setHostnameVerifier(allHostsValid); + ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); + } catch (KeyManagementException | NoSuchAlgorithmException ex) { + System.err.println(String.format("Error setting insecure; %s", ex.getMessage())); + } + + } + else if (con instanceof HttpsURLConnection && sslSocketFactory != null) { ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); + HostnameVerifier allHostsValid = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + ((HttpsURLConnection) con).setHostnameVerifier(allHostsValid); } return con; } From 39d25e4aac1dbcaf49120341add02369f955fb4e Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 2 Sep 2017 16:56:38 -0700 Subject: [PATCH 026/243] Update Jacoco to 0.7.9 and move it to the profile Jacoc agent is quite heavy, and I see no reason in running it by default in any Remoting build. This change moves the agent to an optional profile. It also picks the latest version, so IntellijIDEA is now able to read and show the `jacoco.exe` report --- pom.xml | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index 2fd755ed3..97f567df7 100644 --- a/pom.xml +++ b/pom.xml @@ -416,25 +416,6 @@ THE SOFTWARE. - - org.jacoco - jacoco-maven-plugin - 0.7.0.201403182114 - - - - prepare-agent - - - - report - prepare-package - - report - - - - org.codehaus.mojo findbugs-maven-plugin @@ -569,5 +550,31 @@ THE SOFTWARE. + + jacoco + + + + org.jacoco + jacoco-maven-plugin + 0.7.9 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + From 328a7666d923fd1d7453057c6da36b349c11d1d0 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 3 Nov 2017 14:14:45 +0100 Subject: [PATCH 027/243] Refactor the implementation and reuse old code created by @stephenc --- src/main/java/hudson/remoting/Engine.java | 42 ++++++----- src/main/java/hudson/remoting/Launcher.java | 35 ++++------ src/main/java/hudson/remoting/jnlp/Main.java | 23 ++++-- .../engine/JnlpAgentEndpointResolver.java | 70 +++++++------------ .../util/https/NoCheckHostnameVerifier.java | 45 ++++++++++++ .../util/https/NoCheckTrustManager.java | 50 +++++++++++++ 6 files changed, 171 insertions(+), 94 deletions(-) create mode 100644 src/main/java/org/jenkinsci/remoting/util/https/NoCheckHostnameVerifier.java create mode 100644 src/main/java/org/jenkinsci/remoting/util/https/NoCheckTrustManager.java diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index dbde54be8..96d1b393d 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -140,7 +140,7 @@ public void run() { */ private String tunnel; - private boolean insecure; + private boolean disableHttpsCertValidation; private boolean noReconnect; @@ -150,26 +150,6 @@ public void run() { * @since 2.62.1 */ private boolean keepAlive = true; - - - - /** - * Determines if JNLPAgentEndpointResolver will not perform certificate validation - * @return - */ - public boolean isInsecure() { - return insecure; - } - - /** - * Sets if JNLPAgentEndpointResolver will not perform certificate validation - * - * @param insecure - */ - - public void setInsecure(boolean insecure) { - this.insecure = insecure; - } @CheckForNull private JarCache jarCache = null; @@ -341,7 +321,25 @@ public void setNoReconnect(boolean noReconnect) { this.noReconnect = noReconnect; } + /** + * Determines if JNLPAgentEndpointResolver will not perform certificate validation in the HTTPs mode. + * + * @return {@code true} if the certificate validation is disabled. + * @since TODO + */ + public boolean isDisableHttpsCertValidation() { + return disableHttpsCertValidation; + } + /** + * Sets if JNLPAgentEndpointResolver will not perform certificate validation in the HTTPs mode. + * + * @param disableHttpsCertValidation {@code true} if the certificate validation is disabled. + * @since TODO + */ + public void setDisableHttpsCertValidation(boolean disableHttpsCertValidation) { + this.disableHttpsCertValidation = disableHttpsCertValidation; + } /** * Sets the destination for agent logs. @@ -498,7 +496,7 @@ private void innerRun(IOHub hub, SSLContext context, ExecutorService service) { resolver.setTunnel(tunnel); try { resolver.setSslSocketFactory(getSSLSocketFactory()); - resolver.setInsecure(insecure); + resolver.setInsecure(disableHttpsCertValidation); } catch (Exception e) { events.error(e); } diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index e55a81cbb..10f6af948 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -41,6 +41,8 @@ import org.jenkinsci.remoting.engine.WorkDirManager; import org.jenkinsci.remoting.util.IOUtils; +import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; +import org.jenkinsci.remoting.util.https.NoCheckTrustManager; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -191,6 +193,12 @@ public void addClasspath(String pathList) throws Exception { "certificate file to read.", forbids = "-noCertificateCheck") public List candidateCertificates; + /** + * Disables HTTPs Certificate validation of the server when using {@link org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver}. + * This option is managed by the {@code -noCertificateCheck} option. + */ + private boolean noCertificateCheck = false; + public InetSocketAddress connectionTarget = null; @Option(name="-connectTo",usage="make a TCP connection to the given host and port, then start communication.",metaVar="HOST:PORT") @@ -212,15 +220,13 @@ public void setConnectTo(String target) { @Option(name="-noCertificateCheck", forbids = "-cert") public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmException, KeyManagementException { System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all."); + + this.noCertificateCheck = true; SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); // bypass host name check, too. - HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { - public boolean verify(String s, SSLSession sslSession) { - return true; - } - }); + HttpsURLConnection.setDefaultHostnameVerifier(new NoCheckHostnameVerifier()); } @Option(name="-noReconnect",usage="Doesn't try to reconnect when a communication fail, and exit instead") @@ -347,6 +353,10 @@ public void run() throws Exception { jnlpArgs.add(c); } } + if (noCertificateCheck) { + //TODO: Rename option? + jnlpArgs.add("-disableHttpsCertValidation"); + } try { hudson.remoting.jnlp.Main._main(jnlpArgs.toArray(new String[jnlpArgs.size()])); } catch (CmdLineException e) { @@ -768,21 +778,6 @@ protected void onDead(Throwable cause) { System.err.println("channel stopped"); } - /** - * {@link X509TrustManager} that performs no check at all. - */ - private static class NoCheckTrustManager implements X509TrustManager { - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } - - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - public static boolean isWindows() { return File.pathSeparatorChar==';'; } diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index a04a2b7d7..5363af69e 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -42,6 +42,8 @@ import java.util.logging.Logger; import java.util.logging.Level; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + import java.util.List; import java.util.ArrayList; import java.net.URL; @@ -89,10 +91,6 @@ public class Main { usage="If the connection ends, don't retry and just exit.") public boolean noReconnect = false; - @Option(name="-insecure", - usage="Ignore SSL validation errors - use as a last resort only.") - public boolean insecure = false; - @Option(name="-noKeepAlive", usage="Disable TCP socket keep alive on connection to the master.") public boolean noKeepAlive = false; @@ -103,6 +101,16 @@ public class Main { "certificate file to read.") public List candidateCertificates; + /** + * Disables HTTPs Certificate validation of the server when using {@link org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver}. + * + * This option is not recommended for production use. + * @since TODO + */ + @Option(name="-disableHttpsCertValidation", + usage="Ignore SSL validation errors - use as a last resort only.") + public boolean disableHttpsCertValidation = false; + /** * Specifies a destination for error logs. * If specified, this option overrides the default destination within {@link #workDir}. @@ -243,8 +251,11 @@ public Engine createEngine() { engine.setJarCache(new FileSystemJarCache(jarCache,true)); engine.setNoReconnect(noReconnect); engine.setKeepAlive(!noKeepAlive); - LOGGER.log(INFO, "Insecure Status: {0}", insecure); - engine.setInsecure(insecure); + + if (disableHttpsCertValidation) { + LOGGER.log(WARNING, "Certificate validation for HTTPs endpoints is disabled"); + } + engine.setDisableHttpsCertValidation(disableHttpsCertValidation); // TODO: ideally logging should be initialized before the "Setting up slave" entry diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index bad55a8ec..b0412f63f 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -24,6 +24,10 @@ package org.jenkinsci.remoting.engine; import hudson.remoting.Base64; +import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; +import org.jenkinsci.remoting.util.https.NoCheckTrustManager; +import sun.net.www.protocol.https.HttpsURLConnectionImpl; + import java.io.IOException; import java.net.ConnectException; import java.net.HttpURLConnection; @@ -151,7 +155,7 @@ public void setTunnel(String tunnel) { /** * Determine if certificate checking should be ignored for JNLP endpoint * - * @return if insecure, endpoint check is ignored + * @return if disableHttpsCertValidation, endpoint check is ignored */ public boolean isInsecure() { @@ -159,7 +163,7 @@ public boolean isInsecure() { } /** - * Sets if insecure mode of endpoint should be used. + * Sets if disableHttpsCertValidation mode of endpoint should be used. * * @param insecure */ @@ -437,53 +441,27 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy con.setRequestProperty("Proxy-Authorization", "Basic " + encoding); } - if (insecure && con instanceof HttpsURLConnection) { - System.out.println(String.format("Insecure Status: %s", insecure)); - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - - ctx.init(null, new TrustManager[]{new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - @Override - public void checkClientTrusted(X509Certificate[] certs, - String authType) { - } + if (con instanceof HttpsURLConnection) { + final HttpsURLConnection httpsConnection = (HttpsURLConnection) con; + if (insecure) { + System.out.println(String.format("Insecure Status: %s", insecure)); - @Override - public void checkServerTrusted(X509Certificate[] certs, - String authType) { - } - - }}, new SecureRandom()); - sslSocketFactory = ctx.getSocketFactory(); - - HostnameVerifier allHostsValid = new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom()); + sslSocketFactory = ctx.getSocketFactory(); + + httpsConnection.setHostnameVerifier(new NoCheckHostnameVerifier()); + httpsConnection.setSSLSocketFactory(sslSocketFactory); + } catch (KeyManagementException | NoSuchAlgorithmException ex) { + System.err.println(String.format("Error setting disableHttpsCertValidation; %s", ex.getMessage())); + } - ((HttpsURLConnection) con).setHostnameVerifier(allHostsValid); - ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); - } catch (KeyManagementException | NoSuchAlgorithmException ex) { - System.err.println(String.format("Error setting insecure; %s", ex.getMessage())); + } else if (sslSocketFactory != null) { + httpsConnection.setSSLSocketFactory(sslSocketFactory); + //FIXME: Is it really required in this path? Seems like a bug + httpsConnection.setHostnameVerifier(new NoCheckHostnameVerifier()); } - - } - else if (con instanceof HttpsURLConnection && sslSocketFactory != null) { - ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); - HostnameVerifier allHostsValid = new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - ((HttpsURLConnection) con).setHostnameVerifier(allHostsValid); } return con; } diff --git a/src/main/java/org/jenkinsci/remoting/util/https/NoCheckHostnameVerifier.java b/src/main/java/org/jenkinsci/remoting/util/https/NoCheckHostnameVerifier.java new file mode 100644 index 000000000..943bb0274 --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/util/https/NoCheckHostnameVerifier.java @@ -0,0 +1,45 @@ +/* + * + * The MIT License + * + * Copyright (c) 2017 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package org.jenkinsci.remoting.util.https; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +/** + * Hostname verifier, which accepts any hostname. + */ +@Restricted(NoExternalUse.class) +public class NoCheckHostnameVerifier implements HostnameVerifier { + + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } +} diff --git a/src/main/java/org/jenkinsci/remoting/util/https/NoCheckTrustManager.java b/src/main/java/org/jenkinsci/remoting/util/https/NoCheckTrustManager.java new file mode 100644 index 000000000..18f9909fa --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/util/https/NoCheckTrustManager.java @@ -0,0 +1,50 @@ +/* + * + * The MIT License + * + * Copyright (c) 2016- CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package org.jenkinsci.remoting.util.https; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * {@link X509TrustManager} that performs no check at all. + */ +@Restricted(NoExternalUse.class) +public class NoCheckTrustManager implements X509TrustManager { + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} From b46db643959c9b4c9837d5406cfaf08cad418e6c Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 7 Nov 2017 21:30:12 +0100 Subject: [PATCH 028/243] [JENKINS-45294] - RemoteInvocationHandler#RPCRequest is now a subject for channel status checks (#209) * [JENKINS-45294] - RemoteInvocationHandler#RPCRequest is now a subject for channel status checks It is same as https://github.com/jenkinsci/remoting/pull/175, but for RPC Requests. The idea is to prevent hanging of RPC Requests in edge cases when the channel goes down. * [JENKINS-45294] - Fix the stupid bug * [JENKINS-45294] - Add direct unit test of hanging RPC Call * [JENKINS-45294] - Address comments from @jglick --- src/main/java/hudson/remoting/Channel.java | 8 +-- .../remoting/ImportedClassLoaderTable.java | 2 +- .../hudson/remoting/RemoteClassLoader.java | 5 +- .../remoting/RemoteInvocationHandler.java | 72 ++++++++++++++++--- .../java/hudson/remoting/ChannelTest.java | 65 ++++++++++++++++- .../java/hudson/remoting/PipeWriterTest.java | 3 +- 6 files changed, 135 insertions(+), 20 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 9b8a8bd0c..be178c77a 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -530,7 +530,7 @@ protected Channel(@Nonnull ChannelBuilder settings, @Nonnull CommandTransport tr if(internalExport(IChannel.class, this, false)!=1) throw new AssertionError(); // export number 1 is reserved for the channel itself - remoteChannel = RemoteInvocationHandler.wrap(this,1,IChannel.class,true,false); + remoteChannel = RemoteInvocationHandler.wrap(this,1,IChannel.class,true,false,false); this.remoteCapability = transport.getRemoteCapability(); this.pipeWriter = new PipeWriter(createPipeWriterExecutor()); @@ -678,7 +678,7 @@ private ExecutorService createPipeWriterExecutor() { */ @Override public T export(Class type, T instance) { - return export(type, instance, true); + return export(type, instance, true, true); } /** @@ -695,7 +695,7 @@ public T export(Class type, T instance) { * {@code null} if the input instance is {@code null}. */ @Nullable - /*package*/ T export(Class type, @CheckForNull T instance, boolean userProxy) { + /*package*/ T export(Class type, @CheckForNull T instance, boolean userProxy, boolean userScope) { if(instance==null) { return null; } @@ -714,7 +714,7 @@ public T export(Class type, T instance) { // either local side will auto-unexport, or the remote side will unexport when it's GC-ed boolean autoUnexportByCaller = exportedObjects.isRecording(); final int id = internalExport(type, instance, autoUnexportByCaller); - return RemoteInvocationHandler.wrap(null, id, type, userProxy, autoUnexportByCaller); + return RemoteInvocationHandler.wrap(null, id, type, userProxy, autoUnexportByCaller, userScope); } /*package*/ int internalExport(Class clazz, T instance) { diff --git a/src/main/java/hudson/remoting/ImportedClassLoaderTable.java b/src/main/java/hudson/remoting/ImportedClassLoaderTable.java index 7fb59acae..0ec50287b 100644 --- a/src/main/java/hudson/remoting/ImportedClassLoaderTable.java +++ b/src/main/java/hudson/remoting/ImportedClassLoaderTable.java @@ -51,7 +51,7 @@ final class ImportedClassLoaderTable { */ @Nonnull public synchronized ClassLoader get(int oid) { - return get(RemoteInvocationHandler.wrap(channel,oid,IClassLoader.class,false,false)); + return get(RemoteInvocationHandler.wrap(channel,oid,IClassLoader.class,false,false,false)); } /** diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index 51f3e0bc8..5d59a35ec 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -740,7 +740,10 @@ public static IClassLoader export(@Nonnull ClassLoader cl, Channel local) { return new RemoteIClassLoader(oid,rcl.proxy); } } - return local.export(IClassLoader.class, new ClassLoaderProxy(cl,local), false); + // Remote classloader operates in the System scope (JENKINS-45294). + // It's probably YOLO, but otherwise the termination calls may be unable + // to execute correctly. + return local.export(IClassLoader.class, new ClassLoaderProxy(cl,local), false, false); } public static void pin(ClassLoader cl, Channel local) { diff --git a/src/main/java/hudson/remoting/RemoteInvocationHandler.java b/src/main/java/hudson/remoting/RemoteInvocationHandler.java index 99a088c15..e553d88df 100644 --- a/src/main/java/hudson/remoting/RemoteInvocationHandler.java +++ b/src/main/java/hudson/remoting/RemoteInvocationHandler.java @@ -53,6 +53,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.jenkinsci.remoting.RoleChecker; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Sits behind a proxy object and implements the proxy logic. @@ -117,27 +119,40 @@ final class RemoteInvocationHandler implements InvocationHandler, Serializable { */ private final Throwable origin; + /** + * Indicates that the handler operates in the user space. + * In such case the requests will be automatically failed if the + * Remoting channel is not fully operational. + */ + private final boolean userSpace; + /** * Creates a proxy that wraps an existing OID on the remote. */ - RemoteInvocationHandler(Channel channel, int id, boolean userProxy, boolean autoUnexportByCaller, Class proxyType) { + RemoteInvocationHandler(Channel channel, int id, boolean userProxy, + boolean autoUnexportByCaller, boolean userSpace, + Class proxyType) { this.channel = channel == null ? null : channel.ref(); this.oid = id; this.userProxy = userProxy; this.origin = new Exception("Proxy "+toString()+" was created for "+proxyType); this.autoUnexportByCaller = autoUnexportByCaller; + this.userSpace = userSpace; } /** * Wraps an OID to the typed wrapper. + * + * @param userProxy If {@code true} (recommended), all commands will be wrapped into {@link UserRequest}s. + * @param userSpace If {@code true} (recommended), the requests will be executed in a user scope */ @Nonnull - public static T wrap(Channel channel, int id, Class type, boolean userProxy, boolean autoUnexportByCaller) { + static T wrap(Channel channel, int id, Class type, boolean userProxy, boolean autoUnexportByCaller, boolean userSpace) { ClassLoader cl = type.getClassLoader(); // if the type is a JDK-defined type, classloader should be for IReadResolve if(cl==null || cl==ClassLoader.getSystemClassLoader()) cl = IReadResolve.class.getClassLoader(); - RemoteInvocationHandler handler = new RemoteInvocationHandler(channel, id, userProxy, autoUnexportByCaller, type); + RemoteInvocationHandler handler = new RemoteInvocationHandler(channel, id, userProxy, autoUnexportByCaller, userSpace, type); if (channel != null) { if (!autoUnexportByCaller) { UNEXPORTER.watch(handler); @@ -253,7 +268,9 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl // delegate the rest of the methods to the remote object boolean async = method.isAnnotationPresent(Asynchronous.class); - RPCRequest req = new RPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null); + RPCRequest req = userSpace + ? new UserRPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null) + : new RPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null); try { if(userProxy) { if (async) channelOrFail().callAsync(req); @@ -825,17 +842,17 @@ private void onChannelTermination(Channel channel) { * The downside of this is that the classes used as a parameter/return value * must be available to both JVMs. * - * If used as {@link Callable} in conjunction with {@link UserRequest}, - * this can be used to send a method call to user-level objects, and - * classes for the parameters and the return value are sent remotely if needed. + * For user-space commands and operations, there is a {@link UserRPCRequest} implementation. + * + * @see UserRPCRequest */ - static final class RPCRequest extends Request implements DelegatingCallable { + static class RPCRequest extends Request implements DelegatingCallable { /** * Target object id to invoke. */ - private final int oid; + protected final int oid; - private final String methodName; + protected final String methodName; /** * Type name of the arguments to invoke. They are names because * neither {@link Method} nor {@link Class} is serializable. @@ -935,6 +952,7 @@ Object[] getArguments() { // for debugging return arguments; } + @Override public String toString() { return "RPCRequest("+oid+","+methodName+")"; } @@ -942,5 +960,39 @@ public String toString() { private static final long serialVersionUID = 1L; } + /** + * User-space version of {@link RPCRequest}. + * + * This is an equivalent of {@link UserRequest} for RPC calls. + * Such kind of requests will not be send over closing or malfunctional channel. + * + * If used as {@link Callable} in conjunction with {@link UserRequest}, + * this can be used to send a method call to user-level objects, and + * classes for the parameters and the return value are sent remotely if needed. + */ + static class UserRPCRequest extends RPCRequest { + public UserRPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { + super(oid, m, arguments, cl); + } + + @Override + public String toString() { + return "UserRPCRequest("+oid+","+methodName+")"; + } + + // Same implementation as UserRequest + @Override + public void checkIfCanBeExecutedOnChannel(Channel channel) throws IOException { + // Default check for all requests + super.checkIfCanBeExecutedOnChannel(channel); + + // We also do not want to run UserRequests when the channel is being closed + if (channel.isClosingOrClosed()) { + throw new ChannelClosedException("The request cannot be executed on channel " + channel + ". " + + "The channel is closing down or has closed down", channel.getCloseRequestCause()); + } + } + } + private static final Object[] EMPTY_ARRAY = new Object[0]; } diff --git a/src/test/java/hudson/remoting/ChannelTest.java b/src/test/java/hudson/remoting/ChannelTest.java index 37a9b0b5a..e47d35662 100644 --- a/src/test/java/hudson/remoting/ChannelTest.java +++ b/src/test/java/hudson/remoting/ChannelTest.java @@ -11,15 +11,22 @@ import java.lang.reflect.Proxy; import java.net.URL; import java.net.URLClassLoader; +import java.nio.channels.ClosedChannelException; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nonnull; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; import org.jenkinsci.remoting.RoleChecker; +import org.jvnet.hudson.test.Issue; +import sun.rmi.runtime.Log; /** * @author Kohsuke Kawaguchi @@ -241,7 +248,57 @@ public void testShouldNotAcceptUserRequestsWhenIsBeingClosed() throws Exception assertFailsWithChannelClosedException(TestRunnable.forUserRequest_callAsync(delayedRequest, testPayload)); } } - + + /** + * Checks if {@link UserRequest}s can be executed during the pending close operation. + * @throws Exception Test Error + */ + @Issue("JENKINS-45294") + public void testShouldNotAcceptUserRPCRequestsWhenIsBeingClosed() throws Exception { + + Collection src = new ArrayList<>(); + src.add("Hello"); + src.add("World"); + + //TODO: System request will just hang. Once JENKINS-44785 is implemented, all system requests + // in Remoting codebase must have a timeout. + final Collection remoteList = channel.call(new RMIObjectExportedCallable<>(src, Collection.class, true)); + + try (ChannelCloseLock lock = new ChannelCloseLock(channel)) { + // Call Async + assertFailsWithChannelClosedException(new TestRunnable() { + @Override + public void run(Channel channel) throws Exception, AssertionError { + remoteList.size(); + } + }); + } + } + + private static class RMIObjectExportedCallable implements Callable { + + private final TInterface object; + private final Class clazz; + private final boolean userSpace; + + RMIObjectExportedCallable(TInterface object, Class clazz, boolean userSpace) { + this.object = object; + this.clazz = clazz; + this.userSpace = userSpace; + } + + @Override + public TInterface call() throws Exception { + // UserProxy is used only for the user space, otherwise it will be wrapped into UserRequest + return Channel.current().export(clazz, object, userSpace, userSpace); + } + + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + + } + } + private static final class NeverEverCallable implements Callable { private static final long serialVersionUID = 1L; @@ -361,11 +418,13 @@ private void assertFailsWithChannelClosedException(TestRunnable call) throws Ass try { call.run(channel); } catch(Exception ex) { - if (ex instanceof ChannelClosedException) { + Logger.getLogger(ChannelTest.class.getName()).log(Level.WARNING, "Call execution failed with exception", ex); + Throwable cause = ex instanceof RemotingSystemException ? ex.getCause() : ex; + if (cause instanceof ChannelClosedException) { // Fine return; } else { - throw new AssertionError("Expected ChannelClosedException, but got another exception", ex); + throw new AssertionError("Expected ChannelClosedException, but got another exception", cause); } } fail("Expected ChannelClosedException, but the call has completed without any exception"); diff --git a/src/test/java/hudson/remoting/PipeWriterTest.java b/src/test/java/hudson/remoting/PipeWriterTest.java index 53f6dbacc..3ce358845 100644 --- a/src/test/java/hudson/remoting/PipeWriterTest.java +++ b/src/test/java/hudson/remoting/PipeWriterTest.java @@ -23,7 +23,8 @@ public class PipeWriterTest extends RmiTestBase implements Serializable, PipeWri @Override protected void setUp() throws Exception { super.setUp(); - checker = channel.export(PipeWriterTestChecker.class, this, false); + // Checker operates using the user-space RMI + checker = channel.export(PipeWriterTestChecker.class, this, false, true); } /** From d906a333f34860dbf3a3f0771b1c1dfd1e071ca6 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 8 Nov 2017 15:34:52 +0100 Subject: [PATCH 029/243] [JENKINS-47714] - Introduce SerializableOnlyOverRemoting and cleanup FindBugs in Channel#current(). (#206) * [JENKINS-47714] - Introduce SerializableOnlyOverRemoting and cleanup FindBugs in Channel#current(). `Channel#current()` uses thread-local storage to determine the current channel. It returns null if the channel does not exist Some writeReplace/readObject/etc. serialization logic retrieves the channel in order to export the object via ExportTable. Obviously, such operations will fail if we try to serialize the object without Remoting context. I propose to add a new interface to verify that serialization logic is being invoked for the remoting context and hence to avoid undesired NPEs. * [JENKINS-47714] - Address comments from @jglick * [JENKINS-47714] - Add @since to new API --- pom.xml | 2 +- src/main/java/hudson/remoting/Callable.java | 1 + src/main/java/hudson/remoting/Channel.java | 23 +++++- .../hudson/remoting/ClassLoaderHolder.java | 8 +- .../java/hudson/remoting/JarLoaderImpl.java | 10 ++- src/main/java/hudson/remoting/Pipe.java | 14 ++-- .../hudson/remoting/ProxyOutputStream.java | 5 +- .../hudson/remoting/RemoteClassLoader.java | 8 +- .../hudson/remoting/RemoteInputStream.java | 11 ++- .../remoting/RemoteInvocationHandler.java | 9 ++- .../hudson/remoting/RemoteOutputStream.java | 12 ++- .../java/hudson/remoting/RemoteWriter.java | 11 ++- src/main/java/hudson/remoting/Request.java | 6 +- .../remoting/forward/ForwarderFactory.java | 9 ++- .../remoting/forward/PortForwarder.java | 2 +- .../SerializableOnlyOverRemoting.java | 73 +++++++++++++++++++ .../hudson/remoting/ChannelFilterTest.java | 2 +- .../java/hudson/remoting/ChannelTest.java | 12 +-- .../java/hudson/remoting/ClassFilterTest.java | 5 +- .../hudson/remoting/ClassRemotingTest.java | 2 +- src/test/java/hudson/remoting/PipeTest.java | 2 +- .../java/hudson/remoting/PrefetchingTest.java | 4 +- .../remoting/RemoteInvocationHandlerTest.java | 10 ++- .../protocol/ProtocolStackImplTest.java | 5 +- 24 files changed, 176 insertions(+), 70 deletions(-) create mode 100644 src/main/java/org/jenkinsci/remoting/SerializableOnlyOverRemoting.java diff --git a/pom.xml b/pom.xml index 97f567df7..b62eefdef 100644 --- a/pom.xml +++ b/pom.xml @@ -190,7 +190,7 @@ THE SOFTWARE. org.kohsuke access-modifier-annotation - 1.7 + 1.12 jar true diff --git a/src/main/java/hudson/remoting/Callable.java b/src/main/java/hudson/remoting/Callable.java index e841c3120..780de6ef4 100644 --- a/src/main/java/hudson/remoting/Callable.java +++ b/src/main/java/hudson/remoting/Callable.java @@ -27,6 +27,7 @@ import java.io.Serializable; +//TODO: Make it SerializableOnlyOverRemoting? /** * Represents computation to be done on a remote system. * diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index be178c77a..d36a215ef 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -1175,7 +1175,7 @@ private static final class SetMaximumBytecodeLevel implements Callable { + @Override public Object call() throws InterruptedException { - Channel.current().syncLocalIO(); + Channel.currentOrFail().syncLocalIO(); return null; } @@ -1716,6 +1717,24 @@ public static Channel current() { return CURRENT.get(); } + /** + * Gets current channel or fails with {@link IllegalStateException}. + * + * @return Current channel + * @throws IllegalStateException the calling thread has no associated channel. + * @since 3.14 + * @see org.jenkinsci.remoting.SerializableOnlyOverRemoting + */ + @Nonnull + public static Channel currentOrFail() throws IllegalStateException { + final Channel ch = CURRENT.get(); + if (ch == null) { + final Thread t = Thread.currentThread(); + throw new IllegalStateException("The calling thread " + t + " has no associated channel"); + } + return ch; + } + // TODO: Unrestrict after the merge into the master. // By now one may use it via the reflection logic only /** diff --git a/src/main/java/hudson/remoting/ClassLoaderHolder.java b/src/main/java/hudson/remoting/ClassLoaderHolder.java index 08dbda8c8..cc51ec429 100644 --- a/src/main/java/hudson/remoting/ClassLoaderHolder.java +++ b/src/main/java/hudson/remoting/ClassLoaderHolder.java @@ -1,11 +1,11 @@ package hudson.remoting; import hudson.remoting.RemoteClassLoader.IClassLoader; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.Serializable; import javax.annotation.CheckForNull; /** @@ -14,7 +14,7 @@ * @author Kohsuke Kawaguchi * @since 2.12 */ -public class ClassLoaderHolder implements Serializable { +public class ClassLoaderHolder implements SerializableOnlyOverRemoting { @CheckForNull private transient ClassLoader classLoader; @@ -37,14 +37,14 @@ public void set(@CheckForNull ClassLoader classLoader) { private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { IClassLoader proxy = (IClassLoader)ois.readObject(); - classLoader = proxy==null ? null : Channel.current().importedClassLoaders.get(proxy); + classLoader = proxy==null ? null : getChannelForSerialization().importedClassLoaders.get(proxy); } private void writeObject(ObjectOutputStream oos) throws IOException { if (classLoader==null) oos.writeObject(null); else { - IClassLoader proxy = RemoteClassLoader.export(classLoader, Channel.current()); + IClassLoader proxy = RemoteClassLoader.export(classLoader, getChannelForSerialization()); oos.writeObject(proxy); } } diff --git a/src/main/java/hudson/remoting/JarLoaderImpl.java b/src/main/java/hudson/remoting/JarLoaderImpl.java index 0f8ba7ed8..5b53c9efd 100644 --- a/src/main/java/hudson/remoting/JarLoaderImpl.java +++ b/src/main/java/hudson/remoting/JarLoaderImpl.java @@ -1,9 +1,11 @@ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + import java.io.File; import java.io.IOException; +import java.io.NotSerializableException; import java.io.OutputStream; -import java.io.Serializable; import java.net.URL; import java.util.Collections; import java.util.HashSet; @@ -17,7 +19,7 @@ * @author Kohsuke Kawaguchi */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("SE_BAD_FIELD") -class JarLoaderImpl implements JarLoader, Serializable { +class JarLoaderImpl implements JarLoader, SerializableOnlyOverRemoting { private final ConcurrentMap knownJars = new ConcurrentHashMap<>(); @edu.umd.cs.findbugs.annotations.SuppressWarnings("DMI_COLLECTION_OF_URLS") // TODO: fix this @@ -71,8 +73,8 @@ public Checksum calcChecksum(URL jar) throws IOException { /** * When sent to the remote node, send a proxy. */ - private Object writeReplace() { - return Channel.current().export(JarLoader.class, this); + private Object writeReplace() throws NotSerializableException { + return getChannelForSerialization().export(JarLoader.class, this); } public static final String DIGEST_ALGORITHM = System.getProperty(JarLoaderImpl.class.getName()+".algorithm","SHA-256"); diff --git a/src/main/java/hudson/remoting/Pipe.java b/src/main/java/hudson/remoting/Pipe.java index b0dbdd62f..c0487b955 100644 --- a/src/main/java/hudson/remoting/Pipe.java +++ b/src/main/java/hudson/remoting/Pipe.java @@ -23,12 +23,13 @@ */ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.io.Serializable; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -96,7 +97,7 @@ * * @author Kohsuke Kawaguchi */ -public final class Pipe implements Serializable, ErrorPropagatingOutputStream { +public final class Pipe implements SerializableOnlyOverRemoting, ErrorPropagatingOutputStream { private InputStream in; private OutputStream out; @@ -149,6 +150,8 @@ public static Pipe createLocalToRemote() { } private void writeObject(ObjectOutputStream oos) throws IOException { + final Channel ch = getChannelForSerialization(); + // TODO: there's a discrepancy in the pipe window size and FastPipedInputStream buffer size. // The former uses 1M, while the latter uses 64K, so if the sender is too fast, it'll cause // the pipe IO thread to block other IO activities. Fix this by first using adaptive growing buffer @@ -156,14 +159,14 @@ private void writeObject(ObjectOutputStream oos) throws IOException { if(in!=null && out==null) { // remote will write to local FastPipedOutputStream pos = new FastPipedOutputStream((FastPipedInputStream)in); - int oid = Channel.current().internalExport(Object.class, pos, false); // this export is unexported in ProxyOutputStream.finalize() + int oid = ch.internalExport(Object.class, pos, false); // this export is unexported in ProxyOutputStream.finalize() oos.writeBoolean(true); // marker oos.writeInt(oid); } else { // remote will read from local this object gets unexported when the pipe is connected. // see ConnectCommand - int oid = Channel.current().internalExport(Object.class, out, false); + int oid = ch.internalExport(Object.class, out, false); oos.writeBoolean(false); oos.writeInt(oid); @@ -171,8 +174,7 @@ private void writeObject(ObjectOutputStream oos) throws IOException { } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - final Channel channel = Channel.current(); - assert channel !=null; + final Channel channel = getChannelForSerialization(); if(ois.readBoolean()) { // local will write to remote diff --git a/src/main/java/hudson/remoting/ProxyOutputStream.java b/src/main/java/hudson/remoting/ProxyOutputStream.java index 506ecf213..4c23f9a8d 100644 --- a/src/main/java/hudson/remoting/ProxyOutputStream.java +++ b/src/main/java/hudson/remoting/ProxyOutputStream.java @@ -23,6 +23,7 @@ */ package hudson.remoting; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -67,14 +68,14 @@ public ProxyOutputStream() { * @param oid * The object id of the exported {@link OutputStream}. */ - public ProxyOutputStream(Channel channel, int oid) throws IOException { + public ProxyOutputStream(@Nonnull Channel channel, int oid) throws IOException { connect(channel,oid); } /** * Connects this stream to the specified remote object. */ - synchronized void connect(Channel channel, int oid) throws IOException { + synchronized void connect(@Nonnull Channel channel, int oid) throws IOException { if(this.channel!=null) throw new IllegalStateException("Cannot connect twice"); if(oid==0) diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index 5d59a35ec..cb9e9f4b5 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Method; import java.net.URL; @@ -46,6 +47,7 @@ import java.util.logging.Logger; import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import javax.annotation.CheckForNull; @@ -1034,7 +1036,7 @@ public String toString() { * to work (which will be the remote instance.) Once transferred to the other side, * resolve back to the instance on the server. */ - private static class RemoteIClassLoader implements IClassLoader, Serializable { + private static class RemoteIClassLoader implements IClassLoader, SerializableOnlyOverRemoting { private transient final IClassLoader proxy; private final int oid; @@ -1079,9 +1081,9 @@ public ResourceFile[] getResources2(String name) throws IOException { return proxy.getResources2(name); } - private Object readResolve() { + private Object readResolve() throws ObjectStreamException { try { - return Channel.current().getExportedObject(oid); + return getChannelForSerialization().getExportedObject(oid); } catch (ExecutionException ex) { //TODO: Implement something better? throw new IllegalStateException("Cannot resolve remoting classloader", ex); diff --git a/src/main/java/hudson/remoting/RemoteInputStream.java b/src/main/java/hudson/remoting/RemoteInputStream.java index ff04155e7..7c11a10ab 100644 --- a/src/main/java/hudson/remoting/RemoteInputStream.java +++ b/src/main/java/hudson/remoting/RemoteInputStream.java @@ -23,11 +23,12 @@ */ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + import java.io.BufferedInputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.io.Serializable; import java.io.ObjectOutputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -47,7 +48,7 @@ * * @author Kohsuke Kawaguchi */ -public class RemoteInputStream extends InputStream implements Serializable { +public class RemoteInputStream extends InputStream implements SerializableOnlyOverRemoting { private static final Logger LOGGER = Logger.getLogger(RemoteInputStream.class.getName()); private transient InputStream core; private boolean autoUnexport; @@ -107,7 +108,7 @@ public RemoteInputStream(InputStream core, Set flags) { } private void writeObject(ObjectOutputStream oos) throws IOException { - Channel ch = Channel.current(); + final Channel ch = getChannelForSerialization(); if (ch.remoteCapability.supportsGreedyRemoteInputStream()) { oos.writeBoolean(greedy); @@ -175,9 +176,7 @@ public void run() { } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - final Channel channel = Channel.current(); - assert channel !=null; - + final Channel channel = getChannelForSerialization(); if (channel.remoteCapability.supportsGreedyRemoteInputStream()) { boolean greedy = ois.readBoolean(); if (greedy) { diff --git a/src/main/java/hudson/remoting/RemoteInvocationHandler.java b/src/main/java/hudson/remoting/RemoteInvocationHandler.java index e553d88df..d67a7dddc 100644 --- a/src/main/java/hudson/remoting/RemoteInvocationHandler.java +++ b/src/main/java/hudson/remoting/RemoteInvocationHandler.java @@ -53,6 +53,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.jenkinsci.remoting.RoleChecker; + import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -61,6 +62,8 @@ * * @author Kohsuke Kawaguchi */ +//TODO: Likely should be serializable over Remoting logic, but this class has protection logic +// Use-cases need to be investigated final class RemoteInvocationHandler implements InvocationHandler, Serializable { /** * Our logger. @@ -246,7 +249,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl if(method.getDeclaringClass()==IReadResolve.class) { // readResolve on the proxy. // if we are going back to where we came from, replace the proxy by the real object - if(goingHome) return Channel.current().getExportedObject(oid); + if(goingHome) return Channel.currentOrFail().getExportedObject(oid); else return proxy; } @@ -887,7 +890,7 @@ public RPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { } public Serializable call() throws Throwable { - return perform(Channel.current()); + return perform(Channel.currentOrFail()); } @Override @@ -903,7 +906,7 @@ public ClassLoader getClassLoader() { return getClass().getClassLoader(); } - protected Serializable perform(Channel channel) throws Throwable { + protected Serializable perform(@Nonnull Channel channel) throws Throwable { Object o = channel.getExportedObject(oid); Class[] clazz = channel.getExportedTypes(oid); try { diff --git a/src/main/java/hudson/remoting/RemoteOutputStream.java b/src/main/java/hudson/remoting/RemoteOutputStream.java index 873c561c9..aa956d110 100644 --- a/src/main/java/hudson/remoting/RemoteOutputStream.java +++ b/src/main/java/hudson/remoting/RemoteOutputStream.java @@ -23,11 +23,12 @@ */ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.io.Serializable; /** * {@link OutputStream} that can be sent over to the remote {@link Channel}, @@ -62,7 +63,7 @@ * @see RemoteInputStream * @author Kohsuke Kawaguchi */ -public final class RemoteOutputStream extends OutputStream implements Serializable { +public final class RemoteOutputStream extends OutputStream implements SerializableOnlyOverRemoting { /** * On local machine, this points to the {@link OutputStream} where * the data will be sent ultimately. @@ -79,15 +80,12 @@ public RemoteOutputStream(OutputStream core) { } private void writeObject(ObjectOutputStream oos) throws IOException { - int id = Channel.current().internalExport(OutputStream.class, core, false); // this export is unexported in ProxyOutputStream.finalize() + int id = getChannelForSerialization().internalExport(OutputStream.class, core, false); // this export is unexported in ProxyOutputStream.finalize() oos.writeInt(id); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - final Channel channel = Channel.current(); - assert channel !=null; - - this.core = new ProxyOutputStream(channel, ois.readInt()); + this.core = new ProxyOutputStream(getChannelForSerialization(), ois.readInt()); } private static final long serialVersionUID = 1L; diff --git a/src/main/java/hudson/remoting/RemoteWriter.java b/src/main/java/hudson/remoting/RemoteWriter.java index 07343ecc0..99920f916 100644 --- a/src/main/java/hudson/remoting/RemoteWriter.java +++ b/src/main/java/hudson/remoting/RemoteWriter.java @@ -23,10 +23,11 @@ */ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.Serializable; import java.io.Writer; /** @@ -48,7 +49,7 @@ * @see RemoteInputStream * @author Kohsuke Kawaguchi */ -public final class RemoteWriter extends Writer implements Serializable { +public final class RemoteWriter extends Writer implements SerializableOnlyOverRemoting { /** * On local machine, this points to the {@link Writer} where * the data will be sent ultimately. @@ -63,14 +64,12 @@ public RemoteWriter(Writer core) { } private void writeObject(ObjectOutputStream oos) throws IOException { - int id = Channel.current().internalExport(Writer.class, core, false); // this export is unexported in ProxyWriter.finalize() + int id = getChannelForSerialization().internalExport(Writer.class, core, false); // this export is unexported in ProxyWriter.finalize() oos.writeInt(id); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - final Channel channel = Channel.current(); - assert channel !=null; - + final Channel channel = getChannelForSerialization(); this.core = new ProxyWriter(channel, ois.readInt()); } diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 70575933d..f19a64335 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Request/response pattern over {@link Channel}, the layer-1 service. @@ -51,7 +52,7 @@ abstract class Request extends C * * @param channel * The local channel. From the view point of the JVM that - * {@link #call(Channel) made the call}, this channel is + * {@link #call(Channel)} made the call, this channel is * the remote channel. * @return * the return value will be sent back to the calling process. @@ -59,7 +60,8 @@ abstract class Request extends C * The exception will be forwarded to the calling process. * If no checked exception is supposed to be thrown, use {@link RuntimeException}. */ - protected abstract RSP perform(Channel channel) throws EXC; + @Nullable + protected abstract RSP perform(@Nonnull Channel channel) throws EXC; /** * Uniquely identifies this request. diff --git a/src/main/java/hudson/remoting/forward/ForwarderFactory.java b/src/main/java/hudson/remoting/forward/ForwarderFactory.java index 77cbefbcc..95e8b3fa4 100644 --- a/src/main/java/hudson/remoting/forward/ForwarderFactory.java +++ b/src/main/java/hudson/remoting/forward/ForwarderFactory.java @@ -24,15 +24,16 @@ package hudson.remoting.forward; import hudson.remoting.Callable; -import hudson.remoting.Channel; import hudson.remoting.RemoteOutputStream; import hudson.remoting.SocketChannelStream; import hudson.remoting.VirtualChannel; import org.jenkinsci.remoting.Role; import org.jenkinsci.remoting.RoleChecker; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectStreamException; import java.io.OutputStream; import java.net.Socket; import java.util.logging.Level; @@ -69,7 +70,7 @@ public static Forwarder create(String remoteHost, int remotePort) { return new ForwarderImpl(remoteHost,remotePort); } - private static class ForwarderImpl implements Forwarder { + private static class ForwarderImpl implements Forwarder, SerializableOnlyOverRemoting { private final String remoteHost; private final int remotePort; @@ -99,8 +100,8 @@ public OutputStream connect(OutputStream out) throws IOException { /** * When sent to the remote node, send a proxy. */ - private Object writeReplace() { - return Channel.current().export(Forwarder.class, this); + private Object writeReplace() throws ObjectStreamException { + return getChannelForSerialization().export(Forwarder.class, this); } private static final long serialVersionUID = 8382509901649461466L; diff --git a/src/main/java/hudson/remoting/forward/PortForwarder.java b/src/main/java/hudson/remoting/forward/PortForwarder.java index 33e40d8cf..a1a7ef209 100644 --- a/src/main/java/hudson/remoting/forward/PortForwarder.java +++ b/src/main/java/hudson/remoting/forward/PortForwarder.java @@ -136,7 +136,7 @@ public static ListeningPort create(VirtualChannel ch, final int acceptingPort, F public ListeningPort call() throws IOException { PortForwarder t = new PortForwarder(acceptingPort, proxy); t.start(); - return Channel.current().export(ListeningPort.class,t); + return Channel.currentOrFail().export(ListeningPort.class,t); } @Override diff --git a/src/main/java/org/jenkinsci/remoting/SerializableOnlyOverRemoting.java b/src/main/java/org/jenkinsci/remoting/SerializableOnlyOverRemoting.java new file mode 100644 index 000000000..037f62820 --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/SerializableOnlyOverRemoting.java @@ -0,0 +1,73 @@ +/* + * + * The MIT License + * + * Copyright (c) 2017 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package org.jenkinsci.remoting; + +import hudson.remoting.Channel; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.ProtectedExternally; + +import javax.annotation.Nonnull; +import java.io.NotSerializableException; +import java.io.Serializable; + +/** + * This interface indicates objects which are {@link Serializable} only for sending over the Remoting {@link Channel}. + * + * Usually it means that the object requires export of the class via {@link Channel} + * and {@code hudson.remoting.ExportTable}. + * Attempts to serialize the instance of this interface for different purposes lead to undefined behavior. + * + * @author Oleg Nenashev + * @since 3.14 + */ +public interface SerializableOnlyOverRemoting extends Serializable { + + /** + * Gets current channel or fails with {@link NotSerializableException}. + * + * This method is designed for serialization/deserialization methods in the channel. + * @return Current channel + * @throws NotSerializableException the calling thread has no associated channel. + * In such case the object cannot be serialized. + */ + @Nonnull + @Restricted(ProtectedExternally.class) + default Channel getChannelForSerialization() throws NotSerializableException { + final Channel ch = Channel.current(); + if (ch == null) { + // This logic does not prevent from improperly serializing objects within Remoting calls. + // If it happens in API calls in external usages, we wish good luck with diagnosing Remoting issues + // and leaks in ExportTable. + //TODO: maybe there is a way to actually diagnose this case? + final Thread t = Thread.currentThread(); + throw new NotSerializableException("The calling thread " + t + " has no associated channel. " + + "The current object " + this + " is " + SerializableOnlyOverRemoting.class + + ", but it is likely being serialized/deserialized without the channel"); + } + return ch; + } +} diff --git a/src/test/java/hudson/remoting/ChannelFilterTest.java b/src/test/java/hudson/remoting/ChannelFilterTest.java index 657becddd..be2ef7ef3 100644 --- a/src/test/java/hudson/remoting/ChannelFilterTest.java +++ b/src/test/java/hudson/remoting/ChannelFilterTest.java @@ -86,7 +86,7 @@ public String call() { static class ReverseGunImporter extends CallableBase { public String call() throws Exception { - return Channel.current().call(new GunImporter()); + return Channel.currentOrFail().call(new GunImporter()); } } } diff --git a/src/test/java/hudson/remoting/ChannelTest.java b/src/test/java/hudson/remoting/ChannelTest.java index e47d35662..ac52eeffc 100644 --- a/src/test/java/hudson/remoting/ChannelTest.java +++ b/src/test/java/hudson/remoting/ChannelTest.java @@ -1,14 +1,14 @@ package hudson.remoting; import hudson.remoting.util.GCTask; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import org.jvnet.hudson.test.Bug; import java.io.IOException; import java.io.ObjectInputStream; +import java.io.ObjectStreamException; import java.io.PrintWriter; -import java.io.Serializable; import java.io.StringWriter; -import java.lang.reflect.Proxy; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.ClosedChannelException; @@ -130,7 +130,7 @@ public void testWaitForRemoteProperty() throws Exception { private static class WaitForRemotePropertyCallable extends CallableBase { public Void call() throws Exception { Thread.sleep(500); - Channel.current().setProperty("foo","bar"); + Channel.currentOrFail().setProperty("foo","bar"); return null; } } @@ -139,14 +139,14 @@ public interface Greeter { void greet(String name); } - private static class GreeterImpl implements Greeter, Serializable { + private static class GreeterImpl implements Greeter, SerializableOnlyOverRemoting { String name; public void greet(String name) { this.name = name; } - private Object writeReplace() { - return Channel.current().export(Greeter.class,this); + private Object writeReplace() throws ObjectStreamException { + return getChannelForSerialization().export(Greeter.class,this); } } diff --git a/src/test/java/hudson/remoting/ClassFilterTest.java b/src/test/java/hudson/remoting/ClassFilterTest.java index 08f9845a5..c59fd0006 100644 --- a/src/test/java/hudson/remoting/ClassFilterTest.java +++ b/src/test/java/hudson/remoting/ClassFilterTest.java @@ -2,6 +2,7 @@ import hudson.remoting.Channel.Mode; import hudson.remoting.CommandTransport.CommandReceiver; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import org.jenkinsci.remoting.nio.NioChannelBuilder; import org.junit.After; import org.junit.Test; @@ -250,7 +251,7 @@ private String toString(Throwable t) { * An attack payload that leaves a trace on the receiver side if it gets read from the stream. * Extends from {@link Command} to be able to test command stream. */ - static class Security218 extends Command implements Serializable { + static class Security218 extends Command implements SerializableOnlyOverRemoting { private final String attack; public Security218(String attack) { @@ -259,7 +260,7 @@ public Security218(String attack) { private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); - System.setProperty("attack", attack + ">" + Channel.current().getName()); + System.setProperty("attack", attack + ">" + getChannelForSerialization().getName()); } @Override diff --git a/src/test/java/hudson/remoting/ClassRemotingTest.java b/src/test/java/hudson/remoting/ClassRemotingTest.java index 447bf02c3..76d7395d4 100644 --- a/src/test/java/hudson/remoting/ClassRemotingTest.java +++ b/src/test/java/hudson/remoting/ClassRemotingTest.java @@ -204,7 +204,7 @@ public static Test suite() throws Exception { private static class RemotePropertyVerifier extends CallableBase { public Object call() throws IOException { - Object o = Channel.current().getRemoteProperty("test"); + Object o = Channel.currentOrFail().getRemoteProperty("test"); assertEquals(o.getClass().getName(), CLASSNAME); assertTrue(Channel.class.getClassLoader() != o.getClass().getClassLoader()); assertTrue(o.getClass().getClassLoader() instanceof RemoteClassLoader); diff --git a/src/test/java/hudson/remoting/PipeTest.java b/src/test/java/hudson/remoting/PipeTest.java index 7d7b118ae..202064cbf 100644 --- a/src/test/java/hudson/remoting/PipeTest.java +++ b/src/test/java/hudson/remoting/PipeTest.java @@ -197,7 +197,7 @@ public CreateSaturationTestProxy(Pipe pipe) { } public ISaturationTest call() throws IOException { - return Channel.current().export(ISaturationTest.class, new ISaturationTest() { + return Channel.currentOrFail().export(ISaturationTest.class, new ISaturationTest() { private InputStream in; public void ensureConnected() throws IOException { in = pipe.getIn(); diff --git a/src/test/java/hudson/remoting/PrefetchingTest.java b/src/test/java/hudson/remoting/PrefetchingTest.java index f6faacc7e..902cf00b4 100644 --- a/src/test/java/hudson/remoting/PrefetchingTest.java +++ b/src/test/java/hudson/remoting/PrefetchingTest.java @@ -47,7 +47,7 @@ protected void setUp() throws Exception { channel.setJarCache(new FileSystemJarCache(dir, true)); channel.call(new CallableBase() { public Void call() throws IOException { - Channel.current().setJarCache(new FileSystemJarCache(dir, true)); + Channel.currentOrFail().setJarCache(new FileSystemJarCache(dir, true)); return null; } }); @@ -214,7 +214,7 @@ private ForceJarLoad(Checksum sum) { public Void call() throws IOException { try { - Channel ch = Channel.current(); + final Channel ch = Channel.currentOrFail(); final JarCache jarCache = ch.getJarCache(); if (jarCache == null) { throw new IOException("Cannot Force JAR load, JAR cache is disabled"); diff --git a/src/test/java/hudson/remoting/RemoteInvocationHandlerTest.java b/src/test/java/hudson/remoting/RemoteInvocationHandlerTest.java index b9eea6c4a..5d9ae79a6 100644 --- a/src/test/java/hudson/remoting/RemoteInvocationHandlerTest.java +++ b/src/test/java/hudson/remoting/RemoteInvocationHandlerTest.java @@ -1,5 +1,8 @@ package hudson.remoting; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; + +import java.io.ObjectStreamException; import java.io.Serializable; public class RemoteInvocationHandlerTest extends RmiTestBase { @@ -34,7 +37,7 @@ public interface Contract2 { void meth2(String arg); } - private static class Impl implements Contract, Serializable, Contract2 { + private static class Impl implements Contract, SerializableOnlyOverRemoting, Contract2 { String arg; public void meth(String arg1, String arg2) { assert false : "should be ignored"; @@ -45,8 +48,9 @@ public void meth(String arg1) { public void meth2(String arg) { this.arg = arg; } - private Object writeReplace() { - return Channel.current().export(Contract.class, this); + + private Object writeReplace() throws ObjectStreamException { + return getChannelForSerialization().export(Contract.class, this); } } diff --git a/src/test/java/org/jenkinsci/remoting/protocol/ProtocolStackImplTest.java b/src/test/java/org/jenkinsci/remoting/protocol/ProtocolStackImplTest.java index 4a48b320c..7e37cfae0 100644 --- a/src/test/java/org/jenkinsci/remoting/protocol/ProtocolStackImplTest.java +++ b/src/test/java/org/jenkinsci/remoting/protocol/ProtocolStackImplTest.java @@ -51,7 +51,6 @@ import java.nio.channels.SocketChannel; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; import javax.net.ssl.SSLEngine; import org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer; import org.junit.ClassRule; @@ -1176,7 +1175,7 @@ private static class ProbeCallable implements Callable { @Override public String call() throws IOException { - System.out.println("Hello from: " + Channel.current()); + System.out.println("Hello from: " + Channel.currentOrFail()); return null; } @@ -1194,7 +1193,7 @@ public CreateSaturationTestProxy(hudson.remoting.Pipe pipe) { } public ISaturationTest call() throws IOException { - return Channel.current().export(ISaturationTest.class, new ISaturationTest() { + return Channel.currentOrFail().export(ISaturationTest.class, new ISaturationTest() { private InputStream in; public void ensureConnected() throws IOException { From 49c67eef8616c7bc3588263d4ecc2dbcb51d5bb8 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 8 Nov 2017 18:24:30 +0100 Subject: [PATCH 030/243] [JENKINS-37566] - FindBugs Cleanup. Part #1 (#109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [JENKINS-37566] - FindBugs: Unclosed stream in hudson.remoting.Capability * [JENKINS-37566] - FindBugs: Stream not closed on Exception path in InitializeJarCacheMain#copyFile * [JENKINS-37566] - Fix Util#makeResource and remove obsolete hack * Extra Util#makeResource() polishing * Deprecate obsolete Util#mkdirs() * Exceptional case in setLastModifiedTime * Handle exception case during temp file deletion in FileSystemJarCache * Synchronize ProxyWriter#closed in write() * Synchronization of ProxyWriter#channel * Get rid of the obsolete collection in Channel#properties, fix synchronization * Checksum#calculateFor() - no need to put if absent since there is a check above * UWF_UNWRITTEN_FIELD in ExportTable$Entry * NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE in JnlpAgentEndpointResolver. Now we use a more agressive check * UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR in org.jenkinsci.remoting.nio.NioChannelHub$NioTransport.abort * UPM_UNCALLED_PRIVATE_METHOD - hudson.remoting.ChunkedOutputStream.frameSize() * FindBugs: Revert the stream closure at emoting Capability, just a design to be reworked * Unrealistic NP_NULL_ON_SOME_PATH in NioChannelHub * EI_EXPOSE_REP in DiagnosedStreamCorruptionException (diagnostics code) * SE_TRANSIENT_FIELD_NOT_RESTORED in PreloadJarTask, RemoteClassLoader, RemoteInvocationHandler and UserRequest * Leftover false-positive UG_SYNC_SET_UNSYNC_GET in Channel * EI_EXPOSE_REP in ChannelBuilder#properties * Suppress DMI_NONSERIALIZABLE_OBJECT_WRITTEN in ClassLoaderHolder#writeObject() * Better handling of Streams in hudson.remoting.Capability (still weird) * Fix the Util#makeResource() behavior for nested folders * FileSystemJarCache: tmp file may be renamed * JENKINS-37566 - Modification of Channel#dumpDiagnostics() actually is not required after the merge of #122 * Disable DP_DO_INSIDE_DO_PRIVILEGED as per feeback from @kohsue in #118 * Fix the FindBugs filter * [NP_NULL_ON_SOME_PATH] - Prevent NPE in ExportTable#diagnoseInvalidObjectId() * [RV_RETURN_VALUE_IGNORED_BAD_PRACTICE] - Propagate exceptions when FileSystemJarCache#retrieve() fails to retrieve the target * [DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED] - Ignore the warning as per discussion in #118 * [VO_VOLATILE_INCREMENT] - Suppress warning in Channel#send() * FindBugs - Which#jarFile() should not suppress all exceptions * FindBugs - Suppress URF_UNREAD_FIELD in Pipewindow#Real * [JENKINS-37566] - Channel#properties should be a ConcurrentHashMap Reason - prevent possible delays and deadlocks in the getter method. Addresses the feedback from @stephenc * [JENKINS-37566] - DiagnosedStreamCorruptionException readAhead/readBack handlers should use a more efficient clone() command * [JENKINS-37566] - Suppress UG_SYNC_SET_UNSYNC_GET after switching to the ConcurrentHashMap * [JENKINS-37566] - hudson.remoting.Util should be tolerant against InvalidPathException * [JENKINS-37566] - make ProxyWriter more asynchronous (follow-up to @stephenc’s review) * [JENKINS-37566] - Add a comment about race condition as suggested by @stephenc --- src/findbugs/excludeFilter.xml | 16 +++- .../AbstractByteArrayCommandTransport.java | 3 +- src/main/java/hudson/remoting/Capability.java | 26 +++-- src/main/java/hudson/remoting/Channel.java | 23 ++++- .../java/hudson/remoting/ChannelBuilder.java | 3 +- .../hudson/remoting/ChunkedOutputStream.java | 4 - .../hudson/remoting/ClassLoaderHolder.java | 3 + .../DiagnosedStreamCorruptionException.java | 18 +++- .../java/hudson/remoting/ExportTable.java | 16 +++- .../hudson/remoting/FileSystemJarCache.java | 13 ++- .../remoting/InitializeJarCacheMain.java | 13 +-- src/main/java/hudson/remoting/PipeWindow.java | 3 + .../java/hudson/remoting/PreloadJarTask.java | 1 + .../java/hudson/remoting/ProxyWriter.java | 96 +++++++++++++++---- .../hudson/remoting/RemoteClassLoader.java | 2 + .../remoting/RemoteInvocationHandler.java | 3 + .../java/hudson/remoting/UserRequest.java | 2 + src/main/java/hudson/remoting/Util.java | 73 +++++--------- src/main/java/hudson/remoting/Which.java | 3 +- .../engine/JnlpAgentEndpointResolver.java | 14 ++- .../jenkinsci/remoting/nio/NioChannelHub.java | 15 ++- 21 files changed, 227 insertions(+), 123 deletions(-) diff --git a/src/findbugs/excludeFilter.xml b/src/findbugs/excludeFilter.xml index 6015b02a4..6f349ad07 100644 --- a/src/findbugs/excludeFilter.xml +++ b/src/findbugs/excludeFilter.xml @@ -8,13 +8,19 @@ - - - - + + - + + + + + + + + + 1.4 From a6316a4b30716e44dafa7a307ac2f18ea93f0ea4 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 14:15:40 +0100 Subject: [PATCH 075/243] Changelog: Noting 3.15 (#245) * Changelog: Noting 3.15 * Changelog: Fix typo noticed by @rysteboe * Changelog: Fix the release date --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da171330..48e62f0d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,61 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.15 + +Release date: Dec 22, 2017 + +Enhancements: + +* [JENKINS-48133](https://issues.jenkins-ci.org/browse/JENKINS-48133) - +Channel exceptions now record the channel name and other information when possible +* [PR #210](https://github.com/jenkinsci/remoting/pull/210) - +Allow disabling HTTPs certificate validation of JNLP endpoint when starting Remoting + * **WARNING**: This option undermines the HTTPs security and opens the connection to MiTM attacks + Use it at your own risk +* [JENKINS-48055](https://issues.jenkins-ci.org/browse/JENKINS-48055) - +API: Introduce new `getChannelOrFail()` and `getOpenChannelOrFail()` methods in +[hudson.remoting.Callable](http://javadoc.jenkins.io/component/remoting/hudson/remoting/Callable.html). +* [JENKINS-37566](https://issues.jenkins-ci.org/browse/JENKINS-37566) - +API: `Channel#current()` now explicitly requires checking for `null`. +* [PR #227](https://github.com/jenkinsci/remoting/pull/227) - +API: Deprecate and restrict the [JNLP3-connnect protocol](docs/protocols.md) codebase + +Fixed issues: + +* [JENKINS-48309](https://issues.jenkins-ci.org/browse/JENKINS-48309) - +Prevent timeout in `AsyncFutureImpl#get(timeout)` when a spurious thread wakeup happens +before the timeout expiration. + * The issue also impacts [FutureImpl](http://javadoc.jenkins.io/hudson/model/queue/FutureImpl.html) in the Jenkins core +* [JENKINS-47965](https://issues.jenkins-ci.org/browse/JENKINS-47965) - +Prevent infinite hanging of JNLP4 `IOHub` selector threads when `IOHub` does not get closed properly + * Affected [protocols](docs/protocols.md): JNLP4 only +* [JENKINS-48130](https://issues.jenkins-ci.org/browse/JENKINS-48130) - +Prevent fatal failure of `NIOChannelHub` when an underlying executor service rejects a task execution. +After the change such failure terminates only a single channel + * Affected [protocols](docs/protocols.md): JNLP, JNLP2, CLI and CLI2. JNLP4 is not affected + * The change also improves diagnostics of `RejectedExecutionException` in other execution services +* [JENKINS-37670](https://issues.jenkins-ci.org/browse/JENKINS-37670) - +Throw the standard `UnsupportedClassVersionError` in `RemoteClassLoader` +when the bytecode is not supported. +* [JENKINS-37566](https://issues.jenkins-ci.org/browse/JENKINS-37566) - +Cleanup all issues reported by FindBugs. Notable issues: + * Prevent infinite hanging of `Channel#waitForProperty()` when the channel hangs in the closing state. + * Prevent `NullPointerException`s in `Command#createdAt` handling logic and API + * Prevent serialization of `Callable`s in `NioChannelHub` selectors (JNLP1 and JNLP2 protocols) +* [JENKINS-46724](https://issues.jenkins-ci.org/browse/JENKINS-46724) - +Remove obsolete reflection calls in `RemoteClassloader` and `Launcher#checkTty()` +* [PR #234](https://github.com/jenkinsci/remoting/pull/234) - +`hudson.remoting.Capability` preamble initialization cannot longer throw exceptions + +Build flow: + +* [JENKINS-38696](https://issues.jenkins-ci.org/browse/JENKINS-38696) - +Fix Windows tests and enable them in the pull request builder +* [JENKINS-37566](https://issues.jenkins-ci.org/browse/JENKINS-37566) - +Enforce FindBugs in the pull request builder + + ##### 3.14 Release date: Nov 10, 2017 From 27ba49a885cccb4d659abf564df9099e151f76fc Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 14:18:04 +0100 Subject: [PATCH 076/243] README: Add a link to Remoting Javadoc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 783d7d788..4547613df 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Previous versions: Developer documentation: * [Contributing](CONTRIBUTING.md) +* [Javadoc](http://javadoc.jenkins.io/component/remoting/) * [Channel Termination Process](docs/close.md) ### Reporting issues From 71b365f715498bad5fecf0a4c50d3bdf5a856ff6 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 14:23:12 +0100 Subject: [PATCH 077/243] [maven-release-plugin] prepare release remoting-3.15 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c7f6732a0..7186f20ce 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-SNAPSHOT + 3.15 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.15 From cd5b2987d1519ab8772c5533d58831fefdc93c00 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 14:23:20 +0100 Subject: [PATCH 078/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7186f20ce..609f741a7 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15 + 3.16-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.15 + HEAD From 7f7c94f2a532dd70761dddf4bc027f9fd81766a6 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 17:56:57 +0100 Subject: [PATCH 079/243] Changelog 3.15: Fix typose noticed by @jglick --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48e62f0d9..286d803fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Enhancements: Channel exceptions now record the channel name and other information when possible * [PR #210](https://github.com/jenkinsci/remoting/pull/210) - Allow disabling HTTPs certificate validation of JNLP endpoint when starting Remoting - * **WARNING**: This option undermines the HTTPs security and opens the connection to MiTM attacks + * **WARNING**: This option undermines the HTTPs security and opens the connection to MiTM attacks. Use it at your own risk * [JENKINS-48055](https://issues.jenkins-ci.org/browse/JENKINS-48055) - API: Introduce new `getChannelOrFail()` and `getOpenChannelOrFail()` methods in @@ -24,7 +24,7 @@ API: Introduce new `getChannelOrFail()` and `getOpenChannelOrFail()` methods in * [JENKINS-37566](https://issues.jenkins-ci.org/browse/JENKINS-37566) - API: `Channel#current()` now explicitly requires checking for `null`. * [PR #227](https://github.com/jenkinsci/remoting/pull/227) - -API: Deprecate and restrict the [JNLP3-connnect protocol](docs/protocols.md) codebase +API: Deprecate and restrict the obsolete [JNLP3 protocol](docs/protocols.md) utility classes Fixed issues: @@ -44,7 +44,7 @@ After the change such failure terminates only a single channel Throw the standard `UnsupportedClassVersionError` in `RemoteClassLoader` when the bytecode is not supported. * [JENKINS-37566](https://issues.jenkins-ci.org/browse/JENKINS-37566) - -Cleanup all issues reported by FindBugs. Notable issues: +Clean up all issues reported by FindBugs. Notable issues: * Prevent infinite hanging of `Channel#waitForProperty()` when the channel hangs in the closing state. * Prevent `NullPointerException`s in `Command#createdAt` handling logic and API * Prevent serialization of `Callable`s in `NioChannelHub` selectors (JNLP1 and JNLP2 protocols) From 6a528ddbf1117b0264641868c6e638e260820351 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 18:14:04 +0100 Subject: [PATCH 080/243] Changelog: Update Jenkins Core versions --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 286d803fd..89e99e9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.15 -Release date: Dec 22, 2017 +Release date: Dec 22, 2017 => Jenkins 2.98 Enhancements: @@ -63,7 +63,7 @@ Enforce FindBugs in the pull request builder ##### 3.14 -Release date: Nov 10, 2017 +Release date: Nov 10, 2017 => [Jenkins 2.90](https://jenkins.io/changelog/#v2.90) Fixed issues: @@ -89,7 +89,7 @@ Jacoco does not longer run by default in the build, `jacoco` profile should be u Update Jacoco version to make the reports compatible with Jenkins [Jacoco Plugin](https://plugins.jenkins.io/jacoco). -##### 3.13 +##### 3.13 => [Jenkins 2.85](https://jenkins.io/changelog/#v2.85) Release date: Oct 05, 2017 @@ -105,7 +105,7 @@ Fixed issues: When an agent is waiting for master to be ready, the port was not filled in the `Master isnt ready to talk to us on {0}. Will retry again` log message. -##### 3.12 +##### 3.12 => [Jenkins 2.79](https://jenkins.io/changelog/#v2.79) Release date: Sep 14, 2017 => [Jenkins 2.79](https://jenkins.io/changelog/#v2.79) From 3ce2e9866bdcb74536cffe4f86a96513982e2000 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Tue, 26 Dec 2017 03:00:10 -0500 Subject: [PATCH 081/243] [JENKINS-48686] Switch to calling the agent 'agent' (#244) --- src/main/java/hudson/remoting/Engine.java | 6 +++--- .../hudson/remoting/InitializeJarCacheMain.java | 2 +- src/main/java/hudson/remoting/Launcher.java | 16 ++++++++-------- src/main/java/hudson/remoting/jnlp/Main.java | 14 +++++++------- .../java/hudson/remoting/jnlp/MainDialog.java | 6 +++--- src/main/java/hudson/remoting/package-info.java | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 08ff6dfad..2231bf5b4 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -76,7 +76,7 @@ import org.jenkinsci.remoting.util.KeyUtils; /** - * Slave agent engine that proactively connects to Jenkins master. + * Agent engine that proactively connects to Jenkins master. * * @author Kohsuke Kawaguchi */ @@ -122,7 +122,7 @@ public void run() { private List candidateCertificates; /** - * URL that points to Jenkins's tcp slave agent listener, like http://myhost/hudson/ + * URL that points to Jenkins's tcp agent listener, like http://myhost/hudson/ * *

* This value is determined from {@link #candidateUrls} after a successful connection. @@ -672,7 +672,7 @@ private void onConnectionRejected(String greeting) throws InterruptedException { } /** - * Connects to TCP slave host:port, with a few retries. + * Connects to TCP agent host:port, with a few retries. * @param endpoint Connection endpoint * @throws IOException Connection failure or invalid parameter specification */ diff --git a/src/main/java/hudson/remoting/InitializeJarCacheMain.java b/src/main/java/hudson/remoting/InitializeJarCacheMain.java index 93e0a1314..66f60a4c1 100644 --- a/src/main/java/hudson/remoting/InitializeJarCacheMain.java +++ b/src/main/java/hudson/remoting/InitializeJarCacheMain.java @@ -34,7 +34,7 @@ public boolean accept(File dir, String name) { public static void main(String[] argv) throws Exception { if (argv.length != 2) { throw new IllegalArgumentException( - "Usage: java -cp slave.jar hudson.remoting.InitializeJarCacheMain " + + "Usage: java -cp agent.jar hudson.remoting.InitializeJarCacheMain " + " "); } diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index ec6430f80..ce786139e 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -128,7 +128,7 @@ public class Launcher { public File slaveLog = null; @Option(name="-text",usage="encode communication with the master with base64. " + - "Useful for running slave over 8-bit unsafe protocol like telnet") + "Useful for running agent over 8-bit unsafe protocol like telnet") public void setTextMode(boolean b) { mode = b?Mode.TEXT:Mode.BINARY; System.out.println("Running in "+mode.name().toLowerCase(Locale.ENGLISH)+" mode"); @@ -142,7 +142,7 @@ public void setTextMode(boolean b) { @Option(name="-jnlpCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP requests.") public String slaveJnlpCredentials = null; - @Option(name="-secret", metaVar="HEX_SECRET", usage="Slave connection secret to use instead of -jnlpCredentials.") + @Option(name="-secret", metaVar="HEX_SECRET", usage="Agent connection secret to use instead of -jnlpCredentials.") public String secret; @Option(name="-proxyCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP authenticated proxy requests.") @@ -283,7 +283,7 @@ public static void main(String... args) throws Exception { launcher.run(); } catch (CmdLineException e) { System.err.println(e.getMessage()); - System.err.println("java -jar slave.jar [options...]"); + System.err.println("java -jar agent.jar [options...]"); parser.printUsage(System.err); System.err.println(); } @@ -585,9 +585,9 @@ private static byte[] fromHexString(String data) { return r; } - private static Document loadDom(URL slaveJnlpURL, InputStream is) throws ParserConfigurationException, SAXException, IOException { + private static Document loadDom(URL agentJnlpURL, InputStream is) throws ParserConfigurationException, SAXException, IOException { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - return db.parse(is, slaveJnlpURL.toExternalForm()); + return db.parse(is, agentJnlpURL.toExternalForm()); } /** @@ -701,9 +701,9 @@ private static void ttyCheck() { // we seem to be running from interactive console. issue a warning. // but since this diagnosis could be wrong, go on and do what we normally do anyway. Don't exit. System.out.println( - "WARNING: Are you running slave agent from an interactive console?\n" + + "WARNING: Are you running agent from an interactive console?\n" + "If so, you are probably using it incorrectly.\n" + - "See http://wiki.jenkins-ci.org/display/JENKINS/Launching+slave.jar+from+from+console"); + "See https://wiki.jenkins.io/display/JENKINS/Launching+agent+from+console"); } } @@ -801,7 +801,7 @@ private static void closeWithLogOnly(Closeable stream, String name) { } /** - * Version number of Hudson this slave.jar is from. + * Version number of Hudson this agent.jar is from. */ public static final String VERSION = computeVersion(); diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index 382fa0fe1..7ef203608 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -58,7 +58,7 @@ import javax.annotation.Nonnull; /** - * Entry point to JNLP slave agent. + * Entry point to JNLP agent. * *

* See also slave-agent.jnlp.jelly in the core. @@ -179,7 +179,7 @@ public class Main { /** * 4 mandatory parameters. - * Host name (deprecated), Jenkins URL, secret key, and slave name. + * Host name (deprecated), Jenkins URL, secret key, and agent name. */ @Argument public final List args = new ArrayList(); @@ -189,7 +189,7 @@ public static void main(String[] args) throws IOException, InterruptedException _main(args); } catch (CmdLineException e) { System.err.println(e.getMessage()); - System.err.println("java -jar slave.jar [options...] "); + System.err.println("java -jar agent.jar [options...] "); new CmdLineParser(new Main()).printUsage(System.err); } } @@ -238,11 +238,11 @@ public void main() throws IOException, InterruptedException { } public Engine createEngine() { - String slaveName = args.get(1); - LOGGER.log(INFO, "Setting up slave: {0}", slaveName); + String agentName = args.get(1); + LOGGER.log(INFO, "Setting up agent: {0}", agentName); Engine engine = new Engine( headlessMode ? new CuiListener() : new GuiListener(), - urls, args.get(0), slaveName); + urls, args.get(0), agentName); if(tunnel!=null) engine.setTunnel(tunnel); if(credentials!=null) @@ -260,7 +260,7 @@ public Engine createEngine() { engine.setDisableHttpsCertValidation(disableHttpsCertValidation); - // TODO: ideally logging should be initialized before the "Setting up slave" entry + // TODO: ideally logging should be initialized before the "Setting up agent" entry if (agentLog != null) { try { engine.setAgentLog(PathUtils.fileToPath(agentLog)); diff --git a/src/main/java/hudson/remoting/jnlp/MainDialog.java b/src/main/java/hudson/remoting/jnlp/MainDialog.java index a378a0341..d244b9468 100644 --- a/src/main/java/hudson/remoting/jnlp/MainDialog.java +++ b/src/main/java/hudson/remoting/jnlp/MainDialog.java @@ -31,7 +31,7 @@ import javax.annotation.CheckForNull; /** - * Main window for JNLP slave agent. + * Main window for JNLP agent. * * @author Kohsuke Kawaguchi */ @@ -43,7 +43,7 @@ public class MainDialog extends JFrame { @SuppressFBWarnings(value = "UI_INHERITANCE_UNSAFE_GETRESOURCE", justification = "We allow overriding this resource. Just in case") public MainDialog() throws HeadlessException { - super("Jenkins slave agent"); + super("Jenkins agent"); ImageIcon background = new ImageIcon(getClass().getResource("title.png")); @@ -93,7 +93,7 @@ public void status(String msg) { } /** - * If the current JVM runs a {@link MainDialog} as a JNLP slave agent, + * If the current JVM runs a {@link MainDialog} as a JNLP agent, * return its reference, otherwise {@code null}. */ @CheckForNull diff --git a/src/main/java/hudson/remoting/package-info.java b/src/main/java/hudson/remoting/package-info.java index fba40a24c..27b29ff71 100644 --- a/src/main/java/hudson/remoting/package-info.java +++ b/src/main/java/hudson/remoting/package-info.java @@ -25,7 +25,7 @@ * Remoting infrastructure for Hudson. * *

- * Code in this package is used for running a part of a program in slaves. + * Code in this package is used for running a part of a program in agents. * If you are new to this package, start from {@link hudson.remoting.Channel}. */ package hudson.remoting; From 24a007701f34296cee26e0a877fafddebdff1bb2 Mon Sep 17 00:00:00 2001 From: etiennebec Date: Tue, 2 Jan 2018 11:41:37 +0100 Subject: [PATCH 082/243] Remove import of Sun internal lib and implement Java9 non proxy handling --- .../engine/JnlpAgentEndpointResolver.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 1da1c1bcc..883490087 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -26,8 +26,6 @@ import hudson.remoting.Base64; import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; import org.jenkinsci.remoting.util.https.NoCheckTrustManager; -import sun.misc.RegexpPool; -import sun.net.NetProperties; import java.io.IOException; import java.net.ConnectException; @@ -49,19 +47,10 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.net.ssl.HttpsURLConnection; @@ -383,23 +372,34 @@ static InetSocketAddress getResolvedHttpProxyAddress(@Nonnull String host, int p if (proxy.type() == Proxy.Type.DIRECT) { // Proxy.NO_PROXY with a DIRECT type is returned in two cases: // - when no proxy (none) has been configured in the JVM (either with system properties or by the operating system) - // - when the URI host is part of the exclusion list defined by system property -Dhttp.nonProxyHosts + // - when the host URI is part of the exclusion list defined by system property -Dhttp.nonProxyHosts // // Unfortunately, the Proxy class does not provide a way to differentiate both cases to fallback to // environment variables only when no proxy has been configured. Therefore, we have to recheck if the URI // host is in the exclusion list. - String nonProxyHosts = NetProperties.get("http.nonProxyHosts"); + // + // Warning: + // This code only supports Java 9+ implementation where nonProxyHosts entries are not interpreted as regex expressions anymore. + // Wildcard at the beginning or the end of an expression are the only remaining supported behaviours (e.g. *.jenkins.io or 127.*) + // https://bugs.java.com/view_bug.do?bug_id=8035158 + // http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/50a749f2cade + String nonProxyHosts = System.getProperty("http.nonProxyHosts"); if(nonProxyHosts != null && nonProxyHosts.length() != 0) { - RegexpPool exclusionsPool = new RegexpPool(); - StringTokenizer stringTokenizer = new StringTokenizer(nonProxyHosts, "|", false); - try { - while(stringTokenizer.hasMoreTokens()) { - exclusionsPool.add(stringTokenizer.nextToken().toLowerCase(Locale.ENGLISH), Boolean.TRUE); - } - } catch(sun.misc.REException e) { - LOGGER.log(Level.WARNING, "Malformed exception list in http.nonProxyHosts system property.", e); + // Build a list of regexps matching all nonProxyHosts entries + StringJoiner sj = new StringJoiner("|"); + nonProxyHosts = nonProxyHosts.toLowerCase(Locale.ENGLISH); + for(String entry : nonProxyHosts.split("\\|")) { + if(entry.isEmpty()) + continue; + else if(entry.startsWith("*")) + sj.add(".*" + Pattern.quote(entry.substring(1))); + else if(entry.endsWith("*")) + sj.add(Pattern.quote(entry.substring(0, entry.length() - 1)) + ".*"); + else + sj.add(Pattern.quote(entry)); } - if(exclusionsPool.match(host.toLowerCase(Locale.ENGLISH)) != null) { + Pattern nonProxyRegexps = Pattern.compile(sj.toString()); + if(nonProxyRegexps.matcher(host.toLowerCase(Locale.ENGLISH)).matches()) { return null; } else { break; From 736678d82670c460e445b39281aa0db8efa030e4 Mon Sep 17 00:00:00 2001 From: etiennebec Date: Tue, 2 Jan 2018 11:56:15 +0100 Subject: [PATCH 083/243] Add FIXME keyword on copy/pasted methods from hudson.remoting.Util --- .../jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 883490087..3e5ccf3e4 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -436,6 +436,7 @@ else if(entry.endsWith("*")) * Gets URL connection. * If http_proxy environment variable exists, the connection uses the proxy. * Credentials can be passed e.g. to support running Jenkins behind a (reverse) proxy requiring authorization + * FIXME: similar to hudson.remoting.Util.openURLConnection which is still used in hudson.remoting.Launcher */ static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials, SSLSocketFactory sslSocketFactory, boolean disableHttpsCertValidation) throws IOException { @@ -505,6 +506,8 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy * - To match IPV4/IPV/FQDN: Regular Expressions Cookbook, 2nd Edition (ISBN: 9781449327453) * * Warning: this method won't match shortened representation of IPV6 address + * + * FIXME: duplicate of hudson.remoting.Util.inNoProxyEnvVar */ static boolean inNoProxyEnvVar(String host) { String noProxy = System.getenv("no_proxy"); From 8410222ec268410785af5ee9149075116412684f Mon Sep 17 00:00:00 2001 From: etiennebec Date: Tue, 2 Jan 2018 12:05:10 +0100 Subject: [PATCH 084/243] Add a warning when more than one wildcard is used in a nonProxyHosts entry --- .../jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 3e5ccf3e4..f773a6e1b 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -397,6 +397,9 @@ else if(entry.endsWith("*")) sj.add(Pattern.quote(entry.substring(0, entry.length() - 1)) + ".*"); else sj.add(Pattern.quote(entry)); + // Detect when the pattern contains multiple wildcard, which used to work previous to Java 9 (e.g. 127.*.*.*) + if(entry.split("\\*").length > 2) + LOGGER.log(Level.WARNING, "Using more than one wildcard is not supported in nonProxyHosts entries: {0}", entry); } Pattern nonProxyRegexps = Pattern.compile(sj.toString()); if(nonProxyRegexps.matcher(host.toLowerCase(Locale.ENGLISH)).matches()) { From 1fda115f080dc3fc1063ca3496f49bb2853f380e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 9 Jan 2018 11:08:48 -0500 Subject: [PATCH 085/243] [JENKINS-47736] Introduced ClassFilter.setDefault (#208) * [JENKINS-47736] Introduced ClassFilter.setDefault. * Review comments from @oleg-nenashev. * [JENKINS-47736] - Add some annotations, mostly to kick-off CI --- .../java/hudson/remoting/ClassFilter.java | 80 ++++++++++++++++--- .../java/hudson/remoting/ClassFilterTest.java | 2 +- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/main/java/hudson/remoting/ClassFilter.java b/src/main/java/hudson/remoting/ClassFilter.java index 3889db052..096994853 100644 --- a/src/main/java/hudson/remoting/ClassFilter.java +++ b/src/main/java/hudson/remoting/ClassFilter.java @@ -15,38 +15,61 @@ import java.util.regex.PatternSyntaxException; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** * Restricts what classes can be received through remoting. - * + * The same filter is also applied by Jenkins core for XStream serialization. * @author Kohsuke Kawaguchi * @since 2.53 */ public abstract class ClassFilter { /** - * Property to set to override the blacklist used by {{@link #DEFAULT} with a different set. + * Property to set to override the blacklist used by {@link #STANDARD} with a different set. * The location should point to a a file containing regular expressions (one per line) of classes to blacklist. * If this property is set but the file can not be read the default blacklist will be used. * @since 2.53.2 + * @deprecated use {@link #setDefault} as needed */ + @Deprecated public static final String FILE_OVERRIDE_LOCATION_PROPERTY = "hudson.remoting.ClassFilter.DEFAULTS_OVERRIDE_LOCATION"; private static final Logger LOGGER = Logger.getLogger(ClassFilter.class.getName()); - protected boolean isBlacklisted(String name) { + /** + * Whether a given class should be blocked, before even attempting to load that class. + * @param name {@link Class#getName} + * @return false by default; override to return true to blacklist this class + */ + public boolean isBlacklisted(@Nonnull String name) { return false; } - protected boolean isBlacklisted(Class c) { + /** + * Whether a given class should be blocked, after having loaded that class. + * @param c a loaded class + * @return false by default; override to return true to blacklist this class + */ + public boolean isBlacklisted(@Nonnull Class c) { return false; } + /** + * API version of {@link #isBlacklisted(String)} SPI. + * @return the same {@code name} + * @throws SecurityException if it is blacklisted + */ public final String check(String name) { if (isBlacklisted(name)) throw new SecurityException("Rejected: " +name); return name; } + /** + * API version of {@link #isBlacklisted(Class)} SPI. + * @return the same {@code c} + * @throws SecurityException if it is blacklisted + */ public final Class check(Class c) { if (isBlacklisted(c)) throw new SecurityException("Rejected: " +c.getName()); @@ -62,11 +85,13 @@ public final Class check(Class c) { "^com[.]sun[.]javafx[.].*", "^com[.]sun[.]org[.]apache[.]regex[.]internal[.].*", "^java[.]awt[.].*", + "^java[.]lang[.]reflect[.]Method$", "^java[.]rmi[.].*", "^javax[.]management[.].*", "^javax[.]naming[.].*", "^javax[.]script[.].*", "^javax[.]swing[.].*", + "^net[.]sf[.]json[.].*", "^org[.]apache[.]commons[.]beanutils[.].*", "^org[.]apache[.]commons[.]collections[.]functors[.].*", "^org[.]apache[.]myfaces[.].*", @@ -79,26 +104,53 @@ public final Class check(Class c) { "^sun[.]rmi[.].*", "^javax[.]imageio[.].*", "^java[.]util[.]ServiceLoader$", - "^java[.]net[.]URLClassLoader$" + "^java[.]net[.]URLClassLoader$", + "^java[.]security[.]SignedObject$" }; /** - * A set of sensible default filtering rules to apply, - * unless the context guarantees the trust between two channels. + * The currently used default. + * Defaults to {@link #STANDARD}. + */ + public static final ClassFilter DEFAULT = new ClassFilter() { + @Override + public boolean isBlacklisted(Class c) { + return CURRENT_DEFAULT.isBlacklisted(c); + } + @Override + public boolean isBlacklisted(String name) { + return CURRENT_DEFAULT.isBlacklisted(name); + } + }; + + private static @Nonnull ClassFilter CURRENT_DEFAULT; + + /** + * Changes the effective value of {@link #DEFAULT}. + * @param filter a new default to set; may or may not delegate to {@link STANDARD} + * @since FIXME + */ + public static void setDefault(@Nonnull ClassFilter filter) { + CURRENT_DEFAULT = filter; + } + + /** + * A set of sensible default filtering rules to apply, based on a configurable blacklist. */ - public static final ClassFilter DEFAULT; + public static final ClassFilter STANDARD; static { try { - DEFAULT = createDefaultInstance(); + STANDARD = createDefaultInstance(); } catch (ClassFilterException ex) { LOGGER.log(Level.SEVERE, "Default class filter cannot be initialized. Remoting will not start", ex); throw new ExceptionInInitializerError(ex); } + CURRENT_DEFAULT = STANDARD; } /** - * Adds an additional exclusion to {@link #DEFAULT}. + * Adds an additional exclusion to {@link #STANDARD}. * * Does nothing if the default list has already been customized via {@link #FILE_OVERRIDE_LOCATION_PROPERTY}. * This API is not supposed to be used anywhere outside Jenkins core, calls for other sources may be rejected later. @@ -106,10 +158,12 @@ public final Class check(Class c) { * @throws ClassFilterException Filter pattern cannot be applied. * It means either unexpected processing error or rejection by the internal logic. * @since 3.11 + * @deprecated use {@link #setDefault} as needed */ + @Deprecated public static void appendDefaultFilter(Pattern filter) throws ClassFilterException { if (System.getProperty(FILE_OVERRIDE_LOCATION_PROPERTY) == null) { - ((RegExpClassFilter) DEFAULT).add(filter.toString()); + ((RegExpClassFilter) STANDARD).add(filter.toString()); } } @@ -228,7 +282,7 @@ void add(String pattern) throws ClassFilterException { } @Override - protected boolean isBlacklisted(String name) { + public boolean isBlacklisted(String name) { for (Object p : blacklistPatterns) { if (p instanceof Pattern && ((Pattern)p).matcher(name).matches()) { return true; @@ -252,7 +306,9 @@ public String toString() { /** * Class for propagating exceptions in {@link ClassFilter}. * @since 3.11 + * @deprecated Only used by deprecated {@link #appendDefaultFilter}. */ + @Deprecated public static class ClassFilterException extends Exception { @CheckForNull diff --git a/src/test/java/hudson/remoting/ClassFilterTest.java b/src/test/java/hudson/remoting/ClassFilterTest.java index c59fd0006..266fc803c 100644 --- a/src/test/java/hudson/remoting/ClassFilterTest.java +++ b/src/test/java/hudson/remoting/ClassFilterTest.java @@ -48,7 +48,7 @@ public class ClassFilterTest implements Serializable { private static class TestFilter extends ClassFilter { @Override - protected boolean isBlacklisted(String name) { + public boolean isBlacklisted(String name) { return name.contains("Security218"); } } From 46e873dc09a3d80dced90f319573007eb3360fa8 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 10 Jan 2018 12:04:17 +0100 Subject: [PATCH 086/243] Changelog and Javadoc update towards 2.16 (#246) * Changelog: Noting 3.16 * Javadoc: Add @since to the ClassFilter#setDefault() meethod * Changelog: We already know the target Jenkins weekly release * Changelog: Fix the reference to #208 --- CHANGELOG.md | 19 +++++++++++++++++++ .../java/hudson/remoting/ClassFilter.java | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e99e9da..1938a7543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.16 + +Release date: Jan 10, 2018 => Jenkins 2.102 + +* [PR #208](https://github.com/jenkinsci/remoting/pull/208) - +Introduce the new `ClassFilter.setDefault` API which allows replacing the default Class Filter + * This is a foundation work for [JEP-200](https://github.com/jenkinsci/jep/tree/master/jep/200)/[JENKINS-47736](https://issues.jenkins-ci.org/browse/JENKINS-47736), + which switches the default Remoting/XStream blacklist to whitelist in the Jenkins core + * Other Remoting API users are adviced to do the same +* [PR #208](https://github.com/jenkinsci/remoting/pull/208) - +Update the blacklist in the default Class Filter to align it with the Jenkins core. +New entries: + * `^java[.]lang[.]reflect[.]Method$` + * `^net[.]sf[.]json[.].*` + * `^java[.]security[.]SignedObject$` ([SECURITY-429 advisory](https://jenkins.io/security/advisory/2017-04-26/#cli-unauthenticated-remote-code-execution)) +* [JENKINS-48686](https://issues.jenkins-ci.org/browse/JENKINS-48686) - +Replace the _slave_ term by _agent_ in logging, UI and Javadocs + + ##### 3.15 Release date: Dec 22, 2017 => Jenkins 2.98 diff --git a/src/main/java/hudson/remoting/ClassFilter.java b/src/main/java/hudson/remoting/ClassFilter.java index 096994853..d4d8f50d7 100644 --- a/src/main/java/hudson/remoting/ClassFilter.java +++ b/src/main/java/hudson/remoting/ClassFilter.java @@ -128,7 +128,7 @@ public boolean isBlacklisted(String name) { /** * Changes the effective value of {@link #DEFAULT}. * @param filter a new default to set; may or may not delegate to {@link STANDARD} - * @since FIXME + * @since 3.16 */ public static void setDefault(@Nonnull ClassFilter filter) { CURRENT_DEFAULT = filter; From 7fcf36cdb7e7ec6560ef4b42adb92769a9b20342 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 10 Jan 2018 12:17:38 +0100 Subject: [PATCH 087/243] [maven-release-plugin] prepare release remoting-3.16 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 609f741a7..ff4cba5f6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.16-SNAPSHOT + 3.16 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.16 From fea703f99d437763833b351298602fa0678d1f11 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 10 Jan 2018 12:17:47 +0100 Subject: [PATCH 088/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ff4cba5f6..146e9d804 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.16 + 3.17-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.16 + HEAD From f1c3e201cbdb22b75857cb31287c99ecb5dac336 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 09:40:23 -0500 Subject: [PATCH 089/243] Remove unused imports. --- src/test/java/hudson/remoting/ChannelTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/hudson/remoting/ChannelTest.java b/src/test/java/hudson/remoting/ChannelTest.java index 562d221b9..1589a6d09 100644 --- a/src/test/java/hudson/remoting/ChannelTest.java +++ b/src/test/java/hudson/remoting/ChannelTest.java @@ -11,7 +11,6 @@ import java.io.StringWriter; import java.net.URL; import java.net.URLClassLoader; -import java.nio.channels.ClosedChannelException; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ExecutorService; @@ -22,11 +21,9 @@ import javax.annotation.Nonnull; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; -import static org.hamcrest.MatcherAssert.assertThat; import org.jenkinsci.remoting.RoleChecker; import org.jvnet.hudson.test.Issue; -import sun.rmi.runtime.Log; /** * @author Kohsuke Kawaguchi From 9eaa93c1f1e266bbd563a58aa632fb50cdd77c07 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 09:52:21 -0500 Subject: [PATCH 090/243] [INFRA-1176] Use the Azure mirror when building, so we do not try to contact Central. --- Jenkinsfile | 2 +- settings-azure.xml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 settings-azure.xml diff --git a/Jenkinsfile b/Jenkinsfile index 11fd37c56..874591dcc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,7 +27,7 @@ for (int i = 0; i < platforms.size(); ++i) { 'PATH+JDK=$JAVA_HOME/bin', ]) { timeout(30) { - String command = 'mvn --batch-mode clean install -Dmaven.test.failure.ignore=true' + String command = 'mvn --batch-mode clean install -Dmaven.test.failure.ignore=true -s settings-azure.xml -e' if (isUnix()) { sh command } diff --git a/settings-azure.xml b/settings-azure.xml new file mode 100644 index 000000000..a1e96394d --- /dev/null +++ b/settings-azure.xml @@ -0,0 +1,9 @@ + + + + azure + https://repo.azure.jenkins.io/public/ + repo.jenkins-ci.org + + + From 7c5b873487393ff2929151fa8d720e4bdb42e43a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 10:17:35 -0500 Subject: [PATCH 091/243] Make this theoretically runnable on non-ci.jenkins.io infrastructure. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 874591dcc..179388e56 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,7 +27,7 @@ for (int i = 0; i < platforms.size(); ++i) { 'PATH+JDK=$JAVA_HOME/bin', ]) { timeout(30) { - String command = 'mvn --batch-mode clean install -Dmaven.test.failure.ignore=true -s settings-azure.xml -e' + String command = "mvn --batch-mode clean install -Dmaven.test.failure.ignore=true ${infra.isRunningOnJenkinsInfra() ? '-s settings-azure.xml' : ''} -e" if (isUnix()) { sh command } From 9e6472fbffaf7fc84dd84395725f2db404260def Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 12:36:42 -0500 Subject: [PATCH 092/243] [JENKINS-49027] Better report JEP-200 violations in Remoting (#247) * [JENKINS-49027] Better report JEP-200 violations in Remoting. * Updated test to look for nested causes. --- src/main/java/hudson/remoting/Channel.java | 11 ++++------- src/main/java/hudson/remoting/ClassFilter.java | 10 ++++++---- .../java/hudson/remoting/ChannelFilterTest.java | 13 +++++++++++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index fd9806153..22f52507f 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -47,7 +47,6 @@ import java.lang.ref.WeakReference; import java.net.URL; import java.util.Collections; -import java.util.HashMap; import java.util.Date; import java.util.Hashtable; import java.util.Locale; @@ -910,13 +909,11 @@ V call(Callable callable) throws IOException, T, InterruptedException { // re-wrap the exception so that we can capture the stack trace of the caller. } catch (ClassNotFoundException e) { - IOException x = new IOException("Remote call on "+name+" failed"); - x.initCause(e); - throw x; + throw new IOException("Remote call on " + name + " failed", e); } catch (Error e) { - IOException x = new IOException("Remote call on "+name+" failed"); - x.initCause(e); - throw x; + throw new IOException("Remote call on " + name + " failed", e); + } catch (SecurityException e) { + throw new IOException("Failed to deserialize response to " + request + ": " + e, e); } finally { // since this is synchronous operation, when the round trip is over // we assume all the exported objects are out of scope. diff --git a/src/main/java/hudson/remoting/ClassFilter.java b/src/main/java/hudson/remoting/ClassFilter.java index d4d8f50d7..f12a7490c 100644 --- a/src/main/java/hudson/remoting/ClassFilter.java +++ b/src/main/java/hudson/remoting/ClassFilter.java @@ -60,8 +60,9 @@ public boolean isBlacklisted(@Nonnull Class c) { * @throws SecurityException if it is blacklisted */ public final String check(String name) { - if (isBlacklisted(name)) - throw new SecurityException("Rejected: " +name); + if (isBlacklisted(name)) { + throw new SecurityException("Rejected: " + name + "; see https://jenkins.io/redirect/class-filter/"); + } return name; } @@ -71,8 +72,9 @@ public final String check(String name) { * @throws SecurityException if it is blacklisted */ public final Class check(Class c) { - if (isBlacklisted(c)) - throw new SecurityException("Rejected: " +c.getName()); + if (isBlacklisted(c)) { + throw new SecurityException("Rejected: " + c.getName() + "; see https://jenkins.io/redirect/class-filter/"); + } return c; } diff --git a/src/test/java/hudson/remoting/ChannelFilterTest.java b/src/test/java/hudson/remoting/ChannelFilterTest.java index be2ef7ef3..49669da41 100644 --- a/src/test/java/hudson/remoting/ChannelFilterTest.java +++ b/src/test/java/hudson/remoting/ChannelFilterTest.java @@ -62,11 +62,20 @@ public hudson.remoting.Callable userRequest(hudso try { channel.call(new ReverseGunImporter()); fail("should have failed"); - } catch (SecurityException e) { - assertEquals("Rejecting "+GunImporter.class.getName(),e.getMessage()); + } catch (Exception e) { + assertEquals("Rejecting "+GunImporter.class.getName(), findSecurityException(e).getMessage()); // e.printStackTrace(); } } + private static SecurityException findSecurityException(Throwable x) { + if (x instanceof SecurityException) { + return (SecurityException) x; + } else if (x == null) { + throw new AssertionError("no SecurityException detected"); + } else { + return findSecurityException(x.getCause()); + } + } /* Option 1: From 10752f311eacca1f51ddb216a4c1fa1a962600e6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 12:37:48 -0500 Subject: [PATCH 093/243] [JENKINS-27035] Introduced Channel.Listener.read/write (#204) * Introduced Channel.Listener.read/write. * Make selected Command subtypes public with selected getters. * Rename of listener methods. * More consistent formatting. * Introduced a separate event for responses. * Better RPCRequest.toString(). * Also added onJar event. * UserRPCRequest.toString * Suggestions from @oleg-nenashev. * LoggingChannelListener * FindBugs --- .../AbstractByteArrayCommandTransport.java | 10 +- .../AbstractByteBufferCommandTransport.java | 5 +- ...tSynchronousByteArrayCommandTransport.java | 11 +- src/main/java/hudson/remoting/Channel.java | 117 +++++++++++++++++- .../remoting/ClassicCommandTransport.java | 2 + src/main/java/hudson/remoting/Command.java | 39 ++++-- src/main/java/hudson/remoting/GCCommand.java | 5 + src/main/java/hudson/remoting/Pipe.java | 5 + .../hudson/remoting/ProxyInputStream.java | 6 + .../hudson/remoting/RemoteClassLoader.java | 16 ++- .../remoting/RemoteInvocationHandler.java | 26 ++-- src/main/java/hudson/remoting/Request.java | 37 ++++-- src/main/java/hudson/remoting/Response.java | 63 ++++++++-- .../java/hudson/remoting/UnexportCommand.java | 2 +- .../remoting/util/LoggingChannelListener.java | 85 +++++++++++++ .../java/hudson/remoting/ClassFilterTest.java | 5 + 16 files changed, 378 insertions(+), 56 deletions(-) create mode 100644 src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java diff --git a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java index 1f288ae68..a1970a871 100644 --- a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java @@ -59,8 +59,10 @@ public final void setup(final Channel channel, final CommandReceiver receiver) { setup(new ByteArrayReceiver() { public void handle(byte[] payload) { try { - receiver.handle(Command.readFrom(channel, new ObjectInputStreamEx( - new ByteArrayInputStream(payload),channel.baseClassLoader,channel.classFilter))); + Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( + new ByteArrayInputStream(payload),channel.baseClassLoader,channel.classFilter)); + receiver.handle(cmd); + channel.notifyRead(cmd, payload.length); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to construct Command", e); } catch (ClassNotFoundException e) { @@ -80,7 +82,9 @@ public final void write(Command cmd, boolean last) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(baos); cmd.writeTo(channel,oos); oos.close(); - writeBlock(channel,baos.toByteArray()); + byte[] block = baos.toByteArray(); + channel.notifyWrite(cmd, block.length); + writeBlock(channel, block); } private static final Logger LOGGER = Logger.getLogger(AbstractByteArrayCommandTransport.class.getName()); diff --git a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java index 5d36b2c26..21564922c 100644 --- a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java @@ -199,7 +199,9 @@ private void processCommand() throws IOException { FastByteBufferQueueInputStream is = new FastByteBufferQueueInputStream(receiveQueue, readCommandSizes[0]); try { ObjectInputStreamEx ois = new ObjectInputStreamEx(is, channel.baseClassLoader, channel.classFilter); - receiver.handle(Command.readFrom(channel, ois)); + Command cmd = Command.readFrom(channel, ois); + receiver.handle(cmd); + channel.notifyRead(cmd, readCommandSizes[0]); } catch (IOException e1) { LOGGER.log(Level.WARNING, "Failed to construct Command", e1); } catch (ClassNotFoundException e11) { @@ -290,6 +292,7 @@ public final void write(Command cmd, boolean last) throws IOException { oos.close(); } long remaining = sendStaging.remaining(); + channel.notifyWrite(cmd, remaining); while (remaining > 0L) { int frame = remaining > transportFrameSize ? transportFrameSize diff --git a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java index 4ffc62654..5c42ceb90 100644 --- a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java @@ -31,9 +31,12 @@ public abstract class AbstractSynchronousByteArrayCommandTransport extends Synch @Override public Command read() throws IOException, ClassNotFoundException { - return Command.readFrom(channel,new ObjectInputStreamEx( - new ByteArrayInputStream(readBlock(channel)), + byte[] block = readBlock(channel); + Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( + new ByteArrayInputStream(block), channel.baseClassLoader,channel.classFilter)); + channel.notifyRead(cmd, block.length); + return cmd; } @Override @@ -42,6 +45,8 @@ public void write(Command cmd, boolean last) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(baos); cmd.writeTo(channel,oos); oos.close(); - writeBlock(channel,baos.toByteArray()); + byte[] block = baos.toByteArray(); + channel.notifyWrite(cmd, block.length); + writeBlock(channel, block); } } diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 22f52507f..97aeb45a3 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -38,22 +38,25 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.URL; import java.util.Collections; import java.util.Date; import java.util.Hashtable; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Vector; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -63,6 +66,7 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.annotation.Nullable; +import org.jenkinsci.remoting.util.LoggingChannelListener; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -209,7 +213,7 @@ public class Channel implements VirtualChannel, IChannel, Closeable { /** * Registered listeners. */ - private final Vector listeners = new Vector(); + private final List listeners = new CopyOnWriteArrayList<>(); private int gcCounter; /** @@ -587,6 +591,7 @@ public void terminate(IOException e) { /** * Callback "interface" for changes in the state of {@link Channel}. + * @see LoggingChannelListener */ public static abstract class Listener { /** @@ -598,6 +603,45 @@ public static abstract class Listener { * Otherwise null. */ public void onClosed(Channel channel, IOException cause) {} + + /** + * Called when a command is successfully received by a channel. + * @param channel a channel + * @param cmd a command + * @param blockSize the number of bytes used to read this command + * @since FIXME + */ + public void onRead(Channel channel, Command cmd, long blockSize) {} + + /** + * Called when a command is successfully written to a channel. + * See {@link #onRead} for general usage guidelines. + * @param channel a channel + * @param cmd a command + * @param blockSize the number of bytes used to write this command + * @since FIXME + */ + public void onWrite(Channel channel, Command cmd, long blockSize) {} + + /** + * Called when a response has been read from a channel. + * @param channel a channel + * @param req the original request + * @param rsp the resulting response + * @param totalTime the total time in nanoseconds taken to service the request + * @since FIXME + */ + public void onResponse(Channel channel, Request req, Response rsp, long totalTime) {} + + /** + * Called when a JAR file is being sent to the remote side. + * @param channel a channel + * @param jar the JAR file from which code is being loaded remotely + * @see Capability#supportsPrefetch + * @since FIXME + */ + public void onJar(Channel channel, File jar) {} + } /** @@ -1006,7 +1050,7 @@ public void terminate(@Nonnull IOException e) { } // JENKINS-14909: leave synch block } finally { if (e instanceof OrderlyShutdown) e = null; - for (Listener l : listeners.toArray(new Listener[0])) { + for (Listener l : listeners) { try { l.onClosed(this, e); } catch (Throwable t) { @@ -1229,7 +1273,7 @@ protected void execute(Channel channel) { @Override public String toString() { - return "close"; + return "Close"; } // this value is compatible with remoting < 2.8. I made an incompatible change in 2.8 that got corrected in 2.11. @@ -1817,6 +1861,71 @@ public static void dumpDiagnosticsForAll(@Nonnull PrintWriter w) { } } + /** + * Notification that {@link Command#readFrom} has succeeded. + * @param cmd the resulting command + * @param blockSize the serialized size of the command + * @see CommandListener + */ + void notifyRead(Command cmd, long blockSize) { + for (Listener listener : listeners) { + try { + listener.onRead(this, cmd, blockSize); + } catch (Throwable x) { + logger.log(Level.WARNING, null, x); + } + } + } + + /** + * Notification that {@link Command#writeTo} has succeeded. + * @param cmd the command passed in + * @param blockSize the serialized size of the command + * @see CommandListener + */ + void notifyWrite(Command cmd, long blockSize) { + for (Listener listener : listeners) { + try { + listener.onWrite(this, cmd, blockSize); + } catch (Throwable x) { + logger.log(Level.WARNING, null, x); + } + } + } + + /** + * Notification that a {@link Response} has been received. + * @param req the original request + * @param rsp the resulting response + * @param totalTime the total time in nanoseconds taken to service the request + * @see CommandListener + */ + void notifyResponse(Request req, Response rsp, long totalTime) { + for (Listener listener : listeners) { + try { + listener.onResponse(this, req, rsp, totalTime); + } catch (Throwable x) { + logger.log(Level.WARNING, null, x); + } + } + } + + /** + * Notification that a JAR file will be delivered to the remote side. + * @param jar the JAR file from which code is being loaded remotely + * @see CommandListener + */ + void notifyJar(File jar) { + for (Listener listener : listeners) { + try { + listener.onJar(this, jar); + } catch (Throwable x) { + logger.log(Level.WARNING, null, x); + } + } + } + + /** * Remembers the current "channel" associated for this thread. */ diff --git a/src/main/java/hudson/remoting/ClassicCommandTransport.java b/src/main/java/hudson/remoting/ClassicCommandTransport.java index b4858b396..6b551efc9 100644 --- a/src/main/java/hudson/remoting/ClassicCommandTransport.java +++ b/src/main/java/hudson/remoting/ClassicCommandTransport.java @@ -49,6 +49,7 @@ public Capability getRemoteCapability() throws IOException { public final void write(Command cmd, boolean last) throws IOException { cmd.writeTo(channel,oos); + // TODO notifyWrite using CountingOutputStream oos.flush(); // make sure the command reaches the other end. // unless this is the last command, have OOS and remote OIS forget all the objects we sent @@ -68,6 +69,7 @@ public void closeWrite() throws IOException { public final Command read() throws IOException, ClassNotFoundException { try { Command cmd = Command.readFrom(channel, ois); + // TODO notifyRead using CountingInputStream if (rawIn!=null) rawIn.clear(); return cmd; diff --git a/src/main/java/hudson/remoting/Command.java b/src/main/java/hudson/remoting/Command.java index a3b684048..f999634cb 100644 --- a/src/main/java/hudson/remoting/Command.java +++ b/src/main/java/hudson/remoting/Command.java @@ -41,22 +41,25 @@ * need to have the definition of {@link Command}-implementation. * * @author Kohsuke Kawaguchi + * @see hudson.remoting.Channel.Listener#onRead + * @see hudson.remoting.Channel.Listener#onWrite + * @since FIXME */ -abstract class Command implements Serializable { +public abstract class Command implements Serializable { /** * This exception captures the stack trace of where the Command object is created. * This is useful for diagnosing the error when command fails to execute on the remote peer. * {@code null} if the cause is not recorded. */ @CheckForNull - public final Exception createdAt; + final Exception createdAt; - - protected Command() { + // Prohibited to subclass outside of this package. + Command() { this(true); } - protected Command(Channel channel, @CheckForNull Throwable cause) { + Command(Channel channel, @CheckForNull Throwable cause) { // Command object needs to be deserializable on the other end without requiring custom classloading, // so we wrap this in ProxyException this.createdAt = new Source(cause != null ? new ProxyException(cause) : null); @@ -68,7 +71,7 @@ protected Command(Channel channel, @CheckForNull Throwable cause) { * and cause/effect correlation hard in case of a failure, but it will reduce the amount of the data * transferred. */ - protected Command(boolean recordCreatedAt) { + Command(boolean recordCreatedAt) { if(recordCreatedAt) this.createdAt = new Source(); else @@ -82,19 +85,21 @@ protected Command(boolean recordCreatedAt) { * The {@link Channel} of the remote system. * @throws ExecutionException Execution error */ - protected abstract void execute(Channel channel) throws ExecutionException; + abstract void execute(Channel channel) throws ExecutionException; + /** * Chains the {@link #createdAt} cause. * It will happen if and only if cause recording is enabled. * @param initCause Original Cause. {@code null} causes will be ignored */ - protected final void chainCause(@CheckForNull Throwable initCause) { + final void chainCause(@CheckForNull Throwable initCause) { if (createdAt != null && initCause != null) { createdAt.initCause(initCause); } } + /** Consider calling {@link Channel#notifyWrite} afterwards. */ void writeTo(Channel channel, ObjectOutputStream oos) throws IOException { Channel old = Channel.setCurrent(channel); try { @@ -104,6 +109,7 @@ void writeTo(Channel channel, ObjectOutputStream oos) throws IOException { } } + /** Consider calling {@link Channel#notifyRead} afterwards. */ static Command readFrom(Channel channel, ObjectInputStream ois) throws IOException, ClassNotFoundException { Channel old = Channel.setCurrent(channel); try { @@ -113,6 +119,23 @@ static Command readFrom(Channel channel, ObjectInputStream ois) throws IOExcepti } } + /** + * Obtains a diagnostic stack trace recording the point at which the command was created. + * This is not necessarily the point at which the command was delivered or run. + * Part of the stack trace might have been produced on a remote machine, + * in which case {@link ProxyException} may be used in place of the original. + * @return an information stack trace, or null if not recorded + */ + public @CheckForNull Throwable getCreationStackTrace() { + return createdAt; + } + + /** + * Should provide concise information useful for {@link hudson.remoting.Channel.Listener}. + */ + @Override + public abstract String toString(); + private static final long serialVersionUID = 1L; private final class Source extends Exception { diff --git a/src/main/java/hudson/remoting/GCCommand.java b/src/main/java/hudson/remoting/GCCommand.java index baf26b4f6..78466e781 100644 --- a/src/main/java/hudson/remoting/GCCommand.java +++ b/src/main/java/hudson/remoting/GCCommand.java @@ -34,5 +34,10 @@ protected void execute(Channel channel) { System.gc(); } + @Override + public String toString() { + return "GC"; + } + private static final long serialVersionUID = 1L; } diff --git a/src/main/java/hudson/remoting/Pipe.java b/src/main/java/hudson/remoting/Pipe.java index c0487b955..d10d26b39 100644 --- a/src/main/java/hudson/remoting/Pipe.java +++ b/src/main/java/hudson/remoting/Pipe.java @@ -232,6 +232,11 @@ public void run() { }); } + @Override + public String toString() { + return "Pipe.Connect"; + } + static final long serialVersionUID = -9128735897846418140L; } } diff --git a/src/main/java/hudson/remoting/ProxyInputStream.java b/src/main/java/hudson/remoting/ProxyInputStream.java index a0f4e4e7d..4ffd61674 100644 --- a/src/main/java/hudson/remoting/ProxyInputStream.java +++ b/src/main/java/hudson/remoting/ProxyInputStream.java @@ -144,7 +144,13 @@ protected Buffer perform(Channel channel) throws IOException { return buf; } + @Override + public String toString() { + return "ProxyInputStream.Chunk(len=" + len + ")"; + } + private static final long serialVersionUID = 1L; + } /** diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index a48437105..dcf660148 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -864,12 +864,14 @@ public ClassFile2 fetch4(String className, ClassFile2 referer) throws ClassNotFo Checksum sum = channel.jarLoader.calcChecksum(jar); ResourceImageRef imageRef; - if (referer==null && !channel.jarLoader.isPresentOnRemote(sum)) + if (referer == null && !channel.jarLoader.isPresentOnRemote(sum)) { // for the class being requested, if the remote doesn't have the jar yet // send the image as well, so as not to require another call to get this class loaded + channel.notifyJar(jar); imageRef = new ResourceImageBoth(urlOfClassFile,sum); - else // otherwise just send the checksum and save space + } else { // otherwise just send the checksum and save space imageRef = new ResourceImageInJar(sum,null /* TODO: we need to check if the URL of c points to the expected location of the file */); + } return new ClassFile2(exportId(ecl,channel), imageRef, referer, c, urlOfClassFile); } @@ -945,10 +947,12 @@ private ResourceFile makeResource(String name, URL resource) throws IOException if (jar.isFile()) {// for historical reasons the jarFile method can return a directory Checksum sum = channel.jarLoader.calcChecksum(jar); ResourceImageRef ir; - if (!channel.jarLoader.isPresentOnRemote(sum)) - ir = new ResourceImageBoth(resource,sum); // remote probably doesn't have - else - ir = new ResourceImageInJar(sum,null); + if (!channel.jarLoader.isPresentOnRemote(sum)) { + channel.notifyJar(jar); + ir = new ResourceImageBoth(resource, sum); // remote probably doesn't have + } else { + ir = new ResourceImageInJar(sum, null); + } return new ResourceFile(ir,resource); } } catch (IllegalArgumentException e) { diff --git a/src/main/java/hudson/remoting/RemoteInvocationHandler.java b/src/main/java/hudson/remoting/RemoteInvocationHandler.java index 826642b28..e849855f0 100644 --- a/src/main/java/hudson/remoting/RemoteInvocationHandler.java +++ b/src/main/java/hudson/remoting/RemoteInvocationHandler.java @@ -55,9 +55,6 @@ import javax.annotation.Nullable; import org.jenkinsci.remoting.RoleChecker; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - /** * Sits behind a proxy object and implements the proxy logic. * @@ -856,6 +853,13 @@ static class RPCRequest extends Request implements Deleg */ protected final int oid; + /** + * Type name of declaring class. + * Null if deserialized historically. + */ + @CheckForNull + private final String declaringClassName; + protected final String methodName; /** * Type name of the arguments to invoke. They are names because @@ -882,6 +886,7 @@ public RPCRequest(int oid, Method m, Object[] arguments) { public RPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { this.oid = oid; this.arguments = arguments; + declaringClassName = m.getDeclaringClass().getName(); this.methodName = m.getName(); this.classLoader = cl; @@ -960,7 +965,15 @@ Object[] getArguments() { // for debugging @Override public String toString() { - return "RPCRequest("+oid+","+methodName+")"; + StringBuilder b = new StringBuilder(getClass().getSimpleName()).append(':').append(declaringClassName).append('.').append(methodName).append('['); + for (int i = 0; i < types.length; i++) { + if (i > 0) { + b.append(','); + } + b.append(types[i]); + } + b.append("](").append(oid).append(')'); + return b.toString(); } private static final long serialVersionUID = 1L; @@ -984,11 +997,6 @@ public UserRPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { super(oid, m, arguments, cl); } - @Override - public String toString() { - return "UserRPCRequest("+oid+","+methodName+")"; - } - // Same implementation as UserRequest @Override public void checkIfCanBeExecutedOnChannel(Channel channel) throws IOException { diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index dd8a6fe69..819351b24 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -23,9 +23,9 @@ */ package hudson.remoting; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.Serializable; -import java.nio.channels.ClosedChannelException; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -45,8 +45,9 @@ * * @author Kohsuke Kawaguchi * @see Response + * @since FIXME */ -abstract class Request extends Command { +public abstract class Request extends Command { /** * Executed on a remote system to perform the task. * @@ -61,7 +62,7 @@ abstract class Request extends C * If no checked exception is supposed to be thrown, use {@link RuntimeException}. */ @Nullable - protected abstract RSP perform(@Nonnull Channel channel) throws EXC; + abstract RSP perform(@Nonnull Channel channel) throws EXC; /** * Uniquely identifies this request. @@ -80,10 +81,12 @@ abstract class Request extends C private volatile Response response; + transient long startTime; + /** * While executing the call this is set to the handle of the execution. */ - protected volatile transient Future future; + volatile transient Future future; /** * Set by {@link Response} to point to the I/O ID issued from the other side that this request needs to @@ -100,7 +103,8 @@ abstract class Request extends C @Deprecated /*package*/ volatile transient Future lastIo; - protected Request() { + @SuppressFBWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification="That is why we synchronize on the class.") + Request() { synchronized(Request.class) { id = nextId++; } @@ -113,7 +117,7 @@ protected Request() { * @throws IOException Error with explanation if the request cannot be executed. * @since 3.11 */ - public void checkIfCanBeExecutedOnChannel(@Nonnull Channel channel) throws IOException { + void checkIfCanBeExecutedOnChannel(@Nonnull Channel channel) throws IOException { final Throwable senderCloseCause = channel.getSenderCloseCause(); if (senderCloseCause != null) { // Sender is closed, we won't be able to send anything @@ -135,7 +139,7 @@ public void checkIfCanBeExecutedOnChannel(@Nonnull Channel channel) throws IOExc * @throws EXC * If the {@link #perform(Channel)} throws an exception. */ - public final RSP call(Channel channel) throws EXC, InterruptedException, IOException { + final RSP call(Channel channel) throws EXC, InterruptedException, IOException { checkIfCanBeExecutedOnChannel(channel); lastIoId = channel.lastIoId(); @@ -147,6 +151,7 @@ public final RSP call(Channel channel) throws EXC, InterruptedException, IOExcep response=null; channel.pendingCalls.put(id,this); + startTime = System.nanoTime(); channel.send(this); } } @@ -217,13 +222,14 @@ public final RSP call(Channel channel) throws EXC, InterruptedException, IOExcep * @throws IOException * If there's an error during the communication. */ - public final hudson.remoting.Future callAsync(final Channel channel) throws IOException { + final hudson.remoting.Future callAsync(final Channel channel) throws IOException { checkIfCanBeExecutedOnChannel(channel); response=null; lastIoId = channel.lastIoId(); channel.pendingCalls.put(id,this); + startTime = System.nanoTime(); channel.send(this); return new hudson.remoting.Future() { @@ -326,13 +332,13 @@ public RSP get(long timeout, TimeUnit unit) throws InterruptedException, Executi * Aborts the processing. The calling thread will receive an exception. */ /*package*/ void abort(IOException e) { - onCompleted(new Response(id,0,new RequestAbortedException(e))); + onCompleted(new Response(this, id, 0, new RequestAbortedException(e))); } /** * Schedules the execution of this request. */ - protected final void execute(final Channel channel) { + final void execute(final Channel channel) { channel.executingCalls.put(id,this); future = channel.executor.submit(new Runnable() { @@ -357,10 +363,10 @@ public void run() { RSP r = Request.this.perform(channel); // normal completion - rsp = new Response(id,calcLastIoId(),r); + rsp = new Response(Request.this, id, calcLastIoId(), r); } catch (Throwable t) { // error return - rsp = new Response(id,calcLastIoId(),t); + rsp = new Response(Request.this, id, calcLastIoId(), t); } finally { CURRENT.set(null); } @@ -394,7 +400,7 @@ public void run() { * Set to true to chain {@link Command#createdAt} to track request/response relationship. * This will substantially increase the network traffic, but useful for debugging. */ - public static boolean chainCause = Boolean.getBoolean(Request.class.getName()+".chainCause"); + static boolean chainCause = Boolean.getBoolean(Request.class.getName()+".chainCause"); /** * Set to the {@link Request} object during {@linkplain #perform(Channel) the execution of the call}. @@ -428,6 +434,11 @@ protected void execute(Channel channel) { if(f!=null) f.cancel(true); } + @Override + public String toString() { + return "Request.Cancel"; + } + private static final long serialVersionUID = -1709992419006993208L; } } diff --git a/src/main/java/hudson/remoting/Response.java b/src/main/java/hudson/remoting/Response.java index 6bbb4e56f..4a2614a52 100644 --- a/src/main/java/hudson/remoting/Response.java +++ b/src/main/java/hudson/remoting/Response.java @@ -23,6 +23,10 @@ */ package hudson.remoting; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + /** * Request/response pattern over {@link Command}. * @@ -30,8 +34,9 @@ * * @author Kohsuke Kawaguchi * @see Request + * @since FIXME */ -final class Response extends Command { +public final class Response extends Command { /** * ID of the {@link Request} for which */ @@ -51,14 +56,21 @@ final class Response extends Command { final RSP returnValue; final EXC exception; - Response(int id, int lastIoId, RSP returnValue) { + @SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Only supposed to be defined on one side.") + private transient long totalTime; + @SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Bound after deserialization, in execute.") + private transient Request request; + + Response(Request request, int id, int lastIoId, RSP returnValue) { + this.request = request; this.id = id; this.lastIoId = lastIoId; this.returnValue = returnValue; this.exception = null; } - Response(int id, int lastIoId, EXC exception) { + Response(Request request, int id, int lastIoId, EXC exception) { + this.request = request; this.id = id; this.lastIoId = lastIoId; this.returnValue = null; @@ -69,7 +81,7 @@ final class Response extends Command { * Notifies the waiting {@link Request}. */ @Override - protected void execute(Channel channel) { + void execute(Channel channel) { Request req = channel.pendingCalls.get(id); if(req==null) return; // maybe aborted @@ -77,15 +89,50 @@ protected void execute(Channel channel) { req.onCompleted(this); channel.pendingCalls.remove(id); + request = req; + long startTime = req.startTime; + if (startTime != 0) { + long time = System.nanoTime() - startTime; + totalTime = time; + channel.notifyResponse(req, this, time); + } } + @Override public String toString() { - return "Response[retVal="+toString(returnValue)+",exception="+toString(exception)+"]"; + return "Response" + (request != null ? ":" + request : "") + "(" + (returnValue != null ? returnValue.getClass().getName() : exception != null ? exception.getClass().getName() : null) + ")"; } - private static String toString(Object o) { - if(o==null) return "null"; - else return o.toString(); + /** + * Obtains the matching request. + * @return null if this response has not been processed successfully + */ + public @CheckForNull Request getRequest() { + return request; + } + + /** + * Gets the return value of the response. + * @return null in case {@link #getException} is non-null + */ + public @Nullable RSP getReturnValue() { + return returnValue; + } + + /** + * Gets the exception thrown by the response. + * @return null in case {@link #getReturnValue} is non-null + */ + public @Nullable EXC getException() { + return exception; + } + + /** + * Gets the total time taken on the local side to send the request and receive the response. + * @return the total time in nanoseconds, or zero if unknown, including if this response is being sent to a remote request + */ + public long getTotalTime() { + return totalTime; } private static final long serialVersionUID = 1L; diff --git a/src/main/java/hudson/remoting/UnexportCommand.java b/src/main/java/hudson/remoting/UnexportCommand.java index 2f79c4311..dd2a416b4 100644 --- a/src/main/java/hudson/remoting/UnexportCommand.java +++ b/src/main/java/hudson/remoting/UnexportCommand.java @@ -51,7 +51,7 @@ protected void execute(Channel channel) { @Override public String toString() { - return super.toString(); + return "Unexport"; } private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java b/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java new file mode 100644 index 000000000..3c87515ed --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java @@ -0,0 +1,85 @@ +/* + * The MIT License + * + * Copyright 2018 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.remoting.util; + +import hudson.remoting.Channel; +import hudson.remoting.Command; +import hudson.remoting.Request; +import hudson.remoting.Response; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Channel listener which merely formats events to a logger. + * @since FIXME + */ +public class LoggingChannelListener extends Channel.Listener { + + @SuppressWarnings("NonConstantLogger") + private final Logger logger; + private final Level level; + + public LoggingChannelListener(Logger logger, Level level) { + this.logger = logger; + this.level = level; + } + + @Override + public void onClosed(Channel channel, IOException cause) { + if (logger.isLoggable(level)) { + logger.log(level, null, cause); + } + } + + @Override + public void onRead(Channel channel, Command cmd, long blockSize) { + if (logger.isLoggable(level)) { + logger.log(level, channel.getName() + " read " + blockSize + ": " + cmd); + } + } + + @Override + public void onWrite(Channel channel, Command cmd, long blockSize) { + if (logger.isLoggable(level)) { + logger.log(level, channel.getName() + " wrote " + blockSize + ": " + cmd); + } + } + + @Override + public void onResponse(Channel channel, Request req, Response rsp, long totalTime) { + if (logger.isLoggable(level)) { + logger.log(level, channel.getName() + " received response in " + totalTime / 1_000_000 + "ms: " + req); + } + } + + @Override + public void onJar(Channel channel, File jar) { + if (logger.isLoggable(level)) { + logger.log(level, channel.getName() + " sending " + jar + " of length " + jar.length()); + } + } +} diff --git a/src/test/java/hudson/remoting/ClassFilterTest.java b/src/test/java/hudson/remoting/ClassFilterTest.java index 266fc803c..be8c67770 100644 --- a/src/test/java/hudson/remoting/ClassFilterTest.java +++ b/src/test/java/hudson/remoting/ClassFilterTest.java @@ -267,6 +267,11 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound protected void execute(Channel channel) { // nothing to do here } + + @Override + public String toString() { + return "Security218"; + } } private String getAttack() { From 52dec976d60d35389a54c97ac0e45c8e8673ec50 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 13:04:18 -0500 Subject: [PATCH 094/243] [JENKINS-27035] More documentation. --- docs/logging.md | 16 +++++++++++++--- src/main/java/hudson/remoting/Channel.java | 9 +++++---- src/main/java/hudson/remoting/Command.java | 2 +- src/main/java/hudson/remoting/Request.java | 2 +- src/main/java/hudson/remoting/Response.java | 2 +- .../remoting/util/LoggingChannelListener.java | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/logging.md b/docs/logging.md index 2d0f02637..a75a45c37 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -1,5 +1,6 @@ -Logging -=== +# Logging and Diagnostics + +## Standard logging In Remoting logging is powered by the standard `java.util.logging` engine. The default behavior depends on the [Work Directory](workDir.md) mode. @@ -46,4 +47,13 @@ If `-agentLog` or `-slaveLog` are not specified, `${workDir}/${internalDir}/logs * Remoting does not perform automatic log rotation of this log file Particular Jenkins components use external features to provide better logging in the legacy mode. -E.g. Windows agent services generate logs using features provided by [Windows Service Wrapper (WinSW)](https://github.com/kohsuke/winsw/). \ No newline at end of file +E.g. Windows agent services generate logs using features provided by [Windows Service Wrapper (WinSW)](https://github.com/kohsuke/winsw/). + +## Event listeners + +The `hudson.remoting.Channel.Listener` interface can be used to intercept important events programmatically. +For example, you can be notified when a `Command` has been sent out over the channel, and obtain certain details. + +Normally administrators need not use this API directly. +[Jenkins core will print events to a standard logger](https://github.com/jenkinsci/jenkins/pull/3071), +and the [Support Core plugin will gather aggregate statistics](https://github.com/jenkinsci/support-core-plugin/pull/128). diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 97aeb45a3..9914ddb75 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -591,6 +591,7 @@ public void terminate(IOException e) { /** * Callback "interface" for changes in the state of {@link Channel}. + * @see #addListener * @see LoggingChannelListener */ public static abstract class Listener { @@ -609,7 +610,7 @@ public void onClosed(Channel channel, IOException cause) {} * @param channel a channel * @param cmd a command * @param blockSize the number of bytes used to read this command - * @since FIXME + * @since 3.17 */ public void onRead(Channel channel, Command cmd, long blockSize) {} @@ -619,7 +620,7 @@ public void onRead(Channel channel, Command cmd, long blockSize) {} * @param channel a channel * @param cmd a command * @param blockSize the number of bytes used to write this command - * @since FIXME + * @since 3.17 */ public void onWrite(Channel channel, Command cmd, long blockSize) {} @@ -629,7 +630,7 @@ public void onWrite(Channel channel, Command cmd, long blockSize) {} * @param req the original request * @param rsp the resulting response * @param totalTime the total time in nanoseconds taken to service the request - * @since FIXME + * @since 3.17 */ public void onResponse(Channel channel, Request req, Response rsp, long totalTime) {} @@ -638,7 +639,7 @@ public void onResponse(Channel channel, Request req, Response rsp, l * @param channel a channel * @param jar the JAR file from which code is being loaded remotely * @see Capability#supportsPrefetch - * @since FIXME + * @since 3.17 */ public void onJar(Channel channel, File jar) {} diff --git a/src/main/java/hudson/remoting/Command.java b/src/main/java/hudson/remoting/Command.java index f999634cb..a71a49862 100644 --- a/src/main/java/hudson/remoting/Command.java +++ b/src/main/java/hudson/remoting/Command.java @@ -43,7 +43,7 @@ * @author Kohsuke Kawaguchi * @see hudson.remoting.Channel.Listener#onRead * @see hudson.remoting.Channel.Listener#onWrite - * @since FIXME + * @since 3.17 */ public abstract class Command implements Serializable { /** diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 819351b24..57e6aa8f1 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -45,7 +45,7 @@ * * @author Kohsuke Kawaguchi * @see Response - * @since FIXME + * @since 3.17 */ public abstract class Request extends Command { /** diff --git a/src/main/java/hudson/remoting/Response.java b/src/main/java/hudson/remoting/Response.java index 4a2614a52..a0afcd67c 100644 --- a/src/main/java/hudson/remoting/Response.java +++ b/src/main/java/hudson/remoting/Response.java @@ -34,7 +34,7 @@ * * @author Kohsuke Kawaguchi * @see Request - * @since FIXME + * @since 3.17 */ public final class Response extends Command { /** diff --git a/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java b/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java index 3c87515ed..491ea3052 100644 --- a/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java +++ b/src/main/java/org/jenkinsci/remoting/util/LoggingChannelListener.java @@ -35,7 +35,7 @@ /** * Channel listener which merely formats events to a logger. - * @since FIXME + * @since 3.17 */ public class LoggingChannelListener extends Channel.Listener { From 67e015c1044927c462b902b6a3285801d1646b89 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 30 Jan 2018 19:09:05 +0100 Subject: [PATCH 095/243] [maven-release-plugin] prepare release remoting-3.17 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 146e9d804..332890fe6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.17-SNAPSHOT + 3.17 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.17 From 4c667805cff3c473755d869ec3a5e1cf0a60e243 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 30 Jan 2018 19:09:14 +0100 Subject: [PATCH 096/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 332890fe6..f604cc99c 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.17 + 3.18-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.17 + HEAD From 8ad714667fd8a71a42599c1b1da939be16df6c18 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 31 Jan 2018 18:11:30 +0100 Subject: [PATCH 097/243] Changelog: Noting 3.17 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1938a7543..2f1209897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.17 + +Release date: Jan 30, 2018 + +* [JENKINS-49027](https://issues.jenkins-ci.org/browse/JENKINS-49027) - +Improve reporting of JEP-200 violations in Remoting serialization. + * More info: [Announcement Blogpost](https://jenkins.io/blog/2018/01/13/jep-200/) +* [JENKINS-27035](https://issues.jenkins-ci.org/browse/JENKINS-27035) - +Add read/write events to [Channel.Listener](http://javadoc.jenkins.io/component/remoting/hudson/remoting/Channel.Listener.html) +to support collection of Request/Response statistics. + * More info: [Event Listeners Documentation](/docs/logging.md#event-listeners) + ##### 3.16 Release date: Jan 10, 2018 => Jenkins 2.102 From 5e328717fe3dcb92f75fc46c0ef5b1c68aeb1108 Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Tue, 6 Feb 2018 14:55:05 -0500 Subject: [PATCH 098/243] Add additional UncaughtExceptionHandler --- src/main/java/hudson/remoting/Engine.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 2231bf5b4..55fe3f58f 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -88,14 +88,13 @@ public class Engine extends Thread { private final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); public Thread newThread(final Runnable r) { - Thread t = defaultFactory.newThread(new Runnable() { - public void run() { - CURRENT.set(Engine.this); - r.run(); - } + Thread thread = defaultFactory.newThread(() -> { + CURRENT.set(Engine.this); + r.run(); }); - t.setDaemon(true); - return t; + thread.setDaemon(true); + thread.setUncaughtExceptionHandler((t, e) -> LOGGER.log(Level.SEVERE, "Uncaught exception in thread " + t, e)); + return thread; } }); From ea15ffdb1dad4de557f95ef599ac5c7062bf1d40 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 7 Feb 2018 08:44:44 +0100 Subject: [PATCH 099/243] Changelog: Noting JENKINS-45897 in the 3.17 release + 3.18 heads-up --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f1209897..9d69c0f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,16 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.18 + +Release date: Coming soon + +* [JENKINS-49415](https://issues.jenkins-ci.org/browse/JENKINS-49415) - +Add uncaught exception handler to the Engine's executor service + ##### 3.17 -Release date: Jan 30, 2018 +Release date: Jan 30, 2018 => Jenkins 2.106 * [JENKINS-49027](https://issues.jenkins-ci.org/browse/JENKINS-49027) - Improve reporting of JEP-200 violations in Remoting serialization. @@ -17,6 +24,8 @@ Improve reporting of JEP-200 violations in Remoting serialization. Add read/write events to [Channel.Listener](http://javadoc.jenkins.io/component/remoting/hudson/remoting/Channel.Listener.html) to support collection of Request/Response statistics. * More info: [Event Listeners Documentation](/docs/logging.md#event-listeners) +* [JENKINS-45897](https://issues.jenkins-ci.org/browse/JENKINS-45897) - +Improve string representation of `Request` types to improve log messages ##### 3.16 From c9673314efaa10207c6ce02cf1cb1a7b2ed8b808 Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Fri, 9 Feb 2018 14:09:46 -0500 Subject: [PATCH 100/243] [JENKINS-49472] Log channel name if command not constructed --- .../hudson/remoting/AbstractByteArrayCommandTransport.java | 6 ++---- .../hudson/remoting/AbstractByteBufferCommandTransport.java | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java index a1970a871..cc178ca69 100644 --- a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java @@ -63,10 +63,8 @@ public void handle(byte[] payload) { new ByteArrayInputStream(payload),channel.baseClassLoader,channel.classFilter)); receiver.handle(cmd); channel.notifyRead(cmd, payload.length); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to construct Command", e); - } catch (ClassNotFoundException e) { - LOGGER.log(Level.WARNING, "Failed to construct Command", e); + } catch (IOException | ClassNotFoundException e) { + LOGGER.log(Level.WARNING, "Failed to construct Command in channel " + channel.getName(), e); } } diff --git a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java index 21564922c..d81330ada 100644 --- a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java @@ -202,10 +202,8 @@ private void processCommand() throws IOException { Command cmd = Command.readFrom(channel, ois); receiver.handle(cmd); channel.notifyRead(cmd, readCommandSizes[0]); - } catch (IOException e1) { - LOGGER.log(Level.WARNING, "Failed to construct Command", e1); - } catch (ClassNotFoundException e11) { - LOGGER.log(Level.WARNING, "Failed to construct Command", e11); + } catch (IOException | ClassNotFoundException e) { + LOGGER.log(Level.WARNING, "Failed to construct Command in channel " + channel.getName(), e); } finally { int available = is.available(); if (available > 0) { From c886da1c340d98e1665031622b87a06576528ace Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 6 Mar 2018 13:39:42 -0500 Subject: [PATCH 101/243] Allow the recordCreateAt flag to be propagated to Channel.export, and disable it for RemoteClassLoader calls. --- src/main/java/hudson/remoting/Channel.java | 9 ++--- .../remoting/ImportedClassLoaderTable.java | 2 +- .../hudson/remoting/RemoteClassLoader.java | 2 +- .../remoting/RemoteInvocationHandler.java | 36 ++++++++++--------- src/main/java/hudson/remoting/Request.java | 7 +++- .../java/hudson/remoting/ChannelTest.java | 2 +- .../java/hudson/remoting/PipeWriterTest.java | 2 +- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 9914ddb75..a786ee317 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -537,7 +537,7 @@ protected Channel(@Nonnull ChannelBuilder settings, @Nonnull CommandTransport tr if(internalExport(IChannel.class, this, false)!=1) throw new AssertionError(); // export number 1 is reserved for the channel itself - remoteChannel = RemoteInvocationHandler.wrap(this,1,IChannel.class,true,false,false); + remoteChannel = RemoteInvocationHandler.wrap(this, 1, IChannel.class, true, false, false, true); this.remoteCapability = transport.getRemoteCapability(); this.pipeWriter = new PipeWriter(createPipeWriterExecutor()); @@ -727,7 +727,7 @@ private ExecutorService createPipeWriterExecutor() { */ @Override public T export(Class type, T instance) { - return export(type, instance, true, true); + return export(type, instance, true, true, true); } /** @@ -740,11 +740,12 @@ public T export(Class type, T instance) { * used by {@link RemoteClassLoader}. * * To create proxies for objects inside remoting, pass in false. + * @param recordCreatedAt as in {@link Command#Command(boolean)} * @return Reference to the exported instance wrapped by {@link RemoteInvocationHandler}. * {@code null} if the input instance is {@code null}. */ @Nullable - /*package*/ T export(Class type, @CheckForNull T instance, boolean userProxy, boolean userScope) { + /*package*/ T export(Class type, @CheckForNull T instance, boolean userProxy, boolean userScope, boolean recordCreatedAt) { if(instance==null) { return null; } @@ -763,7 +764,7 @@ public T export(Class type, T instance) { // either local side will auto-unexport, or the remote side will unexport when it's GC-ed boolean autoUnexportByCaller = exportedObjects.isRecording(); final int id = internalExport(type, instance, autoUnexportByCaller); - return RemoteInvocationHandler.wrap(null, id, type, userProxy, autoUnexportByCaller, userScope); + return RemoteInvocationHandler.wrap(null, id, type, userProxy, autoUnexportByCaller, userScope, recordCreatedAt); } /*package*/ int internalExport(Class clazz, T instance) { diff --git a/src/main/java/hudson/remoting/ImportedClassLoaderTable.java b/src/main/java/hudson/remoting/ImportedClassLoaderTable.java index 0ec50287b..f35257ea6 100644 --- a/src/main/java/hudson/remoting/ImportedClassLoaderTable.java +++ b/src/main/java/hudson/remoting/ImportedClassLoaderTable.java @@ -51,7 +51,7 @@ final class ImportedClassLoaderTable { */ @Nonnull public synchronized ClassLoader get(int oid) { - return get(RemoteInvocationHandler.wrap(channel,oid,IClassLoader.class,false,false,false)); + return get(RemoteInvocationHandler.wrap(channel, oid, IClassLoader.class, false, false, false, false)); } /** diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index dcf660148..430d8bfe1 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -759,7 +759,7 @@ public static IClassLoader export(@Nonnull ClassLoader cl, @Nonnull Channel loca // Remote classloader operates in the System scope (JENKINS-45294). // It's probably YOLO, but otherwise the termination calls may be unable // to execute correctly. - return local.export(IClassLoader.class, new ClassLoaderProxy(cl,local), false, false); + return local.export(IClassLoader.class, new ClassLoaderProxy(cl,local), false, false, false); } public static void pin(ClassLoader cl, Channel local) { diff --git a/src/main/java/hudson/remoting/RemoteInvocationHandler.java b/src/main/java/hudson/remoting/RemoteInvocationHandler.java index e849855f0..0776da4f2 100644 --- a/src/main/java/hudson/remoting/RemoteInvocationHandler.java +++ b/src/main/java/hudson/remoting/RemoteInvocationHandler.java @@ -118,7 +118,7 @@ final class RemoteInvocationHandler implements InvocationHandler, Serializable { /** * Diagnostic information that remembers where the proxy was created. */ - private final Throwable origin; + private final @CheckForNull Throwable origin; /** * Indicates that the handler operates in the user space. @@ -127,18 +127,22 @@ final class RemoteInvocationHandler implements InvocationHandler, Serializable { */ private final boolean userSpace; + /** @see Command#Command(boolean) */ + private final boolean recordCreatedAt; + /** * Creates a proxy that wraps an existing OID on the remote. */ - RemoteInvocationHandler(Channel channel, int id, boolean userProxy, + private RemoteInvocationHandler(Channel channel, int id, boolean userProxy, boolean autoUnexportByCaller, boolean userSpace, - Class proxyType) { + Class proxyType, boolean recordCreatedAt) { this.channel = channel == null ? null : channel.ref(); this.oid = id; this.userProxy = userProxy; - this.origin = new Exception("Proxy "+toString()+" was created for "+proxyType); + this.origin = recordCreatedAt ? new Exception("Proxy " + toString() + " was created for " + proxyType) : null; this.autoUnexportByCaller = autoUnexportByCaller; this.userSpace = userSpace; + this.recordCreatedAt = recordCreatedAt; } /** @@ -146,14 +150,15 @@ final class RemoteInvocationHandler implements InvocationHandler, Serializable { * * @param userProxy If {@code true} (recommended), all commands will be wrapped into {@link UserRequest}s. * @param userSpace If {@code true} (recommended), the requests will be executed in a user scope + * @param recordCreatedAt as in {@link Command#Command(boolean)} */ @Nonnull - static T wrap(Channel channel, int id, Class type, boolean userProxy, boolean autoUnexportByCaller, boolean userSpace) { + static T wrap(Channel channel, int id, Class type, boolean userProxy, boolean autoUnexportByCaller, boolean userSpace, boolean recordCreatedAt) { ClassLoader cl = type.getClassLoader(); // if the type is a JDK-defined type, classloader should be for IReadResolve if(cl==null || cl==ClassLoader.getSystemClassLoader()) cl = IReadResolve.class.getClassLoader(); - RemoteInvocationHandler handler = new RemoteInvocationHandler(channel, id, userProxy, autoUnexportByCaller, userSpace, type); + RemoteInvocationHandler handler = new RemoteInvocationHandler(channel, id, userProxy, autoUnexportByCaller, userSpace, type, recordCreatedAt); if (channel != null) { if (!autoUnexportByCaller) { UNEXPORTER.watch(handler); @@ -270,8 +275,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl boolean async = method.isAnnotationPresent(Asynchronous.class); RPCRequest req = userSpace - ? new UserRPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null) - : new RPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null); + ? new UserRPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null, recordCreatedAt) + : new RPCRequest(oid, method, args, userProxy ? dc.getClassLoader() : null, recordCreatedAt); try { if(userProxy) { if (async) channelOrFail().callAsync(req); @@ -353,7 +358,7 @@ private static class PhantomReferenceImpl extends PhantomReference implements Deleg @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "We're fine with the default null on the recipient side") private transient ClassLoader classLoader; - public RPCRequest(int oid, Method m, Object[] arguments) { - this(oid,m,arguments,null); - } - - public RPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { + private RPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl, boolean recordCreatedAt) { + super(recordCreatedAt); this.oid = oid; this.arguments = arguments; declaringClassName = m.getDeclaringClass().getName(); @@ -989,12 +991,12 @@ public String toString() { * this can be used to send a method call to user-level objects, and * classes for the parameters and the return value are sent remotely if needed. */ - static class UserRPCRequest extends RPCRequest { + private static class UserRPCRequest extends RPCRequest { private static final long serialVersionUID = -9185841650347902580L; - public UserRPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl) { - super(oid, m, arguments, cl); + UserRPCRequest(int oid, Method m, Object[] arguments, ClassLoader cl, boolean recordCreatedAt) { + super(oid, m, arguments, cl, recordCreatedAt); } // Same implementation as UserRequest diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 57e6aa8f1..8f3fd61d3 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -103,8 +103,13 @@ public abstract class Request ex @Deprecated /*package*/ volatile transient Future lastIo; - @SuppressFBWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification="That is why we synchronize on the class.") Request() { + this(true); + } + + @SuppressFBWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification="That is why we synchronize on the class.") + Request(boolean recordCreatedAt) { + super(recordCreatedAt); synchronized(Request.class) { id = nextId++; } diff --git a/src/test/java/hudson/remoting/ChannelTest.java b/src/test/java/hudson/remoting/ChannelTest.java index 1589a6d09..5aaf1639a 100644 --- a/src/test/java/hudson/remoting/ChannelTest.java +++ b/src/test/java/hudson/remoting/ChannelTest.java @@ -287,7 +287,7 @@ private static class RMIObjectExportedCallable implements Callable Date: Wed, 7 Mar 2018 14:47:33 -0500 Subject: [PATCH 102/243] [JENKINS-49994] Infrastructure for warning about Remoting serialization of anonymous inner classes and their ilk. --- .../AbstractByteArrayCommandTransport.java | 3 +- .../AbstractByteBufferCommandTransport.java | 3 +- ...tSynchronousByteArrayCommandTransport.java | 3 +- src/main/java/hudson/remoting/Capability.java | 6 + src/main/java/hudson/remoting/Channel.java | 3 +- .../java/hudson/remoting/ChannelBuilder.java | 3 +- .../java/hudson/remoting/ClassFilter.java | 7 + .../remoting/MultiClassLoaderSerializer.java | 2 + .../java/hudson/remoting/UserRequest.java | 3 +- .../impl/ChannelApplicationLayer.java | 3 +- .../remoting/util/AnonymousClassWarnings.java | 128 ++++++++++++++++++ 11 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java diff --git a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java index cc178ca69..dd597dd6e 100644 --- a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java @@ -7,6 +7,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * {@link CommandTransport} that works with {@code byte[]} instead of command object. @@ -77,7 +78,7 @@ public void terminate(IOException e) { @Override public final void write(Command cmd, boolean last) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(baos); cmd.writeTo(channel,oos); oos.close(); byte[] block = baos.toByteArray(); diff --git a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java index d81330ada..165398e00 100644 --- a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java @@ -31,6 +31,7 @@ import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; import org.jenkinsci.remoting.util.ByteBufferQueue; import org.jenkinsci.remoting.util.ByteBufferQueueOutputStream; import org.jenkinsci.remoting.util.FastByteBufferQueueInputStream; @@ -283,7 +284,7 @@ public final synchronized void setup(final Channel channel, final CommandReceive @Override public final void write(Command cmd, boolean last) throws IOException { ByteBufferQueueOutputStream bqos = new ByteBufferQueueOutputStream(sendStaging); - ObjectOutputStream oos = new ObjectOutputStream(bqos); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(bqos); try { cmd.writeTo(channel, oos); } finally { diff --git a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java index 5c42ceb90..e0a116ca9 100644 --- a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java @@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * {@link SynchronousCommandTransport} that works with {@code byte[]} instead of command object. @@ -42,7 +43,7 @@ public Command read() throws IOException, ClassNotFoundException { @Override public void write(Command cmd, boolean last) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(baos); cmd.writeTo(channel,oos); oos.close(); byte[] block = baos.toByteArray(); diff --git a/src/main/java/hudson/remoting/Capability.java b/src/main/java/hudson/remoting/Capability.java index 2c09e355d..cdfb54768 100644 --- a/src/main/java/hudson/remoting/Capability.java +++ b/src/main/java/hudson/remoting/Capability.java @@ -11,6 +11,7 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Represents additional features implemented on {@link Channel}. @@ -123,6 +124,11 @@ public void close() throws IOException { // TODO: Cannot invoke the private clear() method, but GC well do it for us. Not worse than the original solution // Here the code does not close the proxied stream OS on completion } + @Override + protected void annotateClass(Class c) throws IOException { + AnonymousClassWarnings.check(c); + super.annotateClass(c); + } }) { oos.writeObject(this); oos.flush(); diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 9914ddb75..a936bd638 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -140,7 +140,8 @@ public class Channel implements VirtualChannel, IChannel, Closeable { private final String name; private volatile boolean remoteClassLoadingAllowed, arbitraryCallableAllowed; /*package*/ final CallableDecoratorList decorators = new CallableDecoratorList(); - /*package*/ final ExecutorService executor; + @Restricted(NoExternalUse.class) + public final ExecutorService executor; /** * If non-null, the incoming link is already shut down, diff --git a/src/main/java/hudson/remoting/ChannelBuilder.java b/src/main/java/hudson/remoting/ChannelBuilder.java index cca7c18d4..a57912f11 100644 --- a/src/main/java/hudson/remoting/ChannelBuilder.java +++ b/src/main/java/hudson/remoting/ChannelBuilder.java @@ -28,6 +28,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import javax.annotation.CheckForNull; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Factory for {@link Channel}, including hand-shaking between two sides @@ -470,7 +471,7 @@ protected CommandTransport makeTransport(InputStream is, OutputStream os, Mode m if (cap.supportsChunking()) return new ChunkedCommandTransport(cap, mode.wrap(fis), mode.wrap(os), os); else { - ObjectOutputStream oos = new ObjectOutputStream(mode.wrap(os)); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(mode.wrap(os)); oos.flush(); // make sure that stream preamble is sent to the other end. avoids dead-lock return new ClassicCommandTransport( diff --git a/src/main/java/hudson/remoting/ClassFilter.java b/src/main/java/hudson/remoting/ClassFilter.java index f12a7490c..9fed64884 100644 --- a/src/main/java/hudson/remoting/ClassFilter.java +++ b/src/main/java/hudson/remoting/ClassFilter.java @@ -16,6 +16,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * Restricts what classes can be received through remoting. @@ -295,6 +296,12 @@ public boolean isBlacklisted(String name) { return false; } + @Override + public boolean isBlacklisted(Class c) { + AnonymousClassWarnings.check(c); + return false; + } + /** * Report the patterns that it's using to help users verify the use of custom filtering rule * and inspect its content at runtime if necessary. diff --git a/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java b/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java index 44dc99aa0..654ee77db 100644 --- a/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java +++ b/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java @@ -17,6 +17,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * {@link ObjectInputStream}/{@link ObjectOutputStream} pair that can handle object graph that spans across @@ -45,6 +46,7 @@ static final class Output extends ObjectOutputStream { @Override protected void annotateClass(Class c) throws IOException { + AnonymousClassWarnings.check(c); ClassLoader cl = c.getClassLoader(); if (cl==null) {// bootstrap classloader. no need to export. writeInt(TAG_SYSTEMCLASSLOADER); diff --git a/src/main/java/hudson/remoting/UserRequest.java b/src/main/java/hudson/remoting/UserRequest.java index 955b32e01..94333bb60 100644 --- a/src/main/java/hudson/remoting/UserRequest.java +++ b/src/main/java/hudson/remoting/UserRequest.java @@ -39,6 +39,7 @@ import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** * {@link Request} that can take {@link Callable} whose actual implementation @@ -245,7 +246,7 @@ private byte[] _serialize(Object o, final Channel channel) throws IOException { if (channel.remoteCapability.supportsMultiClassLoaderRPC()) oos = new MultiClassLoaderSerializer.Output(channel,baos); else - oos = new ObjectOutputStream(baos); + oos = AnonymousClassWarnings.checkingObjectOutputStream(baos); oos.writeObject(o); return baos.toByteArray(); diff --git a/src/main/java/org/jenkinsci/remoting/protocol/impl/ChannelApplicationLayer.java b/src/main/java/org/jenkinsci/remoting/protocol/impl/ChannelApplicationLayer.java index eea404011..33958b947 100644 --- a/src/main/java/org/jenkinsci/remoting/protocol/impl/ChannelApplicationLayer.java +++ b/src/main/java/org/jenkinsci/remoting/protocol/impl/ChannelApplicationLayer.java @@ -42,6 +42,7 @@ import java.util.concurrent.Future; import javax.annotation.Nullable; import org.jenkinsci.remoting.protocol.ApplicationLayer; +import org.jenkinsci.remoting.util.AnonymousClassWarnings; import org.jenkinsci.remoting.util.ByteBufferUtils; import org.jenkinsci.remoting.util.SettableFuture; import org.jenkinsci.remoting.util.ThrowableUtils; @@ -219,7 +220,7 @@ public void onReadClosed(IOException cause) throws IOException { public void start() throws IOException { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(BinarySafeStream.wrap(bos)); + ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(BinarySafeStream.wrap(bos)); try { oos.writeObject(new Capability()); } finally { diff --git a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java new file mode 100644 index 000000000..7bb8f39c7 --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java @@ -0,0 +1,128 @@ +/* + * The MIT License + * + * Copyright 2018 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.remoting.util; + +import hudson.remoting.Channel; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.URL; +import java.security.CodeSource; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.RejectedExecutionException; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * Issues warnings about attempts to (de-)serialize anonymous, local, or synthetic classes. + * @see https://issues.jenkins-ci.org/browse/JENKINS-49994 + */ +public class AnonymousClassWarnings { + + private static final Logger LOGGER = Logger.getLogger(AnonymousClassWarnings.class.getName()); + private static final Map, Boolean> checked = new WeakHashMap<>(); + + /** + * Checks a class which is being either serialized or deserialized. + * A warning will only be printed once per class per JVM session. + */ + public static void check(@Nonnull Class clazz) { + synchronized (checked) { + if (checked.containsKey(clazz)) { + return; // fast path + } + } + Channel channel = Channel.current(); + if (channel == null) { + doCheck(clazz); + } else { + // May not call methods like Class#isAnonymousClass synchronously, since these can in turn trigger remote class loading. + try { + channel.executor.submit((Runnable) () -> doCheck(clazz)); + } catch (RejectedExecutionException x) { + // never mind, we tried + } + } + } + + private static void doCheck(@Nonnull Class c) { + if (c.isAnonymousClass()) { // e.g., pkg.Outer$1 + warn(c, "anonymous"); + } else if (c.isLocalClass()) { // e.g., pkg.Outer$1Local + warn(c, "local"); + } else if (c.isSynthetic()) { // e.g., pkg.Outer$$Lambda$1/12345678 + warn(c, "synthetic"); + } + } + + private static void warn(@Nonnull Class c, String kind) { + String name = c.getName(); + String codeSource = codeSource(c); + // Need to be very defensive about calling anything while holding this lock, lest we trigger class loading-related deadlocks. + boolean doWarn; + synchronized (checked) { + doWarn = checked.put(c, true) == null; + } + if (doWarn) { + if (codeSource == null) { + // TODO create jenkins.io/redirect + LOGGER.warning("JENKINS-49795: attempt to (de-)serialize " + kind + " class " + name); + } else { + // most easily tracked back to source using javap -classpath -l '' + LOGGER.warning("JENKINS-49795: attempt to (de-)serialize " + kind + " class " + name + " in " + codeSource); + } + } + } + + private static @CheckForNull String codeSource(@Nonnull Class c) { + CodeSource cs = c.getProtectionDomain().getCodeSource(); + if (cs == null) { + return null; + } + URL loc = cs.getLocation(); + if (loc == null) { + return null; + } + return loc.toString(); + } + + /** + * Like {@link ObjectOutputStream#ObjectOutputStream(OutputStream)} but applies {@link #check} when writing classes. + */ + public static @Nonnull ObjectOutputStream checkingObjectOutputStream(@Nonnull OutputStream outputStream) throws IOException { + return new ObjectOutputStream(outputStream) { + @Override + protected void annotateClass(Class c) throws IOException { + check(c); + super.annotateClass(c); + } + }; + } + + private AnonymousClassWarnings() {} + +} From b9b5f307a4c78b6ef9facffdb43484935661343c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Mar 2018 16:13:10 -0500 Subject: [PATCH 103/243] Make use of https://github.com/jenkins-infra/jenkins.io/pull/1439 for messaging. --- .../jenkinsci/remoting/util/AnonymousClassWarnings.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java index 7bb8f39c7..140d934f6 100644 --- a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java +++ b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java @@ -39,7 +39,7 @@ /** * Issues warnings about attempts to (de-)serialize anonymous, local, or synthetic classes. - * @see https://issues.jenkins-ci.org/browse/JENKINS-49994 + * @see More information */ public class AnonymousClassWarnings { @@ -89,11 +89,10 @@ private static void warn(@Nonnull Class c, String kind) { } if (doWarn) { if (codeSource == null) { - // TODO create jenkins.io/redirect - LOGGER.warning("JENKINS-49795: attempt to (de-)serialize " + kind + " class " + name); + LOGGER.warning("Attempt to (de-)serialize " + kind + " class " + name + "; see: https://jenkins.io/redirect/serialization-of-anonymous-classes/"); } else { // most easily tracked back to source using javap -classpath -l '' - LOGGER.warning("JENKINS-49795: attempt to (de-)serialize " + kind + " class " + name + " in " + codeSource); + LOGGER.warning("Attempt to (de-)serialize " + kind + " class " + name + " in " + codeSource + "; see: https://jenkins.io/redirect/serialization-of-anonymous-classes/"); } } } From 2fbfb5b25b68cfe7f57394a2e51dd690c912a870 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 8 Mar 2018 17:27:16 +0100 Subject: [PATCH 104/243] Update Remoting to Parent POM 1.40 with integrated FindBugs check (#257) * Update Remoting to Parent POM 1.40 with integrated FindBugs check * Use the released version of parent POM --- pom.xml | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index f604cc99c..58edc1286 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci jenkins - 1.39 + 1.40 @@ -66,7 +66,9 @@ THE SOFTWARE. UTF-8 UTF-8 UTF-8 - true + Max + ${basedir}/src/findbugs/excludeFilter.xml + Low 1.4 @@ -416,24 +418,6 @@ THE SOFTWARE. - - org.codehaus.mojo - findbugs-maven-plugin - - true - Max - ${basedir}/src/findbugs/excludeFilter.xml - Low - - - - verify - - check - - - - maven-surefire-plugin From f26289881130caf4a037e84c07f50d3758e89b00 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 9 Mar 2018 17:26:22 +0100 Subject: [PATCH 105/243] [maven-release-plugin] prepare release remoting-3.18 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 58edc1286..5a124c244 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.18-SNAPSHOT + 3.18 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.18 From 1c50806bec8d3367ea78b89ca31fb5139fa6f6fb Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 9 Mar 2018 17:26:31 +0100 Subject: [PATCH 106/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5a124c244..ac8dee891 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.18 + 3.19-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.18 + HEAD From 0547952b90d61120ea81d6f64d97eccf36d36774 Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Fri, 9 Mar 2018 12:10:20 -0500 Subject: [PATCH 107/243] Changelog: Noting 3.18 --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d69c0f6e..7820521f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,16 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.18 -Release date: Coming soon +Release date: Mar 9, 2018 * [JENKINS-49415](https://issues.jenkins-ci.org/browse/JENKINS-49415) - Add uncaught exception handler to the Engine's executor service +* [JENKINS-49472](https://issues.jenkins-ci.org/browse/JENKINS-49472) - +Log channel name in StreamCorruptedExceptions +* [JENKINS-48561](https://issues.jenkins-ci.org/browse/JENKINS-48561) - +Give precedence to proxy exclusion list system property over environmental vars. +* [JENKINS-49994](https://issues.jenkins-ci.org/browse/JENKINS-49994) - +Add infrastructure for warning about remoting serialization of anonymous inner classes. ##### 3.17 From 30ee99356c8ed1a12dc3747af31c61e037c9c3c2 Mon Sep 17 00:00:00 2001 From: pvtuan10 Date: Sat, 10 Mar 2018 14:16:07 +0800 Subject: [PATCH 108/243] Display Remoting version in the agent log when starting up the agent --- src/main/java/hudson/remoting/Engine.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 55fe3f58f..9fd8a9072 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -232,6 +232,7 @@ public synchronized void startEngine() throws IOException { * This method can be used for testing startup logic. */ /*package*/ void startEngine(boolean dryRun) throws IOException { + LOGGER.log(Level.INFO, "Using Remoting version: {0}", Launcher.VERSION); @CheckForNull File jarCacheDirectory = null; // Prepare the working directory if required From 6257d7e13672b9b56f3e76c4abbdb52e69e3daad Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Mon, 12 Mar 2018 09:17:56 -0400 Subject: [PATCH 109/243] Note PR 258 in 3.18 changelog (#261) * Note PR 258 in 3.18 changelog * Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7820521f3..e9405a3a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Log channel name in StreamCorruptedExceptions Give precedence to proxy exclusion list system property over environmental vars. * [JENKINS-49994](https://issues.jenkins-ci.org/browse/JENKINS-49994) - Add infrastructure for warning about remoting serialization of anonymous inner classes. +* [PR #258](https://github.com/jenkinsci/remoting/pull/258) - +Improve performance by disabling expensive diagnostics in `RemoteClassLoader` ##### 3.17 From 56782665ff6247a7f31f72d512afc271dc5d16d8 Mon Sep 17 00:00:00 2001 From: Rebecca Ysteboe Date: Wed, 21 Mar 2018 09:52:35 -0400 Subject: [PATCH 110/243] Add jenkins core release version for 3.18 (#264) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9405a3a2..8c84dc4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.18 -Release date: Mar 9, 2018 +Release date: Mar 9, 2018 => Jenkins 2.112 * [JENKINS-49415](https://issues.jenkins-ci.org/browse/JENKINS-49415) - Add uncaught exception handler to the Engine's executor service From a8cb098a37c4c91701e45cdaba86f3cdcb0f3656 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 22 Mar 2018 08:45:52 -0400 Subject: [PATCH 111/243] [JENKINS-50237] When returning an exception to a UserRequest, always include a ProxyException (#263) * [JENKINS-50237] When returning an exception to a UserRequest, always include a ProxyException, since with class filtering we may need it. * Forgot a call to Channel.attachCallSiteStackTrace. --- src/main/java/hudson/remoting/Capability.java | 21 ++++- src/main/java/hudson/remoting/Channel.java | 9 +- .../java/hudson/remoting/UserRequest.java | 83 ++++++++++++++++++- 3 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/main/java/hudson/remoting/Capability.java b/src/main/java/hudson/remoting/Capability.java index cdfb54768..fb5a16513 100644 --- a/src/main/java/hudson/remoting/Capability.java +++ b/src/main/java/hudson/remoting/Capability.java @@ -36,7 +36,7 @@ public final class Capability implements Serializable { } public Capability() { - this(MASK_MULTI_CLASSLOADER|MASK_PIPE_THROTTLING|MASK_MIMIC_EXCEPTION|MASK_PREFETCH|GREEDY_REMOTE_INPUTSTREAM| MASK_PROXY_WRITER_2_35|MASK_CHUNKED_ENCODING); + this(MASK_MULTI_CLASSLOADER | MASK_PIPE_THROTTLING | MASK_MIMIC_EXCEPTION | MASK_PREFETCH | GREEDY_REMOTE_INPUTSTREAM | MASK_PROXY_WRITER_2_35 | MASK_CHUNKED_ENCODING | PROXY_EXCEPTION_FALLBACK); } /** @@ -111,6 +111,15 @@ public boolean supportsProxyWriter2_35() { return (mask & MASK_PROXY_WRITER_2_35) != 0; } + /** + * Supports {@link ProxyException} as a fallback when failing to deserialize {@link UserRequest} exceptions. + * @since 3.19 + * @see JENKINS-50237 + */ + public boolean supportsProxyExceptionFallback() { + return (mask & PROXY_EXCEPTION_FALLBACK) != 0; + } + //TODO: ideally preamble handling needs to be reworked in order to avoid FB suppression /** * Writes out the capacity preamble. @@ -233,6 +242,8 @@ public void close() throws IOException { */ private static final long MASK_CHUNKED_ENCODING = 1L << 7; + private static final long PROXY_EXCEPTION_FALLBACK = 1L << 8; + static final byte[] PREAMBLE = "<===[JENKINS REMOTING CAPACITY]===>".getBytes(StandardCharsets.UTF_8); public static final Capability NONE = new Capability(0); @@ -296,6 +307,14 @@ public String toString() { } sb.append("Chunked encoding"); } + if ((mask & PROXY_EXCEPTION_FALLBACK) != 0) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append("ProxyException fallback"); + } sb.append('}'); return sb.toString(); } diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 3cbfb6a8e..946935816 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -951,7 +951,7 @@ V call(Callable callable) throws IOException, T, InterruptedException { UserRequest request=null; try { request = new UserRequest(this, callable); - UserResponse r = request.call(this); + UserRequest.ResponseToUserRequest r = request.call(this); return r.retrieve(this, UserRequest.getClassLoader(callable)); // re-wrap the exception so that we can capture the stack trace of the caller. @@ -982,9 +982,10 @@ Future callAsync(final Callable callable) throws IOException { + "The channel is closing down or has closed down", getCloseRequestCause()); } - final Future> f = new UserRequest(this, callable).callAsync(this); - return new FutureAdapter>(f) { - protected V adapt(UserResponse r) throws ExecutionException { + final Future> f = new UserRequest(this, callable).callAsync(this); + return new FutureAdapter>(f) { + @Override + protected V adapt(UserRequest.ResponseToUserRequest r) throws ExecutionException { try { return r.retrieve(Channel.this, UserRequest.getClassLoader(callable)); } catch (Throwable t) {// really means catch(T t) diff --git a/src/main/java/hudson/remoting/UserRequest.java b/src/main/java/hudson/remoting/UserRequest.java index 94333bb60..ad53a8ab5 100644 --- a/src/main/java/hudson/remoting/UserRequest.java +++ b/src/main/java/hudson/remoting/UserRequest.java @@ -51,7 +51,7 @@ * * @author Kohsuke Kawaguchi */ -final class UserRequest extends Request,EXC> { +final class UserRequest extends Request,EXC> { private static final Logger LOGGER = Logger.getLogger(UserRequest.class.getName()); @@ -147,7 +147,8 @@ public void checkIfCanBeExecutedOnChannel(Channel channel) throws IOException { } private static boolean workaroundDone = false; - protected UserResponse perform(Channel channel) throws EXC { + @Override + protected ResponseToUserRequest perform(Channel channel) throws EXC { try { ClassLoader cl = channel.importedClassLoaders.get(classLoaderProxy); @@ -219,10 +220,22 @@ protected UserResponse perform(Channel channel) throws EXC { Channel.setCurrent(oldc); } - return new UserResponse(serialize(r,channel),false); + byte[] response = serialize(r,channel); + return channel.remoteCapability.supportsProxyExceptionFallback() ? new NormalResponse<>(response) : new UserResponse<>(response,false); } catch (Throwable e) { // propagate this to the calling process try { + if (channel.remoteCapability.supportsProxyExceptionFallback()) { + byte[] rawResponse = null; + try { + rawResponse = _serialize(e, channel); + } catch (NotSerializableException x) { + // OK + } + byte[] proxyResponse = serialize(new ProxyException(e), channel); + return new ExceptionResponse<>(rawResponse, proxyResponse); + } + // Remote side is old, so need to use less robust variant: byte[] response; try { response = _serialize(e, channel); @@ -291,9 +304,71 @@ public String toString() { } private static final long serialVersionUID = 1L; + + interface ResponseToUserRequest extends Serializable { + /** + * Deserializes the response byte stream into an object. + */ + RSP retrieve(Channel channel, ClassLoader cl) throws IOException, ClassNotFoundException, EXC; + } + + private static final class NormalResponse implements ResponseToUserRequest { + private static final long serialVersionUID = 1L; + private final byte[] response; + NormalResponse(byte[] response) { + this.response = response; + } + @SuppressWarnings("unchecked") + @Override + public RSP retrieve(Channel channel, ClassLoader cl) throws IOException, ClassNotFoundException, EXC { + Channel old = Channel.setCurrent(channel); + try { + return (RSP) deserialize(channel, response, cl); + } finally { + Channel.setCurrent(old); + } + } + } + + private static final class ExceptionResponse implements ResponseToUserRequest { + private static final long serialVersionUID = 1L; + private @CheckForNull final byte[] rawResponse; + private final byte[] proxyResponse; + ExceptionResponse(@CheckForNull byte[] rawResponse, byte[] proxyResponse) { + this.rawResponse = rawResponse; + this.proxyResponse = proxyResponse; + } + @SuppressWarnings("unchecked") + @Override + public RSP retrieve(Channel channel, ClassLoader cl) throws IOException, ClassNotFoundException, EXC { + Channel old = Channel.setCurrent(channel); + try { + Throwable t = null; + if (rawResponse != null) { + try { + t = (Throwable) deserialize(channel, rawResponse, cl); + } catch (Exception x) { + LOGGER.log(Level.FINE, "could not deserialize exception response", x); + } + } + if (t == null) { + t = (Throwable) deserialize(channel, proxyResponse, cl); + } + channel.attachCallSiteStackTrace(t); + throw (EXC) t; + } finally { + Channel.setCurrent(old); + } + } + } + } -final class UserResponse implements Serializable { +/** + * @deprecated Used only when we lack {@link Capability#supportsProxyExceptionFallback}. + */ +@Deprecated +final class UserResponse implements UserRequest.ResponseToUserRequest { private final byte[] response; private final boolean isException; From c567ff80624a2e5bff0bd94c46ac471f5f257864 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 22 Mar 2018 14:04:50 +0100 Subject: [PATCH 112/243] Changelog: Noting Remoting 3.19 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c84dc4c9..0377eeae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.19 + +Release date: Mar 22, 2018 + +* [JENKINS-49618](https://issues.jenkins-ci.org/browse/JENKINS-49618) - +Display Remoting version in the agent log when starting up the agent +* [JENKINS-50237](https://issues.jenkins-ci.org/browse/JENKINS-50237) - +Include a ProxyException to responses when returning an exception to a `UserRequest`s. + * This allows returning the exception details even if Jenkins 2.102+ refuses to deserialize the original exception + due to the whitelist violation + * More info: [JEP-200 announcement](https://jenkins.io/blog/2018/03/15/jep-200-lts/) + ##### 3.18 Release date: Mar 9, 2018 => Jenkins 2.112 From 23b6bb71eb74fcb26cc5aed05bf4b4bd36e9f589 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 22 Mar 2018 14:16:36 +0100 Subject: [PATCH 113/243] [maven-release-plugin] prepare release remoting-3.19 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ac8dee891..ef81fe748 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.19-SNAPSHOT + 3.19 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.19 From c5b88419956251043d87f7e35d06a7b18f86e5a5 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 22 Mar 2018 14:16:45 +0100 Subject: [PATCH 114/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ef81fe748..7f96f7d15 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.19 + 3.20-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.19 + HEAD From 69b9177da37b3b2dc0bf67048f4f8e4892143893 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 18 Apr 2018 15:39:54 +0200 Subject: [PATCH 115/243] [maven-release-plugin] prepare release remoting-3.20 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7f96f7d15..b34fde00f 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.20-SNAPSHOT + 3.20 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.20 From ec1480f149019f1e309039e9b1bbc71e55bfee16 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 18 Apr 2018 15:40:03 +0200 Subject: [PATCH 116/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b34fde00f..0b7ac36af 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.20 + 3.21-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.20 + HEAD From 6520b10588dfaa1036fecef0697d67d8d7689ff1 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 18 Apr 2018 16:09:19 +0200 Subject: [PATCH 117/243] Changelog: Noting 3.20 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0377eeae9..e41aa81ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.20 + +Release date: Apr 18, 2018 + +* Refresh the Code-signing certificate +* No functional changes + ##### 3.19 Release date: Mar 22, 2018 From 3a12fc5f16b0f10cca700b95b9d3db561fe8d926 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 09:23:32 +0200 Subject: [PATCH 118/243] [JENKINS-50933] - Initial commit --- Jenkinsfile | 91 ++++++++++++++++---------------- src/test/it/essentials.yml | 19 +++++++ src/test/it/packager-config.yml | 30 +++++++++++ src/test/it/pom.xml | 93 +++++++++++++++++++++++++++++++++ src/test/it/run.sh | 4 ++ src/test/it/run_pct.sh | 6 +++ 6 files changed, 198 insertions(+), 45 deletions(-) create mode 100644 src/test/it/essentials.yml create mode 100644 src/test/it/packager-config.yml create mode 100644 src/test/it/pom.xml create mode 100644 src/test/it/run.sh create mode 100644 src/test/it/run_pct.sh diff --git a/Jenkinsfile b/Jenkinsfile index 179388e56..efbea4931 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,56 +1,57 @@ #!/usr/bin/env groovy +// TODO: restore original tests (which just fail now) + /* Only keep the 10 most recent builds. */ properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']]]) +node("docker && highmem") { + stage("Checkout") { + infra.checkout() + } -/* These platforms correspond to labels in ci.jenkins.io, see: - * https://github.com/jenkins-infra/documentation/blob/master/ci.adoc - */ -List platforms = ['linux', 'windows'] -Map branches = [:] - -for (int i = 0; i < platforms.size(); ++i) { - String label = platforms[i] - branches[label] = { - node(label) { - timestamps { - stage('Checkout') { - checkout scm - } - - stage('Build') { - withEnv([ - "JAVA_HOME=${tool 'jdk8'}", - "PATH+MVN=${tool 'mvn'}/bin", - 'PATH+JDK=$JAVA_HOME/bin', - ]) { - timeout(30) { - String command = "mvn --batch-mode clean install -Dmaven.test.failure.ignore=true ${infra.isRunningOnJenkinsInfra() ? '-s settings-azure.xml' : ''} -e" - if (isUnix()) { - sh command - } - else { - bat command - } - } - } - } - - stage('Archive') { - /* Archive the test results */ - junit '**/target/surefire-reports/TEST-*.xml' - - if (label == 'linux') { - archiveArtifacts artifacts: 'target/**/*.jar' - findbugs pattern: '**/target/findbugsXml.xml' - } - } + def mvnSettingsFile = "${pwd tmp: true}/settings-azure.xml" + def mvnSettingsFileFound = infra.retrieveMavenSettingsFile(mvnSettingsFile) + def outputWAR + def metadataPath + + dir("src/test/it") { + // TODO: convert to a library method + def outputWARpattern = "target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war" + stage("Build Custom WAR") { + List mavenOptions = [ + '--batch-mode', '--errors', + 'clean', 'install', + "-Dcustom-war-packager.batchMode=true", + "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" + ] + + if (mvnSettingsFileFound) { + mavenOptions << "-s" + mavenOptions << "${mvnSettingsFile}" + mavenOptions << "-Dcustom-war-packager.mvnSettingsFile=${mvnSettingsFile}" } + + infra.runMaven(mavenOptions) + archiveArtifacts artifacts: outputWARpattern + + // Pass variables for the next steps + outputWAR = pwd() + "/" + outputWARpattern + metadataPath = pwd() + "/essentials.yml" } } -} -/* Execute our platforms in parallel */ -parallel(branches) + def fileUri = "file://" + outputWAR + stage("Run ATH") { + dir("ath") { + runATH jenkins: fileUri, metadataFile: metadataPath + } + } + + stage("Run PCT") { + runPCT jenkins: fileUri, metadataFile: metadataPath, + pctUrl: "docker://jenkins/pct:pr74", + javaOptions: ["-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"] + } +} diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml new file mode 100644 index 000000000..5f9e0d194 --- /dev/null +++ b/src/test/it/essentials.yml @@ -0,0 +1,19 @@ +--- +ath: + useLocalSnapshots: false + athRevision: "06caf67d37d945ec5228a80d73714fac692c28d3" + tests: + - "plugins.SshSlavesPluginTest" + - "core.SlaveTest" + categories: + - "org.jenkinsci.test.acceptance.junit.SmokeTest" +pct: + useLocalSnapshots: false + jth: + version: 2.38 + passCustomJenkinsWAR: true + plugins: + - "ssh-slaves" + - "command-launcher" + - "windows-slaves" + # TODO: JTH/PCT does not work reliably against non-bundled plugins (if something else is bundled) - "compress-artifacts" diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml new file mode 100644 index 000000000..1d25063e4 --- /dev/null +++ b/src/test/it/packager-config.yml @@ -0,0 +1,30 @@ +bundle: + groupId: "io.jenkins.tools.war-packager.demo" + artifactId: "jenkins-all-latest" + vendor: "Jenkins project" + title: "Remoting Integration Tests WAR" + description: "Just a Jenkins WAR, which includes all latest libs from master branches" +war: + groupId: "org.jenkins-ci.main" + artifactId: "jenkins-war" + source: + version: "2.107.1" +plugins: + - groupId: "org.jenkins-ci.plugins" + artifactId: "ssh-slaves" + source: + version: "1.25" + - groupId: "org.jenkins-ci.plugins" + artifactId: "command-launcher" + source: + version: "1.2" + - groupId: "org.jenkins-ci.plugins" + artifactId: "windows-slaves" + source: + version: "1.3.1" +libPatches: + - groupId: "org.jenkins-ci.main" + artifactId: "remoting" + source: + git: https://github.com/jenkinsci/remoting.git + diff --git a/src/test/it/pom.xml b/src/test/it/pom.xml new file mode 100644 index 000000000..4649524ac --- /dev/null +++ b/src/test/it/pom.xml @@ -0,0 +1,93 @@ + + + + + 4.0.0 + + io.jenkins.tools.custom-war-packager.demo + remoting-it + 1.0-SNAPSHOT + + pom + + + + false + true + cloudbees-internal-snapshots + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + package + + io.jenkins.tools.custom-war-packager:custom-war-packager-maven-plugin:0.1-alpha-3 + + + + + + + io.jenkins.tools.custom-war-packager + custom-war-packager-maven-plugin + 0.1-alpha-4-20180421.080907-4 + + + package + + custom-war + + + packager-config.yml + 1.0-remoting-it-SNAPSHOT + ${custom-war-packager.mvnSettingsFile} + ${custom-war-packager.batchMode} + ${custom-war-packager.deploySnapshots} + ${custom-war-packager.deploySnapshotsRepositoryId} + + + + + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + diff --git a/src/test/it/run.sh b/src/test/it/run.sh new file mode 100644 index 000000000..86a968730 --- /dev/null +++ b/src/test/it/run.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +JENKINS_HOME=$(pwd)/work java -jar target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war \ + --httpPort=8080 --prefix=/jenkins + diff --git a/src/test/it/run_pct.sh b/src/test/it/run_pct.sh new file mode 100644 index 000000000..d8d635c97 --- /dev/null +++ b/src/test/it/run_pct.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +docker run --rm -v maven-repo:/root/.m2 -v $(pwd)/out:/pct/out \ + -v $(pwd)/target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war:/pct/jenkins.war:ro \ + -e ARTIFACT_ID=copyartifact \ + -e INSTALL_BUNDLED_SNAPSHOTS=true \ + jenkins/pct From 11eef3bfed3ed79710bf939af3b8ec805e6ffd1b Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 09:52:00 +0200 Subject: [PATCH 119/243] [JENKINS-50933] - Fix WAR Naming --- Jenkinsfile | 2 +- src/test/it/packager-config.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index efbea4931..93989a527 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -18,7 +18,7 @@ node("docker && highmem") { dir("src/test/it") { // TODO: convert to a library method - def outputWARpattern = "target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war" + def outputWARpattern = "target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war" stage("Build Custom WAR") { List mavenOptions = [ '--batch-mode', '--errors', diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index 1d25063e4..ea6e8dd29 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -1,6 +1,6 @@ bundle: groupId: "io.jenkins.tools.war-packager.demo" - artifactId: "jenkins-all-latest" + artifactId: "jenkins-remoting-it" vendor: "Jenkins project" title: "Remoting Integration Tests WAR" description: "Just a Jenkins WAR, which includes all latest libs from master branches" From bd791bf3c1056910cf60db31ec5be42dfeca7d3f Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 10:23:15 +0200 Subject: [PATCH 120/243] [JENKINS-50933] - ATH requires newer version of Credentials plugin than the bundled one --- src/test/it/packager-config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index ea6e8dd29..d30825844 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -22,6 +22,10 @@ plugins: artifactId: "windows-slaves" source: version: "1.3.1" + - groupId: "org.jenkins-ci.plugins" + artifactId: "credentials" + source: + version: "2.1.16" libPatches: - groupId: "org.jenkins-ci.main" artifactId: "remoting" From f31a5ab8e8397c4f9b4eca1e68f357d25fe92613 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 10:23:35 +0200 Subject: [PATCH 121/243] [JENKINS-50933] - Fix the runPCT() sample --- src/test/it/run_pct.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/it/run_pct.sh b/src/test/it/run_pct.sh index d8d635c97..f939aa58f 100644 --- a/src/test/it/run_pct.sh +++ b/src/test/it/run_pct.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash docker run --rm -v maven-repo:/root/.m2 -v $(pwd)/out:/pct/out \ - -v $(pwd)/target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war:/pct/jenkins.war:ro \ - -e ARTIFACT_ID=copyartifact \ + -v $(pwd)/target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war:/pct/jenkins.war:ro \ + -e ARTIFACT_ID=windows-slaves \ -e INSTALL_BUNDLED_SNAPSHOTS=true \ jenkins/pct From 55be2720d55dff7e9f1116d9e12d268749967096 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 10:58:42 +0200 Subject: [PATCH 122/243] [JENKINS-50933] - Use relative path to Remoting source codes in integration tests --- src/test/it/packager-config.yml | 3 +-- src/test/it/pom.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index d30825844..09c14f543 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -30,5 +30,4 @@ libPatches: - groupId: "org.jenkins-ci.main" artifactId: "remoting" source: - git: https://github.com/jenkinsci/remoting.git - + dir: ../../.. diff --git a/src/test/it/pom.xml b/src/test/it/pom.xml index 4649524ac..4888db8ff 100644 --- a/src/test/it/pom.xml +++ b/src/test/it/pom.xml @@ -62,7 +62,7 @@ io.jenkins.tools.custom-war-packager custom-war-packager-maven-plugin - 0.1-alpha-4-20180421.080907-4 + 0.1-alpha-4-20180423.085628-5 package From fa4442cf4981c3ff738430c31310b68c4c319b70 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 11:28:03 +0200 Subject: [PATCH 123/243] [JENKINS-50933] - Use LTS with Security fixes as a baseline --- src/test/it/packager-config.yml | 2 +- src/test/it/{run.sh => run_war.sh} | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/test/it/{run.sh => run_war.sh} (61%) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index 09c14f543..d881e3991 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -8,7 +8,7 @@ war: groupId: "org.jenkins-ci.main" artifactId: "jenkins-war" source: - version: "2.107.1" + version: "2.107.2" plugins: - groupId: "org.jenkins-ci.plugins" artifactId: "ssh-slaves" diff --git a/src/test/it/run.sh b/src/test/it/run_war.sh similarity index 61% rename from src/test/it/run.sh rename to src/test/it/run_war.sh index 86a968730..716212852 100644 --- a/src/test/it/run.sh +++ b/src/test/it/run_war.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -JENKINS_HOME=$(pwd)/work java -jar target/custom-war-packager-maven-plugin/output/target/jenkins-war-1.0-remoting-it-SNAPSHOT.war \ +JENKINS_HOME=$(pwd)/work java -jar target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war \ --httpPort=8080 --prefix=/jenkins - From 2dff938f7544aa8975f36887e61499147597d2e0 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 11:31:11 +0200 Subject: [PATCH 124/243] [JENKINS-50933] - Also use SSH Credentials 1.12 --- src/test/it/packager-config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index d881e3991..2396acc63 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -26,6 +26,10 @@ plugins: artifactId: "credentials" source: version: "2.1.16" + - groupId: "org.jenkins-ci.plugins" + artifactId: "ssh-credentials" + source: + version: "1.12" libPatches: - groupId: "org.jenkins-ci.main" artifactId: "remoting" From f90e2b491d5aa980d3c9da80abd15d678e2ad469 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 11:37:31 +0200 Subject: [PATCH 125/243] [JENKINS-50933] - Update the description for IT build --- src/test/it/packager-config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index 2396acc63..c34766413 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -3,7 +3,7 @@ bundle: artifactId: "jenkins-remoting-it" vendor: "Jenkins project" title: "Remoting Integration Tests WAR" - description: "Just a Jenkins WAR, which includes all latest libs from master branches" + description: "Jenkins WAR, which bundles local Remoting build and plugin for PCT" war: groupId: "org.jenkins-ci.main" artifactId: "jenkins-war" @@ -31,6 +31,7 @@ plugins: source: version: "1.12" libPatches: + # Override Remoting by a locally built version - groupId: "org.jenkins-ci.main" artifactId: "remoting" source: From ecd669b20f0192fe7fc2f00b88049ea4fd35935d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 11:48:34 +0200 Subject: [PATCH 126/243] [JENKINS-50933] - Note TODO to cleanup custom logging once runPCT() sets the flag OOTB --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 93989a527..171927951 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -50,6 +50,7 @@ node("docker && highmem") { } stage("Run PCT") { + //TODO: Remove Slf4jMavenTransferListener option once runPCT() invokes it by default runPCT jenkins: fileUri, metadataFile: metadataPath, pctUrl: "docker://jenkins/pct:pr74", javaOptions: ["-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"] From 2844516d80ab725ece7998069158448d598c42c3 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 12:00:26 +0200 Subject: [PATCH 127/243] [JENKINS-50933] - highmem machines on Jenkins.io are not single-shot ones --- Jenkinsfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 171927951..fe57252c0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,6 +7,11 @@ properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']]]) node("docker && highmem") { + // TODO: this VM is not a single-shot one, we need to wipe it on our own + stage("Cleanup workspace") { + deleteDir() + } + stage("Checkout") { infra.checkout() } From a76455dc27e8d31f8a34926a915f59b01820553c Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 12:43:43 +0200 Subject: [PATCH 128/243] [JENKINS-50933] - Use custom workspace for every build to workaround the ATH collision --- Jenkinsfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fe57252c0..b142befd0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,9 +8,7 @@ properties([[$class: 'BuildDiscarderProperty', node("docker && highmem") { // TODO: this VM is not a single-shot one, we need to wipe it on our own - stage("Cleanup workspace") { - deleteDir() - } + dir(env.BUILD_NUMBER) stage("Checkout") { infra.checkout() @@ -60,4 +58,5 @@ node("docker && highmem") { pctUrl: "docker://jenkins/pct:pr74", javaOptions: ["-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"] } + } } From a22f426013606ac325a16b2f141e86074ba3ca9d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 12:51:30 +0200 Subject: [PATCH 129/243] [JENKINS-50933] - Fix syntax error in the Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b142befd0..4646173fe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ properties([[$class: 'BuildDiscarderProperty', node("docker && highmem") { // TODO: this VM is not a single-shot one, we need to wipe it on our own - dir(env.BUILD_NUMBER) + dir(env.BUILD_NUMBER) { stage("Checkout") { infra.checkout() @@ -58,5 +58,5 @@ node("docker && highmem") { pctUrl: "docker://jenkins/pct:pr74", javaOptions: ["-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"] } - } + } } From 2dfe58cd6962a8deadd4c2bc3eb80d355ba5afef Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 13:42:57 +0200 Subject: [PATCH 130/243] [JENKINS-50933] - Downgrade Credentials Plugin to 2.1.10 so that ATH passes --- src/test/it/packager-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index c34766413..56998c776 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -25,7 +25,7 @@ plugins: - groupId: "org.jenkins-ci.plugins" artifactId: "credentials" source: - version: "2.1.16" + version: "2.1.10" - groupId: "org.jenkins-ci.plugins" artifactId: "ssh-credentials" source: From 6b91f7b93afdaa67853265899f3be54a6dcd4ce6 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 14:24:45 +0200 Subject: [PATCH 131/243] [JENKINS-50933] - Recover original tests --- Jenkinsfile | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 4646173fe..307dbf0a0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,6 +6,46 @@ properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']]]) +/* These platforms correspond to labels in ci.jenkins.io, see: + * https://github.com/jenkins-infra/documentation/blob/master/ci.adoc + */ +List platforms = ['linux', 'windows'] +Map branches = [:] + +for (int i = 0; i < platforms.size(); ++i) { + String label = platforms[i] + branches[label] = { + node(label) { + timestamps { + stage('Checkout') { + checkout scm + } + + stage('Build') { + infra.runMaven(["--batch-mode", "clean", "install", "-Dmaven.test.failure.ignore=true"]) + } + + stage('Archive') { + /* Archive the test results */ + try { + junit '**/target/surefire-reports/TEST-*.xml' + } catch(Exception ex) { + echo "Ignoring JUnit step failure: ${ex.message}" + } + + if (label == 'linux') { + archiveArtifacts artifacts: 'target/**/*.jar' + findbugs pattern: '**/target/findbugsXml.xml' + } + } + } + } + } +} + +/* Execute our platforms in parallel */ +parallel(branches) + node("docker && highmem") { // TODO: this VM is not a single-shot one, we need to wipe it on our own dir(env.BUILD_NUMBER) { From ac30daeb23397c1a8956f63834855e598d1db3e0 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 14:28:42 +0200 Subject: [PATCH 132/243] [JENKINS-50933] - Adjust Remoting staging in Jenkinsfile --- Jenkinsfile | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 307dbf0a0..20503a37d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,21 +11,14 @@ properties([[$class: 'BuildDiscarderProperty', */ List platforms = ['linux', 'windows'] Map branches = [:] - -for (int i = 0; i < platforms.size(); ++i) { - String label = platforms[i] - branches[label] = { - node(label) { - timestamps { - stage('Checkout') { + for (int i = 0; i < platforms.size(); ++i) { + String label = platforms[i] + branches[label] = { + node(label) { + timestamps { checkout scm - } - - stage('Build') { infra.runMaven(["--batch-mode", "clean", "install", "-Dmaven.test.failure.ignore=true"]) - } - stage('Archive') { /* Archive the test results */ try { junit '**/target/surefire-reports/TEST-*.xml' @@ -43,14 +36,16 @@ for (int i = 0; i < platforms.size(); ++i) { } } -/* Execute our platforms in parallel */ -parallel(branches) +stage("Build Remoting") { + /* Execute our platforms in parallel */ + parallel(branches) +} node("docker && highmem") { // TODO: this VM is not a single-shot one, we need to wipe it on our own dir(env.BUILD_NUMBER) { - stage("Checkout") { + stage("Integration Tests: Checkout") { infra.checkout() } From 058924ed01c848187a67f36f297e9640d39c4727 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 14:31:10 +0200 Subject: [PATCH 133/243] [JENKINS-50933] - Fix typo --- Jenkinsfile | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 20503a37d..86fdcc51c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,25 +11,24 @@ properties([[$class: 'BuildDiscarderProperty', */ List platforms = ['linux', 'windows'] Map branches = [:] - for (int i = 0; i < platforms.size(); ++i) { - String label = platforms[i] - branches[label] = { - node(label) { - timestamps { - checkout scm - infra.runMaven(["--batch-mode", "clean", "install", "-Dmaven.test.failure.ignore=true"]) +for (int i = 0; i < platforms.size(); ++i) { + String label = platforms[i] + branches[label] = { + node(label) { + timestamps { + checkout scm + infra.runMaven(["--batch-mode", "clean", "install", "-Dmaven.test.failure.ignore=true"]) - /* Archive the test results */ - try { - junit '**/target/surefire-reports/TEST-*.xml' - } catch(Exception ex) { - echo "Ignoring JUnit step failure: ${ex.message}" - } + /* Archive the test results */ + try { + junit '**/target/surefire-reports/TEST-*.xml' + } catch(Exception ex) { + echo "Ignoring JUnit step failure: ${ex.message}" + } - if (label == 'linux') { - archiveArtifacts artifacts: 'target/**/*.jar' - findbugs pattern: '**/target/findbugsXml.xml' - } + if (label == 'linux') { + archiveArtifacts artifacts: 'target/**/*.jar' + findbugs pattern: '**/target/findbugsXml.xml' } } } From afeae80c5ffaa6b90f946efe86d70e964dfdefe9 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 14:49:45 +0200 Subject: [PATCH 134/243] Remoting POM.xml had no pluginRepositories defined --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f96f7d15..8748cb02e 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ THE SOFTWARE. repo.jenkins-ci.org - http://repo.jenkins-ci.org/public/ + https://repo.jenkins-ci.org/public/ true @@ -85,6 +85,12 @@ THE SOFTWARE. + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + From 7d5f2a968b25a25c0244e81922066faf9927f687 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 15:34:44 +0200 Subject: [PATCH 135/243] [JENKINS-50933] - Update ATH to the latest revision --- src/test/it/essentials.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 5f9e0d194..d54373916 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -1,7 +1,7 @@ --- ath: useLocalSnapshots: false - athRevision: "06caf67d37d945ec5228a80d73714fac692c28d3" + athRevision: "dad333092159cb368efc2f9869572f0a05d255ac" tests: - "plugins.SshSlavesPluginTest" - "core.SlaveTest" From ca531327587ac5c8d6de01153765dac2c17f82dc Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 15:35:25 +0200 Subject: [PATCH 136/243] [JENKINS-50933] - Ignore run_pct.sh output files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9f9537682..2a5254e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target /.project /.settings .fbExcludeFilterFile +src/test/it/out From 71b1b0dc8044acac5cc879cea754533e53545ad8 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 23 Apr 2018 21:54:55 +0200 Subject: [PATCH 137/243] Restore timeout in the Maven run --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 86fdcc51c..365355ca9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -70,7 +70,9 @@ node("docker && highmem") { mavenOptions << "-Dcustom-war-packager.mvnSettingsFile=${mvnSettingsFile}" } - infra.runMaven(mavenOptions) + timeout(30) { + infra.runMaven(mavenOptions) + } archiveArtifacts artifacts: outputWARpattern // Pass variables for the next steps From ea9d9a46484bb54ab7ce11dd41f377e00778d295 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 24 Apr 2018 18:22:42 +0200 Subject: [PATCH 138/243] Use the released version of Custom WAR Packager --- src/test/it/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/it/pom.xml b/src/test/it/pom.xml index 4888db8ff..021240e11 100644 --- a/src/test/it/pom.xml +++ b/src/test/it/pom.xml @@ -62,7 +62,7 @@ io.jenkins.tools.custom-war-packager custom-war-packager-maven-plugin - 0.1-alpha-4-20180423.085628-5 + 0.1-alpha-4 package From 3dca719efb45607e25147f3909508b07d92d5867 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Sun, 29 Apr 2018 18:48:19 +0000 Subject: [PATCH 139/243] Add Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4547613df..7eca37a3f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Jenkins Remoting layer ==== +[![Join the chat at https://gitter.im/jenkinsci/remoting](https://badges.gitter.im/jenkinsci/remoting.svg)](https://gitter.im/jenkinsci/remoting?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Jenkins remoting is an executable JAR, which implements communication layer in [Jenkins](https://jenkins.io) automation server. It's being used for master <=> agent(fka "slave") and master <=> CLI communications. From 5be5ad853a40b1030007780d34d719885ac8b8ae Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 7 May 2018 17:14:08 +0200 Subject: [PATCH 140/243] [INFRA-1617] - Modify Remoting Jenkinsfile to use the Pipeline library --- Jenkinsfile | 59 +----------------------- src/test/it/essentials.yml | 3 ++ src/test/it/{ => manualCheck}/pom.xml | 2 +- src/test/it/{ => manualCheck}/run_pct.sh | 0 src/test/it/{ => manualCheck}/run_war.sh | 0 5 files changed, 6 insertions(+), 58 deletions(-) rename src/test/it/{ => manualCheck}/pom.xml (98%) rename src/test/it/{ => manualCheck}/run_pct.sh (100%) rename src/test/it/{ => manualCheck}/run_war.sh (100%) diff --git a/Jenkinsfile b/Jenkinsfile index 365355ca9..7834b5a84 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -37,62 +37,7 @@ for (int i = 0; i < platforms.size(); ++i) { stage("Build Remoting") { /* Execute our platforms in parallel */ - parallel(branches) + //TODO uncomment parallel(branches) } -node("docker && highmem") { - // TODO: this VM is not a single-shot one, we need to wipe it on our own - dir(env.BUILD_NUMBER) { - - stage("Integration Tests: Checkout") { - infra.checkout() - } - - def mvnSettingsFile = "${pwd tmp: true}/settings-azure.xml" - def mvnSettingsFileFound = infra.retrieveMavenSettingsFile(mvnSettingsFile) - def outputWAR - def metadataPath - - dir("src/test/it") { - // TODO: convert to a library method - def outputWARpattern = "target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war" - stage("Build Custom WAR") { - List mavenOptions = [ - '--batch-mode', '--errors', - 'clean', 'install', - "-Dcustom-war-packager.batchMode=true", - "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" - ] - - if (mvnSettingsFileFound) { - mavenOptions << "-s" - mavenOptions << "${mvnSettingsFile}" - mavenOptions << "-Dcustom-war-packager.mvnSettingsFile=${mvnSettingsFile}" - } - - timeout(30) { - infra.runMaven(mavenOptions) - } - archiveArtifacts artifacts: outputWARpattern - - // Pass variables for the next steps - outputWAR = pwd() + "/" + outputWARpattern - metadataPath = pwd() + "/essentials.yml" - } - } - - def fileUri = "file://" + outputWAR - stage("Run ATH") { - dir("ath") { - runATH jenkins: fileUri, metadataFile: metadataPath - } - } - - stage("Run PCT") { - //TODO: Remove Slf4jMavenTransferListener option once runPCT() invokes it by default - runPCT jenkins: fileUri, metadataFile: metadataPath, - pctUrl: "docker://jenkins/pct:pr74", - javaOptions: ["-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"] - } - } -} +essentialsTest(baseDir: "src/test/it") diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index d54373916..975f5d524 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -1,4 +1,7 @@ --- +packaging: + # We cannot use BOM here, because BOM does not support build by relative path + configFile: "packager-config.yml" ath: useLocalSnapshots: false athRevision: "dad333092159cb368efc2f9869572f0a05d255ac" diff --git a/src/test/it/pom.xml b/src/test/it/manualCheck/pom.xml similarity index 98% rename from src/test/it/pom.xml rename to src/test/it/manualCheck/pom.xml index 021240e11..fc3e8ee36 100644 --- a/src/test/it/pom.xml +++ b/src/test/it/manualCheck/pom.xml @@ -70,7 +70,7 @@ custom-war - packager-config.yml + ../packager-config.yml 1.0-remoting-it-SNAPSHOT ${custom-war-packager.mvnSettingsFile} ${custom-war-packager.batchMode} diff --git a/src/test/it/run_pct.sh b/src/test/it/manualCheck/run_pct.sh similarity index 100% rename from src/test/it/run_pct.sh rename to src/test/it/manualCheck/run_pct.sh diff --git a/src/test/it/run_war.sh b/src/test/it/manualCheck/run_war.sh similarity index 100% rename from src/test/it/run_war.sh rename to src/test/it/manualCheck/run_war.sh From 59a75e19006d7d593b59bfaec57151b753c86089 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 7 May 2018 17:42:56 +0200 Subject: [PATCH 141/243] [INFRA-1617] - athRevision is no longer required --- src/test/it/essentials.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 975f5d524..8175c6ec0 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -4,7 +4,6 @@ packaging: configFile: "packager-config.yml" ath: useLocalSnapshots: false - athRevision: "dad333092159cb368efc2f9869572f0a05d255ac" tests: - "plugins.SshSlavesPluginTest" - "core.SlaveTest" From 4d86bd56fa34a5c7fd9df799170aa490d10360eb Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 8 May 2018 12:21:33 +0200 Subject: [PATCH 142/243] Use local ATH image to workaround INFRA-1619 --- src/test/it/essentials.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 8175c6ec0..44651e713 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -4,6 +4,7 @@ packaging: configFile: "packager-config.yml" ath: useLocalSnapshots: false + athImage: "local" tests: - "plugins.SshSlavesPluginTest" - "core.SlaveTest" From 3fb2c44aeff8d3a78ec107e119f28cd9d6d12244 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 8 May 2018 15:02:50 +0200 Subject: [PATCH 143/243] [JENKINS-50933] - For Remoting it makes sense to publish the produced WAR --- src/test/it/essentials.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 44651e713..846437388 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -2,6 +2,7 @@ packaging: # We cannot use BOM here, because BOM does not support build by relative path configFile: "packager-config.yml" + archiveArtifacts: true ath: useLocalSnapshots: false athImage: "local" From 3cc5305ac33a4a7e19ca053ae1dbf85163e0e2a5 Mon Sep 17 00:00:00 2001 From: Peter Darton Date: Wed, 9 May 2018 09:02:37 +0100 Subject: [PATCH 144/243] [JENKINS-50965 ] - Fix log message (#267) Current code logs "INFO: %s class '%s' using classloader: %s" if system property hudson.remoting.RemoteClassLoader.force is set. The code should be using java.text.MessageFormat.format syntax but is mistakenly using String.format syntax instead. --- src/main/java/hudson/remoting/UserRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/UserRequest.java b/src/main/java/hudson/remoting/UserRequest.java index ad53a8ab5..51b90867a 100644 --- a/src/main/java/hudson/remoting/UserRequest.java +++ b/src/main/java/hudson/remoting/UserRequest.java @@ -178,7 +178,7 @@ protected ResponseToUserRequest perform(Channel channel) throws EXC { final Logger logger = Logger.getLogger(RemoteClassLoader.class.getName()); if( logger.isLoggable(logLevel) ) { - logger.log(logLevel, "%s class '%s' using classloader: %s", new String[]{ eventMsg, clazz, cl.toString()} ); + logger.log(logLevel, "{0} class ''{1}'' using classloader: {2}", new Object[]{ eventMsg, clazz, cl.toString()} ); } } From 7043020e1460fd281bc1a6e6fb7663d0e4709f5a Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 9 May 2018 14:47:09 +0200 Subject: [PATCH 145/243] [JENKINS-50933] - Remove obsolete TODO --- Jenkinsfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7834b5a84..a0b6296d7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,5 @@ #!/usr/bin/env groovy -// TODO: restore original tests (which just fail now) - /* Only keep the 10 most recent builds. */ properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']]]) From e9c1af142e12a53301cbbc01d1bde8f8a1e5ef8f Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 10 May 2018 08:33:35 -0600 Subject: [PATCH 146/243] Sinplify NetworkLayerTest, removing duplication. This test has been doing essentially the same thing over and over in a very repetitive way. Some slight, but pretty meaningless variations. And a whole bunch of absolute repetitions. It kind of halfway used theories to reduce duplication, though it still used traditional duplication structures. And the theories mostly ended up just doing the same thing, as far as the Remoting specific code was concerned. Ultimately, these repetitions were testing whether Java and the JVM works properly. Oddly and unfortunately, the answer is no, in this case. There are indications that issues exist, on certain platforms, in some extreme scenarios. Key among the scenarios, is a very rapid creation and teardown of channels, file descriptors, or possibly most significantly pipes. This seems to particularly apply to the Mac OS. Linux can also show it, though in possibly a somewhat different way. This primarily involves nio. These situations can result in hangs in native calls from Java core APIs, post prominently preClose0(). Rather than testing variations in the Remoting library, all of these JVM issues are what these tests have been stressing, and hitting some of these key characteristics. These hangs have existed since this test was first created. They're sporadic, though they're more likely to appear in some situations than others. As a result, it leads to the impression that these tests have worked correctly at some times, when it's just been a matter of not hitting the causes in exactly the right way. There are not any indications that these highly repetitive tests hit conditions that are actually important to any user. This rapid-fire creation and teardown operations are not part of regular operation. These test issues have existed for some time, since the tests were first introduced, and are not known to be related to any customer-observed issued. --- .../remoting/protocol/IOBufferMatcher.java | 3 - .../protocol/IOBufferMatcherLayer.java | 5 - .../protocol/impl/NetworkLayerTest.java | 278 +----------------- 3 files changed, 16 insertions(+), 270 deletions(-) diff --git a/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcher.java b/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcher.java index 2bf170b03..710ce40b3 100644 --- a/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcher.java +++ b/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcher.java @@ -284,7 +284,4 @@ public boolean awaitByteContent(Matcher matcher, long timeout, TimeUnit } } - public void closeRead() throws IOException { - //innerClose(null); - } } diff --git a/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcherLayer.java b/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcherLayer.java index 649eaabe0..90275ea05 100644 --- a/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcherLayer.java +++ b/src/test/java/org/jenkinsci/remoting/protocol/IOBufferMatcherLayer.java @@ -52,11 +52,6 @@ public void close() throws IOException { super.close(); } - @Override - public void closeRead() throws IOException { - doCloseRead(); - super.closeRead(); - } }; } diff --git a/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java b/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java index 6e8011f7e..b5b778625 100644 --- a/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java +++ b/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java @@ -25,30 +25,20 @@ import java.nio.ByteBuffer; import java.nio.channels.Pipe; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import org.apache.commons.io.IOUtils; import org.jenkinsci.remoting.protocol.IOBufferMatcher; import org.jenkinsci.remoting.protocol.IOBufferMatcherLayer; -import org.jenkinsci.remoting.protocol.IOHubRule; +import org.jenkinsci.remoting.protocol.IOHub; import org.jenkinsci.remoting.protocol.NetworkLayerFactory; import org.jenkinsci.remoting.protocol.ProtocolStack; -import org.jenkinsci.remoting.protocol.Repeat; -import org.jenkinsci.remoting.protocol.RepeatRule; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.experimental.theories.DataPoint; -import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; -import org.junit.rules.RuleChain; -import org.junit.rules.TestName; -import org.junit.rules.Timeout; import org.junit.runner.RunWith; import static org.hamcrest.Matchers.is; @@ -57,16 +47,10 @@ @RunWith(Theories.class) public class NetworkLayerTest { - @Rule - public TestName name = new TestName(); - private IOHubRule selector = new IOHubRule(); - @Rule - public RuleChain chain = RuleChain.outerRule(selector) - .around(new RepeatRule()) - .around(new Timeout(10, TimeUnit.MINUTES)); - private Pipe clientToServer; private Pipe serverToClient; + private ExecutorService executorService; + private IOHub hub; @DataPoint("blocking I/O") public static NetworkLayerFactory blocking() { @@ -78,35 +62,18 @@ public static NetworkLayerFactory nonBlocking() { return new NetworkLayerFactory.NIO(); } - @DataPoints - public static BatchSendBufferingFilterLayer[] batchSizes() { - List result = new ArrayList(); - if (Boolean.getBoolean("fullTests")) { - int length = 16; - while (length < 65536) { - result.add(new BatchSendBufferingFilterLayer(length)); - if (length < 16) { - length = length * 2; - } else { - length = length * 3 / 2; - } - } - } else { - result.add(new BatchSendBufferingFilterLayer(16)); - result.add(new BatchSendBufferingFilterLayer(4096)); - result.add(new BatchSendBufferingFilterLayer(65536)); - } - return result.toArray(new BatchSendBufferingFilterLayer[result.size()]); - } - @Before - public void setUpPipe() throws Exception { + public void setUp() throws Exception { clientToServer = Pipe.open(); serverToClient = Pipe.open(); + executorService = Executors.newFixedThreadPool(8); + hub = IOHub.create(executorService); } @After - public void tearDownPipe() throws Exception { + public void tearDown() throws Exception { + hub.close(); + executorService.shutdownNow(); IOUtils.closeQuietly(clientToServer.sink()); IOUtils.closeQuietly(clientToServer.source()); IOUtils.closeQuietly(serverToClient.sink()); @@ -114,42 +81,15 @@ public void tearDownPipe() throws Exception { } @Theory - public void smokes(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { - ProtocolStack client = - ProtocolStack - .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) - .build(new IOBufferMatcherLayer()); - - - ProtocolStack server = - ProtocolStack - .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) - .build(new IOBufferMatcherLayer()); - - byte[] expected = "Here is some sample data".getBytes("UTF-8"); - ByteBuffer data = ByteBuffer.allocate(expected.length); - data.put(expected); - data.flip(); - server.get().send(data); - client.get().awaitByteContent(is(expected)); - assertThat(client.get().asByteArray(), is(expected)); - server.get().close(); - client.get().awaitClose(); - } - - @Theory - public void doCloseRecv(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { - Logger.getAnonymousLogger().log(Level.INFO, "serverFactory: {0} clientFactory: {1}", - new Object[]{serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName()}); + public void doBasicSendReceive(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { ProtocolStack client = ProtocolStack - .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) + .on(clientFactory.create(hub, serverToClient.source(), clientToServer.sink())) .build(new IOBufferMatcherLayer()); - ProtocolStack server = ProtocolStack - .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) + .on(serverFactory.create(hub, clientToServer.source(), serverToClient.sink())) .build(new IOBufferMatcherLayer()); byte[] expected = "Here is some sample data".getBytes("UTF-8"); @@ -159,193 +99,7 @@ public void doCloseRecv(NetworkLayerFactory serverFactory, NetworkLayerFactory c server.get().send(data); client.get().awaitByteContent(is(expected)); assertThat(client.get().asByteArray(), is(expected)); - server.get().closeRead(); - client.get().awaitClose(); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_1_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 1, 1); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_64_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 64, 1); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_1_64(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 1, 64); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_1k_1k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 1024, 1024); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_1k_64k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 1024, 65536); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_64k_1k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 65536, 1024); - } - - @Theory - @Repeat(value = 1024, stopAfter = 1) - public void concurrentStress_64k_64k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 65536, 65536); - } - - @Theory - @Repeat(value = 16, stopAfter = 1) - public void concurrentStress_1m_2m(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 1024 * 1024, 2048 * 1024); - } - - @Theory - @Repeat(value = 16, stopAfter = 1) - public void concurrentStress_2m_1m(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) - throws Exception { - concurrentStress(serverFactory, clientFactory, 2048 * 1024, 1024 * 1024); - } - - private void concurrentStress(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, int serverLimit, - int clientLimit) - throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException { - Logger.getLogger(name.getMethodName()).log( - Level.INFO, "Starting test with server {0} client {1}", - new Object[]{serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName()}); - ProtocolStack clientStack = - ProtocolStack - .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) - .build(new IOBufferMatcherLayer()); - - - ProtocolStack serverStack = - ProtocolStack - .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) - .build(new IOBufferMatcherLayer()); - - final IOBufferMatcher client = clientStack.get(); - final IOBufferMatcher server = serverStack.get(); - Future clientWork = selector.executorService().submit(new SequentialSender(client, clientLimit, 11)); - Future serverWork = selector.executorService().submit(new SequentialSender(server, serverLimit, 13)); - - clientWork.get(); - serverWork.get(); - - client.awaitByteContent(SequentialSender.matcher(serverLimit)); - server.awaitByteContent(SequentialSender.matcher(clientLimit)); - - client.close(); - server.close(); - - client.awaitClose(); - server.awaitClose(); - - assertThat(client.asByteArray(), SequentialSender.matcher(serverLimit)); - assertThat(server.asByteArray(), SequentialSender.matcher(clientLimit)); - } - - @Theory - public void sendingBiggerAndBiggerBatches(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, - BatchSendBufferingFilterLayer batch) - throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException { - Logger.getLogger(name.getMethodName()).log( - Level.INFO, "Starting test with server {0} client {1} batch {2}", new Object[]{ - serverFactory.getClass().getSimpleName(), - clientFactory.getClass().getSimpleName(), - batch - }); - ProtocolStack clientStack = - ProtocolStack - .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) - .build(new IOBufferMatcherLayer()); - - - ProtocolStack serverStack = - ProtocolStack - .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) - .filter(batch) - .build(new IOBufferMatcherLayer()); - - final IOBufferMatcher client = clientStack.get(); - final IOBufferMatcher server = serverStack.get(); - Future serverWork = selector.executorService().submit(new SequentialSender(server, 65536 * 4, 13)); - - serverWork.get(); - batch.flush(); - - client.awaitByteContent(SequentialSender.matcher(65536 * 4), 5, TimeUnit.SECONDS); - - client.awaitByteContent(SequentialSender.matcher(65536 * 4)); - - client.close(); - server.close(); - - client.awaitClose(); - server.awaitClose(); - - assertThat(client.asByteArray(), SequentialSender.matcher(65536 * 4)); - } - - @Theory - public void bidiSendingBiggerAndBiggerBatches(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, - BatchSendBufferingFilterLayer batch) - throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException { - Logger.getLogger(name.getMethodName()).log( - Level.INFO, "Starting test with server {0} client {1} batch {2}", new Object[]{ - serverFactory.getClass().getSimpleName(), - clientFactory.getClass().getSimpleName(), - batch - }); - BatchSendBufferingFilterLayer clientBatch = batch.clone(); - ProtocolStack clientStack = - ProtocolStack - .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) - .filter(new NoOpFilterLayer()) - .filter(clientBatch) - .filter(new NoOpFilterLayer()) - .build(new IOBufferMatcherLayer()); - - - ProtocolStack serverStack = - ProtocolStack - .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) - .filter(new NoOpFilterLayer()) - .filter(batch) - .filter(new NoOpFilterLayer()) - .build(new IOBufferMatcherLayer()); - - final IOBufferMatcher client = clientStack.get(); - final IOBufferMatcher server = serverStack.get(); - Future clientWork = selector.executorService().submit(new SequentialSender(client, 65536 * 4, 11)); - Future serverWork = selector.executorService().submit(new SequentialSender(server, 65536 * 4, 13)); - - clientWork.get(); - serverWork.get(); - clientBatch.flush(); - batch.flush(); - - client.awaitByteContent(SequentialSender.matcher(65536 * 4)); - server.awaitByteContent(SequentialSender.matcher(65536 * 4)); + server.get().close(null); } } From adc0df4ff783938442890f2c65bc0105ce60da96 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 14 May 2018 18:04:36 +0200 Subject: [PATCH 147/243] [JENKINS-50933] - Update patch for JENKINS-51302, remork the manual flow to Makefile --- src/test/it/Makefile | 29 ++++++++++ src/test/it/essentials.yml | 2 + src/test/it/manualCheck/pom.xml | 93 ------------------------------ src/test/it/manualCheck/run_pct.sh | 6 -- src/test/it/manualCheck/run_war.sh | 3 - src/test/it/packager-config.yml | 2 +- 6 files changed, 32 insertions(+), 103 deletions(-) create mode 100644 src/test/it/Makefile delete mode 100644 src/test/it/manualCheck/pom.xml delete mode 100644 src/test/it/manualCheck/run_pct.sh delete mode 100644 src/test/it/manualCheck/run_war.sh diff --git a/src/test/it/Makefile b/src/test/it/Makefile new file mode 100644 index 000000000..09a97c7ae --- /dev/null +++ b/src/test/it/Makefile @@ -0,0 +1,29 @@ +# Just a Makefile for manual testing +.PHONY: all + +ARTIFACT_ID = jenkins-remoting-it +VERSION = 2.107.3-remoting-it-SNAPSHOT +# https://github.com/jenkinsci/custom-war-packager/pull/28 +CWP_VERSION = 0.1-alpha-6-20180514.155901-2 + +all: clean build + +clean: + rm -rf tmp + +build: tmp/output/target/jenkins-war-${VERSION}.war + +tmp/output/target/jenkins-war-${VERSION}.war: + # TODO: https://github.com/jenkinsci/custom-war-packager/pull/26 + mvn io.jenkins.tools.custom-war-packager:custom-war-packager-maven-plugin:${CWP_VERSION}:build -DconfigFile=essentials.yml -Dversion=${VERSION} + +run: tmp/output/target/jenkins-war-${VERSION}.war + JENKINS_HOME=work java -jar tmp/output/target/${ARTIFACT_ID}-${VERSION}.war \ + --httpPort=8080 --prefix=/jenkins + +pct: tmp/output/target/jenkins-war-${VERSION}.war + docker run --rm -v maven-repo:/root/.m2 -v $(shell pwd)/out:/pct/out \ + -v $(shell pwd)/tmp/output/target/${ARTIFACT_ID}-${VERSION}.war:/pct/jenkins.war:ro \ + -e ARTIFACT_ID=ssh-slaves \ + -e INSTALL_BUNDLED_SNAPSHOTS=true \ + jenkins/pct diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 846437388..3a2fcb559 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -3,6 +3,8 @@ packaging: # We cannot use BOM here, because BOM does not support build by relative path configFile: "packager-config.yml" archiveArtifacts: true + # https://github.com/jenkinsci/custom-war-packager/pull/28 + cwpVersion: 0.1-alpha-6-20180514.155850-2 ath: useLocalSnapshots: false athImage: "local" diff --git a/src/test/it/manualCheck/pom.xml b/src/test/it/manualCheck/pom.xml deleted file mode 100644 index fc3e8ee36..000000000 --- a/src/test/it/manualCheck/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - 4.0.0 - - io.jenkins.tools.custom-war-packager.demo - remoting-it - 1.0-SNAPSHOT - - pom - - - - false - true - cloudbees-internal-snapshots - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - package - - io.jenkins.tools.custom-war-packager:custom-war-packager-maven-plugin:0.1-alpha-3 - - - - - - - io.jenkins.tools.custom-war-packager - custom-war-packager-maven-plugin - 0.1-alpha-4 - - - package - - custom-war - - - ../packager-config.yml - 1.0-remoting-it-SNAPSHOT - ${custom-war-packager.mvnSettingsFile} - ${custom-war-packager.batchMode} - ${custom-war-packager.deploySnapshots} - ${custom-war-packager.deploySnapshotsRepositoryId} - - - - - - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - - diff --git a/src/test/it/manualCheck/run_pct.sh b/src/test/it/manualCheck/run_pct.sh deleted file mode 100644 index f939aa58f..000000000 --- a/src/test/it/manualCheck/run_pct.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -docker run --rm -v maven-repo:/root/.m2 -v $(pwd)/out:/pct/out \ - -v $(pwd)/target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war:/pct/jenkins.war:ro \ - -e ARTIFACT_ID=windows-slaves \ - -e INSTALL_BUNDLED_SNAPSHOTS=true \ - jenkins/pct diff --git a/src/test/it/manualCheck/run_war.sh b/src/test/it/manualCheck/run_war.sh deleted file mode 100644 index 716212852..000000000 --- a/src/test/it/manualCheck/run_war.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -JENKINS_HOME=$(pwd)/work java -jar target/custom-war-packager-maven-plugin/output/target/jenkins-remoting-it-1.0-remoting-it-SNAPSHOT.war \ - --httpPort=8080 --prefix=/jenkins diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml index 56998c776..5380bf0da 100644 --- a/src/test/it/packager-config.yml +++ b/src/test/it/packager-config.yml @@ -8,7 +8,7 @@ war: groupId: "org.jenkins-ci.main" artifactId: "jenkins-war" source: - version: "2.107.2" + version: "2.107.3" plugins: - groupId: "org.jenkins-ci.plugins" artifactId: "ssh-slaves" From 05e536698c75402e7d738d15bc7fca988eb76193 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 14 May 2018 19:19:12 +0200 Subject: [PATCH 148/243] [JENKINS-50933] - Disable Windows Agents tests for now --- src/test/it/essentials.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml index 3a2fcb559..020b33cdd 100644 --- a/src/test/it/essentials.yml +++ b/src/test/it/essentials.yml @@ -21,5 +21,6 @@ pct: plugins: - "ssh-slaves" - "command-launcher" - - "windows-slaves" +# JENKINS-50933 +# - "windows-slaves" # TODO: JTH/PCT does not work reliably against non-bundled plugins (if something else is bundled) - "compress-artifacts" From e53c3e51c84528f04c97a4089e230ca827e52bf1 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 14 May 2018 20:18:02 +0200 Subject: [PATCH 149/243] [JENKINS-50933] - Reenable old tests --- Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index a0b6296d7..d2493872c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,7 +35,8 @@ for (int i = 0; i < platforms.size(); ++i) { stage("Build Remoting") { /* Execute our platforms in parallel */ - //TODO uncomment parallel(branches) + parallel(branches) } +// Run integration tests essentialsTest(baseDir: "src/test/it") From d6ca1ffb8f3d77796f0d3ab10f2a90022b90f7dc Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 15 May 2018 13:34:18 +0200 Subject: [PATCH 150/243] Revert "[JENKINS-50933] - Reenable old tests" This reverts commit e53c3e51c84528f04c97a4089e230ca827e52bf1. --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d2493872c..a0b6296d7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,8 +35,7 @@ for (int i = 0; i < platforms.size(); ++i) { stage("Build Remoting") { /* Execute our platforms in parallel */ - parallel(branches) + //TODO uncomment parallel(branches) } -// Run integration tests essentialsTest(baseDir: "src/test/it") From a2469ea784ea3f5b9458332e0043c97d5c530615 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 11 May 2018 11:46:50 -0600 Subject: [PATCH 151/243] Simplify JnlpProtocolHandlerTest. Some of this is similar to the simplifications for NetworkLayerTest in e9c1af14. This test has also been hitting sporadic hangs / failures, primarily on certain OSes, etc. These mostly occur in preClose0() also. Similar to that test, part of the solution here is to eliminate the repetition of the exact same test. As part of understanding and cleaning up this test, I also made a major effort to remove other duplication. And to improve naming and clarity. The individual test methods were lengthy, with lots of duplicated code, making it hard to understand what they were doing. Indeed some of the tests didn't actually match their name / intention. I extracted out the commonality and tried to make it clearer the variation in each test. This test had continued testing all old versions of the JNLP protocol. There had been TODOs marked to remove them from this test. They've been deprecated for a long time. Their implementations aren't quite behaving properly, particularly when it comes to closing them down, which causes contamination with testing the currently supported protocols. With Oracle deprecating JNLP entirely it's time to remove these protocols from this test. --- .../engine/JnlpProtocolHandlerTest.java | 716 ++++-------------- 1 file changed, 149 insertions(+), 567 deletions(-) diff --git a/src/test/java/org/jenkinsci/remoting/engine/JnlpProtocolHandlerTest.java b/src/test/java/org/jenkinsci/remoting/engine/JnlpProtocolHandlerTest.java index 0f0a51412..b19d6fe3b 100644 --- a/src/test/java/org/jenkinsci/remoting/engine/JnlpProtocolHandlerTest.java +++ b/src/test/java/org/jenkinsci/remoting/engine/JnlpProtocolHandlerTest.java @@ -3,6 +3,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.remoting.Channel; import hudson.remoting.TestCallable; + import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -15,25 +16,25 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.net.ssl.SSLContext; + import org.apache.commons.io.IOUtils; import org.jenkinsci.remoting.nio.NioChannelHub; import org.jenkinsci.remoting.protocol.IOHub; -import org.jenkinsci.remoting.protocol.Repeat; -import org.jenkinsci.remoting.protocol.RepeatRule; import org.jenkinsci.remoting.protocol.cert.RSAKeyPairRule; import org.jenkinsci.remoting.protocol.cert.SSLContextRule; import org.jenkinsci.remoting.protocol.cert.X509CertificateRule; +import org.jenkinsci.remoting.protocol.impl.ConnectionHeadersFilterLayer; import org.jenkinsci.remoting.protocol.impl.ConnectionRefusalException; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.Rule; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; @@ -42,21 +43,23 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeThat; @RunWith(Theories.class) public class JnlpProtocolHandlerTest { + private static final Consumer APPROVING_STATE_CONSUMER = JnlpConnectionState::approve; + private static final Consumer REJECTING_STATE_CONSUMER = (event) -> event.reject(new ConnectionRefusalException("I don't like you")); + private static final Consumer IGNORING_STATE_CONSUMER = (event) -> { + }; + private static final String SECRET_KEY = "SecretKey-1234"; + private static ExecutorService executorService; private IOHub selector; private NioChannelHub hub; - private static final Logger LOGGER = Logger.getLogger(JnlpProtocolHandlerTest.class.getName()); - private static RSAKeyPairRule clientKey = new RSAKeyPairRule(); private static RSAKeyPairRule serverKey = new RSAKeyPairRule(); private static RSAKeyPairRule caRootKey = new RSAKeyPairRule(); @@ -110,594 +113,197 @@ public class JnlpProtocolHandlerTest { .around(notYetValidServerCtx) .around(untrustingClientCtx) .around(untrustingServerCtx); - @Rule - public RepeatRule repeater = new RepeatRule(); - private ServerSocketChannel eastServer; - private SocketChannel westChannel; - private SocketChannel eastChannel; - private Channel eastRemoting; - private Channel westRemoting; + + private ServerSocketChannel baseServerSocket; + private SocketChannel clientSocketChannel; + private SocketChannel serverSocketChannel; + private Channel serverRemotingChannel; + private Channel clientRemotingChannel; @BeforeClass - public static void setUpClass() throws Exception { + public static void setUpClass() { + Logger.getLogger(ConnectionHeadersFilterLayer.class.getName()).setLevel(Level.WARNING); executorService = Executors.newCachedThreadPool(); } @AfterClass - public static void tearDownClass() throws Exception { + public static void tearDownClass() { executorService.shutdownNow(); } - @Before public void setUp() throws Exception { selector = IOHub.create(executorService); hub = new NioChannelHub(executorService); executorService.submit(hub); - eastServer = ServerSocketChannel.open(); - eastServer.socket().bind(new InetSocketAddress(0)); - westChannel = SocketChannel.open(); - westChannel.connect(eastServer.getLocalAddress()); - eastChannel = eastServer.accept(); + baseServerSocket = ServerSocketChannel.open(); + baseServerSocket.socket().bind(new InetSocketAddress(0)); + clientSocketChannel = SocketChannel.open(); + clientSocketChannel.connect(baseServerSocket.getLocalAddress()); + serverSocketChannel = baseServerSocket.accept(); } @After - public void tearDown() throws Exception { - IOUtils.closeQuietly(eastRemoting); - IOUtils.closeQuietly(westRemoting); - Thread.sleep(10); - IOUtils.closeQuietly(westChannel); - IOUtils.closeQuietly(eastChannel); - IOUtils.closeQuietly(eastServer); + public void tearDown() { + IOUtils.closeQuietly(serverRemotingChannel); + IOUtils.closeQuietly(clientRemotingChannel); + IOUtils.closeQuietly(clientSocketChannel); + IOUtils.closeQuietly(serverSocketChannel); + IOUtils.closeQuietly(baseServerSocket); IOUtils.closeQuietly(hub); IOUtils.closeQuietly(selector); } - private static void printFactoryInfoMessage(Factory factory, boolean useNioHubServer, boolean useNioHubClient) { - LOGGER.log(Level.WARNING, "Testing factory {0}, nio_server={1}, nio_client={2}", new Object[] {factory, useNioHubServer, useNioHubClient}); - } - @Theory - @Repeat(value = 25, stopAfter = 10, stopAfterUnits = TimeUnit.SECONDS) public void happyPath(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - if (useNioHubClient) { - assumeThat(factory.toString(), not(is("JNLP4-connect"))); - } - if (lastFactory != factory) { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - lastFactory = factory; - } - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return true; - } - - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context() - , useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - assertThat(eastRemoting, notNullValue()); - eastRemoting.callAsync(new TestCallable()); - westRemoting = westChan.get(10, TimeUnit.SECONDS); - assertThat(westRemoting, notNullValue()); + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), APPROVING_STATE_CONSUMER); + serverRemotingChannel = serverChannelFuture.get(10, TimeUnit.SECONDS); + assertThat(serverRemotingChannel, notNullValue()); + serverRemotingChannel.call(new TestCallable()); + clientRemotingChannel = clientChannelFuture.get(10, TimeUnit.SECONDS); + assertThat(clientRemotingChannel, notNullValue()); } - private Factory lastFactory; - @Theory public void serverRejects(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - if (lastFactory != factory) { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - lastFactory = factory; - } - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return true; - } - - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), - useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.reject(new ConnectionRefusalException("I don't like you")); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(IOException.class)); - } - try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), REJECTING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, ConnectionRefusalException.class); } @Theory public void serverIgnores(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - if (lastFactory != factory) { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - lastFactory = factory; - } - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return true; - } - - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), - useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.reject(new ConnectionRefusalException("I don't like you")); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(IOException.class)); - } - try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, IGNORING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), APPROVING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, IOException.class); } @Theory public void clientRejects(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - if (lastFactory != factory) { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - lastFactory = factory; - } - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return true; - } - - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), - useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.reject(new ConnectionRefusalException("I don't like you")); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(IOException.class)); - } - try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), REJECTING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, IOException.class); } @Theory public void clientIgnores(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - if (lastFactory != factory) { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - lastFactory = factory; - } - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return true; - } - - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), - useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.reject(new ConnectionRefusalException("I don't like you")); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } - - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(IOException.class)); - } - try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), IGNORING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, ConnectionRefusalException.class); } @Theory public void doesNotExist(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { - @Override - public boolean exists(String clientName) { - return false; - } + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, false); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, SECRET_KEY); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), APPROVING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, ConnectionRefusalException.class); + } - @Override - public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; - } - }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), - useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "SuperSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } + @Theory + public void wrongSecret(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { + Logger.getLogger(JnlpProtocol4PlainHandler.class.getName()).setLevel(Level.SEVERE); + Logger.getLogger(JnlpProtocol4Handler.class.getName()).setLevel(Level.SEVERE); + JnlpProtocolHandler serverProtocolHandler = createServerProtocolHandler(factory, useNioHubServer, SECRET_KEY, true); + JnlpProtocolHandler clientProtocolHandler = createClientProtocolHandler(factory, useNioHubClient); + HashMap clientProps = createClientProperties(factory, "WrongSecret"); + Future clientChannelFuture = createChannelConnector(clientSocketChannel, clientProtocolHandler, clientProps, APPROVING_STATE_CONSUMER); + readAndCheckProtocol(factory); + Future serverChannelFuture = createChannelHandler(serverSocketChannel, serverProtocolHandler, new HashMap<>(), APPROVING_STATE_CONSUMER); + assertChannelFails(clientChannelFuture, serverChannelFuture, ConnectionRefusalException.class); + } - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } + private Future createChannelConnector(SocketChannel channel, JnlpProtocolHandler protocolHandler, + HashMap properties, + Consumer afterPropertiesConsumer) throws IOException { + return protocolHandler.connect(channel.socket(), properties, new StateListener(afterPropertiesConsumer, Channel.Mode.BINARY)); + } - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - ByteBuffer len = ByteBuffer.wrap(new byte[2]); - while (len.hasRemaining()) { - eastChannel.read(len); - } - byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; - ByteBuffer content = ByteBuffer.wrap(bytes); - while (content.hasRemaining()) { - eastChannel.read(content); - } - assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } + private Future createChannelHandler(SocketChannel channel, JnlpProtocolHandler protocolHandler, + HashMap properties, + Consumer afterPropertiesConsumer) throws IOException { + return protocolHandler.handle(channel.socket(), properties, new StateListener(afterPropertiesConsumer, Channel.Mode.NEGOTIATE)); + } - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } + private HashMap createClientProperties(Factory factory, String secretKey) { + HashMap clientProps = new HashMap<>(); + clientProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "client-" + factory); + clientProps.put(JnlpConnectionState.SECRET_KEY, secretKey); + return clientProps; + } - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); - try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } - try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); - } + private JnlpProtocolHandler createClientProtocolHandler(Factory factory, boolean useNioHubClient) { + return factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), useNioHubClient); } - @Theory - public void wrongSecret(Factory factory, boolean useNioHubServer, boolean useNioHubClient) throws Exception { - printFactoryInfoMessage(factory, useNioHubServer, useNioHubClient); - JnlpProtocolHandler eastProto = factory.create(new JnlpClientDatabase() { + private JnlpProtocolHandler createServerProtocolHandler(Factory factory, + boolean useNioHubServer, + String secretKey, + boolean exists) { + return factory.create(new JnlpClientDatabase() { @Override public boolean exists(String clientName) { - return true; + return exists; } @Override public String getSecretOf(@Nonnull String clientName) { - return "SuperSecret-" + clientName; + return secretKey; } }, executorService, selector, hub, serverCtx.context(), useNioHubServer); - JnlpProtocolHandler westProto = - factory.create(null, executorService, selector, useNioHubClient ? hub : null, clientCtx.context(), useNioHubClient); - HashMap westProps = new HashMap(); - westProps.put(JnlpConnectionState.CLIENT_NAME_KEY, "happy-path-" + factory); - westProps.put(JnlpConnectionState.SECRET_KEY, "WrongSecret-happy-path-" + factory); - Future westChan = westProto - .connect(westChannel.socket(), westProps, new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.BINARY); - } + } - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); + private void readAndCheckProtocol(Factory factory) throws IOException { ByteBuffer len = ByteBuffer.wrap(new byte[2]); while (len.hasRemaining()) { - eastChannel.read(len); + serverSocketChannel.read(len); } byte[] bytes = new byte[((len.get(0) << 8) & 0xff00) + (len.get(1) & 0xff)]; ByteBuffer content = ByteBuffer.wrap(bytes); while (content.hasRemaining()) { - eastChannel.read(content); + serverSocketChannel.read(content); } assertThat(new String(bytes, StandardCharsets.UTF_8), is("Protocol:" + factory.toString())); - Future eastChan = eastProto - .handle(eastChannel.socket(), new HashMap(), new JnlpConnectionStateListener() { - @Override - public void afterProperties(@NonNull JnlpConnectionState event) { - event.approve(); - } - - @Override - public void beforeChannel(@NonNull JnlpConnectionState event) { - event.getChannelBuilder().withMode(Channel.Mode.NEGOTIATE); - } + } - @Override - public void afterChannel(@NonNull JnlpConnectionState event) { - } - }); + private void assertChannelFails(Future clientChannelFuture, + Future serverChannelFuture, + Class serverExceptionType) throws InterruptedException, java.util.concurrent.TimeoutException { try { - eastRemoting = eastChan.get(10, TimeUnit.SECONDS); + serverRemotingChannel = serverChannelFuture.get(10, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); + assertThat(e.getCause(), instanceOf(serverExceptionType)); } try { - westRemoting = westChan.get(10, TimeUnit.SECONDS); + clientRemotingChannel = clientChannelFuture.get(10, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ConnectionRefusalException.class)); + assertThat(e.getCause(), instanceOf(IOException.class)); } } @@ -709,53 +315,6 @@ public static boolean[] useNioHub() { @DataPoints public static Factory[] protocols() { return new Factory[]{ - //TODO: Disable JNLP-1 tests by default? - new Factory() { - @Override - public JnlpProtocolHandler create(JnlpClientDatabase db, - ExecutorService svc, - IOHub selector, NioChannelHub hub, - SSLContext ctx, - boolean preferNio) { - return new JnlpProtocol1Handler(db, svc, hub, preferNio); - } - - @Override - public String toString() { - return "JNLP-connect"; - } - }, - new Factory() { - @Override - public JnlpProtocolHandler create(JnlpClientDatabase db, - ExecutorService svc, - IOHub selector, NioChannelHub hub, - SSLContext ctx, - boolean preferNio) { - return new JnlpProtocol2Handler(db, svc, hub, preferNio); - } - - @Override - public String toString() { - return "JNLP2-connect"; - } - }, - //TODO: Disable JNLP3 tests by default? - new Factory() { - @Override - public JnlpProtocolHandler create(JnlpClientDatabase db, - ExecutorService svc, - IOHub selector, NioChannelHub hub, - SSLContext ctx, - boolean preferNio) { - return new JnlpProtocol3Handler(db, svc, hub, preferNio); - } - - @Override - public String toString() { - return "JNLP3-connect"; - } - }, new Factory() { @Override public JnlpProtocolHandler create(JnlpClientDatabase db, @@ -791,6 +350,29 @@ public String toString() { }; } + private class StateListener extends JnlpConnectionStateListener { + private Channel.Mode mode; + private Consumer afterPropertiesConsumer; + + StateListener(Consumer afterPropertiesConsumer, Channel.Mode mode) { + this.mode = mode; + this.afterPropertiesConsumer = afterPropertiesConsumer; + } + + @Override + public void afterProperties(@NonNull JnlpConnectionState event) { + afterPropertiesConsumer.accept(event); + } + + @Override + public void beforeChannel(@NonNull JnlpConnectionState event) { + event.getChannelBuilder().withMode(mode); + } + + @Override + public void afterChannel(@NonNull JnlpConnectionState event) { + } + } public interface Factory { JnlpProtocolHandler create(JnlpClientDatabase db, ExecutorService svc, From 0272a4302e5eb3d619530fdb3e1ab4f45663a9b3 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 17 May 2018 11:02:24 -0400 Subject: [PATCH 152/243] [JENKINS-51223] catch more domain suffix based no_proxy matches (add curl-like algorithm to existing regexp) remove some of the duplicate JnlpAgentEndpointResolver/Util code --- src/main/java/hudson/remoting/Util.java | 29 ++++++++-- .../engine/JnlpAgentEndpointResolver.java | 54 ++----------------- src/test/java/hudson/remoting/UtilTest.java | 41 ++++++++++++++ 3 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/main/java/hudson/remoting/Util.java b/src/main/java/hudson/remoting/Util.java index 455197033..d017a993a 100644 --- a/src/main/java/hudson/remoting/Util.java +++ b/src/main/java/hudson/remoting/Util.java @@ -30,7 +30,8 @@ * * @author Kohsuke Kawaguchi */ -class Util { +@Restricted(NoExternalUse.class) +public class Util { /** * Gets the file name portion from a qualified '/'-separate resource path name. * @@ -108,7 +109,7 @@ static String indent(String s) { * * Warning: this method won't match shortened representation of IPV6 address */ - static boolean inNoProxyEnvVar(@Nonnull String host) { + public static boolean inNoProxyEnvVar(@Nonnull String host) { String noProxy = System.getenv("no_proxy"); if (noProxy != null) { noProxy = noProxy.trim() @@ -124,6 +125,7 @@ static boolean inNoProxyEnvVar(@Nonnull String host) { } else { int depth = 0; + String originalHost = host; // Loop while we have a valid domain name: acme.com // We add a safeguard to avoid a case where the host would always be valid because the regex would // for example fail to remove subdomains. @@ -135,8 +137,13 @@ static boolean inNoProxyEnvVar(@Nonnull String host) { if (noProxy.matches(".*(^|,)\\Q" + host + "\\E($|,).*")) return true; // Remove first subdomain: master.jenkins.acme.com -> jenkins.acme.com - else - host = host.replaceFirst("^[a-z0-9]+(-[a-z0-9]+)*\\.", ""); + host = host.replaceFirst("^[a-z0-9]+(-[a-z0-9]+)*\\.", ""); + } + + String[] noProxyArray = noProxy.split(","); + // fix for https://issues.jenkins-ci.org/browse/JENKINS-51223, basic suffix match + if (depth > 0 && suffixMatch(originalHost, noProxyArray)) { + return true; } } } @@ -145,6 +152,20 @@ static boolean inNoProxyEnvVar(@Nonnull String host) { return false; } + // fix for https://issues.jenkins-ci.org/browse/JENKINS-51223 + // adds curl-like algorithm for matching to existing regexp used in + // inNoProxyEnvVars + static boolean suffixMatch(String host, String[] noProxyArray) { + for (String proxy : noProxyArray) { + // still needs to capture some form of subdomain, like ".svc" + if (!proxy.contains(".")) + continue; + if (host.endsWith(proxy)) + return true; + } + return false; + } + /** * Gets URL connection. * If http_proxy environment variable exists, the connection uses the proxy. diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index f773a6e1b..b8c9a9da8 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -24,6 +24,8 @@ package org.jenkinsci.remoting.engine; import hudson.remoting.Base64; +import hudson.remoting.Util; + import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; import org.jenkinsci.remoting.util.https.NoCheckTrustManager; @@ -498,58 +500,8 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy return con; } - /** - * Check if given URL is in the exclusion list defined by the no_proxy environment variable. - * On most *NIX system wildcards are not supported but if one top domain is added, all related subdomains will also - * be ignored. Both "mit.edu" and ".mit.edu" are valid syntax. - * http://www.gnu.org/software/wget/manual/html_node/Proxies.html - * - * Regexp: - * - \Q and \E: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html - * - To match IPV4/IPV/FQDN: Regular Expressions Cookbook, 2nd Edition (ISBN: 9781449327453) - * - * Warning: this method won't match shortened representation of IPV6 address - * - * FIXME: duplicate of hudson.remoting.Util.inNoProxyEnvVar - */ static boolean inNoProxyEnvVar(String host) { - String noProxy = System.getenv("no_proxy"); - if (noProxy != null) { - noProxy = noProxy.trim() - // Remove spaces - .replaceAll("\\s+", "") - // Convert .foobar.com to foobar.com - .replaceAll("((?<=^|,)\\.)*(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})(?=($|,))", "$2"); - - if (!noProxy.isEmpty()) { - // IPV4 and IPV6 - if (host.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$") || host - .matches("^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$")) { - return noProxy.matches(".*(^|,)\\Q" + host + "\\E($|,).*"); - } else { - int depth = 0; - // Loop while we have a valid domain name: acme.com - // We add a safeguard to avoid a case where the host would always be valid because the regex would - // for example fail to remove subdomains. - // According to Wikipedia (no RFC defines it), 128 is the max number of subdivision for a valid - // FQDN: - // https://en.wikipedia.org/wiki/Subdomain#Overview - while (host.matches("^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$") && depth < 128) { - ++depth; - // Check if the no_proxy contains the host - if (noProxy.matches(".*(^|,)\\Q" + host + "\\E($|,).*")) { - return true; - } - // Remove first subdomain: master.jenkins.acme.com -> jenkins.acme.com - else { - host = host.replaceFirst("^[a-z0-9]+(-[a-z0-9]+)*\\.", ""); - } - } - } - } - } - - return false; + return Util.inNoProxyEnvVar(host); } @CheckForNull diff --git a/src/test/java/hudson/remoting/UtilTest.java b/src/test/java/hudson/remoting/UtilTest.java index 2f9a9256e..762e79c78 100644 --- a/src/test/java/hudson/remoting/UtilTest.java +++ b/src/test/java/hudson/remoting/UtilTest.java @@ -121,6 +121,47 @@ public void testSubFQDNWithDot() { assertEquals(false, Util.inNoProxyEnvVar("foobar.com")); } + + @Test + public void testSubFWDNWithDotMinimalSuffix() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn(".svc"); + + assertEquals(true, Util.inNoProxyEnvVar("bn-myproj.svc")); + } + + @Test + public void testSubFWDNWithDotMinimalSuffixMixedCase() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn(".svc,.default,.local,localhost,.boehringer.com,10.250.0.0/16,10.251.0.0/16,10.183.195.106,10.183.195.107,10.183.195.108,10.183.195.109,10.183.195.11,10.183.195.111,10.183.195.112,10.183.195.113,10.183.195.13,10.250.127."); + + assertEquals(true, Util.inNoProxyEnvVar("bn-myproj.svc")); + } + + @Test + public void testNoProxyWithInvalidChars() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn("foo+.co=m"); + + assertEquals(false, Util.inNoProxyEnvVar("foo+.co=m")); + } + + @Test + public void testNoProxyWithInvalidChar() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn("foo.co=m"); + + assertEquals(false, Util.inNoProxyEnvVar("foo.co=m")); + } + + @Test + public void testNoProxyWithInvalidCharInMinimalSuffix() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn(".sv=c"); + + assertEquals(false, Util.inNoProxyEnvVar("foo.sv=c")); + } + @Test + public void testSubFWDNWithoutDotMinimalSuffix() { + PowerMockito.when(System.getenv("no_proxy")).thenReturn("svc"); + + assertEquals(false, Util.inNoProxyEnvVar("bn-myproj.svc")); + } @Test public void testMixed() { From 8e8f84bd7037a23b46973429e3bb4b36c197d4a1 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 29 May 2018 15:42:29 -0600 Subject: [PATCH 153/243] Close up a little more cleanly. --- .../jenkinsci/remoting/protocol/impl/NetworkLayerTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java b/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java index b5b778625..09e94ea06 100644 --- a/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java +++ b/src/test/java/org/jenkinsci/remoting/protocol/impl/NetworkLayerTest.java @@ -87,6 +87,7 @@ public void doBasicSendReceive(NetworkLayerFactory serverFactory, NetworkLayerFa .on(clientFactory.create(hub, serverToClient.source(), clientToServer.sink())) .build(new IOBufferMatcherLayer()); + ProtocolStack server = ProtocolStack .on(serverFactory.create(hub, clientToServer.source(), serverToClient.sink())) @@ -99,7 +100,8 @@ public void doBasicSendReceive(NetworkLayerFactory serverFactory, NetworkLayerFa server.get().send(data); client.get().awaitByteContent(is(expected)); assertThat(client.get().asByteArray(), is(expected)); - server.get().close(null); + server.get().close(); + client.get().awaitClose(); } } From 16d05f3b6bfc33f8fc7372566f535243ff3b0783 Mon Sep 17 00:00:00 2001 From: Jeff Thompson <37345299+jeffret-b@users.noreply.github.com> Date: Wed, 30 May 2018 15:33:48 -0600 Subject: [PATCH 154/243] Revert "[JENKINS-50933] - Integration tests with Jenkins Core" --- .gitignore | 1 - Jenkinsfile | 49 +++++++++++++++++++++------------ src/test/it/Makefile | 29 ------------------- src/test/it/essentials.yml | 26 ----------------- src/test/it/packager-config.yml | 38 ------------------------- 5 files changed, 32 insertions(+), 111 deletions(-) delete mode 100644 src/test/it/Makefile delete mode 100644 src/test/it/essentials.yml delete mode 100644 src/test/it/packager-config.yml diff --git a/.gitignore b/.gitignore index 2a5254e0e..9f9537682 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ target /.project /.settings .fbExcludeFilterFile -src/test/it/out diff --git a/Jenkinsfile b/Jenkinsfile index a0b6296d7..179388e56 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,38 +4,53 @@ properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']]]) + /* These platforms correspond to labels in ci.jenkins.io, see: * https://github.com/jenkins-infra/documentation/blob/master/ci.adoc */ List platforms = ['linux', 'windows'] Map branches = [:] + for (int i = 0; i < platforms.size(); ++i) { String label = platforms[i] branches[label] = { node(label) { timestamps { - checkout scm - infra.runMaven(["--batch-mode", "clean", "install", "-Dmaven.test.failure.ignore=true"]) - - /* Archive the test results */ - try { - junit '**/target/surefire-reports/TEST-*.xml' - } catch(Exception ex) { - echo "Ignoring JUnit step failure: ${ex.message}" + stage('Checkout') { + checkout scm } - if (label == 'linux') { - archiveArtifacts artifacts: 'target/**/*.jar' - findbugs pattern: '**/target/findbugsXml.xml' + stage('Build') { + withEnv([ + "JAVA_HOME=${tool 'jdk8'}", + "PATH+MVN=${tool 'mvn'}/bin", + 'PATH+JDK=$JAVA_HOME/bin', + ]) { + timeout(30) { + String command = "mvn --batch-mode clean install -Dmaven.test.failure.ignore=true ${infra.isRunningOnJenkinsInfra() ? '-s settings-azure.xml' : ''} -e" + if (isUnix()) { + sh command + } + else { + bat command + } + } + } + } + + stage('Archive') { + /* Archive the test results */ + junit '**/target/surefire-reports/TEST-*.xml' + + if (label == 'linux') { + archiveArtifacts artifacts: 'target/**/*.jar' + findbugs pattern: '**/target/findbugsXml.xml' + } } } } } } -stage("Build Remoting") { - /* Execute our platforms in parallel */ - //TODO uncomment parallel(branches) -} - -essentialsTest(baseDir: "src/test/it") +/* Execute our platforms in parallel */ +parallel(branches) diff --git a/src/test/it/Makefile b/src/test/it/Makefile deleted file mode 100644 index 09a97c7ae..000000000 --- a/src/test/it/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -# Just a Makefile for manual testing -.PHONY: all - -ARTIFACT_ID = jenkins-remoting-it -VERSION = 2.107.3-remoting-it-SNAPSHOT -# https://github.com/jenkinsci/custom-war-packager/pull/28 -CWP_VERSION = 0.1-alpha-6-20180514.155901-2 - -all: clean build - -clean: - rm -rf tmp - -build: tmp/output/target/jenkins-war-${VERSION}.war - -tmp/output/target/jenkins-war-${VERSION}.war: - # TODO: https://github.com/jenkinsci/custom-war-packager/pull/26 - mvn io.jenkins.tools.custom-war-packager:custom-war-packager-maven-plugin:${CWP_VERSION}:build -DconfigFile=essentials.yml -Dversion=${VERSION} - -run: tmp/output/target/jenkins-war-${VERSION}.war - JENKINS_HOME=work java -jar tmp/output/target/${ARTIFACT_ID}-${VERSION}.war \ - --httpPort=8080 --prefix=/jenkins - -pct: tmp/output/target/jenkins-war-${VERSION}.war - docker run --rm -v maven-repo:/root/.m2 -v $(shell pwd)/out:/pct/out \ - -v $(shell pwd)/tmp/output/target/${ARTIFACT_ID}-${VERSION}.war:/pct/jenkins.war:ro \ - -e ARTIFACT_ID=ssh-slaves \ - -e INSTALL_BUNDLED_SNAPSHOTS=true \ - jenkins/pct diff --git a/src/test/it/essentials.yml b/src/test/it/essentials.yml deleted file mode 100644 index 020b33cdd..000000000 --- a/src/test/it/essentials.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -packaging: - # We cannot use BOM here, because BOM does not support build by relative path - configFile: "packager-config.yml" - archiveArtifacts: true - # https://github.com/jenkinsci/custom-war-packager/pull/28 - cwpVersion: 0.1-alpha-6-20180514.155850-2 -ath: - useLocalSnapshots: false - athImage: "local" - tests: - - "plugins.SshSlavesPluginTest" - - "core.SlaveTest" - categories: - - "org.jenkinsci.test.acceptance.junit.SmokeTest" -pct: - useLocalSnapshots: false - jth: - version: 2.38 - passCustomJenkinsWAR: true - plugins: - - "ssh-slaves" - - "command-launcher" -# JENKINS-50933 -# - "windows-slaves" - # TODO: JTH/PCT does not work reliably against non-bundled plugins (if something else is bundled) - "compress-artifacts" diff --git a/src/test/it/packager-config.yml b/src/test/it/packager-config.yml deleted file mode 100644 index 5380bf0da..000000000 --- a/src/test/it/packager-config.yml +++ /dev/null @@ -1,38 +0,0 @@ -bundle: - groupId: "io.jenkins.tools.war-packager.demo" - artifactId: "jenkins-remoting-it" - vendor: "Jenkins project" - title: "Remoting Integration Tests WAR" - description: "Jenkins WAR, which bundles local Remoting build and plugin for PCT" -war: - groupId: "org.jenkins-ci.main" - artifactId: "jenkins-war" - source: - version: "2.107.3" -plugins: - - groupId: "org.jenkins-ci.plugins" - artifactId: "ssh-slaves" - source: - version: "1.25" - - groupId: "org.jenkins-ci.plugins" - artifactId: "command-launcher" - source: - version: "1.2" - - groupId: "org.jenkins-ci.plugins" - artifactId: "windows-slaves" - source: - version: "1.3.1" - - groupId: "org.jenkins-ci.plugins" - artifactId: "credentials" - source: - version: "2.1.10" - - groupId: "org.jenkins-ci.plugins" - artifactId: "ssh-credentials" - source: - version: "1.12" -libPatches: - # Override Remoting by a locally built version - - groupId: "org.jenkins-ci.main" - artifactId: "remoting" - source: - dir: ../../.. From 5e67d1c900d06a5e18100d073dc56b79d5fa2a29 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 31 May 2018 12:21:12 -0400 Subject: [PATCH 155/243] Suppress some irrelevant stack traces. --- src/main/java/hudson/remoting/Request.java | 6 +++++- src/main/java/org/jenkinsci/remoting/protocol/IOHub.java | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 8f3fd61d3..79d827a59 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -383,7 +383,11 @@ public void run() { } catch (IOException e) { // communication error. // this means the caller will block forever - logger.log(Level.WARNING, "Failed to send back a reply to the request " + this, e); + if (e instanceof ChannelClosedException) { + logger.log(Level.INFO, "Failed to send back a reply to the request {0}: {1}", new Object[] {this, e}); + } else { + logger.log(Level.WARNING, "Failed to send back a reply to the request " + this, e); + } } finally { channel.executingCalls.remove(id); Thread.currentThread().setName(oldThreadName); diff --git a/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java b/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java index abcc62d12..f3ff30f7c 100644 --- a/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java +++ b/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java @@ -423,7 +423,13 @@ public final void unregister(SelectableChannel channel) { } private String getThreadNameBase(String executorThreadName) { - return "IOHub#" + _id + ": Selector[keys:" + selector.keys().size() + ", gen:" + gen + "] / " + executorThreadName; + int keySize; + try { + keySize = selector.keys().size(); + } catch (ClosedSelectorException x) { + keySize = -1; // possibly a race condition, ignore + } + return "IOHub#" + _id + ": Selector[keys:" + keySize + ", gen:" + gen + "] / " + executorThreadName; } /** From 7c08830d2e32e2129e90c827dcba1ac18d667ef4 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 6 Jun 2018 16:52:10 -0400 Subject: [PATCH 156/243] Continue to display a complete stack trace in case we are logging at FINE. --- src/main/java/hudson/remoting/Request.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 79d827a59..8c8c95b42 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -383,7 +383,7 @@ public void run() { } catch (IOException e) { // communication error. // this means the caller will block forever - if (e instanceof ChannelClosedException) { + if (e instanceof ChannelClosedException && !logger.isLoggable(Level.FINE)) { logger.log(Level.INFO, "Failed to send back a reply to the request {0}: {1}", new Object[] {this, e}); } else { logger.log(Level.WARNING, "Failed to send back a reply to the request " + this, e); From bed0a07a1b62ffa195ce330a35906f098e7b1e51 Mon Sep 17 00:00:00 2001 From: Pham Vu Tuan Date: Thu, 7 Jun 2018 22:47:45 +0700 Subject: [PATCH 157/243] [JENKINS-51551] Allow CommandTransport and its sub-classes to be sub-typed from outside (#272) * Change some CommandTransport methods from package-private to protected * Change read() and write() access to public * Make fields public to implement remoting-kafka-agent --- src/main/java/hudson/remoting/CommandTransport.java | 10 ++++------ src/main/java/hudson/remoting/JarCache.java | 2 +- .../hudson/remoting/SynchronousCommandTransport.java | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/hudson/remoting/CommandTransport.java b/src/main/java/hudson/remoting/CommandTransport.java index f4884ce88..a3da02f0d 100644 --- a/src/main/java/hudson/remoting/CommandTransport.java +++ b/src/main/java/hudson/remoting/CommandTransport.java @@ -49,17 +49,15 @@ * @since 2.13 */ public abstract class CommandTransport { - /** - * Package private so as not to allow direct subtyping (just yet.) - */ - /*package*/ CommandTransport() { + + protected CommandTransport() { } /** * SPI implemented by {@link Channel} so that the transport can pass the received command * to {@link Channel} for processing. */ - static interface CommandReceiver { + protected static interface CommandReceiver { /** * Notifies the channel that a new {@link Command} was received from the other side. * @@ -142,7 +140,7 @@ static interface CommandReceiver { * Informational flag that indicates that this is the last * call of the {@link #write(Command, boolean)}. */ - abstract void write(Command cmd, boolean last) throws IOException; + public abstract void write(Command cmd, boolean last) throws IOException; /** * Called to close the write side of the transport, allowing the underlying transport diff --git a/src/main/java/hudson/remoting/JarCache.java b/src/main/java/hudson/remoting/JarCache.java index 33bc00f3c..bb39b4df2 100644 --- a/src/main/java/hudson/remoting/JarCache.java +++ b/src/main/java/hudson/remoting/JarCache.java @@ -24,7 +24,7 @@ public abstract class JarCache { /** * Default JAR cache location for disabled workspace Manager. */ - /*package*/ static final File DEFAULT_NOWS_JAR_CACHE_LOCATION = + public static final File DEFAULT_NOWS_JAR_CACHE_LOCATION = new File(System.getProperty("user.home"),".jenkins/cache/jars"); //TODO: replace by checked exception diff --git a/src/main/java/hudson/remoting/SynchronousCommandTransport.java b/src/main/java/hudson/remoting/SynchronousCommandTransport.java index 6961929ac..263196587 100644 --- a/src/main/java/hudson/remoting/SynchronousCommandTransport.java +++ b/src/main/java/hudson/remoting/SynchronousCommandTransport.java @@ -31,7 +31,7 @@ public abstract class SynchronousCommandTransport extends CommandTransport { /** * Called by {@link Channel} to read the next command to arrive from the stream. */ - abstract Command read() throws IOException, ClassNotFoundException, InterruptedException; + public abstract Command read() throws IOException, ClassNotFoundException, InterruptedException; @Override public void setup(Channel channel, CommandReceiver receiver) { From 304ef82001ab36993ed6677c180bd1e6761880b7 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 7 Jun 2018 11:48:58 -0400 Subject: [PATCH 158/243] Suppress some irrelevant ChannelClosed stack traces if logging level is not FINE (#274) * Suppress some irrelevant stack traces. * Continue to display a complete stack trace in case we are logging at FINE. --- src/main/java/hudson/remoting/Request.java | 6 +++++- src/main/java/org/jenkinsci/remoting/protocol/IOHub.java | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/Request.java b/src/main/java/hudson/remoting/Request.java index 8f3fd61d3..8c8c95b42 100644 --- a/src/main/java/hudson/remoting/Request.java +++ b/src/main/java/hudson/remoting/Request.java @@ -383,7 +383,11 @@ public void run() { } catch (IOException e) { // communication error. // this means the caller will block forever - logger.log(Level.WARNING, "Failed to send back a reply to the request " + this, e); + if (e instanceof ChannelClosedException && !logger.isLoggable(Level.FINE)) { + logger.log(Level.INFO, "Failed to send back a reply to the request {0}: {1}", new Object[] {this, e}); + } else { + logger.log(Level.WARNING, "Failed to send back a reply to the request " + this, e); + } } finally { channel.executingCalls.remove(id); Thread.currentThread().setName(oldThreadName); diff --git a/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java b/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java index abcc62d12..f3ff30f7c 100644 --- a/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java +++ b/src/main/java/org/jenkinsci/remoting/protocol/IOHub.java @@ -423,7 +423,13 @@ public final void unregister(SelectableChannel channel) { } private String getThreadNameBase(String executorThreadName) { - return "IOHub#" + _id + ": Selector[keys:" + selector.keys().size() + ", gen:" + gen + "] / " + executorThreadName; + int keySize; + try { + keySize = selector.keys().size(); + } catch (ClosedSelectorException x) { + keySize = -1; // possibly a race condition, ignore + } + return "IOHub#" + _id + ": Selector[keys:" + keySize + ", gen:" + gen + "] / " + executorThreadName; } /** From 297782efdc2bc9ba75691e459da69924b2175188 Mon Sep 17 00:00:00 2001 From: Andrei Laptsinski Date: Fri, 8 Jun 2018 16:36:51 +0300 Subject: [PATCH 159/243] Add JNLP port availability check --- .../engine/JnlpAgentEndpointResolver.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index b8c9a9da8..3d1c40a14 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -34,9 +34,11 @@ import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.UnknownHostException; import java.net.NoRouteToHostException; import java.net.Proxy; import java.net.ProxySelector; +import java.net.Socket; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.net.URI; @@ -279,6 +281,11 @@ public JnlpAgentEndpoint resolve() throws IOException { firstError = chain(firstError, new IOException(jenkinsUrl + " is publishing an invalid port")); continue; } + if (!isPortVisible(host, port, 5000)) { + firstError = chain(firstError, new IOException(jenkinsUrl + " provided port:" + port + + " is not reachable")); + continue; + } // sort the URLs so that the winner is the one we try first next time final String winningJenkinsUrl = jenkinsUrl; Collections.sort(jenkinsUrls, new Comparator() { @@ -312,6 +319,45 @@ public int compare(String o1, String o2) { return null; } + private boolean isPortVisible(String hostname, int port, int timeout) { + boolean exitStatus = false; + Socket s = null; + String reason = null; + + try { + s = new Socket(); + s.setReuseAddress(true); + SocketAddress sa = new InetSocketAddress(hostname, port); + s.connect(sa, timeout); + } catch (IOException e) { + if (e.getMessage().equals("Connection refused")) { + reason = "port " + port + " on " + hostname + " is closed."; + } + if (e instanceof UnknownHostException) { + reason = "node " + hostname + " is unresolved."; + } + if (e instanceof SocketTimeoutException) { + reason = "timeout while attempting to reach node " + hostname + " on port " + port; + } + } finally { + if (s != null) { + if (s.isConnected()) { + LOGGER.info("Port " + port + " on " + hostname + " is reachable!"); + exitStatus = true; + } else { + LOGGER.warning("Port " + port + " on " + hostname + " is not reachable; reason: " + reason); + } + try { + s.close(); + } catch (IOException e) { + LOGGER.warning(e.getMessage()); + } + } + } + + return exitStatus; + } + @Nonnull private URL toAgentListenerURL(@Nonnull String jenkinsUrl) throws MalformedURLException { return jenkinsUrl.endsWith("/") From 7b48cea4ccbe81f81d36a4a1e969374258270f31 Mon Sep 17 00:00:00 2001 From: Andrei Laptsinski Date: Fri, 8 Jun 2018 17:18:39 +0300 Subject: [PATCH 160/243] Optimize output for log messages in port visible check --- .../engine/JnlpAgentEndpointResolver.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 3d1c40a14..18c154fc8 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -34,7 +34,6 @@ import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; -import java.net.UnknownHostException; import java.net.NoRouteToHostException; import java.net.Proxy; import java.net.ProxySelector; @@ -322,7 +321,6 @@ public int compare(String o1, String o2) { private boolean isPortVisible(String hostname, int port, int timeout) { boolean exitStatus = false; Socket s = null; - String reason = null; try { s = new Socket(); @@ -330,22 +328,11 @@ private boolean isPortVisible(String hostname, int port, int timeout) { SocketAddress sa = new InetSocketAddress(hostname, port); s.connect(sa, timeout); } catch (IOException e) { - if (e.getMessage().equals("Connection refused")) { - reason = "port " + port + " on " + hostname + " is closed."; - } - if (e instanceof UnknownHostException) { - reason = "node " + hostname + " is unresolved."; - } - if (e instanceof SocketTimeoutException) { - reason = "timeout while attempting to reach node " + hostname + " on port " + port; - } + LOGGER.warning(e.getMessage()); } finally { if (s != null) { if (s.isConnected()) { - LOGGER.info("Port " + port + " on " + hostname + " is reachable!"); exitStatus = true; - } else { - LOGGER.warning("Port " + port + " on " + hostname + " is not reachable; reason: " + reason); } try { s.close(); @@ -354,7 +341,6 @@ private boolean isPortVisible(String hostname, int port, int timeout) { } } } - return exitStatus; } From f28f77ebd5622ec5fb94172a8e78eb2f475073de Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 8 Jun 2018 20:22:51 +0200 Subject: [PATCH 161/243] Changelog: Noting 3.21 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e41aa81ea..0e079653b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,31 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.21 + +Enhancements: + +* [JENKINS-51551](https://issues.jenkins-ci.org/browse/JENKINS-51551) - +Developer API: Allow creating custom `CommandTransport` implementation in external +components. + * Reference implementation: [Remoting Kafka Plugin](https://github.com/jenkinsci/remoting-kafka-plugin) +* [PR #274](https://github.com/jenkinsci/remoting/pull/274) - +Do not print channel close reason stack traces for non-sent request responses when +`hudson.remoting.Request` logging level is lower than `FINE`. + +Fixed issues: + +* [JENKINS-51223](https://issues.jenkins-ci.org/browse/JENKINS-51223) - +`no_proxy` environment variable parsing logic did not properly support +domain suffixes in fully-qualified names. +Now it is possible to set suffixes like `.com` in `no_proxy`. +* [JENKINS-50965](https://issues.jenkins-ci.org/browse/JENKINS-50965) - +Fix malformed log message when loading of classes is forced by +the `hudson.remoting.RemoteClassLoader.force` system property. +* [PR #274](https://github.com/jenkinsci/remoting/pull/274) - +Prevent exception in IOHub when retrieving base thread name for handlers +when NIO selector is already closed (race condition). + ##### 3.20 Release date: Apr 18, 2018 From 3c0157dd64b1c63e46fc31c54b0da39bf8d54f1f Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 8 Jun 2018 20:34:20 +0200 Subject: [PATCH 162/243] [maven-release-plugin] prepare release remoting-3.21 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 720996554..aca291e12 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.21-SNAPSHOT + 3.21 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.21 From f7ba8d3d2b297c133149a4151f3724d9df9d9c6a Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 8 Jun 2018 20:34:29 +0200 Subject: [PATCH 163/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aca291e12..66fde72f1 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.21 + 3.22-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.21 + HEAD From 28aa0de53f69dba6a9c2e136d50fbfb383133e02 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 10 Jun 2018 14:30:25 +0200 Subject: [PATCH 164/243] [JENKINS-51841] - Offer a method for standartized command deserialization from the channel --- src/main/java/hudson/remoting/Command.java | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Command.java b/src/main/java/hudson/remoting/Command.java index a71a49862..6c912c400 100644 --- a/src/main/java/hudson/remoting/Command.java +++ b/src/main/java/hudson/remoting/Command.java @@ -23,12 +23,14 @@ */ package hudson.remoting; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.concurrent.ExecutionException; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** * One-way command to be sent over to the remote system and executed there. @@ -108,7 +110,24 @@ void writeTo(Channel channel, ObjectOutputStream oos) throws IOException { Channel.setCurrent(old); } } - + + /** + * Reads command from the specified payload. + * @param channel Channel + * @param payload Payload + * @return Read command + * @throws IOException Read exception + * @throws ClassNotFoundException Deserialization error: class not found + */ + public static Command readFrom(@Nonnull Channel channel, @Nonnull byte[] payload) + throws IOException, ClassNotFoundException { + Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( + new ByteArrayInputStream(payload), + channel.baseClassLoader,channel.classFilter)); + channel.notifyRead(cmd, payload.length); + return cmd; + } + /** Consider calling {@link Channel#notifyRead} afterwards. */ static Command readFrom(Channel channel, ObjectInputStream ois) throws IOException, ClassNotFoundException { Channel old = Channel.setCurrent(channel); From 0a9754c6d9f3bf70cd112b93cc3c520289928570 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 12 Jun 2018 18:39:15 +0200 Subject: [PATCH 165/243] Annotate ChannelBuider#header --- src/main/java/hudson/remoting/ChannelBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/ChannelBuilder.java b/src/main/java/hudson/remoting/ChannelBuilder.java index a57912f11..bc0916b9c 100644 --- a/src/main/java/hudson/remoting/ChannelBuilder.java +++ b/src/main/java/hudson/remoting/ChannelBuilder.java @@ -48,6 +48,7 @@ public class ChannelBuilder { private ClassLoader base = this.getClass().getClassLoader(); private Mode mode = Mode.NEGOTIATE; private Capability capability = new Capability(); + @CheckForNull private OutputStream header; @CheckForNull private JarCache jarCache; @@ -125,11 +126,12 @@ public Capability getCapability() { * when the established communication channel might include some data that might * be useful for debugging/trouble-shooting. */ - public ChannelBuilder withHeaderStream(OutputStream header) { + public ChannelBuilder withHeaderStream(@CheckForNull OutputStream header) { this.header = header; return this; } + @CheckForNull public OutputStream getHeaderStream() { return header; } From c8be1eb72d881e27ffb4f40e52cd720a4a77ec6d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Jun 2018 23:05:16 +0200 Subject: [PATCH 166/243] [JENKINS-51841] - Generalize Command deserialization within Remoting --- .../AbstractByteArrayCommandTransport.java | 5 +--- .../AbstractByteBufferCommandTransport.java | 4 +--- ...tSynchronousByteArrayCommandTransport.java | 6 +---- .../remoting/ClassicCommandTransport.java | 2 +- src/main/java/hudson/remoting/Command.java | 24 +++++++++++++++---- .../hudson/remoting/CommandTransport.java | 7 +++--- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java index dd597dd6e..7daf0acd2 100644 --- a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java @@ -60,10 +60,7 @@ public final void setup(final Channel channel, final CommandReceiver receiver) { setup(new ByteArrayReceiver() { public void handle(byte[] payload) { try { - Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( - new ByteArrayInputStream(payload),channel.baseClassLoader,channel.classFilter)); - receiver.handle(cmd); - channel.notifyRead(cmd, payload.length); + Command cmd = Command.readFrom(channel, payload); } catch (IOException | ClassNotFoundException e) { LOGGER.log(Level.WARNING, "Failed to construct Command in channel " + channel.getName(), e); } diff --git a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java index 165398e00..6d4d7a9c2 100644 --- a/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteBufferCommandTransport.java @@ -199,10 +199,8 @@ private void processCommand() throws IOException { try { FastByteBufferQueueInputStream is = new FastByteBufferQueueInputStream(receiveQueue, readCommandSizes[0]); try { - ObjectInputStreamEx ois = new ObjectInputStreamEx(is, channel.baseClassLoader, channel.classFilter); - Command cmd = Command.readFrom(channel, ois); + Command cmd = Command.readFrom(channel, is, readCommandSizes[0]); receiver.handle(cmd); - channel.notifyRead(cmd, readCommandSizes[0]); } catch (IOException | ClassNotFoundException e) { LOGGER.log(Level.WARNING, "Failed to construct Command in channel " + channel.getName(), e); } finally { diff --git a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java index e0a116ca9..ed30d8a82 100644 --- a/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractSynchronousByteArrayCommandTransport.java @@ -33,11 +33,7 @@ public abstract class AbstractSynchronousByteArrayCommandTransport extends Synch @Override public Command read() throws IOException, ClassNotFoundException { byte[] block = readBlock(channel); - Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( - new ByteArrayInputStream(block), - channel.baseClassLoader,channel.classFilter)); - channel.notifyRead(cmd, block.length); - return cmd; + return Command.readFrom(channel, block); } @Override diff --git a/src/main/java/hudson/remoting/ClassicCommandTransport.java b/src/main/java/hudson/remoting/ClassicCommandTransport.java index 6b551efc9..58fbb233f 100644 --- a/src/main/java/hudson/remoting/ClassicCommandTransport.java +++ b/src/main/java/hudson/remoting/ClassicCommandTransport.java @@ -68,7 +68,7 @@ public void closeWrite() throws IOException { public final Command read() throws IOException, ClassNotFoundException { try { - Command cmd = Command.readFrom(channel, ois); + Command cmd = Command.readFromObjectStream(channel, ois); // TODO notifyRead using CountingInputStream if (rawIn!=null) rawIn.clear(); diff --git a/src/main/java/hudson/remoting/Command.java b/src/main/java/hudson/remoting/Command.java index 6c912c400..ad01dc2a6 100644 --- a/src/main/java/hudson/remoting/Command.java +++ b/src/main/java/hudson/remoting/Command.java @@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -121,15 +122,30 @@ void writeTo(Channel channel, ObjectOutputStream oos) throws IOException { */ public static Command readFrom(@Nonnull Channel channel, @Nonnull byte[] payload) throws IOException, ClassNotFoundException { - Command cmd = Command.readFrom(channel, new ObjectInputStreamEx( - new ByteArrayInputStream(payload), + return readFrom(channel, new ByteArrayInputStream(payload), payload.length); + } + + /** + * Reads command from the specified payload. + * @param channel Channel + * @param istream Input stream + * @param payloadSize Payload size to be read. Used only for logging + * @return Read command + * @throws IOException Read exception + * @throws ClassNotFoundException Deserialization error: class not found + */ + /*package*/ static Command readFrom(@Nonnull Channel channel, @Nonnull InputStream istream, int payloadSize) + throws IOException, ClassNotFoundException { + Command cmd = Command.readFromObjectStream(channel, new ObjectInputStreamEx( + istream, channel.baseClassLoader,channel.classFilter)); - channel.notifyRead(cmd, payload.length); + channel.notifyRead(cmd, payloadSize); return cmd; } + /** Consider calling {@link Channel#notifyRead} afterwards. */ - static Command readFrom(Channel channel, ObjectInputStream ois) throws IOException, ClassNotFoundException { + static Command readFromObjectStream(Channel channel, ObjectInputStream ois) throws IOException, ClassNotFoundException { Channel old = Channel.setCurrent(channel); try { return (Command)ois.readObject(); diff --git a/src/main/java/hudson/remoting/CommandTransport.java b/src/main/java/hudson/remoting/CommandTransport.java index a3da02f0d..9a440cae9 100644 --- a/src/main/java/hudson/remoting/CommandTransport.java +++ b/src/main/java/hudson/remoting/CommandTransport.java @@ -43,7 +43,8 @@ * {@link Command} objects need to be serialized and deseralized in a specific environment * so that {@link Command}s can access {@link Channel} that's using it. Because of this, * a transport needs to use {@link Command#writeTo(Channel, ObjectOutputStream)} and - * {@link Command#readFrom(Channel, ObjectInputStream)}. + * {@link Command#readFromObjectStream(Channel, ObjectInputStream)} or + * {@link Command#readFrom(Channel, byte[])}. * * @author Kohsuke Kawaguchi * @since 2.13 @@ -69,8 +70,8 @@ protected static interface CommandReceiver { * concurrently. * * @param cmd - * The command received. This object must be read from {@link ObjectInputStream} via - * {@link Command#readFrom(Channel, ObjectInputStream)} + * The command received. This object must be read from the payload + * using {@link Command#readFrom(Channel, byte[])}. */ void handle(Command cmd); From 1416d87ccec4f875e2ba7ab8cf185471e70e4a80 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 15 Jun 2018 16:06:04 +0200 Subject: [PATCH 167/243] [JENKINS-51841] - Add missing TODO --- src/main/java/hudson/remoting/Command.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/hudson/remoting/Command.java b/src/main/java/hudson/remoting/Command.java index ad01dc2a6..483ec6cf8 100644 --- a/src/main/java/hudson/remoting/Command.java +++ b/src/main/java/hudson/remoting/Command.java @@ -119,6 +119,7 @@ void writeTo(Channel channel, ObjectOutputStream oos) throws IOException { * @return Read command * @throws IOException Read exception * @throws ClassNotFoundException Deserialization error: class not found + * @since TODO */ public static Command readFrom(@Nonnull Channel channel, @Nonnull byte[] payload) throws IOException, ClassNotFoundException { From 1f9e597ab6b13793e08405ac358ba8e5e59b9bcb Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 16 Jun 2018 00:48:24 +0200 Subject: [PATCH 168/243] [JENKINS-51841] - AbstractByteArrayCommandTransport was missing receiver.handle(cmd) as @jglick noticed --- .../java/hudson/remoting/AbstractByteArrayCommandTransport.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java index 7daf0acd2..7299c6ca7 100644 --- a/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java +++ b/src/main/java/hudson/remoting/AbstractByteArrayCommandTransport.java @@ -61,6 +61,7 @@ public final void setup(final Channel channel, final CommandReceiver receiver) { public void handle(byte[] payload) { try { Command cmd = Command.readFrom(channel, payload); + receiver.handle(cmd); } catch (IOException | ClassNotFoundException e) { LOGGER.log(Level.WARNING, "Failed to construct Command in channel " + channel.getName(), e); } From c6866d15a0099c9b52183cc0a10990061aad23a8 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Tue, 19 Jun 2018 15:59:26 +0200 Subject: [PATCH 169/243] Avoid the configuration.md table to be broken - a comma was added between two TDs --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index d339a5474..f204e067e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -93,7 +93,7 @@ These properties require independent configuration on both sides of the channel. ${PROTOCOL_FULLY_QUALIFIED_NAME}.disabled, where PROTOCOL_FULLY_QUALIFIED_NAME equals - PROTOCOL_HANDLER_CLASSNAME without the Handler suffix., + PROTOCOL_HANDLER_CLASSNAME without the Handler suffix. false 2.59 2.4 From 8ef6c53ab8d5c730d13880de166f62603d79675e Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Jun 2018 10:19:57 +0200 Subject: [PATCH 170/243] [maven-release-plugin] prepare release remoting-3.22 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 66fde72f1..b07f26c24 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.22-SNAPSHOT + 3.22 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.22 From fc75612ee4d2d13a803253d1d428cd363ecbac67 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Jun 2018 10:20:09 +0200 Subject: [PATCH 171/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b07f26c24..aa3873a6d 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.22 + 3.23-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.22 + HEAD From bfcb6e59845405caf714dd436b77cdec9e09b468 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Jun 2018 12:18:00 +0200 Subject: [PATCH 172/243] Changelog: Noting 3.22 + release Jenkins versions --- CHANGELOG.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e079653b..ea90ec3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,20 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.22 + +Release date: Jun 22, 2018 + +* [JENKINS-51818](https://issues.jenkins-ci.org/browse/JENKINS-51818) - +When connectiong over TCP, add a masters TCP Agent Listener port availability check +* [JENKINS-51841](https://issues.jenkins-ci.org/browse/JENKINS-51841) - +Extensibility: Offer a new `Channel#readFrom(Channel, byte[] payload)` method for a standardized command deserialization from the channel +* [PR #277](https://github.com/jenkinsci/remoting/pull/277) - +API: be explicit that `ChannelBuilder#getHeaderStream()` may return null + ##### 3.21 -Enhancements: +Enhancements: Jun 8, 2018 => Jenkins 2.127 * [JENKINS-51551](https://issues.jenkins-ci.org/browse/JENKINS-51551) - Developer API: Allow creating custom `CommandTransport` implementation in external @@ -33,14 +44,14 @@ when NIO selector is already closed (race condition). ##### 3.20 -Release date: Apr 18, 2018 +Release date: Apr 18, 2018 => Jenkins 2.118, 2.121.1 LTS * Refresh the Code-signing certificate * No functional changes ##### 3.19 -Release date: Mar 22, 2018 +Release date: Mar 22, 2018 => Jenkins 2.113 * [JENKINS-49618](https://issues.jenkins-ci.org/browse/JENKINS-49618) - Display Remoting version in the agent log when starting up the agent From 7909e174bbe08eb9e750608225b35589d85a98b0 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Jun 2018 12:25:55 +0200 Subject: [PATCH 173/243] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea90ec3e6..411104cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. Release date: Jun 22, 2018 * [JENKINS-51818](https://issues.jenkins-ci.org/browse/JENKINS-51818) - -When connectiong over TCP, add a masters TCP Agent Listener port availability check +When connecting over TCP, agents will check availability of the master's TCP Agent Listener port * [JENKINS-51841](https://issues.jenkins-ci.org/browse/JENKINS-51841) - Extensibility: Offer a new `Channel#readFrom(Channel, byte[] payload)` method for a standardized command deserialization from the channel * [PR #277](https://github.com/jenkinsci/remoting/pull/277) - From 90d501bca32d0375093ad137c1cd4a8885a7a182 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 28 Jun 2018 14:57:40 +0200 Subject: [PATCH 174/243] [JENKINS-52204] - Document Tunneling options in Remoting --- src/main/java/hudson/remoting/Engine.java | 9 +++++++-- src/main/java/hudson/remoting/jnlp/Main.java | 2 +- .../remoting/engine/JnlpAgentEndpointResolver.java | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 9fd8a9072..097bec254 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -135,8 +135,9 @@ public Thread newThread(final Runnable r) { private String proxyCredentials = System.getProperty("proxyCredentials"); /** - * See Main#tunnel in the jnlp-agent module for the details. + * See {@link hudson.remoting.jnlp.Main#tunnel} for the documentation. */ + @CheckForNull private String tunnel; private boolean disableHttpsCertValidation; @@ -305,7 +306,11 @@ public URL getHudsonUrl() { return hudsonUrl; } - public void setTunnel(String tunnel) { + /** + * If set, connect to the specified host and port instead of connecting directly to Jenkins. + * @param tunnel Value. {@code null} to disable tunneling + */ + public void setTunnel(@CheckForNull String tunnel) { this.tunnel = tunnel; } diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index 7ef203608..d28911fbf 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -69,7 +69,7 @@ public class Main { @Option(name="-tunnel",metaVar="HOST:PORT", usage="Connect to the specified host and port, instead of connecting directly to Jenkins. " + - "Useful when connection to Hudson needs to be tunneled. Can be also HOST: or :PORT, " + + "Useful when connection to Jenkins needs to be tunneled. Can be also HOST: or :PORT, " + "in which case the missing portion will be auto-configured like the default behavior") public String tunnel; diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 18c154fc8..914d9feb6 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -135,11 +135,12 @@ public void setProxyCredentials(String user, String pass) { this.proxyCredentials = user + ":" + pass; } + @CheckForNull public String getTunnel() { return tunnel; } - public void setTunnel(String tunnel) { + public void setTunnel(@CheckForNull String tunnel) { this.tunnel = tunnel; } From 0071eaaf9462588eb9d58e5b578d7d1d8b1a6af5 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 28 Jun 2018 15:00:58 +0200 Subject: [PATCH 175/243] =?UTF-8?q?[JENKINS-52204]=20-=20Do=20not=20check?= =?UTF-8?q?=20port=20availability=20when=20=E2=80=9C-tunnel=E2=80=9D=20opt?= =?UTF-8?q?ion=20is=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engine/JnlpAgentEndpointResolver.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 914d9feb6..da7e5d95e 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -281,10 +281,17 @@ public JnlpAgentEndpoint resolve() throws IOException { firstError = chain(firstError, new IOException(jenkinsUrl + " is publishing an invalid port")); continue; } - if (!isPortVisible(host, port, 5000)) { - firstError = chain(firstError, new IOException(jenkinsUrl + " provided port:" + port - + " is not reachable")); - continue; + if (tunnel == null) { + if (!isPortVisible(host, port, 5000)) { + firstError = chain(firstError, new IOException(jenkinsUrl + " provided port:" + port + + " is not reachable")); + continue; + } else { + LOGGER.log(Level.FINE, "TCP Agent Listener Port availability check passed"); + } + } else { + LOGGER.log(Level.INFO, "Remoting TCP connection tunneling is enabled. " + + "Skipping the TCP Agent Listener Port availability check"); } // sort the URLs so that the winner is the one we try first next time final String winningJenkinsUrl = jenkinsUrl; @@ -307,6 +314,8 @@ public int compare(String o1, String o2) { if (tokens[0].length() > 0) host = tokens[0]; if (tokens[1].length() > 0) port = Integer.parseInt(tokens[1]); } + + //TODO: all the checks above do not make much sense if tunneling is enabled (JENKINS-52246) return new JnlpAgentEndpoint(host, port, identity, agentProtocolNames, selectedJenkinsURL); } finally { From 1b597356b2e02adcaa7a2e36e2720ef1ae8ef47d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 29 Jun 2018 20:39:00 +0200 Subject: [PATCH 176/243] [maven-release-plugin] prepare release remoting-3.23 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aa3873a6d..9d3c3e49b 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.23-SNAPSHOT + 3.23 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.23 From 4bd95eddebee3a8d2116cbc4603d743cb9b35332 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 29 Jun 2018 20:39:12 +0200 Subject: [PATCH 177/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9d3c3e49b..c7e7fd02f 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.23 + 3.24-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.23 + HEAD From cb340bb182efa9b7ec00f76fbff726f05897bba1 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 30 Jun 2018 00:00:38 +0200 Subject: [PATCH 178/243] Noting 3.23 --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411104cbf..6443b24d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,17 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.23 + +Release date: June 29, 2018 + +* [JENKINS-52204](https://issues.jenkins-ci.org/browse/JENKINS-52204) - +Skip Tcp Agent Listener port availability check when `-tunnel` option is set +(regression in 3.22) + ##### 3.22 -Release date: Jun 22, 2018 +Release date: Jun 22, 2018 => 2.129 * [JENKINS-51818](https://issues.jenkins-ci.org/browse/JENKINS-51818) - When connecting over TCP, agents will check availability of the master's TCP Agent Listener port From cd57b947c50ad4dd6db2932903ff99d14659132b Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 12 Jul 2018 14:39:22 -0600 Subject: [PATCH 179/243] [maven-release-plugin] prepare release remoting-3.24 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c7e7fd02f..0df0cca12 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.24-SNAPSHOT + 3.24 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.24 From 65bf8405d9bdde8703a00ac4ade365ba5cd90bbc Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 12 Jul 2018 14:39:29 -0600 Subject: [PATCH 180/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0df0cca12..b510db413 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.24 + 3.25-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.24 + HEAD From bfe8866c0e1e0a5d5f16194a40012788e0851f5b Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 12 Jul 2018 16:07:19 -0600 Subject: [PATCH 181/243] Add pom configuration to gpg plugin. Allow it to run in unattended mode on MacOS with GPG. Without this addition, it gives an error that looks like this: gpg: signing failed: Inappropriate ioctl for device gpg: signing failed: Inappropriate ioctl for device --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index c7e7fd02f..5590913ca 100644 --- a/pom.xml +++ b/pom.xml @@ -498,6 +498,12 @@ THE SOFTWARE. sign + + + --pinentry-mode + loopback + + From bac557efac4cdaa142e196de1653c38992b35c91 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Tue, 31 Jul 2018 19:50:35 +0200 Subject: [PATCH 182/243] [SECURITY-637] --- .../remoting/MultiClassLoaderSerializer.java | 13 +++ .../hudson/remoting/ObjectInputStreamEx.java | 13 +++ .../remoting/URLDeserializationHelper.java | 87 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/main/java/hudson/remoting/URLDeserializationHelper.java diff --git a/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java b/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java index 654ee77db..a9baa5d28 100644 --- a/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java +++ b/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java @@ -9,6 +9,7 @@ import java.io.ObjectStreamClass; import java.io.OutputStream; import java.lang.reflect.Proxy; +import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -88,6 +89,9 @@ static final class Input extends ObjectInputStream { Input(Channel channel, InputStream in) throws IOException { super(in); this.channel = channel; + + // by default, the resolveObject is not called + enableResolveObject(true); } @CheckForNull @@ -167,6 +171,15 @@ protected Class resolveProxyClass(String[] interfaces) throws IOException, Cl return Proxy.getProxyClass(cl, classes); } + + @Override + protected Object resolveObject(Object obj) throws IOException { + if(obj instanceof URL){ + // SECURITY-637, URL deserialization could lead to DNS query + return URLDeserializationHelper.wrapIfRequired((URL) obj); + } + return super.resolveObject(obj); + } } /** diff --git a/src/main/java/hudson/remoting/ObjectInputStreamEx.java b/src/main/java/hudson/remoting/ObjectInputStreamEx.java index 18af937fd..58f9bd913 100644 --- a/src/main/java/hudson/remoting/ObjectInputStreamEx.java +++ b/src/main/java/hudson/remoting/ObjectInputStreamEx.java @@ -29,6 +29,7 @@ import java.io.ObjectStreamClass; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.net.URL; /** * {@link ObjectInputStream} that uses a specific class loader. @@ -48,6 +49,9 @@ public ObjectInputStreamEx(InputStream in, ClassLoader cl, ClassFilter filter) t super(in); this.cl = cl; this.filter = filter; + + // by default, the resolveObject is not called + enableResolveObject(true); } @Override @@ -91,4 +95,13 @@ protected Class resolveProxyClass(String[] interfaces) throws IOException, Cl throw new ClassNotFoundException(null, e); } } + + @Override + protected Object resolveObject(Object obj) throws IOException { + if(obj instanceof URL){ + // SECURITY-637, URL deserialization could lead to DNS query + return URLDeserializationHelper.wrapIfRequired((URL) obj); + } + return super.resolveObject(obj); + } } diff --git a/src/main/java/hudson/remoting/URLDeserializationHelper.java b/src/main/java/hudson/remoting/URLDeserializationHelper.java new file mode 100644 index 000000000..206c1734a --- /dev/null +++ b/src/main/java/hudson/remoting/URLDeserializationHelper.java @@ -0,0 +1,87 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.remoting; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * SECURITY-637, this helper wraps the URL into a "safe" version if the url has a non-empty host + * and the JVM configuration is standard. + * + * Essentially the wrap does not provide the same logic for {@link java.net.URLStreamHandler#hashCode(URL)} + * and {@link java.net.URLStreamHandler#equals(URL, URL)} but a version that use directly the {@code String} representation + * instead of requesting the DNS to have name equivalence. + * + * @since TODO + */ +public class URLDeserializationHelper { + // escape hatch for SECURITY-637 to keep legacy behavior + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + private static boolean AVOID_URL_WRAPPING = Boolean.getBoolean(URLDeserializationHelper.class + ".avoidUrlWrapping"); + + private static final SafeURLStreamHandler SAFE_HANDLER = new SafeURLStreamHandler(); + + /** + * Wraps the given URL into a "safe" version against deserialization attack if the url has a non-empty host + * and the JVM configuration is standard. + */ + public static @Nonnull URL wrapIfRequired(@Nonnull URL url) throws IOException { + if(AVOID_URL_WRAPPING){ + // legacy behavior + return url; + } + + if (url.getHost() == null || url.getHost().isEmpty()) { + // default equals/hashcode are not vulnerable in case the host is null or empty + return url; + } + + return new URL(null, url.toString(), SAFE_HANDLER); + } + + private static class SafeURLStreamHandler extends URLStreamHandler { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new URL(u.toString()).openConnection(); + } + + // actual correction is here (hashCode + equals), we avoid requesting the DNS for the hostname + + @Override + protected int hashCode(URL u) { + return u.toExternalForm().hashCode(); + } + + @Override + protected boolean equals(URL u1, URL u2) { + return u1.toExternalForm().equals(u2.toExternalForm()); + } + } +} From a4ded981ea9ba56db8380a232112d38486cadccf Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 31 Jul 2018 15:12:25 -0600 Subject: [PATCH 183/243] [maven-release-plugin] prepare release remoting-3.25 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index db0a8e560..233ff7fcc 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.25-SNAPSHOT + 3.25 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.25 From c2f428dc2d84477889e6bbe511c0e36c22d9626d Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 31 Jul 2018 15:12:25 -0600 Subject: [PATCH 184/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 233ff7fcc..a63286372 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.25 + 3.26-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.25 + HEAD From 9e2b6d5cacf11007a6b3a0dd1b1719613e524479 Mon Sep 17 00:00:00 2001 From: pvtuan10 Date: Thu, 2 Aug 2018 20:46:43 +0800 Subject: [PATCH 185/243] Update readme about remoting-kafka-plugin --- docs/protocols.md | 10 ++++++++++ docs/remoting-kafka-architecture.png | Bin 0 -> 75964 bytes 2 files changed, 10 insertions(+) create mode 100644 docs/remoting-kafka-architecture.png diff --git a/docs/protocols.md b/docs/protocols.md index 6a7bcd9d6..3692819e2 100644 --- a/docs/protocols.md +++ b/docs/protocols.md @@ -98,6 +98,16 @@ On some configurations only one JNLP3 slave per IP address can be connected. * [JENKINS-34121](https://issues.jenkins-ci.org/browse/JENKINS-34121) - JNLP3 cannot be used on IBM Java, which doesn't support AES/CTR/PKCS5Padding. +## Plugin protocols + +### Remoting Kafka Plugin + +* [Remoting Kafka Plugin](https://github.com/jenkinsci/remoting-kafka-plugin) uses Kafka as fault-tolerant communication layer to support command invocation between Jenkins master and agent. +* The plugin gets rid of current direct TCP connection between master and agent. +* Below picture is the overview architecture of the plugin. + +![Screenshot](remoting-kafka-architecture.png) + ## Test Protocols The protocols below exist for testing purposes only. diff --git a/docs/remoting-kafka-architecture.png b/docs/remoting-kafka-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..357c1aed8ed0040c192fc18e19236adae6eed6a4 GIT binary patch literal 75964 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⪙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 Date: Fri, 3 Aug 2018 21:49:46 +0800 Subject: [PATCH 186/243] Add link to remoting kafka technical documentation --- docs/protocols.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/protocols.md b/docs/protocols.md index 3692819e2..da2d19087 100644 --- a/docs/protocols.md +++ b/docs/protocols.md @@ -104,9 +104,7 @@ JNLP3 cannot be used on IBM Java, which doesn't support AES/CTR/PKCS5Padding. * [Remoting Kafka Plugin](https://github.com/jenkinsci/remoting-kafka-plugin) uses Kafka as fault-tolerant communication layer to support command invocation between Jenkins master and agent. * The plugin gets rid of current direct TCP connection between master and agent. -* Below picture is the overview architecture of the plugin. - -![Screenshot](remoting-kafka-architecture.png) +* More info can be found in the technical [documentation](https://github.com/jenkinsci/remoting-kafka-plugin/blob/master/docs/DOCUMENTATION.md) of the plugin. ## Test Protocols From adef66d97e2b07801ad66da3ae9a3d495f7ba4c9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 20 Aug 2018 13:11:20 -0400 Subject: [PATCH 187/243] AnonymousClassWarnings should not warn about enums. --- .../org/jenkinsci/remoting/util/AnonymousClassWarnings.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java index 140d934f6..34104fb08 100644 --- a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java +++ b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java @@ -70,7 +70,9 @@ public static void check(@Nonnull Class clazz) { } private static void doCheck(@Nonnull Class c) { - if (c.isAnonymousClass()) { // e.g., pkg.Outer$1 + if (c.isEnum()) { // e.g., com.cloudbees.plugins.credentials.CredentialsScope$1 ~ CredentialsScope.SYSTEM + // ignore, enums serialize specially + } else if (c.isAnonymousClass()) { // e.g., pkg.Outer$1 warn(c, "anonymous"); } else if (c.isLocalClass()) { // e.g., pkg.Outer$1Local warn(c, "local"); From 6835d82a4d5cb2823d8155c68f8012e85b7d6a3c Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 20 Aug 2018 13:39:27 -0600 Subject: [PATCH 188/243] Fix [JENKINS-42533] In rare cases, the Unexport command gets a little out of sequence and attempts to unexport an object that has already been unexported. It logs this at SEVERE level, when there is no indication of any harm. The issue is marked as minor. Implement the fix in a similar pattern to [JENKINS-22853], as noted in the PR. That fix reduced the log level for a similar message coming from the EOF command. As with that fix, it is unclear how to create a meaningful test, particularly as it is only a log level change.. --- src/main/java/hudson/remoting/ProxyOutputStream.java | 2 +- src/main/java/hudson/remoting/ProxyWriter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/ProxyOutputStream.java b/src/main/java/hudson/remoting/ProxyOutputStream.java index 6ce1527cc..ed7da73c0 100644 --- a/src/main/java/hudson/remoting/ProxyOutputStream.java +++ b/src/main/java/hudson/remoting/ProxyOutputStream.java @@ -341,7 +341,7 @@ public Unexport(int ioId, int oid) { protected void execute(final Channel channel) { channel.pipeWriter.submit(ioId,new Runnable() { public void run() { - channel.unexport(oid,createdAt); + channel.unexport(oid,createdAt, false); } }); } diff --git a/src/main/java/hudson/remoting/ProxyWriter.java b/src/main/java/hudson/remoting/ProxyWriter.java index 93fedc114..8311bd430 100644 --- a/src/main/java/hudson/remoting/ProxyWriter.java +++ b/src/main/java/hudson/remoting/ProxyWriter.java @@ -377,7 +377,7 @@ public Unexport(int ioId, int oid) { protected void execute(final Channel channel) { channel.pipeWriter.submit(ioId,new Runnable() { public void run() { - channel.unexport(oid,createdAt); + channel.unexport(oid,createdAt, false); } }); } From a68e52ecfc6943e46d7530d0d31d389117a28867 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 21 Aug 2018 09:41:43 -0600 Subject: [PATCH 189/243] Adopt current formatting style. From Ramon's suggestion. --- src/main/java/hudson/remoting/ProxyOutputStream.java | 2 +- src/main/java/hudson/remoting/ProxyWriter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/ProxyOutputStream.java b/src/main/java/hudson/remoting/ProxyOutputStream.java index ed7da73c0..34335125a 100644 --- a/src/main/java/hudson/remoting/ProxyOutputStream.java +++ b/src/main/java/hudson/remoting/ProxyOutputStream.java @@ -341,7 +341,7 @@ public Unexport(int ioId, int oid) { protected void execute(final Channel channel) { channel.pipeWriter.submit(ioId,new Runnable() { public void run() { - channel.unexport(oid,createdAt, false); + channel.unexport(oid,createdAt,false); } }); } diff --git a/src/main/java/hudson/remoting/ProxyWriter.java b/src/main/java/hudson/remoting/ProxyWriter.java index 8311bd430..b37da1034 100644 --- a/src/main/java/hudson/remoting/ProxyWriter.java +++ b/src/main/java/hudson/remoting/ProxyWriter.java @@ -377,7 +377,7 @@ public Unexport(int ioId, int oid) { protected void execute(final Channel channel) { channel.pipeWriter.submit(ioId,new Runnable() { public void run() { - channel.unexport(oid,createdAt, false); + channel.unexport(oid,createdAt,false); } }); } From f7751156aedadece2c9ba8b5bf0b8f4443265dcc Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 23 Aug 2018 15:19:00 -0400 Subject: [PATCH 190/243] https://github.com/jenkinsci/jenkins/pull/3599 does remind me that Class.isEnum is apparently broken in Java 8 (but works in 9) for enums with methods. --- .../org/jenkinsci/remoting/util/AnonymousClassWarnings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java index 34104fb08..917256545 100644 --- a/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java +++ b/src/main/java/org/jenkinsci/remoting/util/AnonymousClassWarnings.java @@ -70,7 +70,7 @@ public static void check(@Nonnull Class clazz) { } private static void doCheck(@Nonnull Class c) { - if (c.isEnum()) { // e.g., com.cloudbees.plugins.credentials.CredentialsScope$1 ~ CredentialsScope.SYSTEM + if (Enum.class.isAssignableFrom(c)) { // e.g., com.cloudbees.plugins.credentials.CredentialsScope$1 ~ CredentialsScope.SYSTEM // ignore, enums serialize specially } else if (c.isAnonymousClass()) { // e.g., pkg.Outer$1 warn(c, "anonymous"); From 1425f9990907598f1fcb0042835df9d69b6b3751 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 28 Aug 2018 16:16:24 -0600 Subject: [PATCH 191/243] Update changlog for last two releases. --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6443b24d6..32aebaa88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,24 @@ Changelog ==== -Below you can changelogs for the trunk version of remoting. +Below you can read the changelogs for the trunk version of remoting. This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.25 + +Release date: July 31, 2018 => Weekly 2.138 / LTS 2.121.3 + +* [SECURITY-637](https://jenkins.io/security/advisory/2018-08-15/) - Prevent deserialization of URL objects with host components + +##### 3.24 + +Release date: July 12, 2018 + +* Refresh the code-signing certificate +* No functional changes + ##### 3.23 Release date: June 29, 2018 From eacaaf4e78bf463398f7521f5932fb9e34977ecc Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 31 Aug 2018 11:39:50 -0600 Subject: [PATCH 192/243] [maven-release-plugin] prepare release remoting-3.26 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a63286372..2f9f38dda 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.26-SNAPSHOT + 3.26 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.26 From d96470bc7e0962dc75153cbb4c27772e0b84907c Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 31 Aug 2018 11:39:57 -0600 Subject: [PATCH 193/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2f9f38dda..6bbd1d6f2 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.26 + 3.27-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.26 + HEAD From 186b6deb89fc3419b0e16a83044f07e552004331 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 31 Aug 2018 12:22:27 -0600 Subject: [PATCH 194/243] Update changelog for 3.26 release. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32aebaa88..ff35bd9cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ See [Jenkins changelog](https://jenkins.io/changelog/) for more details. ##### 3.25 +Release date: August 31, 2018 + +* [JENKINS-52945](https://issues.jenkins-ci.org/browse/JENKINS-52945) - AnonymousClassWarnings should not warn about enums. +* [JENKINS-42533](https://issues.jenkins-ci.org/browse/JENKINS-42533) - Eliminate another excessively severe warning about trying to export already unexported object. + +##### 3.25 + Release date: July 31, 2018 => Weekly 2.138 / LTS 2.121.3 * [SECURITY-637](https://jenkins.io/security/advisory/2018-08-15/) - Prevent deserialization of URL objects with host components From 9a29165079c14d0ea2f53f0606e49c5cb201f4d4 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 4 Sep 2018 18:55:06 +0200 Subject: [PATCH 195/243] Changelog: Fix typo in the version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff35bd9cf..93b0282dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. -##### 3.25 +##### 3.26 Release date: August 31, 2018 From a870ae09de2299b379da04e1f9c07d4d8cdbbe6c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 12 Sep 2018 19:25:29 -0400 Subject: [PATCH 196/243] Channel.notifyJar was being called too often. --- .../java/hudson/remoting/JarLoaderImpl.java | 20 +++++++++++++++++++ .../hudson/remoting/RemoteClassLoader.java | 2 -- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/JarLoaderImpl.java b/src/main/java/hudson/remoting/JarLoaderImpl.java index 5b53c9efd..0abf3230e 100644 --- a/src/main/java/hudson/remoting/JarLoaderImpl.java +++ b/src/main/java/hudson/remoting/JarLoaderImpl.java @@ -6,12 +6,15 @@ import java.io.IOException; import java.io.NotSerializableException; import java.io.OutputStream; +import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Implements {@link JarLoader} to be called from the other side. @@ -20,6 +23,9 @@ */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("SE_BAD_FIELD") class JarLoaderImpl implements JarLoader, SerializableOnlyOverRemoting { + + private static final Logger LOGGER = Logger.getLogger(JarLoaderImpl.class.getName()); + private final ConcurrentMap knownJars = new ConcurrentHashMap<>(); @edu.umd.cs.findbugs.annotations.SuppressWarnings("DMI_COLLECTION_OF_URLS") // TODO: fix this @@ -33,6 +39,20 @@ public void writeJarTo(long sum1, long sum2, OutputStream sink) throws IOExcepti if (url==null) throw new IOException("Unadvertised jar file "+k); + Channel channel = Channel.current(); + if (channel != null) { + if (url.getProtocol().equals("file")) { + try { + channel.notifyJar(new File(url.toURI())); + } catch (URISyntaxException | IllegalArgumentException x) { + LOGGER.log(Level.WARNING, "cannot properly report " + url, x); + } + } else { + LOGGER.log(Level.FINE, "serving non-file URL {0}", url); + } + } else { + LOGGER.log(Level.WARNING, "no active channel"); + } Util.copy(url.openStream(), sink); presentOnRemote.add(k); } diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index 430d8bfe1..10a430ef8 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -867,7 +867,6 @@ public ClassFile2 fetch4(String className, ClassFile2 referer) throws ClassNotFo if (referer == null && !channel.jarLoader.isPresentOnRemote(sum)) { // for the class being requested, if the remote doesn't have the jar yet // send the image as well, so as not to require another call to get this class loaded - channel.notifyJar(jar); imageRef = new ResourceImageBoth(urlOfClassFile,sum); } else { // otherwise just send the checksum and save space imageRef = new ResourceImageInJar(sum,null /* TODO: we need to check if the URL of c points to the expected location of the file */); @@ -948,7 +947,6 @@ private ResourceFile makeResource(String name, URL resource) throws IOException Checksum sum = channel.jarLoader.calcChecksum(jar); ResourceImageRef ir; if (!channel.jarLoader.isPresentOnRemote(sum)) { - channel.notifyJar(jar); ir = new ResourceImageBoth(resource, sum); // remote probably doesn't have } else { ir = new ResourceImageInJar(sum, null); From eebd9c9f3de0d4b11baf01de56a29ca4a9ebdf6f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 13 Sep 2018 15:36:31 -0400 Subject: [PATCH 197/243] =?UTF-8?q?=E2=80=9CI/O=20error=20in=20channel=20?= =?UTF-8?q?=E2=80=A6=E2=80=9D=20exceptions=20are=20pretty=20normal=20(Sock?= =?UTF-8?q?etException,=20EOFException)=20and=20do=20not=20merit=20SEVERE?= =?UTF-8?q?=20level.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/hudson/remoting/SynchronousCommandTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/SynchronousCommandTransport.java b/src/main/java/hudson/remoting/SynchronousCommandTransport.java index 263196587..4cd70d59d 100644 --- a/src/main/java/hudson/remoting/SynchronousCommandTransport.java +++ b/src/main/java/hudson/remoting/SynchronousCommandTransport.java @@ -92,7 +92,7 @@ public void run() { LOGGER.log(Level.SEVERE, "I/O error in channel "+name,e); channel.terminate((InterruptedIOException) new InterruptedIOException().initCause(e)); } catch (IOException e) { - LOGGER.log(Level.SEVERE, "I/O error in channel "+name,e); + LOGGER.log(Level.INFO, "I/O error in channel "+name,e); channel.terminate(e); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Unexpected error in channel "+name,e); From f251a21d9b1d77e35435950d78815e4ff7402678 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 13 Sep 2018 15:36:58 -0400 Subject: [PATCH 198/243] Using proper IOException chaining constructors. --- .../hudson/remoting/SynchronousCommandTransport.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/hudson/remoting/SynchronousCommandTransport.java b/src/main/java/hudson/remoting/SynchronousCommandTransport.java index 4cd70d59d..50188c6b2 100644 --- a/src/main/java/hudson/remoting/SynchronousCommandTransport.java +++ b/src/main/java/hudson/remoting/SynchronousCommandTransport.java @@ -49,7 +49,7 @@ public ReaderThread(CommandReceiver receiver) { this.receiver = receiver; setUncaughtExceptionHandler((t, e) -> { LOGGER.log(Level.SEVERE, "Uncaught exception in SynchronousCommandTransport.ReaderThread " + t, e); - channel.terminate((IOException) new IOException("Unexpected reader termination").initCause(e)); + channel.terminate(new IOException("Unexpected reader termination", e)); }); } @@ -74,9 +74,7 @@ public void run() { LOGGER.log(Level.WARNING, "Socket timeout in the Synchronous channel reader", ex); continue; } catch (EOFException e) { - IOException ioe = new IOException("Unexpected termination of the channel"); - ioe.initCause(e); - throw ioe; + throw new IOException("Unexpected termination of the channel", e); } catch (ClassNotFoundException e) { LOGGER.log(Level.SEVERE, "Unable to read a command (channel " + name + ")",e); continue; @@ -96,11 +94,11 @@ public void run() { channel.terminate(e); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Unexpected error in channel "+name,e); - channel.terminate((IOException) new IOException("Unexpected reader termination").initCause(e)); + channel.terminate(new IOException("Unexpected reader termination", e)); throw e; } catch (Error e) { LOGGER.log(Level.SEVERE, "Unexpected error in channel "+name,e); - channel.terminate((IOException) new IOException("Unexpected reader termination").initCause(e)); + channel.terminate(new IOException("Unexpected reader termination", e)); throw e; } finally { channel.pipeWriter.shutdown(); From 89a51a0a712ae220f37a92cb7d0d86d2bfe7a851 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 19 Sep 2018 10:57:57 -0600 Subject: [PATCH 199/243] [JENKINS-53569] Remove unnecessary locking. There is no indication that this section of code constitutes a critical section. The only meaningful thing it does is atomic and is handled elsewhere in the code by good design. The extra checks serve no significant purpose. The code in Filter that calls this grabs a lock on Filter before it calls this meaning that it already holds one lock. If another thread grabs the lock on ProtocolStack before the Filter than a thread is always possible. Probably more of the locks in this area should be cleaned up and the threading / locking model simplified. --- .../remoting/protocol/ProtocolStack.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/java/org/jenkinsci/remoting/protocol/ProtocolStack.java b/src/main/java/org/jenkinsci/remoting/protocol/ProtocolStack.java index 30c76752b..b9a96ee69 100644 --- a/src/main/java/org/jenkinsci/remoting/protocol/ProtocolStack.java +++ b/src/main/java/org/jenkinsci/remoting/protocol/ProtocolStack.java @@ -597,7 +597,6 @@ public class Ptr { /** * Flag to track this {@link ProtocolLayer} as removed from the stack. */ - @GuardedBy("ProtocolStack.stackLock") private boolean removed; /** @@ -752,22 +751,7 @@ public ProtocolStack stack() { * Requests removal of this {@link ProtocolLayer} from the {@link ProtocolStack} */ public void remove() { - stackLock.readLock().lock(); - try { - if (removed) { - return; - } - if (nextSend == null) { - throw new UnsupportedOperationException("Network layer is not supposed to call remove"); - } - if (nextRecv == null) { - throw new UnsupportedOperationException("Application layer is not supposed to call remove"); - } - // we just want to have a lock, we abuse the read lock here as the readers are eventually consistent - removed = true; - } finally { - stackLock.readLock().unlock(); - } + removed = true; } /** From 0cec987e9a620b11cd63b87aae71af0a81bb0c77 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 28 Sep 2018 09:39:33 -0600 Subject: [PATCH 200/243] [maven-release-plugin] prepare release remoting-3.27 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6bbd1d6f2..d634d471c 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.27-SNAPSHOT + 3.27 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + remoting-3.27 From dd041345e1a8298a6806bcec1d05a0c39baefc0e Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 28 Sep 2018 09:39:40 -0600 Subject: [PATCH 201/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d634d471c..491add3c4 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.27 + 3.28-SNAPSHOT Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.27 + HEAD From 7b021fdfcd55c5e174efbad06c7931f890d12e40 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 28 Sep 2018 10:04:13 -0600 Subject: [PATCH 202/243] Update Changelog for 3.27. --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b0282dd..8c679ff6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.27 + +Release date: September 28, 2018 + +* Channel.notifyJar was being called too often. +* Downgrade error messages from SynchronousCommandTransport. +* [JENKINS-53569](https://issues.jenkins-ci.org/browse/JENKINS-53569) - Remove unnecessary locking that could cause deadlock when removing a filter from the ProtocolStack. + ##### 3.26 Release date: August 31, 2018 From 806e6312067c9dc33a98d7cebb62e6422401593a Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 18 Oct 2018 16:28:18 -0600 Subject: [PATCH 203/243] [JENKINS-54005] Another instance of an unnecessarily severe warning. "Trying to unexport an object that's already unexported". Fix this one the same way as the previous two -- lower the severity. --- src/main/java/hudson/remoting/Channel.java | 2 +- src/main/java/hudson/remoting/ExportTable.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 946935816..d3c6b87c7 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -790,7 +790,7 @@ public T export(Class type, T instance) { } /*package*/ void unexport(int id, @CheckForNull Throwable cause) { - unexport(id, cause, true); + unexport(id, cause, false); } /** diff --git a/src/main/java/hudson/remoting/ExportTable.java b/src/main/java/hudson/remoting/ExportTable.java index e2015d09e..088e68655 100644 --- a/src/main/java/hudson/remoting/ExportTable.java +++ b/src/main/java/hudson/remoting/ExportTable.java @@ -498,7 +498,7 @@ synchronized void unexport(@CheckForNull Object t, Throwable callSite) { * Logs error if the object has been already unexported. */ void unexportByOid(Integer oid, Throwable callSite) { - unexportByOid(oid, callSite, true); + unexportByOid(oid, callSite, false); } /** From a87ba45008397746703be58a8a3b0a23ebbcf04f Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 24 Oct 2018 10:19:11 -0600 Subject: [PATCH 204/243] Re-work the implementation of the no_proxy capability. We've had several issues submitted where it hasn't provided the capabilities people need. The most recent one is JENKINS-48778 but we fixed a very similar one a couple of months ago. The existing implementation was a bit more difficult to follow than necessary. This one adds a few more capabilities, retains the existing ones, and documents the supported formats. --- docs/configuration.md | 8 + docs/no_proxy.md | 28 ++ pom.xml | 24 +- .../hudson/remoting/NoProxyEvaluator.java | 155 +++++++++++ src/main/java/hudson/remoting/Util.java | 74 +----- .../engine/JnlpAgentEndpointResolver.java | 3 +- .../hudson/remoting/NoProxyEvaluatorTest.java | 251 ++++++++++++++++++ src/test/java/hudson/remoting/UtilTest.java | 147 +--------- 8 files changed, 460 insertions(+), 230 deletions(-) create mode 100644 docs/no_proxy.md create mode 100644 src/main/java/hudson/remoting/NoProxyEvaluator.java create mode 100644 src/test/java/hudson/remoting/NoProxyEvaluatorTest.java diff --git a/docs/configuration.md b/docs/configuration.md index f204e067e..dc2dbaaf5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -119,6 +119,14 @@ These properties require independent configuration on both sides of the channel. JENKINS-41730 If specified, only the protocols from the list will be tried during the connection. The option provides protocol names, but the order of the check is defined internally and cannot be changed. + + [NO_PROXY](no_proxy.md) (or no_proxy) + + + + + Provides specifications for hosts that should not be proxied. See the [NO_PROXY](no_proxy.md) page for details on supported specifications. + org.jvnet @@ -207,6 +195,16 @@ THE SOFTWARE. test-annotations test + + org.apache.httpcomponents + httpclient + 4.5.6 + + + commons-net + commons-net + 3.6 + diff --git a/src/main/java/hudson/remoting/NoProxyEvaluator.java b/src/main/java/hudson/remoting/NoProxyEvaluator.java new file mode 100644 index 000000000..f34f35f41 --- /dev/null +++ b/src/main/java/hudson/remoting/NoProxyEvaluator.java @@ -0,0 +1,155 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2018, Sun Microsystems, Inc., Kohsuke Kawaguchi, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.remoting; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.http.conn.util.InetAddressUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +@Restricted(NoExternalUse.class) +public class NoProxyEvaluator { + + private static final Pattern COMMA = Pattern.compile(","); + private static final Pattern PIPE = Pattern.compile("\\|"); + + private final Set noProxyIpAddresses = new HashSet<>(); + private final Set noProxySubnets = new HashSet<>(); + private final Set noProxyDomainsHosts = new HashSet<>(); + + public static boolean shouldProxy(String host) { + NoProxyEvaluator evaluator = new NoProxyEvaluator(getEnvironmentValue()); + return evaluator.shouldProxyHost(host); + } + + NoProxyEvaluator(String noProxySpecification) { + if (noProxySpecification != null) { + processSpecificationsIntoTypes(noProxySpecification); + } + } + + boolean shouldProxyHost(String host) { + if (host.toLowerCase(Locale.ROOT).equals("localhost")) { + return false; + } + if (isIpAddress(host)) { + try { + InetAddress hostAddress = InetAddress.getByName(host); + if (hostAddress.isLoopbackAddress()) { + return false; + } + if (matchesIpAddress(hostAddress)) { + return false; + } + return !matchesSubnet(host); + } catch (UnknownHostException e) { + // Could not process it so just proxy it. + return true; + } + } + return !matchesDomainHost(host); + } + + private static String getEnvironmentValue() { + String noProxy = System.getenv("no_proxy"); + if (noProxy == null) { + noProxy = System.getenv("NO_PROXY"); + } + return noProxy; + } + + private boolean matchesIpAddress(InetAddress hostAddress) { + return noProxyIpAddresses.stream(). + anyMatch(inetAddress -> inetAddress.equals(hostAddress)); + } + + private void processSpecificationsIntoTypes(String noProxySpecification) { + noProxySpecification = noProxySpecification.trim(); + String[] noProxySpecifications = splitComponents(noProxySpecification); + for (String specification : noProxySpecifications){ + specification = stripLeadingStarDot(stripLeadingDot(specification.trim())); + if (specification.isEmpty() ) { + continue; + } + if (isIpAddress(specification)) { + try { + noProxyIpAddresses.add(InetAddress.getByName(specification)); + } catch (UnknownHostException e) { + // Failed to create InetAddress. + } + continue; + } + try { + SubnetUtils subnetUtils = new SubnetUtils(specification); + SubnetUtils.SubnetInfo subnetInfo = subnetUtils.getInfo(); + noProxySubnets.add(subnetInfo); + continue; + } catch (IllegalArgumentException iae) { + // Not a subnet definition. + } + noProxyDomainsHosts.add(specification); + } + } + + private String[] splitComponents(String noProxySpecification) { + String[] noProxySpecifications; + if (noProxySpecification.contains(",")) { + noProxySpecifications = COMMA.split(noProxySpecification); + } else if (noProxySpecification.contains("|")) { + noProxySpecifications = PIPE.split(noProxySpecification); + } else { + noProxySpecifications = new String[] {noProxySpecification}; + } + return noProxySpecifications; + } + + private String stripLeadingDot(String string) { + return string.startsWith(".") ? string.substring(1) : string; + } + + private String stripLeadingStarDot(String string) { + return string.startsWith("*.") ? string.substring(2) : string; + } + + private boolean matchesSubnet(String host) { + return noProxySubnets.stream().anyMatch(subnet -> subnet.isInRange(host)); + } + + private boolean matchesDomainHost(String host) { + return noProxyDomainsHosts.stream(). + anyMatch(host::endsWith); + } + + private boolean isIpAddress(String host) { + return InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host); + } + +} diff --git a/src/main/java/hudson/remoting/Util.java b/src/main/java/hudson/remoting/Util.java index d017a993a..711ccceae 100644 --- a/src/main/java/hudson/remoting/Util.java +++ b/src/main/java/hudson/remoting/Util.java @@ -11,8 +11,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; -import java.net.ProxySelector; -import java.net.URI; import java.net.URLConnection; import java.net.MalformedURLException; import java.net.Proxy; @@ -22,7 +20,6 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.nio.file.Path; /** @@ -97,75 +94,6 @@ static String indent(String s) { return " " + s.trim().replace("\n", "\n "); } - /** - * Check if given URL is in the exclusion list defined by the no_proxy environment variable. - * On most *NIX system wildcards are not supported but if one top domain is added, all related subdomains will also - * be ignored. Both "mit.edu" and ".mit.edu" are valid syntax. - * http://www.gnu.org/software/wget/manual/html_node/Proxies.html - * - * Regexp: - * - \Q and \E: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html - * - To match IPV4/IPV/FQDN: Regular Expressions Cookbook, 2nd Edition (ISBN: 9781449327453) - * - * Warning: this method won't match shortened representation of IPV6 address - */ - public static boolean inNoProxyEnvVar(@Nonnull String host) { - String noProxy = System.getenv("no_proxy"); - if (noProxy != null) { - noProxy = noProxy.trim() - // Remove spaces - .replaceAll("\\s+", "") - // Convert .foobar.com to foobar.com - .replaceAll("((?<=^|,)\\.)*(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})(?=($|,))", "$2"); - - if (!noProxy.isEmpty()) { - // IPV4 and IPV6 - if (host.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$") || host.matches("^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$")) { - return noProxy.matches(".*(^|,)\\Q" + host + "\\E($|,).*"); - } - else { - int depth = 0; - String originalHost = host; - // Loop while we have a valid domain name: acme.com - // We add a safeguard to avoid a case where the host would always be valid because the regex would - // for example fail to remove subdomains. - // According to Wikipedia (no RFC defines it), 128 is the max number of subdivision for a valid FQDN: - // https://en.wikipedia.org/wiki/Subdomain#Overview - while (host.matches("^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$") && depth < 128) { - ++depth; - // Check if the no_proxy contains the host - if (noProxy.matches(".*(^|,)\\Q" + host + "\\E($|,).*")) - return true; - // Remove first subdomain: master.jenkins.acme.com -> jenkins.acme.com - host = host.replaceFirst("^[a-z0-9]+(-[a-z0-9]+)*\\.", ""); - } - - String[] noProxyArray = noProxy.split(","); - // fix for https://issues.jenkins-ci.org/browse/JENKINS-51223, basic suffix match - if (depth > 0 && suffixMatch(originalHost, noProxyArray)) { - return true; - } - } - } - } - - return false; - } - - // fix for https://issues.jenkins-ci.org/browse/JENKINS-51223 - // adds curl-like algorithm for matching to existing regexp used in - // inNoProxyEnvVars - static boolean suffixMatch(String host, String[] noProxyArray) { - for (String proxy : noProxyArray) { - // still needs to capture some form of subdomain, like ".svc" - if (!proxy.contains(".")) - continue; - if (host.endsWith(proxy)) - return true; - } - return false; - } - /** * Gets URL connection. * If http_proxy environment variable exists, the connection uses the proxy. @@ -178,7 +106,7 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy httpProxy = System.getenv("http_proxy"); } URLConnection con = null; - if (httpProxy != null && "http".equals(url.getProtocol()) && !inNoProxyEnvVar(url.getHost())) { + if (httpProxy != null && "http".equals(url.getProtocol()) && NoProxyEvaluator.shouldProxy(url.getHost())) { try { URL proxyUrl = new URL(httpProxy); SocketAddress addr = new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort()); diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index da7e5d95e..ec5fb2781 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -24,6 +24,7 @@ package org.jenkinsci.remoting.engine; import hudson.remoting.Base64; +import hudson.remoting.NoProxyEvaluator; import hudson.remoting.Util; import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; @@ -543,7 +544,7 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy } static boolean inNoProxyEnvVar(String host) { - return Util.inNoProxyEnvVar(host); + return !NoProxyEvaluator.shouldProxy(host); } @CheckForNull diff --git a/src/test/java/hudson/remoting/NoProxyEvaluatorTest.java b/src/test/java/hudson/remoting/NoProxyEvaluatorTest.java new file mode 100644 index 000000000..bc6d9909a --- /dev/null +++ b/src/test/java/hudson/remoting/NoProxyEvaluatorTest.java @@ -0,0 +1,251 @@ +package hudson.remoting; + +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class NoProxyEvaluatorTest { + + @Test + public void testWrongIPV4() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("127.0.0.1"); + + assertTrue(evaluator.shouldProxyHost("10.0.0.1")); + } + + @Test + public void testIPV6() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assertFalse(evaluator.shouldProxyHost("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); + } + + @Test + public void testWrongIPV6() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("0:0:0:0:0:0:0:1"); + + assertTrue(evaluator.shouldProxyHost("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); + } + + @Test + public void testFQDN() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("foobar.com"); + + assertFalse(evaluator.shouldProxyHost("foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.sub.foobar.com")); + + assertTrue(evaluator.shouldProxyHost("foobar.org")); + assertTrue(evaluator.shouldProxyHost("jenkins.com")); + } + + @Test + public void testSubFQDN() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("sub.foobar.com"); + + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.sub.foobar.com")); + + assertTrue(evaluator.shouldProxyHost("foobar.com")); + } + + @Test + public void testFQDNWithDot() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(".foobar.com"); + + assertFalse(evaluator.shouldProxyHost("foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.sub.foobar.com")); + + assertTrue(evaluator.shouldProxyHost("foobar.org")); + assertTrue(evaluator.shouldProxyHost("jenkins.com")); + } + + @Test + public void testSubFQDNWithDot() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(".sub.foobar.com"); + + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.sub.foobar.com")); + + assertTrue(evaluator.shouldProxyHost("foobar.com")); + } + + @Test + public void testSubFWDNWithDotMinimalSuffix() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(".svc"); + + assertFalse(evaluator.shouldProxyHost("bn-myproj.svc")); + } + + @Test + public void testSubFWDNWithDotMinimalSuffixMixedCase() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(".svc,.default,.local,localhost,.boehringer.com,10.250.0.0/16,10.251.0.0/16,10.183.195.106,10.183.195.107,10.183.195.108,10.183.195.109,10.183.195.11,10.183.195.111,10.183.195.112,10.183.195.113,10.183.195.13,10.250.127."); + + assertFalse(evaluator.shouldProxyHost("bn-myproj.svc")); + } + + @Test + public void testWithInvalidCharsMatching() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("foo+.co=m"); + + assertFalse(evaluator.shouldProxyHost("foo+.co=m")); + } + + @Test + public void testWithInvalidCharsNonMatching() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("foo+.co=m"); + + assertTrue(evaluator.shouldProxyHost("foo.com")); + } + + @Test + public void testMixed() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(" 127.0.0.1, 0:0:0:0:0:0:0:1,\tfoobar.com, .jenkins.com"); + + assertFalse(evaluator.shouldProxyHost("127.0.0.1")); + assertFalse(evaluator.shouldProxyHost("0:0:0:0:0:0:0:1")); + assertFalse(evaluator.shouldProxyHost("foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("sub.jenkins.com")); + + assertTrue(evaluator.shouldProxyHost("foobar.org")); + assertTrue(evaluator.shouldProxyHost("jenkins.org")); + assertTrue(evaluator.shouldProxyHost("sub.foobar.org")); + assertTrue(evaluator.shouldProxyHost("sub.jenkins.org")); + } + + @Test + public void testSimpleHostname() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("jenkinsmaster"); + + assertFalse(evaluator.shouldProxyHost("jenkinsmaster")); + } + + @Test + public void testIPv4Loopback() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(null); + + assertFalse(evaluator.shouldProxyHost("127.0.0.1")); + } + + @Test + public void testIPv6LoopbackAbbreviated() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(null); + + assertFalse(evaluator.shouldProxyHost("::1")); + } + + @Test + public void testIPv6LoopbackFullLength() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(null); + + assertFalse(evaluator.shouldProxyHost("0000:0000:0000:0000:0000:0000:0000:0001")); + } + + @Test + public void testLocalhost() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(null); + + assertFalse(evaluator.shouldProxyHost("localhost")); + } + + @Test + public void testNullSpecification() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(null); + + assertTrue(evaluator.shouldProxyHost("jenkins.io")); + } + + @Test + public void testEmptySpecification() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(""); + + assertTrue(evaluator.shouldProxyHost("jenkins.io")); + } + + @Test + public void testBlankSpecification() { + NoProxyEvaluator evaluator = new NoProxyEvaluator(" "); + + assertTrue(evaluator.shouldProxyHost("jenkins.io")); + } + + @Test(expected = NullPointerException.class) + public void testNullHost() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("jenkins.io"); + + assertTrue(evaluator.shouldProxyHost(null)); + } + + @Test + public void testEmptyHost() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("jenkins.io"); + + assertTrue(evaluator.shouldProxyHost("")); + } + + @Test + public void testBlankHost() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("jenkins.io"); + + assertTrue(evaluator.shouldProxyHost(" ")); + } + + @Test + public void testCidrWithin() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("1.232.12.0/20"); + + assertFalse(evaluator.shouldProxyHost("1.232.12.3")); + } + + @Test + public void testCidrBelow() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("1.232.12.0/20"); + + assertFalse(evaluator.shouldProxyHost("1.232.11.3")); + } + + @Test + public void testCidrAbove() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("1.232.12.0/24"); + + assertTrue(evaluator.shouldProxyHost("1.232.13.3")); + } + + @Test + public void testJavaStyleSeparator() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("foobar.com| .jenkins.io"); + + assertFalse(evaluator.shouldProxyHost("sub.foobar.com")); + assertFalse(evaluator.shouldProxyHost("repo.jenkins.io")); + assertTrue(evaluator.shouldProxyHost("foo.com")); + } + + @Test + public void testMixedSeparators() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("foobar.com| .jenkins.io,fred.com"); + + assertFalse(evaluator.shouldProxyHost("sub.fred.com")); + assertTrue(evaluator.shouldProxyHost("sub.foobar.com")); + assertTrue(evaluator.shouldProxyHost("repo.jenkins.io")); + assertTrue(evaluator.shouldProxyHost("foo.com")); + } + + @Test + public void testIPv6Full() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("2001:0db8:0a0b:12f0:0000:0000:0000:0001"); + + assertFalse(evaluator.shouldProxyHost("2001:0db8:0a0b:12f0:0000:0000:0000:0001")); + assertFalse(evaluator.shouldProxyHost("2001:0db8:0a0b:12f0::0001")); + } + + @Test + public void testIPv6Compressed() { + NoProxyEvaluator evaluator = new NoProxyEvaluator("2001:0db8:0a0b:12f0::0001"); + + assertFalse(evaluator.shouldProxyHost("2001:0db8:0a0b:12f0:0000:0000:0000:0001")); + assertFalse(evaluator.shouldProxyHost("2001:0db8:0a0b:12f0::0001")); + } + +} \ No newline at end of file diff --git a/src/test/java/hudson/remoting/UtilTest.java b/src/test/java/hudson/remoting/UtilTest.java index 762e79c78..d1b786d44 100644 --- a/src/test/java/hudson/remoting/UtilTest.java +++ b/src/test/java/hudson/remoting/UtilTest.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016, Schneider Electric + * Copyright (c) 2016-2018, Schneider Electric, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,161 +24,22 @@ package hudson.remoting; -import junit.framework.TestCase; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; import java.io.File; import java.io.IOException; +import static org.junit.Assert.*; + /** * @author Etienne Bec */ -@RunWith(PowerMockRunner.class) -@PrepareForTest(Util.class) -public class UtilTest extends TestCase { +public class UtilTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - @Before - public void mockSystem() { - PowerMockito.mockStatic(System.class); - } - - @Test - public void testIPV4() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("10.0.0.1"); - - assertEquals(true, Util.inNoProxyEnvVar("10.0.0.1")); - } - - @Test - public void testWrongIPV4() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("127.0.0.1"); - - assertEquals(false, Util.inNoProxyEnvVar("10.0.0.1")); - } - - @Test - public void testIPV6() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); - assertEquals(true, Util.inNoProxyEnvVar("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); - } - - @Test - public void testWrongIPV6() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("0:0:0:0:0:0:0:1"); - - assertEquals(false, Util.inNoProxyEnvVar("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); - } - - @Test - public void testFQDN() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("foobar.com"); - - assertEquals(true, Util.inNoProxyEnvVar("foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.sub.foobar.com")); - - assertEquals(false, Util.inNoProxyEnvVar("foobar.org")); - assertEquals(false, Util.inNoProxyEnvVar("jenkins.com")); - } - - @Test - public void testSubFQDN() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("sub.foobar.com"); - - assertEquals(true, Util.inNoProxyEnvVar("sub.foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.sub.foobar.com")); - - assertEquals(false, Util.inNoProxyEnvVar("foobar.com")); - } - - @Test - public void testFQDNWithDot() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(".foobar.com"); - - assertEquals(true, Util.inNoProxyEnvVar("foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.sub.foobar.com")); - - assertEquals(false, Util.inNoProxyEnvVar("foobar.org")); - assertEquals(false, Util.inNoProxyEnvVar("jenkins.com")); - } - - @Test - public void testSubFQDNWithDot() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(".sub.foobar.com"); - - assertEquals(true, Util.inNoProxyEnvVar("sub.foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.sub.foobar.com")); - - assertEquals(false, Util.inNoProxyEnvVar("foobar.com")); - } - - @Test - public void testSubFWDNWithDotMinimalSuffix() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(".svc"); - - assertEquals(true, Util.inNoProxyEnvVar("bn-myproj.svc")); - } - - @Test - public void testSubFWDNWithDotMinimalSuffixMixedCase() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(".svc,.default,.local,localhost,.boehringer.com,10.250.0.0/16,10.251.0.0/16,10.183.195.106,10.183.195.107,10.183.195.108,10.183.195.109,10.183.195.11,10.183.195.111,10.183.195.112,10.183.195.113,10.183.195.13,10.250.127."); - - assertEquals(true, Util.inNoProxyEnvVar("bn-myproj.svc")); - } - - @Test - public void testNoProxyWithInvalidChars() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("foo+.co=m"); - - assertEquals(false, Util.inNoProxyEnvVar("foo+.co=m")); - } - - @Test - public void testNoProxyWithInvalidChar() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("foo.co=m"); - - assertEquals(false, Util.inNoProxyEnvVar("foo.co=m")); - } - - @Test - public void testNoProxyWithInvalidCharInMinimalSuffix() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(".sv=c"); - - assertEquals(false, Util.inNoProxyEnvVar("foo.sv=c")); - } - @Test - public void testSubFWDNWithoutDotMinimalSuffix() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn("svc"); - - assertEquals(false, Util.inNoProxyEnvVar("bn-myproj.svc")); - } - - @Test - public void testMixed() { - PowerMockito.when(System.getenv("no_proxy")).thenReturn(" 127.0.0.1, 0:0:0:0:0:0:0:1,\tfoobar.com, .jenkins.com"); - - assertEquals(true, Util.inNoProxyEnvVar("127.0.0.1")); - assertEquals(true, Util.inNoProxyEnvVar("0:0:0:0:0:0:0:1")); - assertEquals(true, Util.inNoProxyEnvVar("foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.foobar.com")); - assertEquals(true, Util.inNoProxyEnvVar("sub.jenkins.com")); - - assertEquals(false, Util.inNoProxyEnvVar("foobar.org")); - assertEquals(false, Util.inNoProxyEnvVar("jenkins.org")); - assertEquals(false, Util.inNoProxyEnvVar("sub.foobar.org")); - assertEquals(false, Util.inNoProxyEnvVar("sub.jenkins.org")); - } - @Test public void mkdirs() throws IOException { File sandbox = temp.newFolder(); From a3c407a3ac92bed948b19607ef291af70d4cedde Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 26 Oct 2018 15:03:49 -0600 Subject: [PATCH 205/243] Clean up warnings about anonymous callable. Convert them to static, named classes. --- .../remoting/forward/ForwarderFactory.java | 34 ++++++--- .../remoting/forward/PortForwarder.java | 37 +++++---- .../java/hudson/remoting/ClassFilterTest.java | 22 ++++-- .../remoting/DeadRemoteOutputStreamTest.java | 52 +++++++------ src/test/java/hudson/remoting/PipeTest.java | 22 ++++-- .../java/hudson/remoting/PipeWriterTest.java | 72 ++++++++++-------- .../java/hudson/remoting/PrefetchingTest.java | 14 ++-- .../java/hudson/remoting/ProxyWriterTest.java | 76 +++++++++++++------ .../hudson/remoting/throughput/Sender.java | 18 +++-- .../remoting/nio/SocketClientMain.java | 20 +++-- 10 files changed, 234 insertions(+), 133 deletions(-) diff --git a/src/main/java/hudson/remoting/forward/ForwarderFactory.java b/src/main/java/hudson/remoting/forward/ForwarderFactory.java index 95e8b3fa4..892b4c381 100644 --- a/src/main/java/hudson/remoting/forward/ForwarderFactory.java +++ b/src/main/java/hudson/remoting/forward/ForwarderFactory.java @@ -52,18 +52,7 @@ public class ForwarderFactory { * Creates a connector on the remote side that connects to the speicied host and port. */ public static Forwarder create(VirtualChannel channel, final String remoteHost, final int remotePort) throws IOException, InterruptedException { - return channel.call(new Callable() { - public Forwarder call() throws IOException { - return new ForwarderImpl(remoteHost,remotePort); - } - - @Override - public void checkRoles(RoleChecker checker) throws SecurityException { - checker.check(this,ROLE); - } - - private static final long serialVersionUID = 1L; - }); + return channel.call(new ForwarderCallable(remoteHost, remotePort)); } public static Forwarder create(String remoteHost, int remotePort) { @@ -111,4 +100,25 @@ private Object writeReplace() throws ObjectStreamException { * Role that's willing to open a socket to arbitrary node nad forward that to the other side. */ public static final Role ROLE = new Role(ForwarderFactory.class); + + private static class ForwarderCallable implements Callable { + + private static final long serialVersionUID = 1L; + private final String remoteHost; + private final int remotePort; + + public ForwarderCallable(String remoteHost, int remotePort) { + this.remoteHost = remoteHost; + this.remotePort = remotePort; + } + + public Forwarder call() throws IOException { + return new ForwarderImpl(remoteHost, remotePort); + } + + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + checker.check(this,ROLE); + } + } } diff --git a/src/main/java/hudson/remoting/forward/PortForwarder.java b/src/main/java/hudson/remoting/forward/PortForwarder.java index 794565b6b..eb6fb34ac 100644 --- a/src/main/java/hudson/remoting/forward/PortForwarder.java +++ b/src/main/java/hudson/remoting/forward/PortForwarder.java @@ -132,19 +132,7 @@ public static ListeningPort create(VirtualChannel ch, final int acceptingPort, F // need a remotable reference final Forwarder proxy = ch.export(Forwarder.class, forwarder); - return ch.call(new Callable() { - public ListeningPort call() throws IOException { - final Channel channel = getOpenChannelOrFail(); // We initialize it early, so the forwarder won's start its daemon if the channel is shutting down - PortForwarder t = new PortForwarder(acceptingPort, proxy); - t.start(); - return channel.export(ListeningPort.class,t); - } - - @Override - public void checkRoles(RoleChecker checker) throws SecurityException { - checker.check(this,ROLE); - } - }); + return ch.call(new ListeningPortCallable(acceptingPort, proxy)); } private static final Logger LOGGER = Logger.getLogger(PortForwarder.class.getName()); @@ -153,4 +141,27 @@ public void checkRoles(RoleChecker checker) throws SecurityException { * Role that's willing to listen on a socket and forward that to the other side. */ public static final Role ROLE = new Role(PortForwarder.class); + + private static class ListeningPortCallable implements Callable { + private static final long serialVersionUID = 1L; + private final int acceptingPort; + private final Forwarder proxy; + + public ListeningPortCallable(int acceptingPort, Forwarder proxy) { + this.acceptingPort = acceptingPort; + this.proxy = proxy; + } + + public ListeningPort call() throws IOException { + final Channel channel = getOpenChannelOrFail(); // We initialize it early, so the forwarder won's start its daemon if the channel is shutting down + PortForwarder t = new PortForwarder(acceptingPort, proxy); + t.start(); + return channel.export(ListeningPort.class,t); + } + + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + checker.check(this,ROLE); + } + } } diff --git a/src/test/java/hudson/remoting/ClassFilterTest.java b/src/test/java/hudson/remoting/ClassFilterTest.java index be8c67770..07fba2d90 100644 --- a/src/test/java/hudson/remoting/ClassFilterTest.java +++ b/src/test/java/hudson/remoting/ClassFilterTest.java @@ -160,13 +160,7 @@ private void userRequestTestSequence() throws Exception { */ private void fire(String name, Channel from) throws Exception { final Security218 a = new Security218(name); - from.call(new CallableBase() { - @Override - public Void call() throws IOException { - a.toString(); // this will ensure 'a' gets sent over - return null; - } - }); + from.call(new Security218Callable(a)); } /** @@ -281,4 +275,18 @@ private String getAttack() { private void clearRecord() { System.setProperty("attack", ""); } + + private static class Security218Callable extends CallableBase { + private final Security218 a; + + public Security218Callable(Security218 a) { + this.a = a; + } + + @Override + public Void call() throws IOException { + a.toString(); // this will ensure 'a' gets sent over + return null; + } + } } \ No newline at end of file diff --git a/src/test/java/hudson/remoting/DeadRemoteOutputStreamTest.java b/src/test/java/hudson/remoting/DeadRemoteOutputStreamTest.java index c946aabf2..304064a1c 100644 --- a/src/test/java/hudson/remoting/DeadRemoteOutputStreamTest.java +++ b/src/test/java/hudson/remoting/DeadRemoteOutputStreamTest.java @@ -24,29 +24,37 @@ public void write(int b) throws IOException { } }); - channel.call(new CallableBase() { - public Void call() throws Exception { - os.write(0); // this write will go through because we won't notice that it's dead - System.gc(); - Thread.sleep(1000); - - try { - for (int i=0; i<100; i++) { - os.write(0); - System.gc(); - Thread.sleep(10); - } - fail("Expected to see the failure"); - } catch (IOException e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String whole = sw.toString(); - assertTrue(whole, whole.contains(MESSAGE) && whole.contains("hudson.rem0ting.TestCallable")); - } - return null; - } - }); + channel.call(new DeadWriterCallable(os)); } public static final String MESSAGE = "dead man walking"; + + private static class DeadWriterCallable extends CallableBase { + private final OutputStream os; + + public DeadWriterCallable(OutputStream os) { + this.os = os; + } + + public Void call() throws Exception { + os.write(0); // this write will go through because we won't notice that it's dead + System.gc(); + Thread.sleep(1000); + + try { + for (int i=0; i<100; i++) { + os.write(0); + System.gc(); + Thread.sleep(10); + } + fail("Expected to see the failure"); + } catch (IOException e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String whole = sw.toString(); + assertTrue(whole, whole.contains(MESSAGE) && whole.contains("hudson.rem0ting.TestCallable")); + } + return null; + } + } } diff --git a/src/test/java/hudson/remoting/PipeTest.java b/src/test/java/hudson/remoting/PipeTest.java index f62c99306..6d0e7c3bb 100644 --- a/src/test/java/hudson/remoting/PipeTest.java +++ b/src/test/java/hudson/remoting/PipeTest.java @@ -266,13 +266,7 @@ public void _testSendBigStuff() throws Exception { */ public void testQuickBurstWrite() throws Exception { final Pipe p = Pipe.createLocalToRemote(); - Future f = channel.callAsync(new CallableBase() { - public Integer call() throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - IOUtils.copy(p.getIn(), baos); - return baos.size(); - } - }); + Future f = channel.callAsync(new QuickBurstCallable(p)); OutputStream os = p.getOut(); os.write(1); os.close(); @@ -297,4 +291,18 @@ public static Test suite() throws Exception { private Object writeReplace() { return null; } + + private static class QuickBurstCallable extends CallableBase { + private final Pipe p; + + public QuickBurstCallable(Pipe p) { + this.p = p; + } + + public Integer call() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IOUtils.copy(p.getIn(), baos); + return baos.size(); + } + } } diff --git a/src/test/java/hudson/remoting/PipeWriterTest.java b/src/test/java/hudson/remoting/PipeWriterTest.java index fb7c8f0d3..44897de69 100644 --- a/src/test/java/hudson/remoting/PipeWriterTest.java +++ b/src/test/java/hudson/remoting/PipeWriterTest.java @@ -75,11 +75,7 @@ public void run() { * coordinated. */ public void testResponseIoCoord() throws Exception { - channel.call(new ResponseIoCoordCallable() { - void touch() throws IOException { - ros.write(0); - } - }); + channel.call(new ResponseCallableWriter()); // but I/O should be complete before the call returns. assertTrue(slow.written); } @@ -88,11 +84,7 @@ void touch() throws IOException { * Ditto for {@link OutputStream#flush()} */ public void testResponseIoCoordFlush() throws Exception { - channel.call(new ResponseIoCoordCallable() { - void touch() throws IOException { - ros.flush(); - } - }); + channel.call(new ResponseCallableFlusher()); assertTrue(slow.flushed); } @@ -100,11 +92,7 @@ void touch() throws IOException { * Ditto for {@link OutputStream#close()} */ public void testResponseIoCoordClose() throws Exception { - channel.call(new ResponseIoCoordCallable() { - void touch() throws IOException { - ros.close(); - } - }); + channel.call(new ResponseCallableCloser()); assertTrue(slow.closed); } @@ -131,29 +119,17 @@ public Object call() throws IOException { } public void testRequestIoCoord() throws Exception { - channel.call(new RequestIoCoordCallable() { - void touch() throws IOException { - ros.write(0); - } - }); + channel.call(new RequestCallableWriter()); assertSlowStreamTouched(); } public void testRequestIoCoordFlush() throws Exception { - channel.call(new RequestIoCoordCallable() { - void touch() throws IOException { - ros.flush(); - } - }); + channel.call(new RequestCallableFlusher()); assertSlowStreamTouched(); } public void testRequestIoCoordClose() throws Exception { - channel.call(new RequestIoCoordCallable() { - void touch() throws IOException { - ros.close(); - } - }); + channel.call(new RequestCallableCloser()); assertSlowStreamTouched(); } @@ -199,4 +175,40 @@ private void slow() throws InterruptedIOException { } } } + + private class ResponseCallableWriter extends ResponseIoCoordCallable { + void touch() throws IOException { + ros.write(0); + } + } + + private class ResponseCallableFlusher extends ResponseIoCoordCallable { + void touch() throws IOException { + ros.flush(); + } + } + + private class ResponseCallableCloser extends ResponseIoCoordCallable { + void touch() throws IOException { + ros.close(); + } + } + + private class RequestCallableWriter extends RequestIoCoordCallable { + void touch() throws IOException { + ros.write(0); + } + } + + private class RequestCallableFlusher extends RequestIoCoordCallable { + void touch() throws IOException { + ros.flush(); + } + } + + private class RequestCallableCloser extends RequestIoCoordCallable { + void touch() throws IOException { + ros.close(); + } + } } diff --git a/src/test/java/hudson/remoting/PrefetchingTest.java b/src/test/java/hudson/remoting/PrefetchingTest.java index 38c2942cc..036dfdbd2 100644 --- a/src/test/java/hudson/remoting/PrefetchingTest.java +++ b/src/test/java/hudson/remoting/PrefetchingTest.java @@ -45,12 +45,7 @@ protected void setUp() throws Exception { dir.mkdirs(); channel.setJarCache(new FileSystemJarCache(dir, true)); - channel.call(new CallableBase() { - public Void call() throws IOException { - Channel.currentOrFail().setJarCache(new FileSystemJarCache(dir, true)); - return null; - } - }); + channel.call(new JarCacherCallable()); sum1 = channel.jarLoader.calcChecksum(jar1); sum2 = channel.jarLoader.calcChecksum(jar2); } @@ -235,4 +230,11 @@ public Void call() throws IOException { } } } + + private class JarCacherCallable extends CallableBase { + public Void call() throws IOException { + Channel.currentOrFail().setJarCache(new FileSystemJarCache(dir, true)); + return null; + } + } } diff --git a/src/test/java/hudson/remoting/ProxyWriterTest.java b/src/test/java/hudson/remoting/ProxyWriterTest.java index 7cc94f096..48c708397 100644 --- a/src/test/java/hudson/remoting/ProxyWriterTest.java +++ b/src/test/java/hudson/remoting/ProxyWriterTest.java @@ -51,12 +51,7 @@ public void testAllCalls() throws Exception { StringWriter sw = new StringWriter(); final RemoteWriter w = new RemoteWriter(sw); - channel.call(new CallableBase() { - public Void call() throws IOException { - writeBunchOfData(w); - return null; - } - }); + channel.call(new WriteBunchOfDataCallable(w)); StringWriter correct = new StringWriter(); writeBunchOfData(correct); @@ -96,13 +91,7 @@ public void close() throws IOException { }; final RemoteWriter w = new RemoteWriter(sw); - channel.call(new CallableBase() { - public Void call() throws IOException { - w.write("hello"); - W = new WeakReference(w); - return null; - } - }); + channel.call(new WeakReferenceCallable(w)); // induce a GC. There's no good reliable way to do this, // and if GC doesn't happen within this loop, the test can pass @@ -111,12 +100,7 @@ public Void call() throws IOException { assertTrue("There shouldn't be any errors: " + log.toString(), log.size() == 0); Thread.sleep(100); - if (channel.call(new CallableBase() { - public Boolean call() throws IOException { - System.gc(); - return W.get()==null; - } - })) + if (channel.call(new GcCallable())) break; } @@ -136,12 +120,7 @@ public void testWriteAndSync() throws InterruptedException, IOException { final RemoteWriter w = new RemoteWriter(sw); for (int i=0; i<16; i++) { - channel.call(new CallableBase() { - public Void call() throws IOException { - w.write("1--",0,1); - return null; - } - }); + channel.call(new WriterCallable(w)); w.write("2"); } @@ -155,4 +134,51 @@ private Object writeReplace() { public static Test suite() throws Exception { return buildSuite(ProxyWriterTest.class); } + + private static class WriteBunchOfDataCallable extends CallableBase { + private final RemoteWriter w; + + public WriteBunchOfDataCallable(RemoteWriter w) { + this.w = w; + } + + public Void call() throws IOException { + writeBunchOfData(w); + return null; + } + } + + private static class WeakReferenceCallable extends CallableBase { + private final RemoteWriter w; + + public WeakReferenceCallable(RemoteWriter w) { + this.w = w; + } + + public Void call() throws IOException { + w.write("hello"); + W = new WeakReference(w); + return null; + } + } + + private static class GcCallable extends CallableBase { + public Boolean call() throws IOException { + System.gc(); + return W.get()==null; + } + } + + private static class WriterCallable extends CallableBase { + private final RemoteWriter w; + + public WriterCallable(RemoteWriter w) { + this.w = w; + } + + public Void call() throws IOException { + w.write("1--",0,1); + return null; + } + } } diff --git a/src/test/java/hudson/remoting/throughput/Sender.java b/src/test/java/hudson/remoting/throughput/Sender.java index f8da1e3ef..7fe19de73 100644 --- a/src/test/java/hudson/remoting/throughput/Sender.java +++ b/src/test/java/hudson/remoting/throughput/Sender.java @@ -41,11 +41,7 @@ public static void main(String[] args) throws Exception { new BufferedOutputStream(SocketChannelStream.out(s))); final Pipe p = Pipe.createLocalToRemote(); - Future f = ch.callAsync(new CallableBase() { - public byte[] call() throws Exception { - return digest(p.getIn()); - } - }); + Future f = ch.callAsync(new DigestCallable(p)); System.out.println("Started"); long start = System.nanoTime(); @@ -73,4 +69,16 @@ private static byte[] getRandomSequence() { new Random(0).nextBytes(buf); return buf; } + + private static class DigestCallable extends CallableBase { + private final Pipe p; + + public DigestCallable(Pipe p) { + this.p = p; + } + + public byte[] call() throws Exception { + return digest(p.getIn()); + } + } } diff --git a/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java b/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java index 98bd22fb5..467f6c0d3 100644 --- a/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java +++ b/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java @@ -42,13 +42,21 @@ public static void main(String[] args) throws Exception { } private static String echo(Channel ch, final String arg) throws Exception { - return ch.call(new CallableBase() { - public String call() throws Exception { - LOGGER.info("Echoing back "+arg); - return arg; - } - }); + return ch.call(new EchoingCallable(arg)); } private static final Logger LOGGER = Logger.getLogger(SocketClientMain.class.getName()); + + private static class EchoingCallable extends CallableBase { + private final String arg; + + public EchoingCallable(String arg) { + this.arg = arg; + } + + public String call() throws Exception { + LOGGER.info("Echoing back "+ arg); + return arg; + } + } } From e41b2e248ee7f6349ff96e8ce1fce8407c3d4690 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 29 Oct 2018 14:19:44 -0600 Subject: [PATCH 206/243] If we cannot create create the jar cache, leave it null. In some cases, the default directory may not be writeable, which will cause an IO exception trying to create the cache. Fail a bit more gracefully by running without a cache. --- src/main/java/hudson/remoting/ChannelBuilder.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/ChannelBuilder.java b/src/main/java/hudson/remoting/ChannelBuilder.java index bc0916b9c..d92307a30 100644 --- a/src/main/java/hudson/remoting/ChannelBuilder.java +++ b/src/main/java/hudson/remoting/ChannelBuilder.java @@ -217,7 +217,11 @@ public ChannelBuilder withJarCache(@Nonnull JarCache jarCache) { * @throws IOException Default JAR Cache location cannot be initialized */ public ChannelBuilder withJarCacheOrDefault(@CheckForNull JarCache jarCache) throws IOException { - this.jarCache = jarCache != null ? jarCache : JarCache.getDefault(); + try { + this.jarCache = jarCache != null ? jarCache : JarCache.getDefault(); + } catch (IOException ioe) { + LOGGER.log(Level.WARNING, "Could not create jar cache. Running without cache.", ioe); + } return this; } From 3869f62f19e0bcbcbe3acef1a4a2ef193b9c52d3 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 30 Oct 2018 16:33:46 -0600 Subject: [PATCH 207/243] Minor formatting changes. --- src/main/java/hudson/remoting/forward/ForwarderFactory.java | 2 +- src/main/java/hudson/remoting/forward/PortForwarder.java | 2 +- src/test/java/hudson/remoting/ProxyWriterTest.java | 4 ++-- .../java/org/jenkinsci/remoting/nio/SocketClientMain.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/hudson/remoting/forward/ForwarderFactory.java b/src/main/java/hudson/remoting/forward/ForwarderFactory.java index 892b4c381..e63cd5b97 100644 --- a/src/main/java/hudson/remoting/forward/ForwarderFactory.java +++ b/src/main/java/hudson/remoting/forward/ForwarderFactory.java @@ -118,7 +118,7 @@ public Forwarder call() throws IOException { @Override public void checkRoles(RoleChecker checker) throws SecurityException { - checker.check(this,ROLE); + checker.check(this, ROLE); } } } diff --git a/src/main/java/hudson/remoting/forward/PortForwarder.java b/src/main/java/hudson/remoting/forward/PortForwarder.java index eb6fb34ac..5b5bc0823 100644 --- a/src/main/java/hudson/remoting/forward/PortForwarder.java +++ b/src/main/java/hudson/remoting/forward/PortForwarder.java @@ -161,7 +161,7 @@ public ListeningPort call() throws IOException { @Override public void checkRoles(RoleChecker checker) throws SecurityException { - checker.check(this,ROLE); + checker.check(this, ROLE); } } } diff --git a/src/test/java/hudson/remoting/ProxyWriterTest.java b/src/test/java/hudson/remoting/ProxyWriterTest.java index 48c708397..b46290c68 100644 --- a/src/test/java/hudson/remoting/ProxyWriterTest.java +++ b/src/test/java/hudson/remoting/ProxyWriterTest.java @@ -165,7 +165,7 @@ public Void call() throws IOException { private static class GcCallable extends CallableBase { public Boolean call() throws IOException { System.gc(); - return W.get()==null; + return W.get() == null; } } @@ -177,7 +177,7 @@ public WriterCallable(RemoteWriter w) { } public Void call() throws IOException { - w.write("1--",0,1); + w.write("1--", 0, 1); return null; } } diff --git a/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java b/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java index 467f6c0d3..b2d38a0a7 100644 --- a/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java +++ b/src/test/java/org/jenkinsci/remoting/nio/SocketClientMain.java @@ -55,7 +55,7 @@ public EchoingCallable(String arg) { } public String call() throws Exception { - LOGGER.info("Echoing back "+ arg); + LOGGER.info("Echoing back " + arg); return arg; } } From 3a870a12d7d59fba3fdd8007f3e73534e0441f3b Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 1 Nov 2018 16:00:06 -0600 Subject: [PATCH 208/243] Tweak the log messaging on reconnect. This may help reduce some confusion. The messages are clearer and it works fine on my system in my tests. It's unclear whether it's a good idea to skip the System.exit() but that seems to make sense and work fine in this instance. --- src/main/java/hudson/remoting/Engine.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 097bec254..0f1b92ed3 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -664,7 +664,14 @@ public void afterChannel(@Nonnull JnlpConnectionState event) { // try to connect back to the server every 10 secs. resolver.waitForReady(); - events.onReconnect(); + try { + events.status("Performing onReconnect operation."); + events.onReconnect(); + events.status("onReconnect operation completed."); + } catch (NoClassDefFoundError e) { + events.status("onReconnect operation failed."); + LOGGER.log(Level.FINE, "Reconnection error.", e); + } } } catch (Throwable e) { events.error(e); From a852b8ae51cb5e2825400f3abbde7721a022d3f2 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Tue, 6 Nov 2018 15:24:39 -0700 Subject: [PATCH 209/243] Allow remoting to publish incrementals. --- .mvn/extensions.xml | 7 +++++++ pom.xml | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .mvn/extensions.xml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 000000000..a2d496cc2 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.0-beta-4 + + diff --git a/pom.xml b/pom.xml index 2522e7149..e3768dec1 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28-SNAPSHOT + ${revision}${changelist} Jenkins remoting layer @@ -71,6 +71,8 @@ THE SOFTWARE. Low 1.4 + 3.28 + -SNAPSHOT From e09f1c956041d75a1815802b9d5bccef619cf746 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Nov 2018 16:11:12 -0500 Subject: [PATCH 210/243] Picking up https://github.com/jenkinsci/pom/pull/34. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2522e7149..e53312dc4 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci jenkins - 1.49 + 1.50-20181107.211029-2 From ca58e75107ec7272e7dfeabfe1761b13d7ec1e24 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 7 Nov 2018 14:30:27 -0700 Subject: [PATCH 211/243] Revert "Allow remoting to publish incrementals." This reverts commit a852b8ae51cb5e2825400f3abbde7721a022d3f2. --- .mvn/extensions.xml | 7 ------- pom.xml | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 .mvn/extensions.xml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml deleted file mode 100644 index a2d496cc2..000000000 --- a/.mvn/extensions.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - io.jenkins.tools.incrementals - git-changelist-maven-extension - 1.0-beta-4 - - diff --git a/pom.xml b/pom.xml index e3768dec1..2522e7149 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${revision}${changelist} + 3.28-SNAPSHOT Jenkins remoting layer @@ -71,8 +71,6 @@ THE SOFTWARE. Low 1.4 - 3.28 - -SNAPSHOT From 33bc3f99f7155ec090e01bbd158cd3105cd2e487 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 7 Nov 2018 14:35:48 -0700 Subject: [PATCH 212/243] Try the automated way to add incremental support. --- .mvn/extensions.xml | 7 +++++++ .mvn/maven.config | 2 ++ pom.xml | 6 ++++-- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .mvn/extensions.xml create mode 100644 .mvn/maven.config diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 000000000..94863e605 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.0-beta-7 + + diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 000000000..2a0299c48 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,2 @@ +-Pconsume-incrementals +-Pmight-produce-incrementals diff --git a/pom.xml b/pom.xml index 2522e7149..be99851f8 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28-SNAPSHOT + ${revision}${changelist} Jenkins remoting layer @@ -57,10 +57,12 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - HEAD + ${scmTag} + 3.28 + -SNAPSHOT 8 private UTF-8 From 4131e0604ab6a90c57a00301de62d86fa69a6d17 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Nov 2018 21:11:32 -0500 Subject: [PATCH 213/243] Allow snapshots to be downloaded. --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index e53312dc4..9623d382b 100644 --- a/pom.xml +++ b/pom.xml @@ -77,12 +77,6 @@ THE SOFTWARE. repo.jenkins-ci.org https://repo.jenkins-ci.org/public/ - - true - - - false - From 47998be6dc8aec9bbd1bbdd1aeac3b3919042fa5 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Nov 2018 21:28:23 -0500 Subject: [PATCH 214/243] In this case, updating Surefire seems to be necessary. Otherwise get repeated errors: Error: Could not find or load main class hudson.remoting.Launcher Perhaps some test is loading surefirebooter.jar in another JVM? --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 9623d382b..a8fa942c6 100644 --- a/pom.xml +++ b/pom.xml @@ -418,6 +418,7 @@ THE SOFTWARE. maven-surefire-plugin + 3.0.0-M1 false 4 From 31ad512cab2c079b754abc737c317634d6f5a94a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 8 Nov 2018 10:37:38 -0500 Subject: [PATCH 215/243] 1.50 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8fa942c6..20f298f35 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci jenkins - 1.50-20181107.211029-2 + 1.50 From 85700ff99b1af9354ddbcc5a06048d365f946490 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 15 Nov 2018 18:50:00 -0500 Subject: [PATCH 216/243] No point in displaying Command.createdAt if it is null. --- src/main/java/hudson/remoting/Channel.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index d3c6b87c7..1c4572d9a 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -568,7 +568,9 @@ public void handle(Command cmd) { } } catch (Throwable t) { logger.log(Level.SEVERE, "Failed to execute command " + cmd + " (channel " + Channel.this.name + ")", t); - logger.log(Level.SEVERE, "This command is created here", cmd.createdAt); + if (cmd.createdAt != null) { + logger.log(Level.SEVERE, "This command is created here", cmd.createdAt); + } } } From 6ee933a51cd72ce2cb94708803e43457d84d6772 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 15 Nov 2018 18:51:03 -0500 Subject: [PATCH 217/243] [JENKINS-54566] Ignore attempts to flush a ProxyOutputStream which has already been finalized. --- src/main/java/hudson/remoting/ProxyOutputStream.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/hudson/remoting/ProxyOutputStream.java b/src/main/java/hudson/remoting/ProxyOutputStream.java index 34335125a..c0c1387e0 100644 --- a/src/main/java/hudson/remoting/ProxyOutputStream.java +++ b/src/main/java/hudson/remoting/ProxyOutputStream.java @@ -25,7 +25,6 @@ import javax.annotation.Nonnull; import java.io.IOException; -import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.util.concurrent.ExecutionException; @@ -152,8 +151,9 @@ a fragmentation will happen. We start sending out a small Chunk at a time (say 4 } public synchronized void flush() throws IOException { - if(channel!=null) - channel.send(new Flush(channel.newIoId(),oid)); + if (channel != null && /* see #finalize */ oid != -1) { + channel.send(new Flush(channel.newIoId(), oid)); + } } public synchronized void close() throws IOException { From 61225426b8605645ad9a6eb3a87909c65f1b55b6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 30 Nov 2018 09:35:03 -0500 Subject: [PATCH 218/243] Better diagnostics for errors from RemoteClassLoader.fetch4. --- src/main/java/hudson/remoting/RemoteClassLoader.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/hudson/remoting/RemoteClassLoader.java b/src/main/java/hudson/remoting/RemoteClassLoader.java index 10a430ef8..9d5ff6905 100644 --- a/src/main/java/hudson/remoting/RemoteClassLoader.java +++ b/src/main/java/hudson/remoting/RemoteClassLoader.java @@ -839,19 +839,20 @@ public ClassFile fetch2(String className) throws ClassNotFoundException { /** * Fetch a single class and creates a {@link ClassFile2} for it. */ - public ClassFile2 fetch4(String className, ClassFile2 referer) throws ClassNotFoundException { + public ClassFile2 fetch4(String className, @CheckForNull ClassFile2 referer) throws ClassNotFoundException { + Class referrerClass = referer == null ? null : referer.clazz; Class c; try { c = (referer==null?this.cl:referer.clazz.getClassLoader()).loadClass(className); } catch (LinkageError e) { - throw (LinkageError)new LinkageError("Failed to load "+className).initCause(e); + throw new LinkageError("Failed to load " + className + " via " + referrerClass, e); } ClassLoader ecl = c.getClassLoader(); if (ecl == null) { if (USE_BOOTSTRAP_CLASSLOADER) { ecl = PSEUDO_BOOTSTRAP; } else { - throw new ClassNotFoundException("Classloading from system classloader disabled"); + throw new ClassNotFoundException("Bootstrap pseudo-classloader disabled: " + className + " via " + referrerClass); } } @@ -880,7 +881,7 @@ public ClassFile2 fetch4(String className, ClassFile2 referer) throws ClassNotFo } return fetch2(className).upconvert(referer,c,urlOfClassFile); } catch (IOException e) { - throw new ClassNotFoundException(); + throw new ClassNotFoundException("Failed to load " + className + " via " + referrerClass, e); } } From c5077020227f512af531bf959aafb0c4d678bb21 Mon Sep 17 00:00:00 2001 From: jakub-bochenski Date: Fri, 7 Dec 2018 15:54:36 +0100 Subject: [PATCH 219/243] Update broken link Link to video since the original document seems to be no longer available. <3 CB for keeping stable URLs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7eca37a3f..1398f405b 100644 --- a/README.md +++ b/README.md @@ -56,5 +56,5 @@ by Winston Prakash, Oracle (the information is outdated) * [Making your plugin behave in distributed Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/Making+your+plugin+behave+in+distributed+Jenkins) * [Writing an SCM plugin. Remoting examples](https://wiki.jenkins-ci.org/display/JENKINS/Remoting) * [Troubleshooting remoting issues](https://wiki.jenkins-ci.org/display/JENKINS/Remoting+issue) -* [Scaling Jenkins to Hundreds of Nodes](https://www.cloudbees.com/jenkins/juc-2015/abstracts/us-west/02-01-1600) -by Akshay Dayal, Google (remoting optimization, JNLP3) \ No newline at end of file +* [Scaling Jenkins to Hundreds of Nodes](https://www.youtube.com/watch?v=9-DUVroz7yk) +by Akshay Dayal, Google (remoting optimization, JNLP3) From b77a7c9d7c1419b2cbd300b09e971c82292d06bc Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Fri, 7 Dec 2018 10:05:05 -0700 Subject: [PATCH 220/243] Change new no_proxy impl to not draw in new dependencies. For the changes to the no_proxy capability, I added a couple of dependencies for checking and handling ip addresses and subnets. One of them was clearly the wrong one to use when I started integrating. Rather, than add dependencies, we decided to copy in utilities. This same pattern was followed recently in Jenkins core to avoid introducing new dependencies into Jenkins core that plugins may become dependent on. --- pom.xml | 10 - .../hudson/remoting/NoProxyEvaluator.java | 6 +- .../apache/commons/net/util/SubnetUtils.java | 368 +++++++++++ .../routines/InetAddressValidator.java | 196 ++++++ .../validator/routines/RegexValidator.java | 241 +++++++ .../commons/net/util/SubnetUtilsTest.java | 340 ++++++++++ .../routines/InetAddressValidatorTest.java | 604 ++++++++++++++++++ .../routines/RegexValidatorTest.java | 296 +++++++++ 8 files changed, 2048 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtils.java create mode 100644 src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidator.java create mode 100644 src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidator.java create mode 100644 src/test/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtilsTest.java create mode 100644 src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidatorTest.java create mode 100644 src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidatorTest.java diff --git a/pom.xml b/pom.xml index ac8762618..30271c88a 100644 --- a/pom.xml +++ b/pom.xml @@ -191,16 +191,6 @@ THE SOFTWARE. test-annotations test - - org.apache.httpcomponents - httpclient - 4.5.6 - - - commons-net - commons-net - 3.6 - diff --git a/src/main/java/hudson/remoting/NoProxyEvaluator.java b/src/main/java/hudson/remoting/NoProxyEvaluator.java index f34f35f41..6dcfe0b80 100644 --- a/src/main/java/hudson/remoting/NoProxyEvaluator.java +++ b/src/main/java/hudson/remoting/NoProxyEvaluator.java @@ -23,8 +23,8 @@ */ package hudson.remoting; -import org.apache.commons.net.util.SubnetUtils; -import org.apache.http.conn.util.InetAddressUtils; +import org.jenkinsci.remoting.org.apache.commons.net.util.SubnetUtils; +import org.jenkinsci.remoting.org.apache.commons.validator.routines.InetAddressValidator; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -149,7 +149,7 @@ private boolean matchesDomainHost(String host) { } private boolean isIpAddress(String host) { - return InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host); + return InetAddressValidator.getInstance().isValid(host); } } diff --git a/src/main/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtils.java b/src/main/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtils.java new file mode 100644 index 000000000..994a04a71 --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtils.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jenkinsci.remoting.org.apache.commons.net.util; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A class that performs some subnet calculations given a network address and a subnet mask. + * @see "http://www.faqs.org/rfcs/rfc1519.html" + * @since 2.0 + */ +//[PATCH] +@Restricted(NoExternalUse.class) +// end of [PATCH] +public class SubnetUtils { + + private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; + private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})"; + private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS); + private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT); + private static final int NBITS = 32; + + private int netmask = 0; + private int address = 0; + private int network = 0; + private int broadcast = 0; + + /** Whether the broadcast/network address are included in host count */ + private boolean inclusiveHostCount = false; + + + /** + * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" + * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" + * @throws IllegalArgumentException if the parameter is invalid, + * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32 + */ + public SubnetUtils(String cidrNotation) { + calculate(cidrNotation); + } + + /** + * Constructor that takes a dotted decimal address and a dotted decimal mask. + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + * @throws IllegalArgumentException if the address or mask is invalid, + * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros + */ + public SubnetUtils(String address, String mask) { + calculate(toCidrNotation(address, mask)); + } + + + /** + * Returns true if the return value of {@link SubnetInfo#getAddressCount()} + * includes the network and broadcast addresses. + * @since 2.2 + * @return true if the hostcount includes the network and broadcast addresses + */ + public boolean isInclusiveHostCount() { + return inclusiveHostCount; + } + + /** + * Set to true if you want the return value of {@link SubnetInfo#getAddressCount()} + * to include the network and broadcast addresses. + * @param inclusiveHostCount true if network and broadcast addresses are to be included + * @since 2.2 + */ + public void setInclusiveHostCount(boolean inclusiveHostCount) { + this.inclusiveHostCount = inclusiveHostCount; + } + + + + /** + * Convenience container for subnet summary information. + * + */ + public final class SubnetInfo { + /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ + private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; + + private SubnetInfo() {} + + private int netmask() { return netmask; } + private int network() { return network; } + private int address() { return address; } + private int broadcast() { return broadcast; } + + // long versions of the values (as unsigned int) which are more suitable for range checking + private long networkLong() { return network & UNSIGNED_INT_MASK; } + private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; } + + private int low() { + return (isInclusiveHostCount() ? network() : + broadcastLong() - networkLong() > 1 ? network() + 1 : 0); + } + + private int high() { + return (isInclusiveHostCount() ? broadcast() : + broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0); + } + + /** + * Returns true if the parameter address is in the + * range of usable endpoint addresses for this subnet. This excludes the + * network and broadcast adresses. + * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" + * @return True if in range, false otherwise + */ + public boolean isInRange(String address) { + return isInRange(toInteger(address)); + } + + /** + * + * @param address the address to check + * @return true if it is in range + * @since 3.4 (made public) + */ + public boolean isInRange(int address) { + long addLong = address & UNSIGNED_INT_MASK; + long lowLong = low() & UNSIGNED_INT_MASK; + long highLong = high() & UNSIGNED_INT_MASK; + return addLong >= lowLong && addLong <= highLong; + } + + public String getBroadcastAddress() { + return format(toArray(broadcast())); + } + + public String getNetworkAddress() { + return format(toArray(network())); + } + + public String getNetmask() { + return format(toArray(netmask())); + } + + public String getAddress() { + return format(toArray(address())); + } + + /** + * Return the low address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getLowAddress() { + return format(toArray(low())); + } + + /** + * Return the high address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getHighAddress() { + return format(toArray(high())); + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} + * @deprecated (3.4) use {@link #getAddressCountLong()} instead + */ + @Deprecated + public int getAddressCount() { + long countLong = getAddressCountLong(); + if (countLong > Integer.MAX_VALUE) { + throw new RuntimeException("Count is larger than an integer: " + countLong); + } + // N.B. cannot be negative + return (int)countLong; + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @since 3.4 + */ + public long getAddressCountLong() { + long b = broadcastLong(); + long n = networkLong(); + long count = b - n + (isInclusiveHostCount() ? 1 : -1); + return count < 0 ? 0 : count; + } + + public int asInteger(String address) { + return toInteger(address); + } + + public String getCidrSignature() { + return toCidrNotation( + format(toArray(address())), + format(toArray(netmask())) + ); + } + + public String[] getAllAddresses() { + int ct = getAddressCount(); + String[] addresses = new String[ct]; + if (ct == 0) { + return addresses; + } + for (int add = low(), j=0; add <= high(); ++add, ++j) { + addresses[j] = format(toArray(add)); + } + return addresses; + } + + /** + * {@inheritDoc} + * @since 2.2 + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]") + .append(" Netmask: [").append(getNetmask()).append("]\n") + .append("Network:\t[").append(getNetworkAddress()).append("]\n") + .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n") + .append("First Address:\t[").append(getLowAddress()).append("]\n") + .append("Last Address:\t[").append(getHighAddress()).append("]\n") + .append("# Addresses:\t[").append(getAddressCount()).append("]\n"); + return buf.toString(); + } + } + + /** + * Return a {@link SubnetInfo} instance that contains subnet-specific statistics + * @return new instance + */ + public final SubnetInfo getInfo() { return new SubnetInfo(); } + + /* + * Initialize the internal fields from the supplied CIDR mask + */ + private void calculate(String mask) { + Matcher matcher = cidrPattern.matcher(mask); + + if (matcher.matches()) { + address = matchAddress(matcher); + + /* Create a binary netmask from the number of bits specification /x */ + int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); + for (int j = 0; j < cidrPart; ++j) { + netmask |= (1 << 31 - j); + } + + /* Calculate base network address */ + network = (address & netmask); + + /* Calculate broadcast address */ + broadcast = network | ~(netmask); + } else { + throw new IllegalArgumentException("Could not parse [" + mask + "]"); + } + } + + /* + * Convert a dotted decimal format address to a packed integer format + */ + private int toInteger(String address) { + Matcher matcher = addressPattern.matcher(address); + if (matcher.matches()) { + return matchAddress(matcher); + } else { + throw new IllegalArgumentException("Could not parse [" + address + "]"); + } + } + + /* + * Convenience method to extract the components of a dotted decimal address and + * pack into an integer using a regex match + */ + private int matchAddress(Matcher matcher) { + int addr = 0; + for (int i = 1; i <= 4; ++i) { + int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255)); + addr |= ((n & 0xff) << 8*(4-i)); + } + return addr; + } + + /* + * Convert a packed integer address into a 4-element array + */ + private int[] toArray(int val) { + int ret[] = new int[4]; + for (int j = 3; j >= 0; --j) { + ret[j] |= ((val >>> 8*(3-j)) & (0xff)); + } + return ret; + } + + /* + * Convert a 4-element array into dotted decimal format + */ + private String format(int[] octets) { + StringBuilder str = new StringBuilder(); + for (int i =0; i < octets.length; ++i){ + str.append(octets[i]); + if (i != octets.length - 1) { + str.append("."); + } + } + return str.toString(); + } + + /* + * Convenience function to check integer boundaries. + * Checks if a value x is in the range [begin,end]. + * Returns x if it is in range, throws an exception otherwise. + */ + private int rangeCheck(int value, int begin, int end) { + if (value >= begin && value <= end) { // (begin,end] + return value; + } + + throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); + } + + /* + * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy + * see Hacker's Delight section 5.1 + */ + int pop(int x) { + x = x - ((x >>> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); + x = (x + (x >>> 4)) & 0x0F0F0F0F; + x = x + (x >>> 8); + x = x + (x >>> 16); + return x & 0x0000003F; + } + + /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format + * by counting the 1-bit population in the mask address. (It may be better to count + * NBITS-#trailing zeroes for this case) + */ + private String toCidrNotation(String addr, String mask) { + return addr + "/" + pop(toInteger(mask)); + } +} diff --git a/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidator.java b/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidator.java new file mode 100644 index 000000000..2f409fe36 --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidator.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copied from commons-validator:commons-validator:1.6, with [PATCH] modifications */ +package org.jenkinsci.remoting.org.apache.commons.validator.routines; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *

InetAddress validation and conversion routines (java.net.InetAddress).

+ * + *

This class provides methods to validate a candidate IP address. + * + *

+ * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method. + *

+ * + * @version $Revision: 1783032 $ + * @since Validator 1.4 + */ +//[PATCH] +@Restricted(NoExternalUse.class) +// end of [PATCH] +public class InetAddressValidator implements Serializable { + + private static final int IPV4_MAX_OCTET_VALUE = 255; + + private static final int MAX_UNSIGNED_SHORT = 0xffff; + + private static final int BASE_16 = 16; + + private static final long serialVersionUID = -919201640201914789L; + + private static final String IPV4_REGEX = + "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + + // Max number of hex groups (separated by :) in an IPV6 address + private static final int IPV6_MAX_HEX_GROUPS = 8; + + // Max hex digits in each IPv6 group + private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + /** + * Singleton instance of this class. + */ + private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); + + /** IPv4 RegexValidator */ + private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); + + /** + * Returns the singleton instance of this validator. + * @return the singleton instance of this validator + */ + public static InetAddressValidator getInstance() { + return VALIDATOR; + } + + /** + * Checks if the specified string is a valid IP address. + * @param inetAddress the string to validate + * @return true if the string validates as an IP address + */ + public boolean isValid(String inetAddress) { + return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); + } + + /** + * Validates an IPv4 address. Returns true if valid. + * @param inet4Address the IPv4 address to validate + * @return true if the argument contains a valid IPv4 address + */ + public boolean isValidInet4Address(String inet4Address) { + // verify that address conforms to generic IPv4 format + String[] groups = ipv4Validator.match(inet4Address); + + if (groups == null) { + return false; + } + + // verify that address subgroups are legal + for (String ipSegment : groups) { + if (ipSegment == null || ipSegment.length() == 0) { + return false; + } + + int iIpSegment = 0; + + try { + iIpSegment = Integer.parseInt(ipSegment); + } catch(NumberFormatException e) { + return false; + } + + if (iIpSegment > IPV4_MAX_OCTET_VALUE) { + return false; + } + + if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { + return false; + } + + } + + return true; + } + + /** + * Validates an IPv6 address. Returns true if valid. + * @param inet6Address the IPv6 address to validate + * @return true if the argument contains a valid IPv6 address + * + * @since 1.4.1 + */ + public boolean isValidInet6Address(String inet6Address) { + boolean containsCompressedZeroes = inet6Address.contains("::"); + if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) { + return false; + } + if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::")) + || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) { + return false; + } + String[] octets = inet6Address.split(":"); + if (containsCompressedZeroes) { + List octetList = new ArrayList(Arrays.asList(octets)); + if (inet6Address.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { + octetList.remove(0); + } + octets = octetList.toArray(new String[octetList.size()]); + } + if (octets.length > IPV6_MAX_HEX_GROUPS) { + return false; + } + int validOctets = 0; + int emptyOctets = 0; // consecutive empty chunks + for (int index = 0; index < octets.length; index++) { + String octet = octets[index]; + if (octet.length() == 0) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + } else { + emptyOctets = 0; + // Is last chunk an IPv4 address? + if (index == octets.length - 1 && octet.contains(".")) { + if (!isValidInet4Address(octet)) { + return false; + } + validOctets += 2; + continue; + } + if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + int octetInt = 0; + try { + octetInt = Integer.parseInt(octet, BASE_16); + } catch (NumberFormatException e) { + return false; + } + if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { + return false; + } + } + validOctets++; + } + if (validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidator.java b/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidator.java new file mode 100644 index 000000000..b5e9e3e3d --- /dev/null +++ b/src/main/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidator.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copied from commons-validator:commons-validator:1.6, with [PATCH] modifications */ +package org.jenkinsci.remoting.org.apache.commons.validator.routines; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Regular Expression validation (using JDK 1.4+ regex support). + *

+ * Construct the validator either for a single regular expression or a set (array) of + * regular expressions. By default validation is case sensitive but constructors + * are provided to allow case in-sensitive validation. For example to create + * a validator which does case in-sensitive validation for a set of regular + * expressions: + *

+ *
+ * 
+ * String[] regexs = new String[] {...};
+ * RegexValidator validator = new RegexValidator(regexs, false);
+ * 
+ * 
+ * + *
    + *
  • Validate true or false:
  • + *
  • + *
      + *
    • boolean valid = validator.isValidRootUrl(value);
    • + *
    + *
  • + *
  • Validate returning an aggregated String of the matched groups:
  • + *
  • + *
      + *
    • String result = validator.validate(value);
    • + *
    + *
  • + *
  • Validate returning the matched groups:
  • + *
  • + *
      + *
    • String[] result = validator.match(value);
    • + *
    + *
  • + *
+ * + * Note that patterns are matched against the entire input. + * + *

+ * Cached instances pre-compile and re-use {@link Pattern}(s) - which according + * to the {@link Pattern} API are safe to use in a multi-threaded environment. + *

+ * + * @version $Revision: 1739356 $ + * @since Validator 1.4 + */ +//[PATCH] +@Restricted(NoExternalUse.class) +// end of [PATCH] +public class RegexValidator implements Serializable { + + private static final long serialVersionUID = -8832409930574867162L; + + private final Pattern[] patterns; + + /** + * Construct a case sensitive validator for a single + * regular expression. + * + * @param regex The regular expression this validator will + * validate against + */ + public RegexValidator(String regex) { + this(regex, true); + } + + /** + * Construct a validator for a single regular expression + * with the specified case sensitivity. + * + * @param regex The regular expression this validator will + * validate against + * @param caseSensitive when true matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(String regex, boolean caseSensitive) { + this(new String[] {regex}, caseSensitive); + } + + /** + * Construct a case sensitive validator that matches any one + * of the set of regular expressions. + * + * @param regexs The set of regular expressions this validator will + * validate against + */ + public RegexValidator(String[] regexs) { + this(regexs, true); + } + + /** + * Construct a validator that matches any one of the set of regular + * expressions with the specified case sensitivity. + * + * @param regexs The set of regular expressions this validator will + * validate against + * @param caseSensitive when true matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(String[] regexs, boolean caseSensitive) { + if (regexs == null || regexs.length == 0) { + throw new IllegalArgumentException("Regular expressions are missing"); + } + patterns = new Pattern[regexs.length]; + int flags = (caseSensitive ? 0: Pattern.CASE_INSENSITIVE); + for (int i = 0; i < regexs.length; i++) { + if (regexs[i] == null || regexs[i].length() == 0) { + throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); + } + patterns[i] = Pattern.compile(regexs[i], flags); + } + } + + /** + * Validate a value against the set of regular expressions. + * + * @param value The value to validate. + * @return true if the value is valid + * otherwise false. + */ + public boolean isValid(String value) { + if (value == null) { + return false; + } + for (int i = 0; i < patterns.length; i++) { + if (patterns[i].matcher(value).matches()) { + return true; + } + } + return false; + } + + /** + * Validate a value against the set of regular expressions + * returning the array of matched groups. + * + * @param value The value to validate. + * @return String array of the groups matched if + * valid or null if invalid + */ +//[PATCH] + @SuppressFBWarnings("PZLA") // Remoting uses Low threshold +// end of [PATCH] + public String[] match(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + String[] groups = new String[count]; + for (int j = 0; j < count; j++) { + groups[j] = matcher.group(j+1); + } + return groups; + } + } + return null; + } + + + /** + * Validate a value against the set of regular expressions + * returning a String value of the aggregated groups. + * + * @param value The value to validate. + * @return Aggregated String value comprised of the + * groups matched if valid or null if invalid + */ + public String validate(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + if (count == 1) { + return matcher.group(1); + } + StringBuilder buffer = new StringBuilder(); + for (int j = 0; j < count; j++) { + String component = matcher.group(j+1); + if (component != null) { + buffer.append(component); + } + } + return buffer.toString(); + } + } + return null; + } + + /** + * Provide a String representation of this validator. + * @return A String representation of this validator + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("RegexValidator{"); + for (int i = 0; i < patterns.length; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(patterns[i].pattern()); + } + buffer.append("}"); + return buffer.toString(); + } + +} diff --git a/src/test/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtilsTest.java b/src/test/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtilsTest.java new file mode 100644 index 000000000..3bb9e96c0 --- /dev/null +++ b/src/test/java/org/jenkinsci/remoting/org/apache/commons/net/util/SubnetUtilsTest.java @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jenkinsci.remoting.org.apache.commons.net.util; + +import org.jenkinsci.remoting.org.apache.commons.net.util.SubnetUtils.SubnetInfo; + +import junit.framework.TestCase; + +@SuppressWarnings("deprecation") // deliberate use of deprecated methods +public class SubnetUtilsTest extends TestCase { + + // TODO Lower address test + public void testAddresses() { + SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); + SubnetInfo info = utils.getInfo(); + assertTrue(info.isInRange("192.168.0.1")); + // We don't count the broadcast address as usable + assertFalse(info.isInRange("192.168.0.7")); + assertFalse(info.isInRange("192.168.0.8")); + assertFalse(info.isInRange("10.10.2.1")); + assertFalse(info.isInRange("192.168.1.1")); + assertFalse(info.isInRange("192.168.0.255")); + } + + /** + * Test using the inclusiveHostCount flag, which includes the network and broadcast addresses in host counts + */ + public void testCidrAddresses() { + SubnetUtils utils = new SubnetUtils("192.168.0.1/8"); + utils.setInclusiveHostCount(true); + SubnetInfo info = utils.getInfo(); + assertEquals("255.0.0.0", info.getNetmask()); + assertEquals(16777216, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/9"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.128.0.0", info.getNetmask()); + assertEquals(8388608, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/10"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.192.0.0", info.getNetmask()); + assertEquals(4194304, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/11"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.224.0.0", info.getNetmask()); + assertEquals(2097152, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/12"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.240.0.0", info.getNetmask()); + assertEquals(1048576, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/13"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.248.0.0", info.getNetmask()); + assertEquals(524288, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/14"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.252.0.0", info.getNetmask()); + assertEquals(262144, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/15"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.254.0.0", info.getNetmask()); + assertEquals(131072, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/16"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.0.0", info.getNetmask()); + assertEquals(65536, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/17"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.128.0", info.getNetmask()); + assertEquals(32768, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/18"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.192.0", info.getNetmask()); + assertEquals(16384, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/19"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.224.0", info.getNetmask()); + assertEquals(8192, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/20"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.240.0", info.getNetmask()); + assertEquals(4096, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/21"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.248.0", info.getNetmask()); + assertEquals(2048, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/22"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.252.0", info.getNetmask()); + assertEquals(1024, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/23"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.254.0", info.getNetmask()); + assertEquals(512, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/24"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.0", info.getNetmask()); + assertEquals(256, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/25"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.128", info.getNetmask()); + assertEquals(128, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/26"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.192", info.getNetmask()); + assertEquals(64, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/27"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.224", info.getNetmask()); + assertEquals(32, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/28"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.240", info.getNetmask()); + assertEquals(16, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/29"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.248", info.getNetmask()); + assertEquals(8, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/30"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.252", info.getNetmask()); + assertEquals(4, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/31"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.254", info.getNetmask()); + assertEquals(2, info.getAddressCount()); + + utils = new SubnetUtils("192.168.0.1/32"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("255.255.255.255", info.getNetmask()); + assertEquals(1, info.getAddressCount()); + + new SubnetUtils("192.168.0.1/1"); + } + + public void testInvalidMasks() { + try { + new SubnetUtils("192.168.0.1/33"); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // Ignored + } + } + + public void testNET428_31() throws Exception { + final SubnetUtils subnetUtils = new SubnetUtils("1.2.3.4/31"); + assertEquals(0, subnetUtils.getInfo().getAddressCount()); + String[] address = subnetUtils.getInfo().getAllAddresses(); + assertNotNull(address); + assertEquals(0, address.length); + } + + public void testNET428_32() throws Exception { + final SubnetUtils subnetUtils = new SubnetUtils("1.2.3.4/32"); + assertEquals(0, subnetUtils.getInfo().getAddressCount()); + String[] address = subnetUtils.getInfo().getAllAddresses(); + assertNotNull(address); + assertEquals(0, address.length); + } + + public void testParseSimpleNetmask() { + final String address = "192.168.0.1"; + final String masks[] = new String[] { "255.0.0.0", "255.255.0.0", "255.255.255.0", "255.255.255.248" }; + final String bcastAddresses[] = new String[] { "192.255.255.255", "192.168.255.255", "192.168.0.255", + "192.168.0.7" }; + final String lowAddresses[] = new String[] { "192.0.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1" }; + final String highAddresses[] = new String[] { "192.255.255.254", "192.168.255.254", "192.168.0.254", + "192.168.0.6" }; + final String networkAddresses[] = new String[] { "192.0.0.0", "192.168.0.0", "192.168.0.0", "192.168.0.0" }; + final String cidrSignatures[] = new String[] { "192.168.0.1/8", "192.168.0.1/16", "192.168.0.1/24", + "192.168.0.1/29" }; + final int usableAddresses[] = new int[] { 16777214, 65534, 254, 6 }; + + for (int i = 0; i < masks.length; ++i) { + SubnetUtils utils = new SubnetUtils(address, masks[i]); + SubnetInfo info = utils.getInfo(); + assertEquals(bcastAddresses[i], info.getBroadcastAddress()); + assertEquals(cidrSignatures[i], info.getCidrSignature()); + assertEquals(lowAddresses[i], info.getLowAddress()); + assertEquals(highAddresses[i], info.getHighAddress()); + assertEquals(networkAddresses[i], info.getNetworkAddress()); + assertEquals(usableAddresses[i], info.getAddressCount()); + } + } + + public void testParseSimpleNetmaskExclusive() { + String address = "192.168.15.7"; + String masks[] = new String[] { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; + String bcast[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + String netwk[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + String lowAd[] = new String[] { "192.168.15.5", "0.0.0.0", "0.0.0.0" }; + String highA[] = new String[] { "192.168.15.6", "0.0.0.0", "0.0.0.0" }; + String cidrS[] = new String[] { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; + int usableAd[] = new int[] { 2, 0, 0 }; + // low and high addresses don't exist + + for (int i = 0; i < masks.length; ++i) { + SubnetUtils utils = new SubnetUtils(address, masks[i]); + utils.setInclusiveHostCount(false); + SubnetInfo info = utils.getInfo(); + assertEquals("ci " + masks[i], cidrS[i], info.getCidrSignature()); + assertEquals("bc " + masks[i], bcast[i], info.getBroadcastAddress()); + assertEquals("nw " + masks[i], netwk[i], info.getNetworkAddress()); + assertEquals("ac " + masks[i], usableAd[i], info.getAddressCount()); + assertEquals("lo " + masks[i], lowAd[i], info.getLowAddress()); + assertEquals("hi " + masks[i], highA[i], info.getHighAddress()); + } + } + + public void testParseSimpleNetmaskInclusive() { + String address = "192.168.15.7"; + String masks[] = new String[] { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; + String bcast[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + String netwk[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + String lowAd[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + String highA[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + String cidrS[] = new String[] { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; + int usableAd[] = new int[] { 4, 2, 1 }; + + for (int i = 0; i < masks.length; ++i) { + SubnetUtils utils = new SubnetUtils(address, masks[i]); + utils.setInclusiveHostCount(true); + SubnetInfo info = utils.getInfo(); + assertEquals("ci " + masks[i], cidrS[i], info.getCidrSignature()); + assertEquals("bc " + masks[i], bcast[i], info.getBroadcastAddress()); + assertEquals("ac " + masks[i], usableAd[i], info.getAddressCount()); + assertEquals("nw " + masks[i], netwk[i], info.getNetworkAddress()); + assertEquals("lo " + masks[i], lowAd[i], info.getLowAddress()); + assertEquals("hi " + masks[i], highA[i], info.getHighAddress()); + } + } + + public void testZeroAddressAndCidr() { + new SubnetUtils("0.0.0.0/0"); + } + + public void testNET521() { + SubnetUtils utils; + SubnetInfo info; + + utils = new SubnetUtils("0.0.0.0/0"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("0.0.0.0", info.getNetmask()); + assertEquals(4294967296L, info.getAddressCountLong()); + try { + info.getAddressCount(); + fail("Expected RuntimeException"); + } catch (RuntimeException expected) { + // ignored + } + utils = new SubnetUtils("128.0.0.0/1"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("128.0.0.0", info.getNetmask()); + assertEquals(2147483648L, info.getAddressCountLong()); + try { + info.getAddressCount(); + fail("Expected RuntimeException"); + } catch (RuntimeException expected) { + // ignored + } + // if we exclude the broadcast and network addresses, the count is less than Integer.MAX_VALUE + utils.setInclusiveHostCount(false); + info = utils.getInfo(); + assertEquals(2147483646, info.getAddressCount()); + } + + public void testNET520() { + SubnetUtils utils = new SubnetUtils("0.0.0.0/0"); + utils.setInclusiveHostCount(true); + SubnetInfo info = utils.getInfo(); + assertEquals("0.0.0.0",info.getNetworkAddress()); + assertEquals("255.255.255.255",info.getBroadcastAddress()); + assertTrue(info.isInRange("127.0.0.0")); + utils.setInclusiveHostCount(false); + assertTrue(info.isInRange("127.0.0.0")); + } +} diff --git a/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidatorTest.java b/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidatorTest.java new file mode 100644 index 000000000..c2b14a415 --- /dev/null +++ b/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/InetAddressValidatorTest.java @@ -0,0 +1,604 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jenkinsci.remoting.org.apache.commons.validator.routines; + +import junit.framework.TestCase; + +/** + * Test cases for InetAddressValidator. + * + * @version $Revision$ + */ +public class InetAddressValidatorTest extends TestCase { + + private InetAddressValidator validator; + + /** + * Constructor. + * @param name + */ + public InetAddressValidatorTest(String name) { + super(name); + } + + @Override + protected void setUp() { + validator = new InetAddressValidator(); + } + + /** + * Test IPs that point to real, well-known hosts (without actually looking them up). + */ + public void testInetAddressesFromTheWild() { + assertTrue("www.apache.org IP should be valid", validator.isValid("140.211.11.130")); + assertTrue("www.l.google.com IP should be valid", validator.isValid("72.14.253.103")); + assertTrue("fsf.org IP should be valid", validator.isValid("199.232.41.5")); + assertTrue("appscs.ign.com IP should be valid", validator.isValid("216.35.123.87")); + } + + public void testVALIDATOR_335() { + assertTrue("2001:0438:FFFE:0000:0000:0000:0000:0A35 should be valid", validator.isValid("2001:0438:FFFE:0000:0000:0000:0000:0A35")); + } + + /** + * Test valid and invalid IPs from each address class. + */ + public void testInetAddressesByClass() { + assertTrue("class A IP should be valid", validator.isValid("24.25.231.12")); + assertFalse("illegal class A IP should be invalid", validator.isValid("2.41.32.324")); + + assertTrue("class B IP should be valid", validator.isValid("135.14.44.12")); + assertFalse("illegal class B IP should be invalid", validator.isValid("154.123.441.123")); + + assertTrue("class C IP should be valid", validator.isValid("213.25.224.32")); + assertFalse("illegal class C IP should be invalid", validator.isValid("201.543.23.11")); + + assertTrue("class D IP should be valid", validator.isValid("229.35.159.6")); + assertFalse("illegal class D IP should be invalid", validator.isValid("231.54.11.987")); + + assertTrue("class E IP should be valid", validator.isValid("248.85.24.92")); + assertFalse("illegal class E IP should be invalid", validator.isValid("250.21.323.48")); + } + + /** + * Test reserved IPs. + */ + public void testReservedInetAddresses() { + assertTrue("localhost IP should be valid", validator.isValid("127.0.0.1")); + assertTrue("broadcast IP should be valid", validator.isValid("255.255.255.255")); + } + + /** + * Test obviously broken IPs. + */ + public void testBrokenInetAddresses() { + assertFalse("IP with characters should be invalid", validator.isValid("124.14.32.abc")); + assertFalse("IP with leading zeroes should be invalid", validator.isValid("124.14.32.01")); + assertFalse("IP with three groups should be invalid", validator.isValid("23.64.12")); + assertFalse("IP with five groups should be invalid", validator.isValid("26.34.23.77.234")); + } + + /** + * Test IPv6 addresses. + *

These tests were ported from a + * Perl script.

+ * + */ + public void testIPv6() { + // The original Perl script contained a lot of duplicate tests. + // I removed the duplicates I noticed, but there may be more. + assertFalse("IPV6 empty string should be invalid", validator.isValidInet6Address(""));// empty string + assertTrue("IPV6 ::1 should be valid", validator.isValidInet6Address("::1"));// loopback, compressed, non-routable + assertTrue("IPV6 :: should be valid", validator.isValidInet6Address("::"));// unspecified, compressed, non-routable + assertTrue("IPV6 0:0:0:0:0:0:0:1 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:1"));// loopback, full + assertTrue("IPV6 0:0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0:0"));// unspecified, full + assertTrue("IPV6 2001:DB8:0:0:8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A"));// unicast, full + assertTrue("IPV6 FF01:0:0:0:0:0:0:101 should be valid", validator.isValidInet6Address("FF01:0:0:0:0:0:0:101"));// multicast, full + assertTrue("IPV6 2001:DB8::8:800:200C:417A should be valid", validator.isValidInet6Address("2001:DB8::8:800:200C:417A"));// unicast, compressed + assertTrue("IPV6 FF01::101 should be valid", validator.isValidInet6Address("FF01::101"));// multicast, compressed + assertFalse("IPV6 2001:DB8:0:0:8:800:200C:417A:221 should be invalid", validator.isValidInet6Address("2001:DB8:0:0:8:800:200C:417A:221"));// unicast, full + assertFalse("IPV6 FF01::101::2 should be invalid", validator.isValidInet6Address("FF01::101::2"));// multicast, compressed + assertTrue("IPV6 fe80::217:f2ff:fe07:ed62 should be valid", validator.isValidInet6Address("fe80::217:f2ff:fe07:ed62")); + assertTrue("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 should be valid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876")); + assertTrue("IPV6 3ffe:0b00:0000:0000:0001:0000:0000:000a should be valid", validator.isValidInet6Address("3ffe:0b00:0000:0000:0001:0000:0000:000a")); + assertTrue("IPV6 FF02:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0001")); + assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0001 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0001")); + assertTrue("IPV6 0000:0000:0000:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0000")); + assertFalse("IPV6 02001:0000:1234:0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("02001:0000:1234:0000:0000:C1C0:ABCD:0876")); // extra 0 not allowed! + assertFalse("IPV6 2001:0000:1234:0000:00001:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:00001:C1C0:ABCD:0876")); // extra 0 not allowed! + assertFalse("IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0 should be invalid", validator.isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876 0")); // junk after valid address + assertFalse("IPV6 2001:0000:1234: 0000:0000:C1C0:ABCD:0876 should be invalid", validator.isValidInet6Address("2001:0000:1234: 0000:0000:C1C0:ABCD:0876")); // internal space + assertFalse("IPV6 3ffe:0b00:0000:0001:0000:0000:000a should be invalid", validator.isValidInet6Address("3ffe:0b00:0000:0001:0000:0000:000a")); // seven segments + assertFalse("IPV6 FF02:0000:0000:0000:0000:0000:0000:0000:0001 should be invalid", validator.isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0000:0001")); // nine segments + assertFalse("IPV6 3ffe:b00::1::a should be invalid", validator.isValidInet6Address("3ffe:b00::1::a")); // double "::" + assertFalse("IPV6 ::1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address("::1111:2222:3333:4444:5555:6666::")); // double "::" + assertTrue("IPV6 2::10 should be valid", validator.isValidInet6Address("2::10")); + assertTrue("IPV6 ff02::1 should be valid", validator.isValidInet6Address("ff02::1")); + assertTrue("IPV6 fe80:: should be valid", validator.isValidInet6Address("fe80::")); + assertTrue("IPV6 2002:: should be valid", validator.isValidInet6Address("2002::")); + assertTrue("IPV6 2001:db8:: should be valid", validator.isValidInet6Address("2001:db8::")); + assertTrue("IPV6 2001:0db8:1234:: should be valid", validator.isValidInet6Address("2001:0db8:1234::")); + assertTrue("IPV6 ::ffff:0:0 should be valid", validator.isValidInet6Address("::ffff:0:0")); + assertTrue("IPV6 1:2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:7:8")); + assertTrue("IPV6 1:2:3:4:5:6::8 should be valid", validator.isValidInet6Address("1:2:3:4:5:6::8")); + assertTrue("IPV6 1:2:3:4:5::8 should be valid", validator.isValidInet6Address("1:2:3:4:5::8")); + assertTrue("IPV6 1:2:3:4::8 should be valid", validator.isValidInet6Address("1:2:3:4::8")); + assertTrue("IPV6 1:2:3::8 should be valid", validator.isValidInet6Address("1:2:3::8")); + assertTrue("IPV6 1:2::8 should be valid", validator.isValidInet6Address("1:2::8")); + assertTrue("IPV6 1::8 should be valid", validator.isValidInet6Address("1::8")); + assertTrue("IPV6 1::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("1::2:3:4:5:6:7")); + assertTrue("IPV6 1::2:3:4:5:6 should be valid", validator.isValidInet6Address("1::2:3:4:5:6")); + assertTrue("IPV6 1::2:3:4:5 should be valid", validator.isValidInet6Address("1::2:3:4:5")); + assertTrue("IPV6 1::2:3:4 should be valid", validator.isValidInet6Address("1::2:3:4")); + assertTrue("IPV6 1::2:3 should be valid", validator.isValidInet6Address("1::2:3")); + assertTrue("IPV6 ::2:3:4:5:6:7:8 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7:8")); + assertTrue("IPV6 ::2:3:4:5:6:7 should be valid", validator.isValidInet6Address("::2:3:4:5:6:7")); + assertTrue("IPV6 ::2:3:4:5:6 should be valid", validator.isValidInet6Address("::2:3:4:5:6")); + assertTrue("IPV6 ::2:3:4:5 should be valid", validator.isValidInet6Address("::2:3:4:5")); + assertTrue("IPV6 ::2:3:4 should be valid", validator.isValidInet6Address("::2:3:4")); + assertTrue("IPV6 ::2:3 should be valid", validator.isValidInet6Address("::2:3")); + assertTrue("IPV6 ::8 should be valid", validator.isValidInet6Address("::8")); + assertTrue("IPV6 1:2:3:4:5:6:: should be valid", validator.isValidInet6Address("1:2:3:4:5:6::")); + assertTrue("IPV6 1:2:3:4:5:: should be valid", validator.isValidInet6Address("1:2:3:4:5::")); + assertTrue("IPV6 1:2:3:4:: should be valid", validator.isValidInet6Address("1:2:3:4::")); + assertTrue("IPV6 1:2:3:: should be valid", validator.isValidInet6Address("1:2:3::")); + assertTrue("IPV6 1:2:: should be valid", validator.isValidInet6Address("1:2::")); + assertTrue("IPV6 1:: should be valid", validator.isValidInet6Address("1::")); + assertTrue("IPV6 1:2:3:4:5::7:8 should be valid", validator.isValidInet6Address("1:2:3:4:5::7:8")); + assertFalse("IPV6 1:2:3::4:5::7:8 should be invalid", validator.isValidInet6Address("1:2:3::4:5::7:8")); // Double "::" + assertFalse("IPV6 12345::6:7:8 should be invalid", validator.isValidInet6Address("12345::6:7:8")); + assertTrue("IPV6 1:2:3:4::7:8 should be valid", validator.isValidInet6Address("1:2:3:4::7:8")); + assertTrue("IPV6 1:2:3::7:8 should be valid", validator.isValidInet6Address("1:2:3::7:8")); + assertTrue("IPV6 1:2::7:8 should be valid", validator.isValidInet6Address("1:2::7:8")); + assertTrue("IPV6 1::7:8 should be valid", validator.isValidInet6Address("1::7:8")); + // IPv4 addresses as dotted-quads + assertTrue("IPV6 1:2:3:4:5:6:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5:6:1.2.3.4")); + assertTrue("IPV6 1:2:3:4:5::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4:5::1.2.3.4")); + assertTrue("IPV6 1:2:3:4::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::1.2.3.4")); + assertTrue("IPV6 1:2:3::1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::1.2.3.4")); + assertTrue("IPV6 1:2::1.2.3.4 should be valid", validator.isValidInet6Address("1:2::1.2.3.4")); + assertTrue("IPV6 1::1.2.3.4 should be valid", validator.isValidInet6Address("1::1.2.3.4")); + assertTrue("IPV6 1:2:3:4::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3:4::5:1.2.3.4")); + assertTrue("IPV6 1:2:3::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2:3::5:1.2.3.4")); + assertTrue("IPV6 1:2::5:1.2.3.4 should be valid", validator.isValidInet6Address("1:2::5:1.2.3.4")); + assertTrue("IPV6 1::5:1.2.3.4 should be valid", validator.isValidInet6Address("1::5:1.2.3.4")); + assertTrue("IPV6 1::5:11.22.33.44 should be valid", validator.isValidInet6Address("1::5:11.22.33.44")); + assertFalse("IPV6 1::5:400.2.3.4 should be invalid", validator.isValidInet6Address("1::5:400.2.3.4")); + assertFalse("IPV6 1::5:260.2.3.4 should be invalid", validator.isValidInet6Address("1::5:260.2.3.4")); + assertFalse("IPV6 1::5:256.2.3.4 should be invalid", validator.isValidInet6Address("1::5:256.2.3.4")); + assertFalse("IPV6 1::5:1.256.3.4 should be invalid", validator.isValidInet6Address("1::5:1.256.3.4")); + assertFalse("IPV6 1::5:1.2.256.4 should be invalid", validator.isValidInet6Address("1::5:1.2.256.4")); + assertFalse("IPV6 1::5:1.2.3.256 should be invalid", validator.isValidInet6Address("1::5:1.2.3.256")); + assertFalse("IPV6 1::5:300.2.3.4 should be invalid", validator.isValidInet6Address("1::5:300.2.3.4")); + assertFalse("IPV6 1::5:1.300.3.4 should be invalid", validator.isValidInet6Address("1::5:1.300.3.4")); + assertFalse("IPV6 1::5:1.2.300.4 should be invalid", validator.isValidInet6Address("1::5:1.2.300.4")); + assertFalse("IPV6 1::5:1.2.3.300 should be invalid", validator.isValidInet6Address("1::5:1.2.3.300")); + assertFalse("IPV6 1::5:900.2.3.4 should be invalid", validator.isValidInet6Address("1::5:900.2.3.4")); + assertFalse("IPV6 1::5:1.900.3.4 should be invalid", validator.isValidInet6Address("1::5:1.900.3.4")); + assertFalse("IPV6 1::5:1.2.900.4 should be invalid", validator.isValidInet6Address("1::5:1.2.900.4")); + assertFalse("IPV6 1::5:1.2.3.900 should be invalid", validator.isValidInet6Address("1::5:1.2.3.900")); + assertFalse("IPV6 1::5:300.300.300.300 should be invalid", validator.isValidInet6Address("1::5:300.300.300.300")); + assertFalse("IPV6 1::5:3000.30.30.30 should be invalid", validator.isValidInet6Address("1::5:3000.30.30.30")); + assertFalse("IPV6 1::400.2.3.4 should be invalid", validator.isValidInet6Address("1::400.2.3.4")); + assertFalse("IPV6 1::260.2.3.4 should be invalid", validator.isValidInet6Address("1::260.2.3.4")); + assertFalse("IPV6 1::256.2.3.4 should be invalid", validator.isValidInet6Address("1::256.2.3.4")); + assertFalse("IPV6 1::1.256.3.4 should be invalid", validator.isValidInet6Address("1::1.256.3.4")); + assertFalse("IPV6 1::1.2.256.4 should be invalid", validator.isValidInet6Address("1::1.2.256.4")); + assertFalse("IPV6 1::1.2.3.256 should be invalid", validator.isValidInet6Address("1::1.2.3.256")); + assertFalse("IPV6 1::300.2.3.4 should be invalid", validator.isValidInet6Address("1::300.2.3.4")); + assertFalse("IPV6 1::1.300.3.4 should be invalid", validator.isValidInet6Address("1::1.300.3.4")); + assertFalse("IPV6 1::1.2.300.4 should be invalid", validator.isValidInet6Address("1::1.2.300.4")); + assertFalse("IPV6 1::1.2.3.300 should be invalid", validator.isValidInet6Address("1::1.2.3.300")); + assertFalse("IPV6 1::900.2.3.4 should be invalid", validator.isValidInet6Address("1::900.2.3.4")); + assertFalse("IPV6 1::1.900.3.4 should be invalid", validator.isValidInet6Address("1::1.900.3.4")); + assertFalse("IPV6 1::1.2.900.4 should be invalid", validator.isValidInet6Address("1::1.2.900.4")); + assertFalse("IPV6 1::1.2.3.900 should be invalid", validator.isValidInet6Address("1::1.2.3.900")); + assertFalse("IPV6 1::300.300.300.300 should be invalid", validator.isValidInet6Address("1::300.300.300.300")); + assertFalse("IPV6 1::3000.30.30.30 should be invalid", validator.isValidInet6Address("1::3000.30.30.30")); + assertFalse("IPV6 ::400.2.3.4 should be invalid", validator.isValidInet6Address("::400.2.3.4")); + assertFalse("IPV6 ::260.2.3.4 should be invalid", validator.isValidInet6Address("::260.2.3.4")); + assertFalse("IPV6 ::256.2.3.4 should be invalid", validator.isValidInet6Address("::256.2.3.4")); + assertFalse("IPV6 ::1.256.3.4 should be invalid", validator.isValidInet6Address("::1.256.3.4")); + assertFalse("IPV6 ::1.2.256.4 should be invalid", validator.isValidInet6Address("::1.2.256.4")); + assertFalse("IPV6 ::1.2.3.256 should be invalid", validator.isValidInet6Address("::1.2.3.256")); + assertFalse("IPV6 ::300.2.3.4 should be invalid", validator.isValidInet6Address("::300.2.3.4")); + assertFalse("IPV6 ::1.300.3.4 should be invalid", validator.isValidInet6Address("::1.300.3.4")); + assertFalse("IPV6 ::1.2.300.4 should be invalid", validator.isValidInet6Address("::1.2.300.4")); + assertFalse("IPV6 ::1.2.3.300 should be invalid", validator.isValidInet6Address("::1.2.3.300")); + assertFalse("IPV6 ::900.2.3.4 should be invalid", validator.isValidInet6Address("::900.2.3.4")); + assertFalse("IPV6 ::1.900.3.4 should be invalid", validator.isValidInet6Address("::1.900.3.4")); + assertFalse("IPV6 ::1.2.900.4 should be invalid", validator.isValidInet6Address("::1.2.900.4")); + assertFalse("IPV6 ::1.2.3.900 should be invalid", validator.isValidInet6Address("::1.2.3.900")); + assertFalse("IPV6 ::300.300.300.300 should be invalid", validator.isValidInet6Address("::300.300.300.300")); + assertFalse("IPV6 ::3000.30.30.30 should be invalid", validator.isValidInet6Address("::3000.30.30.30")); + assertTrue("IPV6 fe80::217:f2ff:254.7.237.98 should be valid", validator.isValidInet6Address("fe80::217:f2ff:254.7.237.98")); + assertTrue("IPV6 ::ffff:192.168.1.26 should be valid", validator.isValidInet6Address("::ffff:192.168.1.26")); + assertFalse("IPV6 2001:1:1:1:1:1:255Z255X255Y255 should be invalid", validator.isValidInet6Address("2001:1:1:1:1:1:255Z255X255Y255")); // garbage instead of "." in IPv4 + assertFalse("IPV6 ::ffff:192x168.1.26 should be invalid", validator.isValidInet6Address("::ffff:192x168.1.26")); // ditto + assertTrue("IPV6 ::ffff:192.168.1.1 should be valid", validator.isValidInet6Address("::ffff:192.168.1.1")); + assertTrue("IPV6 0:0:0:0:0:0:13.1.68.3 should be valid", validator.isValidInet6Address("0:0:0:0:0:0:13.1.68.3"));// IPv4-compatible IPv6 address, full, deprecated + assertTrue("IPV6 0:0:0:0:0:FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("0:0:0:0:0:FFFF:129.144.52.38"));// IPv4-mapped IPv6 address, full + assertTrue("IPV6 ::13.1.68.3 should be valid", validator.isValidInet6Address("::13.1.68.3"));// IPv4-compatible IPv6 address, compressed, deprecated + assertTrue("IPV6 ::FFFF:129.144.52.38 should be valid", validator.isValidInet6Address("::FFFF:129.144.52.38"));// IPv4-mapped IPv6 address, compressed + assertTrue("IPV6 fe80:0:0:0:204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:254.157.241.86")); + assertTrue("IPV6 fe80::204:61ff:254.157.241.86 should be valid", validator.isValidInet6Address("fe80::204:61ff:254.157.241.86")); + assertTrue("IPV6 ::ffff:12.34.56.78 should be valid", validator.isValidInet6Address("::ffff:12.34.56.78")); + assertFalse("IPV6 ::ffff:2.3.4 should be invalid", validator.isValidInet6Address("::ffff:2.3.4")); + assertFalse("IPV6 ::ffff:257.1.2.3 should be invalid", validator.isValidInet6Address("::ffff:257.1.2.3")); + assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4")); + assertFalse("IPV6 1.2.3.4:1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333:4444::5555")); + assertFalse("IPV6 1.2.3.4:1111:2222:3333::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222:3333::5555")); + assertFalse("IPV6 1.2.3.4:1111:2222::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111:2222::5555")); + assertFalse("IPV6 1.2.3.4:1111::5555 should be invalid", validator.isValidInet6Address("1.2.3.4:1111::5555")); + assertFalse("IPV6 1.2.3.4::5555 should be invalid", validator.isValidInet6Address("1.2.3.4::5555")); + assertFalse("IPV6 1.2.3.4:: should be invalid", validator.isValidInet6Address("1.2.3.4::")); + // Testing IPv4 addresses represented as dotted-quads + // Leading zeroes in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an octal number + // Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero + assertFalse("IPV6 fe80:0000:0000:0000:0204:61ff:254.157.241.086 should be invalid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:254.157.241.086")); + assertTrue("IPV6 ::ffff:192.0.2.128 should be valid", validator.isValidInet6Address("::ffff:192.0.2.128")); // but this is OK, since there's a single digit + assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4 should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:00.00.00.00 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:00.00.00.00")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:000.000.000.000 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:000.000.000.000")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:256.256.256.256 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:256.256.256.256")); + assertTrue("IPV6 fe80:0000:0000:0000:0204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0000:0000:0000:0204:61ff:fe9d:f156")); + assertTrue("IPV6 fe80:0:0:0:204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80:0:0:0:204:61ff:fe9d:f156")); + assertTrue("IPV6 fe80::204:61ff:fe9d:f156 should be valid", validator.isValidInet6Address("fe80::204:61ff:fe9d:f156")); + assertFalse("IPV6 : should be invalid", validator.isValidInet6Address(":")); + assertTrue("IPV6 ::ffff:c000:280 should be valid", validator.isValidInet6Address("::ffff:c000:280")); + assertFalse("IPV6 1111:2222:3333:4444::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::5555:")); + assertFalse("IPV6 1111:2222:3333::5555: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:")); + assertFalse("IPV6 1111:2222::5555: should be invalid", validator.isValidInet6Address("1111:2222::5555:")); + assertFalse("IPV6 1111::5555: should be invalid", validator.isValidInet6Address("1111::5555:")); + assertFalse("IPV6 ::5555: should be invalid", validator.isValidInet6Address("::5555:")); + assertFalse("IPV6 ::: should be invalid", validator.isValidInet6Address(":::")); + assertFalse("IPV6 1111: should be invalid", validator.isValidInet6Address("1111:")); + assertFalse("IPV6 :1111:2222:3333:4444::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::5555")); + assertFalse("IPV6 :1111:2222:3333::5555 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555")); + assertFalse("IPV6 :1111:2222::5555 should be invalid", validator.isValidInet6Address(":1111:2222::5555")); + assertFalse("IPV6 :1111::5555 should be invalid", validator.isValidInet6Address(":1111::5555")); + assertFalse("IPV6 :::5555 should be invalid", validator.isValidInet6Address(":::5555")); + assertTrue("IPV6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 should be valid", validator.isValidInet6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); + assertTrue("IPV6 2001:db8:85a3:0:0:8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3:0:0:8a2e:370:7334")); + assertTrue("IPV6 2001:db8:85a3::8a2e:370:7334 should be valid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370:7334")); + assertTrue("IPV6 2001:0db8:0000:0000:0000:0000:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000:0000:1428:57ab")); + assertTrue("IPV6 2001:0db8:0000:0000:0000::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0000:0000:0000::1428:57ab")); + assertTrue("IPV6 2001:0db8:0:0:0:0:1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0:0:0:1428:57ab")); + assertTrue("IPV6 2001:0db8:0:0::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8:0:0::1428:57ab")); + assertTrue("IPV6 2001:0db8::1428:57ab should be valid", validator.isValidInet6Address("2001:0db8::1428:57ab")); + assertTrue("IPV6 2001:db8::1428:57ab should be valid", validator.isValidInet6Address("2001:db8::1428:57ab")); + assertTrue("IPV6 ::ffff:0c22:384e should be valid", validator.isValidInet6Address("::ffff:0c22:384e")); + assertTrue("IPV6 2001:0db8:1234:0000:0000:0000:0000:0000 should be valid", validator.isValidInet6Address("2001:0db8:1234:0000:0000:0000:0000:0000")); + assertTrue("IPV6 2001:0db8:1234:ffff:ffff:ffff:ffff:ffff should be valid", validator.isValidInet6Address("2001:0db8:1234:ffff:ffff:ffff:ffff:ffff")); + assertTrue("IPV6 2001:db8:a::123 should be valid", validator.isValidInet6Address("2001:db8:a::123")); + assertFalse("IPV6 123 should be invalid", validator.isValidInet6Address("123")); + assertFalse("IPV6 ldkfj should be invalid", validator.isValidInet6Address("ldkfj")); + assertFalse("IPV6 2001::FFD3::57ab should be invalid", validator.isValidInet6Address("2001::FFD3::57ab")); + assertFalse("IPV6 2001:db8:85a3::8a2e:37023:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:37023:7334")); + assertFalse("IPV6 2001:db8:85a3::8a2e:370k:7334 should be invalid", validator.isValidInet6Address("2001:db8:85a3::8a2e:370k:7334")); + assertFalse("IPV6 1:2:3:4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3:4:5:6:7:8:9")); + assertFalse("IPV6 1::2::3 should be invalid", validator.isValidInet6Address("1::2::3")); + assertFalse("IPV6 1:::3:4:5 should be invalid", validator.isValidInet6Address("1:::3:4:5")); + assertFalse("IPV6 1:2:3::4:5:6:7:8:9 should be invalid", validator.isValidInet6Address("1:2:3::4:5:6:7:8:9")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:7777:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::")); + assertTrue("IPV6 1111:2222:3333:4444:: should be valid", validator.isValidInet6Address("1111:2222:3333:4444::")); + assertTrue("IPV6 1111:2222:3333:: should be valid", validator.isValidInet6Address("1111:2222:3333::")); + assertTrue("IPV6 1111:2222:: should be valid", validator.isValidInet6Address("1111:2222::")); + assertTrue("IPV6 1111:: should be valid", validator.isValidInet6Address("1111::")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888")); + assertTrue("IPV6 1111:2222:3333:4444::8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::8888")); + assertTrue("IPV6 1111:2222:3333::8888 should be valid", validator.isValidInet6Address("1111:2222:3333::8888")); + assertTrue("IPV6 1111:2222::8888 should be valid", validator.isValidInet6Address("1111:2222::8888")); + assertTrue("IPV6 1111::8888 should be valid", validator.isValidInet6Address("1111::8888")); + assertTrue("IPV6 ::8888 should be valid", validator.isValidInet6Address("::8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888")); + assertTrue("IPV6 1111:2222:3333::7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::7777:8888")); + assertTrue("IPV6 1111:2222::7777:8888 should be valid", validator.isValidInet6Address("1111:2222::7777:8888")); + assertTrue("IPV6 1111::7777:8888 should be valid", validator.isValidInet6Address("1111::7777:8888")); + assertTrue("IPV6 ::7777:8888 should be valid", validator.isValidInet6Address("::7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888")); + assertTrue("IPV6 1111:2222::6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::6666:7777:8888")); + assertTrue("IPV6 1111::6666:7777:8888 should be valid", validator.isValidInet6Address("1111::6666:7777:8888")); + assertTrue("IPV6 ::6666:7777:8888 should be valid", validator.isValidInet6Address("::6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888")); + assertTrue("IPV6 1111::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::5555:6666:7777:8888")); + assertTrue("IPV6 ::5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 ::2222:3333:4444:5555:6666:7777:8888 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888")); + assertTrue("IPV6 1111:2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444:5555::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444:5555::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::123.123.123.123")); + assertTrue("IPV6 1111:2222::123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::123.123.123.123")); + assertTrue("IPV6 1111::123.123.123.123 should be valid", validator.isValidInet6Address("1111::123.123.123.123")); + assertTrue("IPV6 ::123.123.123.123 should be valid", validator.isValidInet6Address("::123.123.123.123")); + assertTrue("IPV6 1111:2222:3333:4444::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333:4444::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::6666:123.123.123.123")); + assertTrue("IPV6 1111::6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::6666:123.123.123.123")); + assertTrue("IPV6 ::6666:123.123.123.123 should be valid", validator.isValidInet6Address("::6666:123.123.123.123")); + assertTrue("IPV6 1111:2222:3333::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222:3333::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::5555:6666:123.123.123.123")); + assertTrue("IPV6 ::5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::5555:6666:123.123.123.123")); + assertTrue("IPV6 1111:2222::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111:2222::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 ::4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 1111::3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("1111::3333:4444:5555:6666:123.123.123.123")); + assertTrue("IPV6 ::2222:3333:4444:5555:6666:123.123.123.123 should be valid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:123.123.123.123")); + // Trying combinations of "0" and "::" + // These are all syntactically correct, but are bad form + // because "0" adjacent to "::" should be combined into "::" + assertTrue("IPV6 ::0:0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0:0")); + assertTrue("IPV6 ::0:0:0:0 should be valid", validator.isValidInet6Address("::0:0:0:0")); + assertTrue("IPV6 ::0:0:0 should be valid", validator.isValidInet6Address("::0:0:0")); + assertTrue("IPV6 ::0:0 should be valid", validator.isValidInet6Address("::0:0")); + assertTrue("IPV6 ::0 should be valid", validator.isValidInet6Address("::0")); + assertTrue("IPV6 0:0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0:0::")); + assertTrue("IPV6 0:0:0:0:: should be valid", validator.isValidInet6Address("0:0:0:0::")); + assertTrue("IPV6 0:0:0:: should be valid", validator.isValidInet6Address("0:0:0::")); + assertTrue("IPV6 0:0:: should be valid", validator.isValidInet6Address("0:0::")); + assertTrue("IPV6 0:: should be valid", validator.isValidInet6Address("0::")); + // Invalid data + assertFalse("IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX should be invalid", validator.isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX")); + // Too many components + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:9999")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888::")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888:9999 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:9999")); + // Too few components + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666")); + assertFalse("IPV6 1111:2222:3333:4444:5555 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555")); + assertFalse("IPV6 1111:2222:3333:4444 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444")); + assertFalse("IPV6 1111:2222:3333 should be invalid", validator.isValidInet6Address("1111:2222:3333")); + assertFalse("IPV6 1111:2222 should be invalid", validator.isValidInet6Address("1111:2222")); + assertFalse("IPV6 1111 should be invalid", validator.isValidInet6Address("1111")); + // Missing : + assertFalse("IPV6 11112222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:22223333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:33334444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:44445555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:55556666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:66667777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66667777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:77778888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:77778888")); + // Missing : intended for :: + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:")); + assertFalse("IPV6 1111:2222:3333:4444:5555: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:")); + assertFalse("IPV6 1111:2222:3333:4444: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:")); + assertFalse("IPV6 1111:2222:3333: should be invalid", validator.isValidInet6Address("1111:2222:3333:")); + assertFalse("IPV6 1111:2222: should be invalid", validator.isValidInet6Address("1111:2222:")); + assertFalse("IPV6 :8888 should be invalid", validator.isValidInet6Address(":8888")); + assertFalse("IPV6 :7777:8888 should be invalid", validator.isValidInet6Address(":7777:8888")); + assertFalse("IPV6 :6666:7777:8888 should be invalid", validator.isValidInet6Address(":6666:7777:8888")); + assertFalse("IPV6 :5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":5555:6666:7777:8888")); + assertFalse("IPV6 :4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":4444:5555:6666:7777:8888")); + assertFalse("IPV6 :3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777:8888")); + // ::: + assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::7777:8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::8888")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::")); + // Double :: + assertFalse("IPV6 ::2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:7777:8888")); + assertFalse("IPV6 ::2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:7777:8888")); + assertFalse("IPV6 ::2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:7777:8888")); + assertFalse("IPV6 ::2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::7777:8888")); + assertFalse("IPV6 ::2222:3333:4444:5555:7777::8888 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777::8888")); + assertFalse("IPV6 ::2222:3333:4444:5555:7777:8888:: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:7777:8888::")); + assertFalse("IPV6 1111::3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:7777:8888")); + assertFalse("IPV6 1111::3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:7777:8888")); + assertFalse("IPV6 1111::3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::7777:8888")); + assertFalse("IPV6 1111::3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666::8888")); + assertFalse("IPV6 1111::3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777::")); + assertFalse("IPV6 1111:2222::4444::6666:7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:7777:8888")); + assertFalse("IPV6 1111:2222::4444:5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::7777:8888")); + assertFalse("IPV6 1111:2222::4444:5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666::8888")); + assertFalse("IPV6 1111:2222::4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777::")); + assertFalse("IPV6 1111:2222:3333::5555::7777:8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::7777:8888")); + assertFalse("IPV6 1111:2222:3333::5555:6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666::8888")); + assertFalse("IPV6 1111:2222:3333::5555:6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777::")); + assertFalse("IPV6 1111:2222:3333:4444::6666::8888 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666::8888")); + assertFalse("IPV6 1111:2222:3333:4444::6666:7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777::")); + assertFalse("IPV6 1111:2222:3333:4444:5555::7777:: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777::")); + // Too many components" + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:1.2.3.4.5 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:1.2.3.4.5")); + // Too few components + assertFalse("IPV6 1111:2222:3333:4444:5555:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:1.2.3.4")); + assertFalse("IPV6 1111:2222:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:1.2.3.4")); + assertFalse("IPV6 1111:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:1.2.3.4")); + assertFalse("IPV6 1.2.3.4 should be invalid", validator.isValidInet6Address("1.2.3.4")); + // Missing : + assertFalse("IPV6 11112222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("11112222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:22223333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:22223333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:33334444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:33334444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:44445555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:44445555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:55556666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:55556666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:66661.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:66661.2.3.4")); + // Missing . + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255255.255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255255.255.255")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255255.255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255255.255")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:255.255.255255 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:255.255.255255")); + // Missing : intended for :: + assertFalse("IPV6 :1.2.3.4 should be invalid", validator.isValidInet6Address(":1.2.3.4")); + assertFalse("IPV6 :6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":6666:1.2.3.4")); + assertFalse("IPV6 :5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":5555:6666:1.2.3.4")); + assertFalse("IPV6 :4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4")); + // ::: + assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:::3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:::5555:6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::6666:1.2.3.4")); + assertFalse("IPV6 1111:2222:3333:4444:5555:::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::1.2.3.4")); + // Double :: + assertFalse("IPV6 ::2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 ::2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 1111::3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 1111::3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 1111::3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111::3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 1111:2222::4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444::6666:1.2.3.4")); + assertFalse("IPV6 1111:2222::4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222::4444:5555::1.2.3.4")); + assertFalse("IPV6 1111:2222:3333::5555::1.2.3.4 should be invalid", validator.isValidInet6Address("1111:2222:3333::5555::1.2.3.4")); + // Missing parts + assertFalse("IPV6 ::. should be invalid", validator.isValidInet6Address("::.")); + assertFalse("IPV6 ::.. should be invalid", validator.isValidInet6Address("::..")); + assertFalse("IPV6 ::... should be invalid", validator.isValidInet6Address("::...")); + assertFalse("IPV6 ::1... should be invalid", validator.isValidInet6Address("::1...")); + assertFalse("IPV6 ::1.2.. should be invalid", validator.isValidInet6Address("::1.2..")); + assertFalse("IPV6 ::1.2.3. should be invalid", validator.isValidInet6Address("::1.2.3.")); + assertFalse("IPV6 ::.2.. should be invalid", validator.isValidInet6Address("::.2..")); + assertFalse("IPV6 ::.2.3. should be invalid", validator.isValidInet6Address("::.2.3.")); + assertFalse("IPV6 ::.2.3.4 should be invalid", validator.isValidInet6Address("::.2.3.4")); + assertFalse("IPV6 ::..3. should be invalid", validator.isValidInet6Address("::..3.")); + assertFalse("IPV6 ::..3.4 should be invalid", validator.isValidInet6Address("::..3.4")); + assertFalse("IPV6 ::...4 should be invalid", validator.isValidInet6Address("::...4")); + // Extra : in front + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:7777:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:7777::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::")); + assertFalse("IPV6 :1111:2222:3333:4444:: should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::")); + assertFalse("IPV6 :1111:2222:3333:: should be invalid", validator.isValidInet6Address(":1111:2222:3333::")); + assertFalse("IPV6 :1111:2222:: should be invalid", validator.isValidInet6Address(":1111:2222::")); + assertFalse("IPV6 :1111:: should be invalid", validator.isValidInet6Address(":1111::")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666::8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::8888")); + assertFalse("IPV6 :1111:2222:3333:4444::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::8888")); + assertFalse("IPV6 :1111:2222:3333::8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::8888")); + assertFalse("IPV6 :1111:2222::8888 should be invalid", validator.isValidInet6Address(":1111:2222::8888")); + assertFalse("IPV6 :1111::8888 should be invalid", validator.isValidInet6Address(":1111::8888")); + assertFalse("IPV6 :::8888 should be invalid", validator.isValidInet6Address(":::8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::7777:8888")); + assertFalse("IPV6 :1111:2222:3333::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::7777:8888")); + assertFalse("IPV6 :1111:2222::7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::7777:8888")); + assertFalse("IPV6 :1111::7777:8888 should be invalid", validator.isValidInet6Address(":1111::7777:8888")); + assertFalse("IPV6 :::7777:8888 should be invalid", validator.isValidInet6Address(":::7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:7777:8888")); + assertFalse("IPV6 :1111:2222::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::6666:7777:8888")); + assertFalse("IPV6 :1111::6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::6666:7777:8888")); + assertFalse("IPV6 :::6666:7777:8888 should be invalid", validator.isValidInet6Address(":::6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:7777:8888")); + assertFalse("IPV6 :1111::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::5555:6666:7777:8888")); + assertFalse("IPV6 :::5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :::2222:3333:4444:5555:6666:7777:8888 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:7777:8888")); + assertFalse("IPV6 :1111:2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444:5555::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444:5555::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::1.2.3.4")); + assertFalse("IPV6 :1111:2222::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::1.2.3.4")); + assertFalse("IPV6 :1111::1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::1.2.3.4")); + assertFalse("IPV6 :::1.2.3.4 should be invalid", validator.isValidInet6Address(":::1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333:4444::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333:4444::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::6666:1.2.3.4")); + assertFalse("IPV6 :1111::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::6666:1.2.3.4")); + assertFalse("IPV6 :::6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222:3333::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222:3333::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::5555:6666:1.2.3.4")); + assertFalse("IPV6 :::5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111:2222::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111:2222::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :::4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :1111::3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":1111::3333:4444:5555:6666:1.2.3.4")); + assertFalse("IPV6 :::2222:3333:4444:5555:6666:1.2.3.4 should be invalid", validator.isValidInet6Address(":::2222:3333:4444:5555:6666:1.2.3.4")); + // Extra : at end + assertFalse("IPV6 1111:2222:3333:4444:5555:6666:7777::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:::")); + assertFalse("IPV6 1111:2222:3333:4444::: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:::")); + assertFalse("IPV6 1111:2222:3333::: should be invalid", validator.isValidInet6Address("1111:2222:3333:::")); + assertFalse("IPV6 1111:2222::: should be invalid", validator.isValidInet6Address("1111:2222:::")); + assertFalse("IPV6 1111::: should be invalid", validator.isValidInet6Address("1111:::")); + assertFalse("IPV6 1111:2222:3333:4444:5555:6666::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555:6666::8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::8888:")); + assertFalse("IPV6 1111:2222:3333:4444::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::8888:")); + assertFalse("IPV6 1111:2222:3333::8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::8888:")); + assertFalse("IPV6 1111:2222::8888: should be invalid", validator.isValidInet6Address("1111:2222::8888:")); + assertFalse("IPV6 1111::8888: should be invalid", validator.isValidInet6Address("1111::8888:")); + assertFalse("IPV6 ::8888: should be invalid", validator.isValidInet6Address("::8888:")); + assertFalse("IPV6 1111:2222:3333:4444:5555::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444:5555::7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::7777:8888:")); + assertFalse("IPV6 1111:2222:3333::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::7777:8888:")); + assertFalse("IPV6 1111:2222::7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::7777:8888:")); + assertFalse("IPV6 1111::7777:8888: should be invalid", validator.isValidInet6Address("1111::7777:8888:")); + assertFalse("IPV6 ::7777:8888: should be invalid", validator.isValidInet6Address("::7777:8888:")); + assertFalse("IPV6 1111:2222:3333:4444::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333:4444::6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::6666:7777:8888:")); + assertFalse("IPV6 1111:2222::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::6666:7777:8888:")); + assertFalse("IPV6 1111::6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::6666:7777:8888:")); + assertFalse("IPV6 ::6666:7777:8888: should be invalid", validator.isValidInet6Address("::6666:7777:8888:")); + assertFalse("IPV6 1111:2222:3333::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222:3333::5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::5555:6666:7777:8888:")); + assertFalse("IPV6 1111::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::5555:6666:7777:8888:")); + assertFalse("IPV6 ::5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::5555:6666:7777:8888:")); + assertFalse("IPV6 1111:2222::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111:2222::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::4444:5555:6666:7777:8888:")); + assertFalse("IPV6 1111::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("1111::3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::3333:4444:5555:6666:7777:8888:")); + assertFalse("IPV6 ::2222:3333:4444:5555:6666:7777:8888: should be invalid", validator.isValidInet6Address("::2222:3333:4444:5555:6666:7777:8888:")); + assertTrue("IPV6 0:a:b:c:d:e:f:: should be valid", validator.isValidInet6Address("0:a:b:c:d:e:f::")); + assertTrue("IPV6 ::0:a:b:c:d:e:f should be valid", validator.isValidInet6Address("::0:a:b:c:d:e:f")); // syntactically correct, but bad form (::0:... could be combined) + assertTrue("IPV6 a:b:c:d:e:f:0:: should be valid", validator.isValidInet6Address("a:b:c:d:e:f:0::")); + assertFalse("IPV6 ':10.0.0.1 should be invalid", validator.isValidInet6Address("':10.0.0.1")); + } +} + + diff --git a/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidatorTest.java b/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidatorTest.java new file mode 100644 index 000000000..3c0fbc60e --- /dev/null +++ b/src/test/java/org/jenkinsci/remoting/org/apache/commons/validator/routines/RegexValidatorTest.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jenkinsci.remoting.org.apache.commons.validator.routines; + +import java.util.regex.PatternSyntaxException; + +import junit.framework.TestCase; + +/** + * Test Case for RegexValidatorTest. + * + * @version $Revision$ + * @since Validator 1.4 + */ +public class RegexValidatorTest extends TestCase { + + private static final String REGEX = "^([abc]*)(?:\\-)([DEF]*)(?:\\-)([123]*)$"; + + private static final String COMPONENT_1 = "([abc]{3})"; + private static final String COMPONENT_2 = "([DEF]{3})"; + private static final String COMPONENT_3 = "([123]{3})"; + private static final String SEPARATOR_1 = "(?:\\-)"; + private static final String SEPARATOR_2 = "(?:\\s)"; + private static final String REGEX_1 = "^" + COMPONENT_1 + SEPARATOR_1 + COMPONENT_2 + SEPARATOR_1 + COMPONENT_3 + "$"; + private static final String REGEX_2 = "^" + COMPONENT_1 + SEPARATOR_2 + COMPONENT_2 + SEPARATOR_2 + COMPONENT_3 + "$"; + private static final String REGEX_3 = "^" + COMPONENT_1 + COMPONENT_2 + COMPONENT_3 + "$"; + private static final String[] MULTIPLE_REGEX = new String[] {REGEX_1, REGEX_2, REGEX_3}; + + /** + * Constrct a new test case. + * @param name The name of the test + */ + public RegexValidatorTest(String name) { + super(name); + } + + /** + * Set Up. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * Tear Down. + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Test instance methods with single regular expression. + */ + public void testSingle() { + RegexValidator sensitive = new RegexValidator(REGEX); + RegexValidator insensitive = new RegexValidator(REGEX, false); + + // isValid() + assertEquals("Sensitive isValid() valid", true, sensitive.isValid("ac-DE-1")); + assertEquals("Sensitive isValid() invalid", false, sensitive.isValid("AB-de-1")); + assertEquals("Insensitive isValid() valid", true, insensitive.isValid("AB-de-1")); + assertEquals("Insensitive isValid() invalid", false, insensitive.isValid("ABd-de-1")); + + // validate() + assertEquals("Sensitive validate() valid", "acDE1", sensitive.validate("ac-DE-1")); + assertEquals("Sensitive validate() invalid", null, sensitive.validate("AB-de-1")); + assertEquals("Insensitive validate() valid", "ABde1", insensitive.validate("AB-de-1")); + assertEquals("Insensitive validate() invalid", null, insensitive.validate("ABd-de-1")); + + // match() + checkArray("Sensitive match() valid", new String[] {"ac", "DE", "1"}, sensitive.match("ac-DE-1")); + checkArray("Sensitive match() invalid", null, sensitive.match("AB-de-1")); + checkArray("Insensitive match() valid", new String[] {"AB", "de", "1"}, insensitive.match("AB-de-1")); + checkArray("Insensitive match() invalid", null, insensitive.match("ABd-de-1")); + assertEquals("validate one", "ABC", (new RegexValidator("^([A-Z]*)$")).validate("ABC")); + checkArray("match one", new String[] {"ABC"}, (new RegexValidator("^([A-Z]*)$")).match("ABC")); + } + + /** + * Test with multiple regular expressions (case sensitive). + */ + public void testMultipleSensitive() { + + // ------------ Set up Sensitive Validators + RegexValidator multiple = new RegexValidator(MULTIPLE_REGEX); + RegexValidator single1 = new RegexValidator(REGEX_1); + RegexValidator single2 = new RegexValidator(REGEX_2); + RegexValidator single3 = new RegexValidator(REGEX_3); + + // ------------ Set up test values + String value = "aac FDE 321"; + String expect = "aacFDE321"; + String[] array = new String[] {"aac", "FDE", "321"}; + + // isValid() + assertEquals("Sensitive isValid() Multiple", true, multiple.isValid(value)); + assertEquals("Sensitive isValid() 1st", false, single1.isValid(value)); + assertEquals("Sensitive isValid() 2nd", true, single2.isValid(value)); + assertEquals("Sensitive isValid() 3rd", false, single3.isValid(value)); + + // validate() + assertEquals("Sensitive validate() Multiple", expect, multiple.validate(value)); + assertEquals("Sensitive validate() 1st", null, single1.validate(value)); + assertEquals("Sensitive validate() 2nd", expect, single2.validate(value)); + assertEquals("Sensitive validate() 3rd", null, single3.validate(value)); + + // match() + checkArray("Sensitive match() Multiple", array, multiple.match(value)); + checkArray("Sensitive match() 1st", null, single1.match(value)); + checkArray("Sensitive match() 2nd", array, single2.match(value)); + checkArray("Sensitive match() 3rd", null, single3.match(value)); + + // All invalid + value = "AAC*FDE*321"; + assertEquals("isValid() Invalid", false, multiple.isValid(value)); + assertEquals("validate() Invalid", null, multiple.validate(value)); + assertEquals("match() Multiple", null, multiple.match(value)); + } + + /** + * Test with multiple regular expressions (case in-sensitive). + */ + public void testMultipleInsensitive() { + + // ------------ Set up In-sensitive Validators + RegexValidator multiple = new RegexValidator(MULTIPLE_REGEX, false); + RegexValidator single1 = new RegexValidator(REGEX_1, false); + RegexValidator single2 = new RegexValidator(REGEX_2, false); + RegexValidator single3 = new RegexValidator(REGEX_3, false); + + // ------------ Set up test values + String value = "AAC FDE 321"; + String expect = "AACFDE321"; + String[] array = new String[] {"AAC", "FDE", "321"}; + + // isValid() + assertEquals("isValid() Multiple", true, multiple.isValid(value)); + assertEquals("isValid() 1st", false, single1.isValid(value)); + assertEquals("isValid() 2nd", true, single2.isValid(value)); + assertEquals("isValid() 3rd", false, single3.isValid(value)); + + // validate() + assertEquals("validate() Multiple", expect, multiple.validate(value)); + assertEquals("validate() 1st", null, single1.validate(value)); + assertEquals("validate() 2nd", expect, single2.validate(value)); + assertEquals("validate() 3rd", null, single3.validate(value)); + + // match() + checkArray("match() Multiple", array, multiple.match(value)); + checkArray("match() 1st", null, single1.match(value)); + checkArray("match() 2nd", array, single2.match(value)); + checkArray("match() 3rd", null, single3.match(value)); + + // All invalid + value = "AAC*FDE*321"; + assertEquals("isValid() Invalid", false, multiple.isValid(value)); + assertEquals("validate() Invalid", null, multiple.validate(value)); + assertEquals("match() Multiple", null, multiple.match(value)); + } + + /** + * Test Null value + */ + public void testNullValue() { + + RegexValidator validator = new RegexValidator(REGEX); + assertEquals("Instance isValid()", false, validator.isValid(null)); + assertEquals("Instance validate()", null, validator.validate(null)); + assertEquals("Instance match()", null, validator.match(null)); + } + + /** + * Test exceptions + */ + public void testMissingRegex() { + + // Single Regular Expression - null + try { + new RegexValidator((String)null); + fail("Single Null - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Single Null", "Regular expression[0] is missing", e.getMessage()); + } + + // Single Regular Expression - Zero Length + try { + new RegexValidator(""); + fail("Single Zero Length - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Single Zero Length", "Regular expression[0] is missing", e.getMessage()); + } + + // Multiple Regular Expression - Null array + try { + new RegexValidator((String[])null); + fail("Null Array - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Null Array", "Regular expressions are missing", e.getMessage()); + } + + // Multiple Regular Expression - Zero Length array + try { + new RegexValidator(new String[0]); + fail("Zero Length Array - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Zero Length Array", "Regular expressions are missing", e.getMessage()); + } + + // Multiple Regular Expression - Array has Null + String[] expressions = new String[] {"ABC", null}; + try { + new RegexValidator(expressions); + fail("Array has Null - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Array has Null", "Regular expression[1] is missing", e.getMessage()); + } + + // Multiple Regular Expression - Array has Zero Length + expressions = new String[] {"", "ABC"}; + try { + new RegexValidator(expressions); + fail("Array has Zero Length - expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Array has Zero Length", "Regular expression[0] is missing", e.getMessage()); + } + } + + /** + * Test exceptions + */ + public void testExceptions() { + String invalidRegex = "^([abCD12]*$"; + try { + new RegexValidator(invalidRegex); + } catch (PatternSyntaxException e) { + // expected + } + } + + /** + * Test toString() method + */ + public void testToString() { + RegexValidator single = new RegexValidator(REGEX); + assertEquals("Single", "RegexValidator{" + REGEX + "}", single.toString()); + + RegexValidator multiple = new RegexValidator(new String[] {REGEX, REGEX}); + assertEquals("Multiple", "RegexValidator{" + REGEX + "," + REGEX + "}", multiple.toString()); + } + + /** + * Compare two arrays + * @param label Label for the test + * @param expect Expected array + * @param result Actual array + */ + private void checkArray(String label, String[] expect, String[] result) { + + // Handle nulls + if (expect == null || result == null) { + if (expect == null && result == null) { + return; // valid, both null + } else { + fail(label + " Null expect=" + expect + " result=" + result); + } + return; // not strictly necessary, but prevents possible NPE below + } + + // Check Length + if (expect.length != result.length) { + fail(label + " Length expect=" + expect.length + " result=" + result.length); + } + + // Check Values + for (int i = 0; i < expect.length; i++) { + assertEquals(label +" value[" + i + "]", expect[i], result[i]); + } + } + +} From 777e0203e94466e8f9a366f99ba5b12ad3a5a011 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:06:24 -0700 Subject: [PATCH 221/243] Update changelog for 3.28. --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c679ff6a..995d718ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ This file also provides links to Jenkins versions, which bundle the specified remoting version. See [Jenkins changelog](https://jenkins.io/changelog/) for more details. +##### 3.28 + +Release date: December 10, 2018 + +* [JENKINS-48778](https://issues.jenkins-ci.org/browse/JENKINS-48778) Enhance the no_proxy configurations. See [NO_PROXY Environment Variable](docs/no_proxy.md) for documentation. +* Better diagnostics for errors from RemoteClassLoader.fetch4. +* Ignore attempts to flush a ProxyOutputStream which has already been finalized. +* [JENKINS-51108](https://issues.jenkins-ci.org/browse/JENKINS-51108) - Allow remoting to publish incrementals. +* [JENKINS-47977](https://issues.jenkins-ci.org/browse/JENKINS-47977) - Jenkins build failed if Remoting could not create the JAR cache. +* [JENKINS-50730](https://issues.jenkins-ci.org/browse/JENKINS-50730) - Improve log messaging on reconnect. +* [JENKINS-49987](https://issues.jenkins-ci.org/browse/JENKINS-49987) - Clean up warnings about anonymous callable. +* [JENKINS-54005](https://issues.jenkins-ci.org/browse/JENKINS-49987) - Another instance of an unnecessarily severe warning when unexporting. + ##### 3.27 Release date: September 28, 2018 From df7d88479fb04e947b5b7147b08772686a993171 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:23:18 -0700 Subject: [PATCH 222/243] [maven-release-plugin] prepare release remoting-3.28 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 30271c88a..e0dfd8213 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${revision}${changelist} + 3.28 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - ${scmTag} + remoting-3.28 From 392ae679c2f9a0fbc150f8a160d4dce1dbb7ab36 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:23:30 -0700 Subject: [PATCH 223/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e0dfd8213..c7c1e6672 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28 + ${revision}${changelist} Jenkins remoting layer @@ -57,11 +57,11 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.28 + ${scmTag} - 3.28 + 3.29 -SNAPSHOT 8 private From ed3edf9a3717965f533c2fadf7177cb35f8533f9 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:39:19 -0700 Subject: [PATCH 224/243] Revert "[maven-release-plugin] prepare for next development iteration" This reverts commit 392ae679c2f9a0fbc150f8a160d4dce1dbb7ab36. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c7c1e6672..e0dfd8213 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${revision}${changelist} + 3.28 Jenkins remoting layer @@ -57,11 +57,11 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - ${scmTag} + remoting-3.28 - 3.29 + 3.28 -SNAPSHOT 8 private From c772f5d01e7ff2ffba6735bbbd3b7939ff1731e6 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:40:17 -0700 Subject: [PATCH 225/243] Revert "[maven-release-plugin] prepare release remoting-3.28" This reverts commit df7d88479fb04e947b5b7147b08772686a993171. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e0dfd8213..30271c88a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28 + ${revision}${changelist} Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.28 + ${scmTag} From cb9e3f8a2860e0d47e823d785490df7ba2b7dd44 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:44:10 -0700 Subject: [PATCH 226/243] [maven-release-plugin] prepare release remoting-3.28 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 30271c88a..e0dfd8213 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${revision}${changelist} + 3.28 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - ${scmTag} + remoting-3.28 From eb9bf4db4436ba2cdc3bfd5d829670869b25ac07 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:46:52 -0700 Subject: [PATCH 227/243] Revert "[maven-release-plugin] prepare release remoting-3.28" This reverts commit cb9e3f8a2860e0d47e823d785490df7ba2b7dd44. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e0dfd8213..30271c88a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28 + ${revision}${changelist} Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.28 + ${scmTag} From 0eefd1a3dd7dd4266a5551dfb4c1959695939a66 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 14:49:13 -0700 Subject: [PATCH 228/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30271c88a..c7c1e6672 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ THE SOFTWARE. - 3.28 + 3.29 -SNAPSHOT 8 private From bdac682e6b827d2dcaff23f3209af1176761ad0f Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 15:09:07 -0700 Subject: [PATCH 229/243] Revert "[maven-release-plugin] prepare for next development iteration" This reverts commit 0eefd1a3dd7dd4266a5551dfb4c1959695939a66. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c7c1e6672..30271c88a 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ THE SOFTWARE. - 3.29 + 3.28 -SNAPSHOT 8 private From 9dba9202178f1ec00863aa5dadb5839467fae9cb Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 15:13:39 -0700 Subject: [PATCH 230/243] [maven-release-plugin] prepare release remoting-3.28 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 30271c88a..e0dfd8213 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${revision}${changelist} + 3.28 Jenkins remoting layer @@ -57,7 +57,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - ${scmTag} + remoting-3.28 From 66c24285f55fe9057cb08369dfa19eacf10608d0 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 15:13:49 -0700 Subject: [PATCH 231/243] [maven-release-plugin] prepare for next development iteration --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e0dfd8213..c7c1e6672 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.28 + ${revision}${changelist} Jenkins remoting layer @@ -57,11 +57,11 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/remoting.git scm:git:ssh://git@github.com/jenkinsci/remoting.git https://github.com/jenkinsci/remoting - remoting-3.28 + ${scmTag} - 3.28 + 3.29 -SNAPSHOT 8 private From 3107bf3a4cbd11f0541d77bd53010f487d48fd2b Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 10 Dec 2018 15:38:38 -0700 Subject: [PATCH 232/243] Adjust config doc to link to no_proxy page. --- docs/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index dc2dbaaf5..20f0b7505 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -120,12 +120,12 @@ These properties require independent configuration on both sides of the channel. If specified, only the protocols from the list will be tried during the connection. The option provides protocol names, but the order of the check is defined internally and cannot be changed. - [NO_PROXY](no_proxy.md) (or no_proxy) + NO_PROXY (or no_proxy) - - Provides specifications for hosts that should not be proxied. See the [NO_PROXY](no_proxy.md) page for details on supported specifications. + + Provides specifications for hosts that should not be proxied. See the NO_PROXY Environment Variable page for details on supported specifications.