diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2014-09-12 10:19:06 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2014-09-12 10:19:06 +0200 |
commit | aeee283d2109c425828c96bc2952b947cce56566 (patch) | |
tree | e00e42fa78c92dd499a231bf9628c6f121919051 /server/sonar-process/src | |
parent | 39f52452ee8a3d414dd9f2a4dee3dfffdb082c1d (diff) | |
download | sonarqube-aeee283d2109c425828c96bc2952b947cce56566.tar.gz sonarqube-aeee283d2109c425828c96bc2952b947cce56566.zip |
SONAR-4898 refactor process monitoring
Diffstat (limited to 'server/sonar-process/src')
45 files changed, 2978 insertions, 0 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java new file mode 100644 index 00000000000..204ae1a6b6b --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java @@ -0,0 +1,133 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; + +import javax.annotation.Nullable; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.IOException; +import java.security.Key; +import java.security.SecureRandom; + +final class AesCipher implements Cipher { + + // Can't be increased because of Java 6 policy files : + // https://confluence.terena.org/display/~visser/No+256+bit+ciphers+for+Java+apps + // http://java.sun.com/javase/6/webnotes/install/jre/README + public static final int KEY_SIZE_IN_BITS = 128; + + private static final String CRYPTO_KEY = "AES"; + + /** + * Duplication from CoreProperties.ENCRYPTION_SECRET_KEY_PATH + */ + static final String ENCRYPTION_SECRET_KEY_PATH = "sonar.secretKeyPath"; + + private String pathToSecretKey; + + AesCipher(@Nullable String pathToSecretKey) { + this.pathToSecretKey = pathToSecretKey; + } + + @Override + public String encrypt(String clearText) { + try { + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY); + cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile()); + return new String(Base64.encodeBase64(cipher.doFinal(clearText.getBytes("UTF-8")))); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public String decrypt(String encryptedText) { + try { + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY); + cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile()); + byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText))); + return new String(cipherData); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * This method checks the existence of the file, but not the validity of the contained key. + */ + boolean hasSecretKey() { + String path = getPathToSecretKey(); + if (StringUtils.isNotBlank(path)) { + File file = new File(path); + return file.exists() && file.isFile(); + } + return false; + } + + private Key loadSecretFile() throws IOException { + String path = getPathToSecretKey(); + return loadSecretFileFromFile(path); + } + + Key loadSecretFileFromFile(String path) throws IOException { + if (StringUtils.isBlank(path)) { + throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH); + } + File file = new File(path); + if (!file.exists() || !file.isFile()) { + throw new IllegalStateException("The property " + ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path); + } + String s = FileUtils.readFileToString(file); + if (StringUtils.isBlank(s)) { + throw new IllegalStateException("No secret key in the file: " + path); + } + return new SecretKeySpec(Base64.decodeBase64(StringUtils.trim(s)), CRYPTO_KEY); + } + + String generateRandomSecretKey() { + try { + KeyGenerator keyGen = KeyGenerator.getInstance(CRYPTO_KEY); + keyGen.init(KEY_SIZE_IN_BITS, new SecureRandom()); + SecretKey secretKey = keyGen.generateKey(); + return new String(Base64.encodeBase64(secretKey.getEncoded())); + + } catch (Exception e) { + throw new IllegalStateException("Fail to generate secret key", e); + } + } + + String getPathToSecretKey() { + if (StringUtils.isBlank(pathToSecretKey)) { + pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath(); + } + return pathToSecretKey; + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java b/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java new file mode 100644 index 00000000000..5eb3eecd541 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java @@ -0,0 +1,35 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.codec.binary.Base64; + +final class Base64Cipher implements Cipher { + @Override + public String encrypt(String clearText) { + return new String(Base64.encodeBase64(clearText.getBytes())); + } + + @Override + public String decrypt(String encryptedText) { + return new String(Base64.decodeBase64(encryptedText)); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Cipher.java b/server/sonar-process/src/main/java/org/sonar/process/Cipher.java new file mode 100644 index 00000000000..4c437057757 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Cipher.java @@ -0,0 +1,27 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +interface Cipher { + String encrypt(String clearText); + + String decrypt(String encryptedText); +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java new file mode 100644 index 00000000000..b4f86457555 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java @@ -0,0 +1,70 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.text.StrSubstitutor; + +import java.io.File; +import java.io.FileReader; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; + +public final class ConfigurationUtils { + + private ConfigurationUtils() { + // Utility class + } + + public static Properties interpolateVariables(Properties properties, Map<String, String> variables) { + Properties result = new Properties(); + Enumeration keys = properties.keys(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + String value = (String) properties.get(key); + String interpolatedValue = StrSubstitutor.replace(value, variables, "${env:", "}"); + result.setProperty(key, interpolatedValue); + } + return result; + } + + static Props loadPropsFromCommandLineArgs(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Only a single command-line argument is accepted " + + "(absolute path to configuration file)"); + } + + File propertyFile = new File(args[0]); + Properties properties = new Properties(); + FileReader reader = null; + try { + reader = new FileReader(propertyFile); + properties.load(reader); + } catch (Exception e) { + throw new IllegalStateException("Could not read properties from file: " + args[0], e); + } finally { + IOUtils.closeQuietly(reader); + FileUtils.deleteQuietly(propertyFile); + } + return new Props(properties); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Encryption.java b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java new file mode 100644 index 00000000000..cca05e6c780 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java @@ -0,0 +1,64 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @since 3.0 + */ +public final class Encryption { + + private static final String BASE64_ALGORITHM = "b64"; + + private static final String AES_ALGORITHM = "aes"; + private final AesCipher aesCipher; + + private final Map<String, Cipher> ciphers = new HashMap<String, Cipher>(); + private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)"); + + public Encryption(@Nullable String pathToSecretKey) { + aesCipher = new AesCipher(pathToSecretKey); + ciphers.put(BASE64_ALGORITHM, new Base64Cipher()); + ciphers.put(AES_ALGORITHM, aesCipher); + } + + public boolean isEncrypted(String value) { + return value.indexOf('{') == 0 && value.indexOf('}') > 1; + } + + public String decrypt(String encryptedText) { + Matcher matcher = ENCRYPTED_PATTERN.matcher(encryptedText); + if (matcher.matches()) { + Cipher cipher = ciphers.get(matcher.group(1).toLowerCase(Locale.ENGLISH)); + if (cipher != null) { + return cipher.decrypt(matcher.group(2)); + } + } + return encryptedText; + } + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java b/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java new file mode 100644 index 00000000000..c0cf02bcc89 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java @@ -0,0 +1,81 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.remote.JMXServiceURL; + +import java.lang.management.ManagementFactory; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.MalformedURLException; + +public class JmxUtils { + + private JmxUtils() { + // only static stuff + } + + public static final String DOMAIN = "org.sonar"; + public static final String NAME_PROPERTY = "name"; + + public static final String WEB_SERVER_NAME = "web"; + public static final String SEARCH_SERVER_NAME = "search"; + + public static ObjectName objectName(String name) { + try { + return new ObjectName(DOMAIN, NAME_PROPERTY, name); + } catch (MalformedObjectNameException e) { + throw new IllegalStateException("Cannot create ObjectName for " + name, e); + } + } + + public static void registerMBean(Object mbean, String name) { + try { + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + ObjectName oName = objectName(name); + // Check if already registered in JVM (might run multiple instance in JUnits) + if (mbeanServer.isRegistered(oName)) { + mbeanServer.unregisterMBean(oName); + } + mbeanServer.registerMBean(mbean, oName); + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new IllegalStateException("Fail to register JMX MBean named " + name, e); + } + } + + public static JMXServiceURL serviceUrl(InetAddress host, int port) { + String address = host.getHostAddress(); + if (host instanceof Inet6Address) { + // See http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXServiceURL.html + // "The host is a host name, an IPv4 numeric host address, or an IPv6 numeric address enclosed in square brackets." + address = String.format("[%s]", address); + } + try { + return new JMXServiceURL("rmi", address, port, String.format("/jndi/rmi://%s:%d/jmxrmi", address, port)); + } catch (MalformedURLException e) { + throw new IllegalStateException("JMX url does not look well formed", e); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java new file mode 100644 index 00000000000..a492ac751ca --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java @@ -0,0 +1,56 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +public class Lifecycle { + + private State state = State.INIT; + + public State getState() { + return state; + } + + public synchronized boolean tryToMoveTo(State to) { + if (state.ordinal() < to.ordinal()) { + state = to; + return true; + } + return false; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Lifecycle lifecycle = (Lifecycle) o; + return state == lifecycle.state; + } + + @Override + public int hashCode() { + return state.hashCode(); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java b/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java new file mode 100644 index 00000000000..2302a626d2c --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java @@ -0,0 +1,71 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +public class LoopbackAddress { + + private static InetAddress instance; + + private LoopbackAddress() { + // only static stuff + } + + /** + * Quite similar to {@code InetAddress.getLoopbackAddress()} which was introduced in Java 7. This + * method aims to support Java 6. It returns an IPv4 address, but not IPv6 in order to + * support {@code -Djava.net.preferIPv4Stack=true} which is recommended for Elasticsearch. + */ + public static InetAddress get() { + if (instance == null) { + try { + instance = doGet(NetworkInterface.getNetworkInterfaces()); + } catch (SocketException e) { + throw new IllegalStateException("Fail to browse network interfaces", e); + } + + } + return instance; + } + + static InetAddress doGet(Enumeration<NetworkInterface> ifaces) { + InetAddress result = null; + while (ifaces.hasMoreElements() && result == null) { + NetworkInterface iface = ifaces.nextElement(); + Enumeration<InetAddress> addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr.isLoopbackAddress() && addr instanceof Inet4Address) { + result = addr; + break; + } + } + } + if (result == null) { + throw new IllegalStateException("Impossible to get a IPv4 loopback address"); + } + return result; + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/MessageException.java b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java new file mode 100644 index 00000000000..5b86ef66c64 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java @@ -0,0 +1,36 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +public class MessageException extends RuntimeException { + public MessageException(String message) { + super(message); + } + + /** + * Does not fill in the stack trace + * + * @see Throwable#fillInStackTrace() + */ + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java new file mode 100644 index 00000000000..2389fa5aa18 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java @@ -0,0 +1,86 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class MinimumViableSystem { + + private final Map<String, String> requiredJavaOptions = new HashMap<String, String>(); + + public MinimumViableSystem setRequiredJavaOption(String propertyKey, String expectedValue) { + requiredJavaOptions.put(propertyKey, expectedValue); + return this; + } + + /** + * Entry point for all checks + */ + public void check() { + checkJavaVersion(); + checkJavaOptions(); + checkWritableTempDir(); + } + + /** + * Verify that temp directory is writable + */ + private void checkWritableTempDir() { + checkWritableDir(System.getProperty("java.io.tmpdir")); + } + + void checkWritableDir(String tempPath) { + try { + File tempFile = File.createTempFile("check", "tmp", new File(tempPath)); + FileUtils.deleteQuietly(tempFile); + } catch (IOException e) { + throw new IllegalStateException(String.format("Temp directory is not writable: %s", tempPath), e); + } + } + + void checkJavaOptions() { + for (Map.Entry<String, String> entry : requiredJavaOptions.entrySet()) { + String value = System.getProperty(entry.getKey()); + if (!StringUtils.equals(value, entry.getValue())) { + throw new MessageException(String.format( + "JVM option '%s' must be set to '%s'. Got '%s'", entry.getKey(), entry.getValue(), StringUtils.defaultString(value))); + } + } + } + + void checkJavaVersion() { + String javaVersion = System.getProperty("java.specification.version"); + checkJavaVersion(javaVersion); + } + + void checkJavaVersion(String javaVersion) { + if (!javaVersion.startsWith("1.6") && !javaVersion.startsWith("1.7") && !javaVersion.startsWith("1.8")) { + // still better than "java.lang.UnsupportedClassVersionError: Unsupported major.minor version 49.0 + throw new MessageException(String.format("Supported versions of Java are 1.6, 1.7 and 1.8. Got %s.", javaVersion)); + } + } + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java b/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java new file mode 100644 index 00000000000..6ee84d00744 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java @@ -0,0 +1,31 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +public interface MonitoredProcess extends Terminable { + + /** + * Starts and blocks until ready + */ + void start(); + + void awaitTermination(); + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java new file mode 100644 index 00000000000..074cb8cf5c2 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java @@ -0,0 +1,40 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import java.net.ServerSocket; + +public class NetworkUtils { + + private NetworkUtils() { + // only static stuff + } + + public static int freePort() { + try { + ServerSocket s = new ServerSocket(0); + int port = s.getLocalPort(); + s.close(); + return port; + } catch (Exception e) { + throw new IllegalStateException("Can not find an open network port", e); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java new file mode 100644 index 00000000000..79ec79f74b7 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java @@ -0,0 +1,148 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class ProcessEntryPoint implements ProcessMXBean { + + public static final String PROPERTY_PROCESS_KEY = "process.key"; + public static final String PROPERTY_AUTOKILL_DISABLED = "process.autokill.disabled"; + public static final String PROPERTY_AUTOKILL_PING_TIMEOUT = "process.autokill.pingTimeout"; + public static final String PROPERTY_AUTOKILL_PING_INTERVAL = "process.autokill.pingInterval"; + public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout"; + + private final Props props; + private final Lifecycle lifecycle = new Lifecycle(); + private volatile MonitoredProcess monitoredProcess; + private volatile long lastPing = 0L; + private volatile StopperThread stopperThread; + private final SystemExit exit; + private Thread shutdownHook = new Thread(new Runnable() { + @Override + public void run() { + exit.setInShutdownHook(); + terminate(); + } + }); + + ProcessEntryPoint(Props props, SystemExit exit) { + this.props = props; + this.exit = exit; + } + + public Props getProps() { + return props; + } + + /** + * Launch process and waits until it's down + */ + public void launch(MonitoredProcess mp) { + if (!lifecycle.tryToMoveTo(State.STARTING)) { + throw new IllegalStateException("Already started"); + } + monitoredProcess = mp; + + // TODO check if these properties are available in System Info + JmxUtils.registerMBean(this, props.nonNullValue(PROPERTY_PROCESS_KEY)); + Runtime.getRuntime().addShutdownHook(shutdownHook); + if (!props.valueAsBoolean(PROPERTY_AUTOKILL_DISABLED, false)) { + // mainly for Java Debugger + scheduleAutokill(); + } + + try { + monitoredProcess.start(); + if (lifecycle.tryToMoveTo(State.STARTED)) { + monitoredProcess.awaitTermination(); + } + } catch (Exception ignored) { + } finally { + terminate(); + } + } + + @Override + public boolean isReady() { + return lifecycle.getState() == State.STARTED; + } + + @Override + public void ping() { + lastPing = System.currentTimeMillis(); + } + + /** + * Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread}) + */ + @Override + public void terminate() { + if (lifecycle.tryToMoveTo(State.STOPPING)) { + stopperThread = new StopperThread(monitoredProcess, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT))); + stopperThread.start(); + } + try { + // stopperThread is not null for sure + // join() does nothing if thread already finished + stopperThread.join(); + lifecycle.tryToMoveTo(State.STOPPED); + } catch (InterruptedException e) { + // nothing to do, the process is going to be exited + } + exit.exit(0); + } + + private void scheduleAutokill() { + final long autokillPingTimeoutMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_TIMEOUT); + long autokillPingIntervalMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_INTERVAL); + Runnable autokiller = new Runnable() { + @Override + public void run() { + long time = System.currentTimeMillis(); + if (time - lastPing > autokillPingTimeoutMs) { + LoggerFactory.getLogger(getClass()).info(String.format( + "Did not receive any ping during %d seconds. Shutting down.", autokillPingTimeoutMs / 1000)); + terminate(); + } + } + }; + lastPing = System.currentTimeMillis(); + ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1); + monitor.scheduleWithFixedDelay(autokiller, autokillPingIntervalMs, autokillPingIntervalMs, TimeUnit.MILLISECONDS); + } + + State getState() { + return lifecycle.getState(); + } + + Thread getShutdownHook() { + return shutdownHook; + } + + public static ProcessEntryPoint createForArguments(String[] args) { + Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); + return new ProcessEntryPoint(props, new SystemExit()); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java new file mode 100644 index 00000000000..dacddd91847 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java @@ -0,0 +1,53 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; +import org.slf4j.LoggerFactory; + +public class ProcessLogging { + + private static final String PATH_LOGS_PROPERTY = "sonar.path.logs"; + + public void configure(Props props, String logbackXmlResource) { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + context.putProperty(PATH_LOGS_PROPERTY, props.nonNullValue(PATH_LOGS_PROPERTY)); + doConfigure(configurator, logbackXmlResource); + } catch (JoranException ignored) { + // StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + + } + + /** + * Extracted only for unit testing + */ + void doConfigure(JoranConfigurator configurator, String logbackXmlResource) throws JoranException { + configurator.doConfigure(getClass().getResource(logbackXmlResource)); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java new file mode 100644 index 00000000000..3d024d420eb --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java @@ -0,0 +1,28 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +public interface ProcessMXBean extends Terminable { + + boolean isReady(); + + void ping(); + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java new file mode 100644 index 00000000000..bdefa116949 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java @@ -0,0 +1,77 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.io.IOUtils; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; + +public class ProcessUtils { + + private ProcessUtils() { + // only static stuff + } + + /** + * Do not abuse to this method. It uses exceptions to get status. + * @return false if process is null or terminated, else true. + */ + public static boolean isAlive(@Nullable Process process) { + boolean alive = false; + if (process != null) { + try { + process.exitValue(); + } catch (IllegalThreadStateException ignored) { + alive = true; + } + } + return alive; + } + + /** + * Destroys process (equivalent to kill -9) if alive + * @return true if the process was destroyed, false if process is null or already destroyed. + */ + public static boolean destroyQuietly(@Nullable Process process) { + boolean destroyed = false; + if (isAlive(process)) { + try { + process.destroy(); + while (isAlive(process)) { + // destroy() sends the signal, it does not wait for the process to be down + Thread.sleep(100L); + } + destroyed = true; + } catch (Exception e) { + LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process); + } + } + return destroyed; + } + + public static void closeStreams(@Nullable Process process) { + if (process != null) { + IOUtils.closeQuietly(process.getInputStream()); + IOUtils.closeQuietly(process.getOutputStream()); + IOUtils.closeQuietly(process.getErrorStream()); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Props.java b/server/sonar-process/src/main/java/org/sonar/process/Props.java new file mode 100644 index 00000000000..b868702eafc --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java @@ -0,0 +1,120 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.lang.StringUtils; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import java.io.File; +import java.util.Properties; + +public class Props { + + private final Properties properties; + private final Encryption encryption; + + public Props(Properties props) { + this.properties = props; + this.encryption = new Encryption(props.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH)); + } + + public boolean contains(String key) { + return properties.containsKey(key); + } + + @CheckForNull + public String value(String key) { + String value = properties.getProperty(key); + if (value != null && encryption.isEncrypted(value)) { + value = encryption.decrypt(value); + } + return value; + } + + public String nonNullValue(String key) { + String value = value(key); + if (value == null) { + throw new IllegalArgumentException("Missing property: " + key); + } + return value; + } + + @CheckForNull + public String value(String key, @Nullable String defaultValue) { + String s = value(key); + return s == null ? defaultValue : s; + } + + public boolean valueAsBoolean(String key) { + String s = value(key); + return s != null && Boolean.parseBoolean(s); + } + + public boolean valueAsBoolean(String key, boolean defaultValue) { + String s = value(key); + return s != null ? Boolean.parseBoolean(s) : defaultValue; + } + + public File nonNullValueAsFile(String key) { + String s = value(key); + if (s == null) { + throw new IllegalArgumentException("Property " + key + " is missing"); + } + return new File(s); + } + + @CheckForNull + public Integer valueAsInt(String key) { + String s = value(key); + if (s != null && !"".equals(s)) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new IllegalStateException("Value of property " + key + " is not an integer: " + s, e); + } + } + return null; + } + + public int valueAsInt(String key, int defaultValue) { + Integer i = valueAsInt(key); + return i == null ? defaultValue : i; + } + + public Properties rawProperties() { + return properties; + } + + public Props set(String key, @Nullable String value) { + if (value != null) { + properties.setProperty(key, value); + } + return this; + } + + public void setDefault(String key, String value) { + String s = properties.getProperty(key); + if (StringUtils.isBlank(s)) { + properties.setProperty(key, value); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/State.java b/server/sonar-process/src/main/java/org/sonar/process/State.java new file mode 100644 index 00000000000..9d773d2bc23 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/State.java @@ -0,0 +1,26 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +public enum State { + + INIT, STARTING, STARTED, STOPPING, STOPPED + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java new file mode 100644 index 00000000000..2d0c6734b30 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java @@ -0,0 +1,57 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Gracefully stops process, but exits JVM if too long + */ +class StopperThread extends Thread { + + private final Terminable terminable; + private final long terminationTimeout; + + StopperThread(Terminable terminable, long terminationTimeout) { + super("Stopper"); + this.terminable = terminable; + this.terminationTimeout = terminationTimeout; + } + + @Override + public void run() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(new Runnable() { + @Override + public void run() { + terminable.terminate(); + } + }); + try { + future.get(terminationTimeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + future.cancel(true); + executor.shutdownNow(); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java b/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java new file mode 100644 index 00000000000..8f12a210633 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Calls {@link System#exit(int)} except from shutdown hooks, to prevent + * deadlocks. See http://stackoverflow.com/a/19552359/229031 + */ +public class SystemExit { + + private final AtomicBoolean inShutdownHook = new AtomicBoolean(false); + + public void exit(int code) { + if (!inShutdownHook.get()) { + doExit(code); + } + } + + public boolean isInShutdownHook() { + return inShutdownHook.get(); + } + + /** + * Declarative approach. I don't know how to get this lifecycle state from Java API. + */ + public void setInShutdownHook() { + inShutdownHook.set(true); + } + + void doExit(int code) { + System.exit(code); + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Terminable.java b/server/sonar-process/src/main/java/org/sonar/process/Terminable.java new file mode 100644 index 00000000000..a8670609fe2 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Terminable.java @@ -0,0 +1,28 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +/** + * This term "terminate" is used in order to not conflict with {@link Thread#stop()}. + */ +public interface Terminable { + + void terminate(); +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/package-info.java new file mode 100644 index 00000000000..09da5ce266d --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.process; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java b/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java new file mode 100644 index 00000000000..8350eafaa3e --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java @@ -0,0 +1,185 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import com.google.common.io.Resources; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import javax.crypto.BadPaddingException; +import java.io.File; +import java.security.InvalidKeyException; +import java.security.Key; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + + +public class AesCipherTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void generateRandomSecretKey() { + AesCipher cipher = new AesCipher(null); + + String key = cipher.generateRandomSecretKey(); + + assertThat(StringUtils.isNotBlank(key)).isTrue(); + assertThat(Base64.isArrayByteBase64(key.getBytes())).isTrue(); + } + + @Test + public void encrypt() throws Exception { + AesCipher cipher = new AesCipher(pathToSecretKey()); + + String encryptedText = cipher.encrypt("this is a secret"); + + assertThat(StringUtils.isNotBlank(encryptedText)).isTrue(); + assertThat(Base64.isArrayByteBase64(encryptedText.getBytes())).isTrue(); + } + + @Test + public void encrypt_bad_key() throws Exception { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Invalid AES key"); + + AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt")); + + cipher.encrypt("this is a secret"); + } + + @Test + public void decrypt() throws Exception { + AesCipher cipher = new AesCipher(pathToSecretKey()); + + // the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt + String clearText = cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + + assertThat(clearText).isEqualTo("this is a secret"); + } + + @Test + public void decrypt_bad_key() throws Exception { + AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt")); + + try { + cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + fail(); + + } catch (RuntimeException e) { + assertThat(e.getCause()).isInstanceOf(InvalidKeyException.class); + } + } + + @Test + public void decrypt_other_key() throws Exception { + AesCipher cipher = new AesCipher(getPath("other_secret_key.txt")); + + try { + // text encrypted with another key + cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + fail(); + + } catch (RuntimeException e) { + assertThat(e.getCause()).isInstanceOf(BadPaddingException.class); + } + } + + @Test + public void encryptThenDecrypt() throws Exception { + AesCipher cipher = new AesCipher(pathToSecretKey()); + + assertThat(cipher.decrypt(cipher.encrypt("foo"))).isEqualTo("foo"); + } + + @Test + public void testDefaultPathToSecretKey() { + AesCipher cipher = new AesCipher(null); + + String path = cipher.getPathToSecretKey(); + + assertThat(StringUtils.isNotBlank(path)).isTrue(); + assertThat(new File(path).getName()).isEqualTo("sonar-secret.txt"); + } + + @Test + public void loadSecretKeyFromFile() throws Exception { + AesCipher cipher = new AesCipher(null); + Key secretKey = cipher.loadSecretFileFromFile(pathToSecretKey()); + assertThat(secretKey.getAlgorithm()).isEqualTo("AES"); + assertThat(secretKey.getEncoded().length).isGreaterThan(10); + } + + @Test + public void loadSecretKeyFromFile_trim_content() throws Exception { + String path = getPath("non_trimmed_secret_key.txt"); + AesCipher cipher = new AesCipher(null); + + Key secretKey = cipher.loadSecretFileFromFile(path); + + assertThat(secretKey.getAlgorithm()).isEqualTo("AES"); + assertThat(secretKey.getEncoded().length).isGreaterThan(10); + } + + @Test + public void loadSecretKeyFromFile_file_does_not_exist() throws Exception { + thrown.expect(IllegalStateException.class); + + AesCipher cipher = new AesCipher(null); + cipher.loadSecretFileFromFile("/file/does/not/exist"); + } + + @Test + public void loadSecretKeyFromFile_no_property() throws Exception { + thrown.expect(IllegalStateException.class); + + AesCipher cipher = new AesCipher(null); + cipher.loadSecretFileFromFile(null); + } + + @Test + public void hasSecretKey() throws Exception { + AesCipher cipher = new AesCipher(pathToSecretKey()); + + assertThat(cipher.hasSecretKey()).isTrue(); + } + + @Test + public void doesNotHaveSecretKey() throws Exception { + AesCipher cipher = new AesCipher("/my/twitter/id/is/SimonBrandhof"); + + assertThat(cipher.hasSecretKey()).isFalse(); + } + + private static String getPath(String file) { + return Resources.getResource(AesCipherTest.class, "AesCipherTest/" + file).getPath(); + } + + private static String pathToSecretKey() throws Exception { + return getPath("aes_secret_key.txt"); + } + +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java b/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java new file mode 100644 index 00000000000..2045cd4516d --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; + +public abstract class BaseProcessTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public static final String DUMMY_OK_APP = "org.sonar.application.DummyOkProcess"; + + int freePort; + File dummyAppJar; + Process proc; + + @Before + public void setup() throws IOException { + ServerSocket socket = new ServerSocket(0); + freePort = socket.getLocalPort(); + socket.close(); + + dummyAppJar = FileUtils.toFile(getClass().getResource("/sonar-dummy-app.jar")); + } + + @After + public void tearDown() { + if (proc != null) { + ProcessUtils.destroyQuietly(proc); + } + } + +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java new file mode 100644 index 00000000000..de928b93850 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java @@ -0,0 +1,95 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import com.google.common.collect.Maps; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.util.Map; +import java.util.Properties; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class ConfigurationUtilsTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void shouldInterpolateVariables() { + Properties input = new Properties(); + input.setProperty("hello", "world"); + input.setProperty("url", "${env:SONAR_JDBC_URL}"); + input.setProperty("do_not_change", "${SONAR_JDBC_URL}"); + Map<String, String> variables = Maps.newHashMap(); + variables.put("SONAR_JDBC_URL", "jdbc:h2:mem"); + + Properties output = ConfigurationUtils.interpolateVariables(input, variables); + + assertThat(output).hasSize(3); + assertThat(output.getProperty("hello")).isEqualTo("world"); + assertThat(output.getProperty("url")).isEqualTo("jdbc:h2:mem"); + assertThat(output.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}"); + + // input is not changed + assertThat(input).hasSize(3); + assertThat(input.getProperty("hello")).isEqualTo("world"); + assertThat(input.getProperty("url")).isEqualTo("${env:SONAR_JDBC_URL}"); + assertThat(input.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}"); + } + + @Test + public void loadPropsFromCommandLineArgs_missing_argument() throws Exception { + try { + ConfigurationUtils.loadPropsFromCommandLineArgs(new String[0]); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage()).startsWith("Only a single command-line argument is accepted"); + } + } + + @Test + public void loadPropsFromCommandLineArgs_load_properties_from_file() throws Exception { + File propsFile = temp.newFile(); + FileUtils.write(propsFile, "foo=bar"); + + Props result = ConfigurationUtils.loadPropsFromCommandLineArgs(new String[] {propsFile.getAbsolutePath()}); + assertThat(result.value("foo")).isEqualTo("bar"); + assertThat(result.rawProperties()).hasSize(1); + } + + @Test + public void loadPropsFromCommandLineArgs_file_does_not_exist() throws Exception { + File propsFile = temp.newFile(); + FileUtils.deleteQuietly(propsFile); + + try { + ConfigurationUtils.loadPropsFromCommandLineArgs(new String[]{propsFile.getAbsolutePath()}); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Could not read properties from file: " + propsFile.getAbsolutePath()); + } + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java b/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java new file mode 100644 index 00000000000..0c11856b0fa --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class EncryptionTest { + + @Test + public void isEncrypted() { + Encryption encryption = new Encryption(null); + assertThat(encryption.isEncrypted("{aes}ADASDASAD"), is(true)); + assertThat(encryption.isEncrypted("{b64}ADASDASAD"), is(true)); + assertThat(encryption.isEncrypted("{abc}ADASDASAD"), is(true)); + + assertThat(encryption.isEncrypted("{}"), is(false)); + assertThat(encryption.isEncrypted("{foo"), is(false)); + assertThat(encryption.isEncrypted("foo{aes}"), is(false)); + } + + @Test + public void decrypt() { + Encryption encryption = new Encryption(null); + assertThat(encryption.decrypt("{b64}Zm9v"), is("foo")); + } + + @Test + public void decrypt_unknown_algorithm() { + Encryption encryption = new Encryption(null); + assertThat(encryption.decrypt("{xxx}Zm9v"), is("{xxx}Zm9v")); + } + + @Test + public void decrypt_uncrypted_text() { + Encryption encryption = new Encryption(null); + assertThat(encryption.decrypt("foo"), is("foo")); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java new file mode 100644 index 00000000000..599ea5d7a30 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java @@ -0,0 +1,120 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.remote.JMXServiceURL; + +import java.lang.management.ManagementFactory; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class JmxUtilsTest { + + class MyBean implements ProcessMXBean { + + + @Override + public void terminate() { + + } + + @Override + public void ping() { + + } + + @Override + public boolean isReady() { + return true; + } + } + + @Test + public void construct_jmx_objectName() throws Exception { + MyBean mxBean = new MyBean(); + ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName()); + assertThat(objectName).isNotNull(); + assertThat(objectName.getDomain()).isEqualTo(JmxUtils.DOMAIN); + assertThat(objectName.getKeyProperty(JmxUtils.NAME_PROPERTY)).isEqualTo(mxBean.getClass().getSimpleName()); + } + + @Test + public void fail_jmx_objectName() throws Exception { + try { + JmxUtils.objectName(":"); + fail(); + } catch (Exception e) { + assertThat(e.getMessage()).isEqualTo("Cannot create ObjectName for :"); + } + } + + @Test + public void testRegisterMBean() throws Exception { + // 0 Get mbServer and create out test MXBean + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + MyBean mxBean = new MyBean(); + ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName()); + + // 1 assert that mxBean gets registered + assertThat(mbeanServer.isRegistered(objectName)).isFalse(); + JmxUtils.registerMBean(mxBean, mxBean.getClass().getSimpleName()); + assertThat(mbeanServer.isRegistered(objectName)).isTrue(); + } + + @Test + public void serviceUrl_ipv4() throws Exception { + JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet4Address.class), 1234); + assertThat(url).isNotNull(); + assertThat(url.getPort()).isEqualTo(1234); + } + + @Test + public void serviceUrl_ipv6() throws Exception { + JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet6Address.class), 1234); + assertThat(url).isNotNull(); + assertThat(url.getPort()).isEqualTo(1234); + } + + private static InetAddress ip(Class inetAddressClass) throws SocketException { + Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); + while (ifaces.hasMoreElements()) { + NetworkInterface iface = ifaces.nextElement(); + Enumeration<InetAddress> addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr.getClass().isAssignableFrom(inetAddressClass)) { + return addr; + } + } + } + throw new IllegalStateException("no ipv4 address"); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java new file mode 100644 index 00000000000..23886125212 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class LifecycleTest { + + @Test + public void equals_and_hashcode() throws Exception { + Lifecycle init = new Lifecycle(); + assertThat(init.equals(init)).isTrue(); + assertThat(init.equals(new Lifecycle())).isTrue(); + assertThat(init.equals("INIT")).isFalse(); + assertThat(init.equals(null)).isFalse(); + assertThat(init.hashCode()).isEqualTo(new Lifecycle().hashCode()); + + // different state + Lifecycle stopping = new Lifecycle(); + stopping.tryToMoveTo(State.STOPPING); + assertThat(stopping).isNotEqualTo(init); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java b/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java new file mode 100644 index 00000000000..6a8819c3a81 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import com.google.common.collect.Iterators; +import org.junit.Test; + +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.Enumeration; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class LoopbackAddressTest { + + @Test + public void get() throws Exception { + assertThat(LoopbackAddress.get()).isNotNull(); + assertThat(LoopbackAddress.get().isLoopbackAddress()).isTrue(); + assertThat(LoopbackAddress.get().getHostAddress()).isNotNull(); + } + + @Test + public void fail_to_get_loopback_address() throws Exception { + Enumeration<NetworkInterface> ifaces = Iterators.asEnumeration(Collections.<NetworkInterface>emptyList().iterator()); + try { + LoopbackAddress.doGet(ifaces); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Impossible to get a IPv4 loopback address"); + } + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java b/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java new file mode 100644 index 00000000000..83841c79952 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java @@ -0,0 +1,102 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.fest.assertions.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.fest.assertions.Fail.fail; + +public class MinimumViableSystemTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + /** + * Verifies that all checks can be verified without error. + * Test environment does not necessarily follows all checks. + */ + @Test + public void check() throws Exception { + MinimumViableSystem mve = new MinimumViableSystem(); + + try { + mve.check(); + // ok + } catch (MessageException e) { + // also ok. All other exceptions are errors. + } + } + + @Test + public void checkJavaVersion() throws Exception { + MinimumViableSystem mve = new MinimumViableSystem(); + + // yes, sources are compiled with a supported Java version! + mve.checkJavaVersion(); + mve.checkJavaVersion("1.6"); + + try { + mve.checkJavaVersion("1.9"); + fail(); + } catch (MessageException e) { + Assertions.assertThat(e).hasMessage("Supported versions of Java are 1.6, 1.7 and 1.8. Got 1.9."); + } + } + + @Test + public void checkJavaOption() throws Exception { + String key = "MinimumViableEnvironmentTest.test.prop"; + MinimumViableSystem mve = new MinimumViableSystem() + .setRequiredJavaOption(key, "true"); + + try { + System.setProperty(key, "false"); + mve.checkJavaOptions(); + fail(); + } catch (MessageException e) { + Assertions.assertThat(e).hasMessage("JVM option '" + key + "' must be set to 'true'. Got 'false'"); + } + + System.setProperty(key, "true"); + mve.checkJavaOptions(); + // do not fail + } + + @Test + public void checkWritableTempDir() throws Exception { + File dir = temp.newFolder(); + MinimumViableSystem mve = new MinimumViableSystem(); + + mve.checkWritableDir(dir.getAbsolutePath()); + + dir.delete(); + try { + mve.checkWritableDir(dir.getAbsolutePath()); + fail(); + } catch (IllegalStateException e) { + Assertions.assertThat(e).hasMessage("Temp directory is not writable: " + dir.getAbsolutePath()); + } + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java new file mode 100644 index 00000000000..09f6a597209 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java @@ -0,0 +1,61 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import java.net.ServerSocket; + +import static org.fest.assertions.Assertions.assertThat; + +public class NetworkUtilsTest { + + + @Test + public void find_free_port() throws Exception { + int port = NetworkUtils.freePort(); + assertThat(port).isGreaterThan(1024); + } + + @Test + public void find_multiple_free_port() throws Exception { + int port1 = NetworkUtils.freePort(); + int port2 = NetworkUtils.freePort(); + + assertThat(port1).isGreaterThan(1024); + assertThat(port2).isGreaterThan(1024); + + assertThat(port1).isNotSameAs(port2); + } + + @Test + public void find_multiple_free_non_adjacent_port() throws Exception { + int port1 = NetworkUtils.freePort(); + + ServerSocket socket = new ServerSocket(port1 + 1); + + int port2 = NetworkUtils.freePort(); + + assertThat(port1).isGreaterThan(1024); + assertThat(port2).isGreaterThan(1024); + + assertThat(port1).isNotSameAs(port2); + } +}
\ No newline at end of file diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java new file mode 100644 index 00000000000..55bf8d679d1 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java @@ -0,0 +1,224 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.Timeout; +import org.sonar.process.test.StandardProcess; + +import java.io.File; +import java.util.Properties; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.mock; + +public class ProcessEntryPointTest { + + SystemExit exit = mock(SystemExit.class); + + /** + * Safeguard + */ + @Rule + public Timeout timeout = new Timeout(10000); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void load_properties_from_file() throws Exception { + File propsFile = temp.newFile(); + FileUtils.write(propsFile, "sonar.foo=bar"); + + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[]{propsFile.getAbsolutePath()}); + assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar"); + } + + @Test + public void test_initial_state() throws Exception { + Props props = new Props(new Properties()); + ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + + assertThat(entryPoint.getProps()).isSameAs(props); + assertThat(entryPoint.isReady()).isFalse(); + assertThat(entryPoint.getState()).isEqualTo(State.INIT); + + // do not fail + entryPoint.ping(); + } + + @Test + public void fail_to_launch_if_missing_monitor_properties() throws Exception { + Props props = new Props(new Properties()); + ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + + StandardProcess process = new StandardProcess(); + try { + entryPoint.launch(process); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Missing property: process.key"); + assertThat(process.getState()).isEqualTo(State.INIT); + } + } + + @Test + public void fail_to_launch_multiple_times() throws Exception { + Props props = new Props(new Properties()); + props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); + props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); + ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + + entryPoint.launch(new NoopProcess()); + try { + entryPoint.launch(new NoopProcess()); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Already started"); + } + } + + @Test + public void launch_then_request_graceful_termination() throws Exception { + Props props = new Props(new Properties()); + props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); + props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final StandardProcess process = new StandardProcess(); + + Thread runner = new Thread() { + @Override + public void run() { + // starts and waits until terminated + entryPoint.launch(process); + } + }; + runner.start(); + + while (process.getState() != State.STARTED) { + Thread.sleep(10L); + } + + // requests for termination -> waits until down + // Should terminate before the timeout of 30s + entryPoint.terminate(); + + assertThat(process.getState()).isEqualTo(State.STOPPED); + } + + @Test + public void autokill_if_no_pings() throws Exception { + Props props = new Props(new Properties()); + props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); + props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, "5"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, "1"); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final StandardProcess process = new StandardProcess(); + + entryPoint.launch(process); + + assertThat(process.getState()).isEqualTo(State.STOPPED); + } + + @Test + public void terminate_if_unexpected_shutdown() throws Exception { + Props props = new Props(new Properties()); + props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); + props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final StandardProcess process = new StandardProcess(); + + Thread runner = new Thread() { + @Override + public void run() { + // starts and waits until terminated + entryPoint.launch(process); + } + }; + runner.start(); + while (process.getState() != State.STARTED) { + Thread.sleep(10L); + } + + // emulate signal to shutdown process + entryPoint.getShutdownHook().start(); + while (process.getState() != State.STOPPED) { + Thread.sleep(10L); + } + // exit before test timeout, ok ! + } + + @Test + public void terminate_if_startup_error() throws Exception { + Props props = new Props(new Properties()); + props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo"); + props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); + props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final MonitoredProcess process = new StartupErrorProcess(); + + entryPoint.launch(process); + assertThat(entryPoint.getState()).isEqualTo(State.STOPPED); + } + + private static class NoopProcess implements MonitoredProcess { + + @Override + public void start() { + + } + + @Override + public void awaitTermination() { + + } + + @Override + public void terminate() { + + } + } + + private static class StartupErrorProcess implements MonitoredProcess { + + @Override + public void start() { + throw new IllegalStateException("ERROR"); + } + + @Override + public void awaitTermination() { + + } + + @Override + public void terminate() { + + } + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java new file mode 100644 index 00000000000..6f93126516e --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java @@ -0,0 +1,28 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class ProcessUtilsTest { + +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java b/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java new file mode 100644 index 00000000000..5d283b44f8f --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import java.util.Properties; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class PropsTest { + + @Test + public void of() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "bar"); + Props props = new Props(p); + + assertThat(props.value("foo")).isEqualTo("bar"); + assertThat(props.value("foo", "default value")).isEqualTo("bar"); + assertThat(props.value("unknown")).isNull(); + assertThat(props.value("unknown", "default value")).isEqualTo("default value"); + } + + @Test + public void intOf() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "33"); + p.setProperty("blank", ""); + Props props = new Props(p); + + assertThat(props.valueAsInt("foo")).isEqualTo(33); + assertThat(props.valueAsInt("foo", 44)).isEqualTo(33); + assertThat(props.valueAsInt("blank")).isNull(); + assertThat(props.valueAsInt("blank", 55)).isEqualTo(55); + assertThat(props.valueAsInt("unknown")).isNull(); + assertThat(props.valueAsInt("unknown", 44)).isEqualTo(44); + } + + @Test + public void intOf_not_integer() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "bar"); + Props props = new Props(p); + + try { + props.valueAsInt("foo"); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Value of property foo is not an integer: bar"); + } + } + + @Test + public void booleanOf() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "True"); + p.setProperty("bar", "false"); + Props props = new Props(p); + + assertThat(props.valueAsBoolean("foo")).isTrue(); + assertThat(props.valueAsBoolean("bar")).isFalse(); + assertThat(props.valueAsBoolean("unknown")).isFalse(); + } + + @Test + public void booleanOf_default_value() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "true"); + p.setProperty("bar", "false"); + Props props = new Props(p); + + assertThat(props.valueAsBoolean("unset", false)).isFalse(); + assertThat(props.valueAsBoolean("unset", true)).isTrue(); + assertThat(props.valueAsBoolean("foo", false)).isTrue(); + assertThat(props.valueAsBoolean("bar", true)).isFalse(); + } + + @Test + public void setDefault() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "foo_value"); + Props props = new Props(p); + props.setDefault("foo", "foo_def"); + props.setDefault("bar", "bar_def"); + + assertThat(props.value("foo")).isEqualTo("foo_value"); + assertThat(props.value("bar")).isEqualTo("bar_def"); + assertThat(props.value("other")).isNull(); + } + + @Test + public void set() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "old_foo"); + Props props = new Props(p); + props.set("foo", "new_foo"); + props.set("bar", "new_bar"); + + assertThat(props.value("foo")).isEqualTo("new_foo"); + assertThat(props.value("bar")).isEqualTo("new_bar"); + } + + @Test + public void raw_properties() throws Exception { + Properties p = new Properties(); + p.setProperty("encrypted_prop", "{aes}abcde"); + p.setProperty("clear_prop", "foo"); + Props props = new Props(p); + + assertThat(props.rawProperties()).hasSize(2); + // do not decrypt + assertThat(props.rawProperties().get("encrypted_prop")).isEqualTo("{aes}abcde"); + assertThat(props.rawProperties().get("clear_prop")).isEqualTo("foo"); + + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java b/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java new file mode 100644 index 00000000000..e02b07cf555 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.fest.assertions.Assertions.assertThat; + +public class SystemExitTest { + + @Test + public void do_not_exit_if_in_shutdown_hook() throws Exception { + SystemExit systemExit = new SystemExit(); + + systemExit.setInShutdownHook(); + assertThat(systemExit.isInShutdownHook()).isTrue(); + + systemExit.exit(0); + // still there + } + + @Test + public void exit_if_not_in_shutdown_hook() throws Exception { + final AtomicInteger got = new AtomicInteger(); + SystemExit systemExit = new SystemExit() { + @Override + void doExit(int code) { + got.set(code); + } + }; + + assertThat(systemExit.isInShutdownHook()).isFalse(); + systemExit.exit(1); + + assertThat(got.get()).isEqualTo(1); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java new file mode 100644 index 00000000000..307abed6d0b --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java @@ -0,0 +1,116 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process.test; + +import org.apache.commons.io.FileUtils; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.sonar.process.MonitoredProcess; +import org.sonar.process.ProcessEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.File; +import java.io.IOException; + +/** + * Http server used for testing (see MonitorTest). It accepts HTTP commands /ping and /kill to hardly exit. + * It also pushes status to temp files, so test can verify what was really done (when server went ready state and + * if it was gracefully terminated) + */ +public class HttpProcess implements MonitoredProcess { + + private final Server server; + // temp dir is specific to this process + private final File tempDir = new File(System.getProperty("java.io.tmpdir")); + + public HttpProcess(int httpPort) { + server = new Server(httpPort); + } + + @Override + public void start() { + writeTimeToFile("startingAt"); + ContextHandler context = new ContextHandler(); + context.setContextPath("/"); + context.setClassLoader(Thread.currentThread().getContextClassLoader()); + server.setHandler(context); + context.setHandler(new AbstractHandler() { + @Override + public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { + if ("/ping".equals(target)) { + request.setHandled(true); + httpServletResponse.getWriter().print("ping"); + } else if ("/kill".equals(target)) { + writeTimeToFile("killedAt"); + System.exit(0); + } + } + }); + try { + server.start(); + while (!server.isStarted()) { + Thread.sleep(100L); + } + writeTimeToFile("readyAt"); + + } catch (Exception e) { + throw new IllegalStateException("Fail to start Jetty", e); + } + } + + @Override + public void awaitTermination() { + try { + server.join(); + } catch (InterruptedException ignore) { + + } + } + + @Override + public void terminate() { + try { + if (!server.isStopped()) { + server.stop(); + writeTimeToFile("terminatedAt"); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to stop Jetty", e); + } + } + + private void writeTimeToFile(String filename) { + try { + FileUtils.write(new File(tempDir, filename), String.valueOf(System.currentTimeMillis())); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static void main(String[] args) { + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); + entryPoint.launch(new HttpProcess(entryPoint.getProps().valueAsInt("httpPort"))); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java new file mode 100644 index 00000000000..121784f633b --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java @@ -0,0 +1,81 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.process.test; + +import org.sonar.process.MonitoredProcess; +import org.sonar.process.ProcessEntryPoint; +import org.sonar.process.State; + +public class StandardProcess implements MonitoredProcess { + + private State state = State.INIT; + + private final Thread daemon = new Thread() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(100L); + } + } catch (InterruptedException e) { + // return + } + } + }; + + /** + * Blocks until started() + */ + @Override + public void start() { + state = State.STARTING; + daemon.start(); + state = State.STARTED; + } + + @Override + public void awaitTermination() { + try { + daemon.join(); + } catch (InterruptedException e) { + // interrupted by call to terminate() + } + } + + /** + * Blocks until stopped + */ + @Override + public void terminate() { + state = State.STOPPING; + daemon.interrupt(); + state = State.STOPPED; + } + + public State getState() { + return state; + } + + public static void main(String[] args) { + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); + entryPoint.launch(new StandardProcess()); + System.exit(0); + } +} diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt new file mode 100644 index 00000000000..65b98c522da --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt @@ -0,0 +1 @@ +0PZz+G+f8mjr3sPn4+AhHg==
\ No newline at end of file diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt new file mode 100644 index 00000000000..b33e179e5c8 --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt @@ -0,0 +1 @@ +badbadbad==
\ No newline at end of file diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt new file mode 100644 index 00000000000..ab83e4adc03 --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt @@ -0,0 +1,3 @@ + + 0PZz+G+f8mjr3sPn4+AhHg== + diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt new file mode 100644 index 00000000000..23f5ecf5104 --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt @@ -0,0 +1 @@ +IBxEUxZ41c8XTxyaah1Qlg==
\ No newline at end of file diff --git a/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml b/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml new file mode 100644 index 00000000000..298193e01fa --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml @@ -0,0 +1 @@ +<configuration/> diff --git a/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties b/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties new file mode 100644 index 00000000000..1577a214b3b --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties @@ -0,0 +1,212 @@ +# This file must contain only ISO 8859-1 characters +# see http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Properties.html#load(java.io.InputStream) +# +# To use an environment variable, use the following syntax : ${env:NAME_OF_ENV_VARIABLE} +# For example: +# sonar.jdbc.url= ${env:SONAR_JDBC_URL} +# +# +# See also the file conf/wrapper.conf for JVM advanced settings + + + +#-------------------------------------------------------------------------------------------------- +# DATABASE +# +# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only. +# Please use a production-ready database. Supported databases are MySQL, Oracle, PostgreSQL +# and Microsoft SQLServer. + +# Permissions to create tables, indices and triggers must be granted to JDBC user. +# The schema must be created first. +sonar.jdbc.username=sonar +sonar.jdbc.password=sonar + +#----- Embedded database H2 +# Note: it does not accept connections from remote hosts, so the +# SonarQube server and the maven plugin must be executed on the same host. + +# Comment the following line to deactivate the default embedded database. +sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar + +# directory containing H2 database files. By default it's the /data directory in the SonarQube installation. +#sonar.embeddedDatabase.dataDir= +# H2 embedded database server listening port, defaults to 9092 +#sonar.embeddedDatabase.port=9092 + + +#----- MySQL 5.x +# Comment the embedded database and uncomment the following line to use MySQL +#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true + + +#----- Oracle 10g/11g +# To connect to Oracle database: +# +# - It's recommended to use the latest version of the JDBC driver (ojdbc6.jar). +# Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html +# - Copy the driver to the directory extensions/jdbc-driver/oracle/ +# - If you need to set the schema, please refer to http://jira.codehaus.org/browse/SONAR-5000 +# - Comment the embedded database and uncomment the following line: +#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE + + +#----- PostgreSQL 8.x/9.x +# Comment the embedded database and uncomment the following property to use PostgreSQL. +# If you don't use the schema named "public", please refer to http://jira.codehaus.org/browse/SONAR-5000 +#sonar.jdbc.url=jdbc:postgresql://localhost/sonar + + +#----- Microsoft SQLServer +# The Jtds open source driver is available in extensions/jdbc-driver/mssql. More details on http://jtds.sourceforge.net +#sonar.jdbc.url=jdbc:jtds:sqlserver://localhost/sonar;SelectMethod=Cursor + + +#----- Connection pool settings +sonar.jdbc.maxActive=20 +sonar.jdbc.maxIdle=5 +sonar.jdbc.minIdle=2 +sonar.jdbc.maxWait=5000 +sonar.jdbc.minEvictableIdleTimeMillis=600000 +sonar.jdbc.timeBetweenEvictionRunsMillis=30000 + + + +#-------------------------------------------------------------------------------------------------- +# WEB SERVER + +# Binding IP address. For servers with more than one IP address, this property specifies which +# address will be used for listening on the specified ports. +# By default, ports will be used on all IP addresses associated with the server. +#sonar.web.host=0.0.0.0 + +# Web context. When set, it must start with forward slash (for example /sonarqube). +# The default value is root context (empty value). +#sonar.web.context= + +# TCP port for incoming HTTP connections. Disabled when value is -1. +#sonar.web.port=9000 + +# TCP port for incoming HTTPS connections. Disabled when value is -1 (default). +#sonar.web.https.port=-1 + +# HTTPS - the alias used to for the server certificate in the keystore. +# If not specified the first key read in the keystore is used. +#sonar.web.https.keyAlias= + +# HTTPS - the password used to access the server certificate from the +# specified keystore file. The default value is "changeit". +#sonar.web.https.keyPass=changeit + +# HTTPS - the pathname of the keystore file where is stored the server certificate. +# By default, the pathname is the file ".keystore" in the user home. +# If keystoreType doesn't need a file use empty value. +#sonar.web.https.keystoreFile= + +# HTTPS - the password used to access the specified keystore file. The default +# value is the value of sonar.web.https.keyPass. +#sonar.web.https.keystorePass= + +# HTTPS - the type of keystore file to be used for the server certificate. +# The default value is JKS (Java KeyStore). +#sonar.web.https.keystoreType=JKS + +# HTTPS - the name of the keystore provider to be used for the server certificate. +# If not specified, the list of registered providers is traversed in preference order +# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType). +#sonar.web.https.keystoreProvider= + +# HTTPS - the pathname of the truststore file which contains trusted certificate authorities. +# By default, this would be the cacerts file in your JRE. +# If truststoreFile doesn't need a file use empty value. +#sonar.web.https.truststoreFile= + +# HTTPS - the password used to access the specified truststore file. +#sonar.web.https.truststorePass= + +# HTTPS - the type of truststore file to be used. +# The default value is JKS (Java KeyStore). +#sonar.web.https.truststoreType=JKS + +# HTTPS - the name of the truststore provider to be used for the server certificate. +# If not specified, the list of registered providers is traversed in preference order +# and the first provider that supports the truststore type is used (see sonar.web.https.truststoreType). +#sonar.web.https.truststoreProvider= + +# HTTPS - whether to enable client certificate authentication. +# The default is false (client certificates disabled). +# Other possible values are 'want' (certificates will be requested, but not required), +# and 'true' (certificates are required). +#sonar.web.https.clientAuth=false + +# The maximum number of connections that the server will accept and process at any given time. +# When this number has been reached, the server will not accept any more connections until +# the number of connections falls below this value. The operating system may still accept connections +# based on the sonar.web.connections.acceptCount property. The default value is 50 for each +# enabled connector. +#sonar.web.http.maxThreads=50 +#sonar.web.https.maxThreads=50 + +# The minimum number of threads always kept running. The default value is 5 for each +# enabled connector. +#sonar.web.http.minThreads=5 +#sonar.web.https.minThreads=5 + +# The maximum queue length for incoming connection requests when all possible request processing +# threads are in use. Any requests received when the queue is full will be refused. +# The default value is 25 for each enabled connector. +#sonar.web.http.acceptCount=25 +#sonar.web.https.acceptCount=25 + +# Access logs are generated in the file logs/access.log. This file is rolled over when it's 5Mb. +# An archive of 3 files is kept in the same directory. +# Access logs are enabled by default. +#sonar.web.accessLogs.enable=true + +# TCP port for incoming AJP connections. Disabled when value is -1. +# sonar.ajp.port=9009 + + + +#-------------------------------------------------------------------------------------------------- +# UPDATE CENTER + +# The Update Center requires an internet connection to request http://update.sonarsource.org +# It is enabled by default. +#sonar.updatecenter.activate=true + +# HTTP proxy (default none) +#http.proxyHost= +#http.proxyPort= + +# NT domain name if NTLM proxy is used +#http.auth.ntlm.domain= + +# SOCKS proxy (default none) +#socksProxyHost= +#socksProxyPort= + +# proxy authentication. The 2 following properties are used for HTTP and SOCKS proxies. +#http.proxyUser= +#http.proxyPassword= + + +#-------------------------------------------------------------------------------------------------- +# NOTIFICATIONS + +# Delay in seconds between processing of notification queue. Default is 60. +#sonar.notifications.delay=60 + + +#-------------------------------------------------------------------------------------------------- +# PROFILING +# Level of information displayed in the logs: NONE (default), BASIC (functional information) and FULL (functional and technical details) +#sonar.log.profilingLevel=NONE + + +#-------------------------------------------------------------------------------------------------- +# DEVELOPMENT MODE +# Only for debugging + +# Set to true to apply Ruby on Rails code changes on the fly +#sonar.rails.dev=false diff --git a/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties b/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties new file mode 100644 index 00000000000..5c06e58a32e --- /dev/null +++ b/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties @@ -0,0 +1,3 @@ +hello: world +foo=bar +java.io.tmpdir=/should/be/overridden diff --git a/server/sonar-process/src/test/resources/sonar-dummy-app.jar b/server/sonar-process/src/test/resources/sonar-dummy-app.jar Binary files differnew file mode 100644 index 00000000000..6dfd458329a --- /dev/null +++ b/server/sonar-process/src/test/resources/sonar-dummy-app.jar |