/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.application.config;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Collections;
import java.util.Enumeration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.FromDataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.process.MessageException;
import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
import static org.sonar.process.ProcessProperties.CLUSTER_NETWORK_INTERFACES;
import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.JDBC_URL;
import static org.sonar.process.ProcessProperties.SEARCH_HOST;
@RunWith(Theories.class)
public class ClusterSettingsLoopbackTest {
private TestAppSettings settings;
private static final String LOOPBACK_FORBIDDEN = " must not be a loopback address";
private static final String NOT_LOCAL_ADDRESS = " is not a local address";
private static final String NOT_RESOLVABLE = " cannot be resolved";
@DataPoints("parameter")
public static final ValueAndResult[] VALID_SINGLE_IP = {
// Valid IPs
new ValueAndResult("1.2.3.4", NOT_LOCAL_ADDRESS),
new ValueAndResult("1.2.3.4:9001", NOT_LOCAL_ADDRESS),
new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
// Valid Name
new ValueAndResult("www.sonarqube.org", NOT_LOCAL_ADDRESS),
new ValueAndResult("www.google.fr", NOT_LOCAL_ADDRESS),
new ValueAndResult("www.google.com, www.sonarsource.com, wwww.sonarqube.org", NOT_LOCAL_ADDRESS),
new ValueAndResult("...", NOT_RESOLVABLE),
new ValueAndResult("භඦආ\uD801\uDC8C\uD801\uDC8B", NOT_RESOLVABLE),
// Valide IPs List
new ValueAndResult("1.2.3.4,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
new ValueAndResult("1.2.3.4:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,1.2.3.4:9001", NOT_LOCAL_ADDRESS),
new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccc", NOT_LOCAL_ADDRESS),
// Loopback IPs
new ValueAndResult("localhost", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.0.0.1", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.1.1.1", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.243.136.241", LOOPBACK_FORBIDDEN),
new ValueAndResult("::1", LOOPBACK_FORBIDDEN),
new ValueAndResult("0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
// Loopback IPs list
new ValueAndResult("127.0.0.1,192.168.11.25", LOOPBACK_FORBIDDEN),
new ValueAndResult("192.168.11.25,127.1.1.1", LOOPBACK_FORBIDDEN),
new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
new ValueAndResult("0:0:0:0:0:0:0:1,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
new ValueAndResult("2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,::1", LOOPBACK_FORBIDDEN),
new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("127.0.0.1,192.168.11.25:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("192.168.11.25:9001,127.1.1.1:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[0:0:0:0:0:0:0:1]:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[::1]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN)
};
@DataPoints("key")
public static final Key[] KEYS = {
new Key(SEARCH_HOST, false, false),
new Key(CLUSTER_NETWORK_INTERFACES, true, false),
new Key(CLUSTER_SEARCH_HOSTS, true, true),
new Key(CLUSTER_HOSTS, true, true)
};
@DataPoints("unresolvable_hosts")
public static final String[] UNRESOLVABLE_HOSTS = {
};
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void resetSettings() {
settings = getClusterSettings();
}
@Theory
public void accept_throws_MessageException(
@FromDataPoints("key") Key propertyKey,
@FromDataPoints("parameter") ValueAndResult valueAndResult) {
// Skip the test if the value is a list and if the key is not accepting a list
if ((valueAndResult.isList() && propertyKey.acceptList) || !valueAndResult.isList()) {
settings.set(propertyKey.getKey(), valueAndResult.getValue());
// If the key accept non local IPs there won't be any exception
if (!propertyKey.acceptNonLocal || valueAndResult.getMessage() != NOT_LOCAL_ADDRESS) {
expectedException.expect(MessageException.class);
expectedException.expectMessage(valueAndResult.getMessage());
}
new ClusterSettings().accept(settings.getProps());
}
}
private static TestAppSettings getClusterSettings() {
String localAddress = null;
try {
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface networkInterface : Collections.list(nets)) {
if (!networkInterface.isLoopback() && networkInterface.isUp()) {
localAddress = networkInterface.getInetAddresses().nextElement().getHostAddress();
}
}
if (localAddress == null) {
throw new RuntimeException("Cannot find a non loopback card required for tests");
}
} catch (SocketException e) {
throw new RuntimeException("Cannot find a non loopback card required for tests");
}
TestAppSettings testAppSettings = new TestAppSettings()
.set(CLUSTER_ENABLED, "true")
.set(CLUSTER_SEARCH_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
.set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
.set(SEARCH_HOST, localAddress)
.set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
return testAppSettings;
}
private static class Key {
private final String key;
private final boolean acceptList;
private final boolean acceptNonLocal;
private Key(String key, boolean acceptList, boolean acceptNonLocal) {
this.key = key;
this.acceptList = acceptList;
this.acceptNonLocal = acceptNonLocal;
}
public String getKey() {
return key;
}
public boolean acceptList() {
return acceptList;
}
public boolean acceptNonLocal() {
return acceptNonLocal;
}
}
private static class ValueAndResult {
private final String value;
private final String message;
private ValueAndResult(String value, String message) {
this.value = value;
this.message = message;
}
public String getValue() {
return value;
}
public String getMessage() {
return message;
}
public boolean isList() {
return value != null && value.contains(",");
}
}
}