Skip to content

Commit

Permalink
SONAR-9062 Use the network interface defined by sonar.search.host whe…
Browse files Browse the repository at this point in the history
…n resolving sonar.search.port=0
  • Loading branch information
Simon Brandhof committed Apr 11, 2017
1 parent 90dadc2 commit 7053ab1
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 126 deletions.
Expand Up @@ -22,29 +22,29 @@
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;

import static java.lang.String.format;
import static java.util.Collections.list;
import static org.apache.commons.lang.StringUtils.isBlank;

public final class NetworkUtils {

private static final RandomPortFinder RANDOM_PORT_FINDER = new RandomPortFinder();
private static final Set<Integer> ALREADY_ALLOCATED = new HashSet<>();
private static final int MAX_TRIES = 50;

private NetworkUtils() {
// only statics
// prevent instantiation
}

public static int freePort() {
return RANDOM_PORT_FINDER.getNextAvailablePort();
public static int getNextAvailablePort(InetAddress address) {
return getNextAvailablePort(address, PortAllocator.INSTANCE);
}

/**
Expand All @@ -64,18 +64,15 @@ public static String getHostName() {

try {
ips = list(NetworkInterface.getNetworkInterfaces()).stream()
.flatMap(netif ->
list(netif.getInetAddresses()).stream()
.filter(inetAddress ->
// Removing IPv6 for the time being
inetAddress instanceof Inet4Address &&
// Removing loopback addresses, useless for identifying a server
!inetAddress.isLoopbackAddress() &&
// Removing interfaces without IPs
!isBlank(inetAddress.getHostAddress())
)
.map(InetAddress::getHostAddress)
)
.flatMap(netif -> list(netif.getInetAddresses()).stream()
.filter(inetAddress ->
// Removing IPv6 for the time being
inetAddress instanceof Inet4Address &&
// Removing loopback addresses, useless for identifying a server
!inetAddress.isLoopbackAddress() &&
// Removing interfaces without IPs
!isBlank(inetAddress.getHostAddress()))
.map(InetAddress::getHostAddress))
.filter(p -> !isBlank(p))
.collect(Collectors.joining(","));
} catch (SocketException e) {
Expand All @@ -85,41 +82,30 @@ public static String getHostName() {
return format("%s (%s)", hostname, ips);
}

static class RandomPortFinder {
private static final int MAX_TRY = 10;
// Firefox blocks some reserved ports : http://www-archive.mozilla.org/projects/netlib/PortBanning.html
private static final int[] BLOCKED_PORTS = {2049, 4045, 6000};

public int getNextAvailablePort() {
for (int i = 0; i < MAX_TRY; i++) {
try {
int port = getRandomUnusedPort();
if (isValidPort(port)) {
return port;
}
} catch (Exception e) {
throw new IllegalStateException("Can't find an open network port", e);
}
static int getNextAvailablePort(InetAddress address, PortAllocator portAllocator) {
for (int i = 0; i < MAX_TRIES; i++) {
int port = portAllocator.getAvailable(address);
if (isValidPort(port)) {
ALREADY_ALLOCATED.add(port);
return port;
}

throw new IllegalStateException("Can't find an open network port");
}
throw new IllegalStateException("Fail to find an available port on " + address);
}

private static boolean isValidPort(int port) {
return port > 1023 && !ALREADY_ALLOCATED.contains(port);
}

static class PortAllocator {
private static final PortAllocator INSTANCE = new PortAllocator();

public int getRandomUnusedPort() throws IOException {
ServerSocket socket = null;
try {
socket = new ServerSocket();
socket.bind(new InetSocketAddress("localhost", 0));
int getAvailable(InetAddress address) {
try (ServerSocket socket = new ServerSocket(0, 50, address)) {
return socket.getLocalPort();
} catch (IOException e) {
throw new IllegalStateException("Can not find a free network port", e);
} finally {
IOUtils.closeQuietly(socket);
throw new IllegalStateException("Fail to find an available port on " + address, e);
}
}

public static boolean isValidPort(int port) {
return port > 1023 && !ArrayUtils.contains(BLOCKED_PORTS, port);
}
}
}
Expand Up @@ -19,7 +19,8 @@
*/
package org.sonar.process;

import java.util.HashMap;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Properties;

Expand Down Expand Up @@ -94,23 +95,25 @@ public static void completeDefaults(Props props) {
props.setDefault(entry.getKey().toString(), entry.getValue().toString());
}

// init ports
for (Map.Entry<String, Integer> entry : defaultPorts().entrySet()) {
String key = entry.getKey();
int port = props.valueAsInt(key, -1);
if (port == -1) {
// default port
props.set(key, String.valueOf((int) entry.getValue()));
} else if (port == 0) {
// pick one available port
props.set(key, String.valueOf(NetworkUtils.freePort()));
fixPortIfZero(props, SEARCH_HOST, SEARCH_PORT);
}

private static void fixPortIfZero(Props props, String addressPropertyKey, String portPropertyKey) {
String port = props.value(portPropertyKey);
if ("0".equals(port)) {
String address = props.nonNullValue(addressPropertyKey);
try {
props.set(portPropertyKey, String.valueOf(NetworkUtils.getNextAvailablePort(InetAddress.getByName(address))));
} catch (UnknownHostException e) {
throw new IllegalStateException("Cannot resolve address [" + address + "] set by property [" + addressPropertyKey + "]", e);
}
}
}

public static Properties defaults() {
Properties defaults = new Properties();
defaults.put(SEARCH_HOST, "127.0.0.1");
defaults.put(SEARCH_HOST, InetAddress.getLoopbackAddress().getHostAddress());
defaults.put(SEARCH_PORT, "9001");
defaults.put(SEARCH_JAVA_OPTS, "-Xmx1G -Xms256m -Xss256k -Djna.nosys=true " +
"-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly " +
"-XX:+HeapDumpOnOutOfMemoryError");
Expand Down Expand Up @@ -144,10 +147,4 @@ public static Properties defaults() {

return defaults;
}

private static Map<String, Integer> defaultPorts() {
Map<String, Integer> defaults = new HashMap<>();
defaults.put(SEARCH_PORT, 9001);
return defaults;
}
}
Expand Up @@ -19,61 +19,64 @@
*/
package org.sonar.process;

import java.io.IOException;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.process.NetworkUtils.RandomPortFinder;
import org.junit.rules.ExpectedException;

import static java.net.InetAddress.getLoopbackAddress;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.process.NetworkUtils.getNextAvailablePort;

public class NetworkUtilsTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void shouldGetAvailablePortWithoutLockingHost() {
for (int i = 0; i < 1000; i++) {
/*
* The Well Known Ports are those from 0 through 1023.
* DCCP Well Known ports SHOULD NOT be used without IANA registration.
*/
assertThat(NetworkUtils.freePort()).isGreaterThan(1023);
public void getNextAvailablePort_never_returns_the_same_port_in_current_jvm() {
Set<Integer> ports = new HashSet<>();
for (int i = 0; i < 100; i++) {
int port = getNextAvailablePort(getLoopbackAddress());
assertThat(port).isGreaterThan(1_023);
ports.add(port);
}
assertThat(ports).hasSize(100);
}

@Test
public void shouldGetRandomPort() {
assertThat(NetworkUtils.freePort()).isNotEqualTo(NetworkUtils.freePort());
}
public void getNextAvailablePort_retries_to_get_available_port_when_port_has_already_been_allocated() {
NetworkUtils.PortAllocator portAllocator = mock(NetworkUtils.PortAllocator.class);
when(portAllocator.getAvailable(any(InetAddress.class))).thenReturn(9_000, 9_000, 9_000, 9_100);

@Test
public void shouldNotBeValidPorts() {
assertThat(RandomPortFinder.isValidPort(0)).isFalse();// <=1023
assertThat(RandomPortFinder.isValidPort(50)).isFalse();// <=1023
assertThat(RandomPortFinder.isValidPort(1023)).isFalse();// <=1023
assertThat(RandomPortFinder.isValidPort(2049)).isFalse();// NFS
assertThat(RandomPortFinder.isValidPort(4045)).isFalse();// lockd
InetAddress address = getLoopbackAddress();
assertThat(getNextAvailablePort(address, portAllocator)).isEqualTo(9_000);
assertThat(getNextAvailablePort(address, portAllocator)).isEqualTo(9_100);
}

@Test
public void shouldBeValidPorts() {
assertThat(RandomPortFinder.isValidPort(1059)).isTrue();
}
public void getNextAvailablePort_does_not_return_special_ports() {
NetworkUtils.PortAllocator portAllocator = mock(NetworkUtils.PortAllocator.class);
when(portAllocator.getAvailable(any(InetAddress.class))).thenReturn(900, 903, 1_059);

@Test(expected = IllegalStateException.class)
public void shouldFailWhenNoValidPortIsAvailable() throws IOException {
RandomPortFinder randomPortFinder = spy(new RandomPortFinder());
doReturn(0).when(randomPortFinder).getRandomUnusedPort();

randomPortFinder.getNextAvailablePort();
// the two first ports are banned because < 1023, so 1_059 is returned
assertThat(getNextAvailablePort(getLoopbackAddress(), portAllocator)).isEqualTo(1_059);
}

@Test(expected = IllegalStateException.class)
public void shouldFailWhenItsNotPossibleToOpenASocket() throws IOException {
RandomPortFinder randomPortFinder = spy(new RandomPortFinder());
doThrow(new IOException("Not possible")).when(randomPortFinder).getRandomUnusedPort();
@Test
public void getNextAvailablePort_throws_ISE_if_too_many_attempts() {
NetworkUtils.PortAllocator portAllocator = mock(NetworkUtils.PortAllocator.class);
when(portAllocator.getAvailable(any(InetAddress.class))).thenReturn(900);

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Fail to find an available port on ");

randomPortFinder.getNextAvailablePort();
getNextAvailablePort(getLoopbackAddress(), portAllocator);
}

@Test
Expand Down
Expand Up @@ -19,6 +19,7 @@
*/
package org.sonar.process;

import java.net.InetAddress;
import java.util.Properties;
import org.junit.Test;
import org.sonar.test.TestUtils;
Expand All @@ -28,7 +29,7 @@
public class ProcessPropertiesTest {

@Test
public void init_defaults() {
public void completeDefaults_adds_default_values() {
Props props = new Props(new Properties());
ProcessProperties.completeDefaults(props);

Expand All @@ -37,7 +38,7 @@ public void init_defaults() {
}

@Test
public void do_not_override_existing_properties() {
public void completeDefaults_does_not_override_existing_properties() {
Properties p = new Properties();
p.setProperty("sonar.jdbc.username", "angela");
Props props = new Props(p);
Expand All @@ -47,7 +48,20 @@ public void do_not_override_existing_properties() {
}

@Test
public void use_random_port_if_zero() {
public void completeDefaults_set_default_elasticsearch_port_and_bind_address() throws Exception{
Properties p = new Properties();
Props props = new Props(p);
ProcessProperties.completeDefaults(props);

String address = props.value("sonar.search.host");
assertThat(address).isNotEmpty();
assertThat(InetAddress.getByName(address).isLoopbackAddress()).isTrue();

assertThat(props.valueAsInt("sonar.search.port")).isEqualTo(9001);
}

@Test
public void completeDefaults_sets_the_port_of_elasticsearch_if_value_is_zero() {
Properties p = new Properties();
p.setProperty("sonar.search.port", "0");
Props props = new Props(p);
Expand Down
Expand Up @@ -54,7 +54,7 @@ public class SearchServerTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();

private int port = NetworkUtils.freePort();
private int port = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress());
private Client client;
private SearchServer underTest;

Expand Down
Expand Up @@ -21,7 +21,7 @@

import java.io.File;
import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.URL;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
Expand Down Expand Up @@ -54,17 +54,17 @@ public void start() throws Exception {
props.set("sonar.path.logs", temp.newFolder().getAbsolutePath());

// start server on a random port
int httpPort = NetworkUtils.freePort();
int ajpPort = NetworkUtils.freePort();
InetAddress address = InetAddress.getLoopbackAddress();
int httpPort = NetworkUtils.getNextAvailablePort(address);
props.set("sonar.web.host", address.getHostAddress());
props.set("sonar.web.port", String.valueOf(httpPort));
props.set("sonar.ajp.port", String.valueOf(ajpPort));
EmbeddedTomcat tomcat = new EmbeddedTomcat(props);
assertThat(tomcat.getStatus()).isEqualTo(EmbeddedTomcat.Status.DOWN);
tomcat.start();
assertThat(tomcat.getStatus()).isEqualTo(EmbeddedTomcat.Status.UP);

// check that http connector accepts requests
URL url = new URL("http://" + Inet4Address.getLocalHost().getHostAddress() + ":" + httpPort);
URL url = new URL("http://" + address.getHostAddress() + ":" + httpPort);
url.openConnection().connect();

// stop server
Expand Down

0 comments on commit 7053ab1

Please sign in to comment.