Repository: hbase Updated Branches: refs/heads/master 23ec9e290 -> 1a6ec1bac
HBASE-14784 Port conflict is not resolved in HBaseTestingUtility.randomFreePort() (Youngjoon Kim) Project: http://git-wip-us.apache.org/repos/asf/hbase/repo Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/1a6ec1ba Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/1a6ec1ba Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/1a6ec1ba Branch: refs/heads/master Commit: 1a6ec1bac65fa04a30a57891fb66dd248f3bed9c Parents: 23ec9e2 Author: stack <[email protected]> Authored: Tue Nov 10 09:53:30 2015 -1000 Committer: stack <[email protected]> Committed: Tue Nov 10 09:53:30 2015 -1000 ---------------------------------------------------------------------- .../hadoop/hbase/HBaseTestingUtility.java | 99 +++++++++++++------- .../hadoop/hbase/TestHBaseTestingUtility.java | 34 ++++++- 2 files changed, 99 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hbase/blob/1a6ec1ba/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java index de79bdf..5bb25db 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java @@ -203,9 +203,6 @@ public class HBaseTestingUtility extends HBaseCommonTestingUtility { /** Filesystem URI used for map-reduce mini-cluster setup */ private static String FS_URI; - /** A set of ports that have been claimed using {@link #randomFreePort()}. */ - private static final Set<Integer> takenRandomPorts = new HashSet<Integer>(); - /** Compression algorithms to use in parameterized JUnit 4 tests */ public static final List<Object[]> COMPRESSION_ALGORITHMS_PARAMETERIZED = Arrays.asList(new Object[][] { @@ -3429,41 +3426,77 @@ public class HBaseTestingUtility extends HBaseCommonTestingUtility { return table; } - private static final int MIN_RANDOM_PORT = 0xc000; - private static final int MAX_RANDOM_PORT = 0xfffe; private static Random random = new Random(); - /** - * Returns a random port. These ports cannot be registered with IANA and are - * intended for dynamic allocation (see http://bit.ly/dynports). - */ - public static int randomPort() { - return MIN_RANDOM_PORT - + random.nextInt(MAX_RANDOM_PORT - MIN_RANDOM_PORT); - } + private static final PortAllocator portAllocator = new PortAllocator(random); - /** - * Returns a random free port and marks that port as taken. Not thread-safe. Expected to be - * called from single-threaded test setup code/ - */ public static int randomFreePort() { - int port = 0; - do { - port = randomPort(); - if (takenRandomPorts.contains(port)) { - port = 0; - continue; - } - takenRandomPorts.add(port); + return portAllocator.randomFreePort(); + } - try { - ServerSocket sock = new ServerSocket(port); - sock.close(); - } catch (IOException ex) { - port = 0; - } - } while (port == 0); - return port; + static class PortAllocator { + private static final int MIN_RANDOM_PORT = 0xc000; + private static final int MAX_RANDOM_PORT = 0xfffe; + + /** A set of ports that have been claimed using {@link #randomFreePort()}. */ + private final Set<Integer> takenRandomPorts = new HashSet<Integer>(); + + private final Random random; + private final AvailablePortChecker portChecker; + + public PortAllocator(Random random) { + this.random = random; + this.portChecker = new AvailablePortChecker() { + public boolean available(int port) { + try { + ServerSocket sock = new ServerSocket(port); + sock.close(); + return true; + } catch (IOException ex) { + return false; + } + } + }; + } + + public PortAllocator(Random random, AvailablePortChecker portChecker) { + this.random = random; + this.portChecker = portChecker; + } + + /** + * Returns a random free port and marks that port as taken. Not thread-safe. Expected to be + * called from single-threaded test setup code/ + */ + public int randomFreePort() { + int port = 0; + do { + port = randomPort(); + if (takenRandomPorts.contains(port)) { + port = 0; + continue; + } + takenRandomPorts.add(port); + + if (!portChecker.available(port)) { + port = 0; + } + } while (port == 0); + return port; + } + + /** + * Returns a random port. These ports cannot be registered with IANA and are + * intended for dynamic allocation (see http://bit.ly/dynports). + */ + private int randomPort() { + return MIN_RANDOM_PORT + + random.nextInt(MAX_RANDOM_PORT - MIN_RANDOM_PORT); + } + + interface AvailablePortChecker { + boolean available(int port); + } } http://git-wip-us.apache.org/repos/asf/hbase/blob/1a6ec1ba/hbase-server/src/test/java/org/apache/hadoop/hbase/TestHBaseTestingUtility.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestHBaseTestingUtility.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestHBaseTestingUtility.java index dd09c37..10c9d86 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestHBaseTestingUtility.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestHBaseTestingUtility.java @@ -21,6 +21,7 @@ package org.apache.hadoop.hbase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import org.apache.commons.logging.Log; @@ -40,9 +41,13 @@ import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.io.File; import java.util.List; +import java.util.Random; /** * Test our testing utility class @@ -385,5 +390,32 @@ public class TestHBaseTestingUtility { assertTrue(hbt.cleanupTestDir()); } -} + @Test public void testResolvePortConflict() throws Exception { + // raises port conflict between 1st call and 2nd call of randomPort() by mocking Random object + Random random = Mockito.mock(Random.class); + Mockito.when(random.nextInt(Mockito.any(Integer.class))) + .thenAnswer(new Answer<Integer>() { + int[] numbers = { 1, 1, 2 }; + int count = 0; + + @Override + public Integer answer(InvocationOnMock invocation) { + int ret = numbers[count]; + count++; + return ret; + } + }); + + HBaseTestingUtility.PortAllocator.AvailablePortChecker portChecker = + Mockito.mock(HBaseTestingUtility.PortAllocator.AvailablePortChecker.class); + Mockito.when(portChecker.available(Mockito.any(Integer.class))).thenReturn(true); + HBaseTestingUtility.PortAllocator portAllocator = + new HBaseTestingUtility.PortAllocator(random, portChecker); + + int port1 = portAllocator.randomFreePort(); + int port2 = portAllocator.randomFreePort(); + assertNotEquals(port1, port2); + Mockito.verify(random, Mockito.times(3)).nextInt(Mockito.any(Integer.class)); + } +}
