From a7dd4eb12a655bbacd84cfe8e925ee9cce374879 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Thu, 8 Oct 2026 12:29:53 +0300 Subject: [PATCH 1/2] [#1180] Report the fail-over failure of the Grizzly proxy listener tests instead of timing out A failed connection inside the overridden handleAccept left the context the test waits for unresolved, so any failure showed up as a bare TimeoutException after 10 seconds, as on the Windows CI run. Resolve that context with the failure, and wait longer than the default connect timeout of the two nested connections. The bind variant failed the bind with a plain result, which the listener cannot encode as a bind response: it closed the connection and the cause was lost. Fail it with a bind result. Both tests now share the offline-then-online fail-over, which closes its connection factories and connects to the numeric loopback address; the client factories are closed as well. --- .../grizzly/GrizzlyLDAPListenerTestCase.java | 147 +++++++++--------- 1 file changed, 71 insertions(+), 76 deletions(-) diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java index fae5bbd806..3d6bbdd1e3 100644 --- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java +++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java @@ -204,6 +204,39 @@ private static ServerSocket reserveSocketAddress() throws IOException { return socket; } + /** + * Fails over the way a proxy does: connects to a free port, which must refuse the connection, + * then to the online server. + */ + private static void failOverToOnlineServer(final InetSocketAddress onlineAddress) throws LdapException { + // The numeric host avoids a reverse lookup of the loopback address. + final InetSocketAddress offlineAddress = findFreeSocketAddress(); + final LDAPConnectionFactory offlineFactory = + new LDAPConnectionFactory(offlineAddress.getHostString(), offlineAddress.getPort()); + try { + offlineFactory.getConnection().close(); + } catch (final ConnectionException expected) { + // This is expected - so go to online server. + final LDAPConnectionFactory onlineFactory = + new LDAPConnectionFactory(onlineAddress.getHostName(), onlineAddress.getPort()); + try { + onlineFactory.getConnection().close(); + return; + } catch (final Exception e) { + throw newLdapException(ResultCode.OTHER, + "Unexpected exception when connecting to online server", e); + } finally { + onlineFactory.close(); + } + } catch (final Exception e) { + throw newLdapException(ResultCode.OTHER, + "Unexpected exception when connecting to offline server", e); + } finally { + offlineFactory.close(); + } + throw newLdapException(ResultCode.OTHER, "Connection to offline server succeeded unexpectedly"); + } + /** Disables logging before the tests. */ @BeforeClass public void disableLogging() { @@ -534,7 +567,7 @@ public void handleBind(final Integer requestContext, final int version, * @throws Exception * If an unexpected exception occurred. */ - @Test + @Test(timeOut = 60000) public void testLDAPListenerProxyDuringHandleAccept() throws Exception { final MockServerConnection onlineServerConnection = new MockServerConnection(); final MockServerConnectionFactory onlineServerConnectionFactory = @@ -552,39 +585,15 @@ public void testLDAPListenerProxyDuringHandleAccept() throws Exception { @Override public ServerConnection handleAccept( final LDAPClientContext clientContext) throws LdapException { - // First attempt offline server. - InetSocketAddress offlineAddress = findFreeSocketAddress(); - LDAPConnectionFactory lcf = - new LDAPConnectionFactory(offlineAddress.getHostName(), - offlineAddress.getPort()); try { - // This is expected to fail. - lcf.getConnection().close(); - throw newLdapException(ResultCode.OTHER, - "Connection to offline server succeeded unexpectedly"); - } catch (final ConnectionException ce) { - // This is expected - so go to online server. - try { - lcf = - new LDAPConnectionFactory( - onlineServerAddr.getHostName(), - onlineServerAddr.getPort()); - lcf.getConnection().close(); - } catch (final Exception e) { - // Unexpected. - throw newLdapException( - ResultCode.OTHER, - "Unexpected exception when connecting to online server", - e); - } - } catch (final Exception e) { - // Unexpected. - throw newLdapException( - ResultCode.OTHER, - "Unexpected exception when connecting to offline server", - e); + failOverToOnlineServer(onlineServerAddr); + } catch (final LdapException e) { + // The listener only closes the client connection when handleAccept + // fails: report the failure through the context the test waits for, + // instead of leaving that wait to time out without a cause. + proxyServerConnection.context.handleException(e); + throw e; } - return super.handleAccept(clientContext); } @@ -593,23 +602,28 @@ public ServerConnection handleAccept( final LDAPListener proxyListener = new LDAPListener(Collections.singleton(loopbackWithDynamicPort()), new ServerConnectionFactoryAdapter(Options.defaultOptions().get(LDAP_DECODE_OPTIONS), proxyServerConnectionFactory)); + // Connect to the proxy listener (not to the online server + // directly, otherwise the proxy handleAccept is never invoked) + // and close. + final InetSocketAddress proxyAddr = proxyListener.firstSocketAddress(); + final LDAPConnectionFactory proxyClientFactory = + new LDAPConnectionFactory(proxyAddr.getHostName(), proxyAddr.getPort()); try { - // Connect to the proxy listener (not to the online server - // directly, otherwise the proxy handleAccept is never invoked) - // and close. - final InetSocketAddress proxyAddr = proxyListener.firstSocketAddress(); - final Connection connection = - new LDAPConnectionFactory(proxyAddr.getHostName(), - proxyAddr.getPort()).getConnection(); - - assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); - assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); - - connection.close(); + final Connection connection = proxyClientFactory.getConnection(); + try { + // handleAccept makes both fail-over connections before it resolves the context, + // and each of them may take up to the default connect timeout (10 seconds): + // wait longer than that, so that a failed attempt reports its own cause. + assertThat(proxyServerConnection.context.get(30, TimeUnit.SECONDS)).isNotNull(); + assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); + } finally { + connection.close(); + } // Wait for connect/close to complete. - proxyServerConnection.isClosed.await(); + assertThat(proxyServerConnection.isClosed.await(10, TimeUnit.SECONDS)).isTrue(); } finally { + proxyClientFactory.close(); proxyListener.close(); } } finally { @@ -624,7 +638,7 @@ public ServerConnection handleAccept( * @throws Exception * If an unexpected exception occurred. */ - @Test(timeOut = 10000) + @Test(timeOut = 60000) public void testLDAPListenerProxyDuringHandleBind() throws Exception { final MockServerConnection onlineServerConnection = new MockServerConnection(); final MockServerConnectionFactory onlineServerConnectionFactory = @@ -643,34 +657,14 @@ public void handleBind(final Integer requestContext, final int version, final IntermediateResponseHandler intermediateResponseHandler, final LdapResultHandler resultHandler) throws UnsupportedOperationException { - // First attempt offline server. - InetSocketAddress offlineAddress = findFreeSocketAddress(); - LDAPConnectionFactory lcf = new LDAPConnectionFactory(offlineAddress.getHostName(), - offlineAddress.getPort()); try { - // This is expected to fail. - lcf.getConnection().close(); - resultHandler.handleException(newLdapException( - ResultCode.OTHER, - "Connection to offline server succeeded unexpectedly")); - } catch (final ConnectionException ce) { - // This is expected - so go to online server. - try { - lcf = new LDAPConnectionFactory(onlineServerAddr.getHostName(), - onlineServerAddr.getPort()); - lcf.getConnection().close(); - resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS)); - } catch (final Exception e) { - // Unexpected. - resultHandler.handleException(newLdapException( - ResultCode.OTHER, - "Unexpected exception when connecting to online server", e)); - } - } catch (final Exception e) { - // Unexpected. - resultHandler.handleException(newLdapException( - ResultCode.OTHER, - "Unexpected exception when connecting to offline server", e)); + failOverToOnlineServer(onlineServerAddr); + resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS)); + } catch (final LdapException e) { + // A bind must fail with a bind result: the listener cannot encode any other + // result as a bind response, and would then never answer the bind. + resultHandler.handleException(newLdapException(Responses.newBindResult(ResultCode.OTHER) + .setDiagnosticMessage(e.getResult().getDiagnosticMessage()).setCause(e))); } } @@ -681,11 +675,11 @@ public void handleBind(final Integer requestContext, final int version, new ServerConnectionFactoryAdapter(Options.defaultOptions().get(LDAP_DECODE_OPTIONS), proxyServerConnectionFactory)); final InetSocketAddress proxyAddr = proxyListener.firstSocketAddress(); + final LDAPConnectionFactory proxyClientFactory = + new LDAPConnectionFactory(proxyAddr.getHostName(), proxyAddr.getPort()); try { // Connect, bind, and close. - final Connection connection = - new LDAPConnectionFactory(proxyAddr.getHostName(), - proxyAddr.getPort()).getConnection(); + final Connection connection = proxyClientFactory.getConnection(); try { connection.bind("cn=test", "password".toCharArray()); @@ -699,6 +693,7 @@ public void handleBind(final Integer requestContext, final int version, // Wait for connect/close to complete. proxyServerConnection.isClosed.await(); } finally { + proxyClientFactory.close(); proxyListener.close(); } } finally { From afbe983b7af1dd0fbe056f5bcff30efa0555a950 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Thu, 8 Oct 2026 16:27:09 +0300 Subject: [PATCH 2/2] [#1180] Report every fail-over failure of the Grizzly proxy listener tests with its cause An unchecked exception from the fail-over, such as a failed loopback bind in findFreeSocketAddress, still escaped both overrides without an answer: handleAccept left the context unresolved, so the test waited out 30 seconds for a bare TimeoutException, and handleBind left the bind unanswered, so the client saw only "Server Connection Closed". Report it like the expected failures. A bind result reaches the client with its diagnostic message only, never with its cause: append the nested cause to that message, so that a failed connection says whether it was refused, timed out or not resolved. The offline host was not numeric: the address of the reserved socket keeps the host name "localhost", which getHostString returns as well. Connect to its numeric address instead. The listener addresses have no host name, so getHostName reverse-resolved them, inside handleAccept for the online server: use getHostString for them. --- .../grizzly/GrizzlyLDAPListenerTestCase.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java index 3d6bbdd1e3..e340003b0b 100644 --- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java +++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java @@ -209,16 +209,16 @@ private static ServerSocket reserveSocketAddress() throws IOException { * then to the online server. */ private static void failOverToOnlineServer(final InetSocketAddress onlineAddress) throws LdapException { - // The numeric host avoids a reverse lookup of the loopback address. + // The numeric host avoids a forward lookup of "localhost". final InetSocketAddress offlineAddress = findFreeSocketAddress(); final LDAPConnectionFactory offlineFactory = - new LDAPConnectionFactory(offlineAddress.getHostString(), offlineAddress.getPort()); + new LDAPConnectionFactory(offlineAddress.getAddress().getHostAddress(), offlineAddress.getPort()); try { offlineFactory.getConnection().close(); } catch (final ConnectionException expected) { // This is expected - so go to online server. final LDAPConnectionFactory onlineFactory = - new LDAPConnectionFactory(onlineAddress.getHostName(), onlineAddress.getPort()); + new LDAPConnectionFactory(onlineAddress.getHostString(), onlineAddress.getPort()); try { onlineFactory.getConnection().close(); return; @@ -593,6 +593,10 @@ public ServerConnection handleAccept( // instead of leaving that wait to time out without a cause. proxyServerConnection.context.handleException(e); throw e; + } catch (final RuntimeException e) { + proxyServerConnection.context.handleException( + newLdapException(ResultCode.OTHER, e)); + throw e; } return super.handleAccept(clientContext); } @@ -607,7 +611,7 @@ public ServerConnection handleAccept( // and close. final InetSocketAddress proxyAddr = proxyListener.firstSocketAddress(); final LDAPConnectionFactory proxyClientFactory = - new LDAPConnectionFactory(proxyAddr.getHostName(), proxyAddr.getPort()); + new LDAPConnectionFactory(proxyAddr.getHostString(), proxyAddr.getPort()); try { final Connection connection = proxyClientFactory.getConnection(); try { @@ -662,9 +666,17 @@ public void handleBind(final Integer requestContext, final int version, resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS)); } catch (final LdapException e) { // A bind must fail with a bind result: the listener cannot encode any other - // result as a bind response, and would then never answer the bind. + // result as a bind response, and would then never answer the bind. Only the + // diagnostic message reaches the client, so it carries the nested cause. + final Throwable cause = e.getCause(); + final String message = cause != null + ? e.getResult().getDiagnosticMessage() + ": " + cause + : e.getResult().getDiagnosticMessage(); resultHandler.handleException(newLdapException(Responses.newBindResult(ResultCode.OTHER) - .setDiagnosticMessage(e.getResult().getDiagnosticMessage()).setCause(e))); + .setDiagnosticMessage(message).setCause(e))); + } catch (final RuntimeException e) { + resultHandler.handleException(newLdapException(Responses.newBindResult(ResultCode.OTHER) + .setDiagnosticMessage(e.toString()).setCause(e))); } } @@ -676,7 +688,7 @@ public void handleBind(final Integer requestContext, final int version, proxyServerConnectionFactory)); final InetSocketAddress proxyAddr = proxyListener.firstSocketAddress(); final LDAPConnectionFactory proxyClientFactory = - new LDAPConnectionFactory(proxyAddr.getHostName(), proxyAddr.getPort()); + new LDAPConnectionFactory(proxyAddr.getHostString(), proxyAddr.getPort()); try { // Connect, bind, and close. final Connection connection = proxyClientFactory.getConnection();