Test: ensure ports that should fail are not bound to by other tests

In the SessionFactoryLoadBalancingTests, we sometime want a connection to a certain IP and Port to
fail as a way to mock an unresponsive/disconnected LDAP server. The test does this by starting up
multiple LDAP servers and then shutting some down. When the server is shut down the port that it
was bound to is open for another process or test to bind to, which can lead to sporadic failures in
CI. This change is a best effort attempt to prevent this by binding a server socket to the port and
filling its backlog so other connections should fail.

Relates elastic/x-pack-elasticsearch#1195

Original commit: elastic/x-pack-elasticsearch@b31a560c93
This commit is contained in:
jaymode 2017-04-25 10:48:11 -04:00
parent 2d9fd0ed16
commit ef571568f4
1 changed files with 62 additions and 21 deletions

View File

@ -12,12 +12,19 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.mocksocket.MockServerSocket;
import org.elasticsearch.mocksocket.MockSocket;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.ssl.SSLService;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
@ -26,6 +33,7 @@ import static org.hamcrest.Matchers.not;
/**
* Tests that the server sets properly load balance connections without throwing exceptions
*/
@TestLogging("org.elasticsearch.xpack.security.authc.ldap.support:DEBUG")
public class SessionFactoryLoadBalancingTests extends LdapTestCase {
public void testRoundRobin() throws Exception {
@ -62,9 +70,13 @@ public class SessionFactoryLoadBalancingTests extends LdapTestCase {
final int numberToKill = randomIntBetween(1, numberOfLdapServers - 1);
logger.debug("killing [{}] servers", numberToKill);
// get a subset to kil
// get a subset to kill
final List<InMemoryDirectoryServer> ldapServersToKill = randomSubsetOf(numberToKill, ldapServers);
final List<InMemoryDirectoryServer> ldapServersList = Arrays.asList(ldapServers);
final List<MockServerSocket> boundSockets = new ArrayList<>();
final List<Thread> listenThreads = new ArrayList<>();
final CountDownLatch latch = new CountDownLatch(ldapServersToKill.size());
try {
for (InMemoryDirectoryServer ldapServerToKill : ldapServersToKill) {
final int index = ldapServersList.indexOf(ldapServerToKill);
assertThat(index, greaterThanOrEqualTo(0));
@ -72,23 +84,52 @@ public class SessionFactoryLoadBalancingTests extends LdapTestCase {
logger.debug("shutting down server index [{}] listening on [{}]", index, port);
assertTrue(ports.remove(port));
ldapServers[index].shutDown(true);
// when running multiple test jvms, there is a chance that something else could
// start listening on this port so we try to avoid this by binding again to this
// port with a server socket. The server socket only has a backlog of size one and
// we start a thread that connects to this socket but the connection is never
// accepted so other connections will be rejected. The socket attempts a blocking
// read, which will hold up the thread until the server socket is closed at the end
// of the test.
// NOTE: this is not perfect as there is a small amount of time between the shutdown
// of the ldap server and the opening of the socket
logger.debug("opening mock server socket listening on [{}]", port);
MockServerSocket mockServerSocket = new MockServerSocket(port, 1, InetAddress.getByName("localhost"));
Runnable runnable = () -> {
try (Socket socket = new MockSocket(InetAddress.getByName("localhost"), port)) {
logger.debug("opened socket [{}] and blocking other connections", socket);
latch.countDown();
socket.getInputStream().read();
} catch (IOException e) {
logger.trace("caught io exception", e);
}
};
Thread thread = new Thread(runnable);
thread.start();
boundSockets.add(mockServerSocket);
listenThreads.add(thread);
assertThat(ldapServers[index].getListenPort(), is(-1));
}
latch.await();
final int numberOfIterations = randomIntBetween(1, 5);
for (int iteration = 0; iteration < numberOfIterations; iteration++) {
logger.debug("iteration [{}]", iteration);
for (Integer port : ports) {
LDAPConnection connection = null;
try {
logger.debug("attempting connection with expected port [{}]", port);
connection = LdapUtils.privilegedConnect(testSessionFactory.getServerSet()::getConnection);
try (LDAPConnection connection = LdapUtils.privilegedConnect(testSessionFactory.getServerSet()::getConnection)) {
assertThat(connection.getConnectedPort(), is(port));
}
}
}
} finally {
if (connection != null) {
connection.close();
}
for (MockServerSocket socket : boundSockets) {
socket.close();
}
for (Thread t : listenThreads) {
t.join();
}
}
}