@@ -27,6 +27,7 @@ include 'sonar-plugin-api' | |||
include 'sonar-scanner-engine' | |||
include 'sonar-scanner-engine-shaded' | |||
include 'sonar-scanner-protocol' | |||
include 'sonar-shutdowner' | |||
include 'sonar-testing-harness' | |||
include 'sonar-ws' | |||
include 'sonar-ws-generator' |
@@ -11,6 +11,7 @@ configurations { | |||
jsw | |||
scanner | |||
web | |||
shutdowner | |||
jdbc_mssql { | |||
transitive = false | |||
} | |||
@@ -48,6 +49,7 @@ dependencies { | |||
scanner project(path: ':sonar-scanner-engine-shaded', configuration: 'shadow') | |||
web project(':server:sonar-vsts') | |||
web project(':server:sonar-web') | |||
shutdowner project(':sonar-shutdowner') | |||
jdbc_h2 'com.h2database:h2' | |||
jdbc_mssql 'com.microsoft.sqlserver:mssql-jdbc' | |||
@@ -194,6 +196,9 @@ task zip(type: Zip, dependsOn: [configurations.compile]) { | |||
into("${archiveDir}/lib/jdbc/h2/") { | |||
from configurations.jdbc_h2 | |||
} | |||
into("${archiveDir}/lib/") { | |||
from configurations.shutdowner | |||
} | |||
} | |||
// Create the empty dir required by elasticsearch | |||
zip.doFirst { |
@@ -24,6 +24,7 @@ APP_LONG_NAME="${DEF_APP_LONG_NAME}" | |||
# Wrapper | |||
WRAPPER_CMD="./wrapper" | |||
WRAPPER_CONF="../../conf/wrapper.conf" | |||
SHUTDOWNER_LIB_DIR="../../lib" | |||
# Priority at which to run the wrapper. See "man nice" for valid priorities. | |||
# nice is only used if a priority is specified. | |||
@@ -124,6 +125,12 @@ if [ "$FIRST_CHAR" != "/" ] | |||
then | |||
WRAPPER_CONF=$REALDIR/$WRAPPER_CONF | |||
fi | |||
# Same test for SHUTDOWNER_LIB_DIR | |||
FIRST_CHAR=`echo $SHUTDOWNER_LIB_DIR | cut -c1,1` | |||
if [ "$FIRST_CHAR" != "/" ] | |||
then | |||
SHUTDOWNER_LIB_DIR=$REALDIR/$SHUTDOWNER_LIB_DIR | |||
fi | |||
# Process ID | |||
ANCHORFILE="$PIDDIR/$APP_NAME.anchor" | |||
@@ -264,6 +271,13 @@ else | |||
CMDNICE="nice -$PRIORITY" | |||
fi | |||
CMDJAVA="java" | |||
# read java command from wrapper.conf as first uncommented line containing "wrapper.java.command=" | |||
grep "wrapper.java.command=" "$WRAPPER_CONF" | grep -v "^#" | while read -r line; do | |||
CMDJAVA="${line#*=}" | |||
break | |||
done | |||
# Build the anchor file clause. | |||
if [ "X$IGNORE_SIGNALS" = "X" ] | |||
then | |||
@@ -427,9 +441,44 @@ start() { | |||
echo "Failed to start $APP_LONG_NAME." | |||
fi | |||
} | |||
waitforstop() { | |||
# We can not predict how long it will take for the wrapper to | |||
# actually stop as it depends on settings in wrapper.conf. | |||
# Loop until it does. | |||
savepid=$pid | |||
CNT=0 | |||
TOTCNT=0 | |||
while [ "X$pid" != "X" ] | |||
do | |||
# Show a waiting message every 5 seconds. | |||
if [ "$CNT" -lt "5" ] | |||
then | |||
CNT=`expr $CNT + 1` | |||
else | |||
echo "Waiting for $APP_LONG_NAME to exit..." | |||
CNT=0 | |||
fi | |||
TOTCNT=`expr $TOTCNT + 1` | |||
sleep 1 | |||
testpid | |||
done | |||
pid=$savepid | |||
testpid | |||
if [ "X$pid" != "X" ] | |||
then | |||
echo "Failed to stop $APP_LONG_NAME." | |||
exit 1 | |||
else | |||
echo "Stopped $APP_LONG_NAME." | |||
fi | |||
} | |||
stopit() { | |||
echo "Stopping $APP_LONG_NAME..." | |||
echo "Gracefully stopping $APP_LONG_NAME..." | |||
getpid | |||
if [ "X$pid" = "X" ] | |||
then | |||
@@ -455,38 +504,28 @@ stopit() { | |||
fi | |||
fi | |||
# We can not predict how long it will take for the wrapper to | |||
# actually stop as it depends on settings in wrapper.conf. | |||
# Loop until it does. | |||
savepid=$pid | |||
CNT=0 | |||
TOTCNT=0 | |||
while [ "X$pid" != "X" ] | |||
do | |||
# Show a waiting message every 5 seconds. | |||
if [ "$CNT" -lt "5" ] | |||
then | |||
CNT=`expr $CNT + 1` | |||
else | |||
echo "Waiting for $APP_LONG_NAME to exit..." | |||
CNT=0 | |||
fi | |||
TOTCNT=`expr $TOTCNT + 1` | |||
waitforstop | |||
fi | |||
} | |||
sleep 1 | |||
forcestopit() { | |||
getpid | |||
if [ "X$pid" = "X" ] | |||
then | |||
echo "$APP_LONG_NAME not running" | |||
exit 1 | |||
fi | |||
testpid | |||
done | |||
testpid | |||
if [ "X$pid" != "X" ] | |||
then | |||
# start shutdowner from SQ installation directory | |||
cd "../.." | |||
pid=$savepid | |||
testpid | |||
if [ "X$pid" != "X" ] | |||
then | |||
echo "Failed to stop $APP_LONG_NAME." | |||
exit 1 | |||
else | |||
echo "Stopped $APP_LONG_NAME." | |||
fi | |||
echo "Force stopping $APP_LONG_NAME..." | |||
${CMDJAVA} -classpath "${SHUTDOWNER_LIB_DIR}/*" "org.sonar.application.Shutdowner" | |||
waitforstop | |||
fi | |||
} | |||
@@ -539,6 +578,11 @@ case "$1" in | |||
stopit | |||
;; | |||
'force-stop') | |||
checkUser "" $1 | |||
forcestopit | |||
;; | |||
'restart') | |||
checkUser touchlock $1 | |||
stopit | |||
@@ -556,7 +600,7 @@ case "$1" in | |||
;; | |||
*) | |||
echo "Usage: $0 { console | start | stop | restart | status | dump }" | |||
echo "Usage: $0 { console | start | stop | force-stop | restart | status | dump }" | |||
exit 1 | |||
;; | |||
esac |
@@ -0,0 +1,22 @@ | |||
sonarqube { | |||
properties { | |||
property 'sonar.projectName', "${projectTitle} :: Shutdowner" | |||
} | |||
} | |||
dependencies { | |||
// please keep list ordered | |||
compileOnly 'com.google.code.findbugs:jsr305' | |||
testCompile 'junit:junit' | |||
testCompile 'org.assertj:assertj-core' | |||
testCompile 'org.mockito:mockito-core' | |||
} | |||
jar { | |||
manifest { | |||
attributes( | |||
'Main-Class': 'org.sonar.application.Shutdowner' | |||
) | |||
} | |||
} |
@@ -0,0 +1,102 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.application; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStreamReader; | |||
import java.io.RandomAccessFile; | |||
import java.io.Reader; | |||
import java.net.URISyntaxException; | |||
import java.nio.MappedByteBuffer; | |||
import java.nio.channels.FileChannel; | |||
import java.util.Optional; | |||
import java.util.Properties; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
public class Shutdowner { | |||
public static void main(String[] args) { | |||
try { | |||
new Shutdowner().run(); | |||
} catch (Exception e) { | |||
System.err.println(e.getMessage()); | |||
System.exit(1); | |||
} | |||
} | |||
void run() throws IOException { | |||
File homeDir = detectHomeDir(); | |||
Properties p = loadPropertiesFile(homeDir); | |||
File tmpDir = resolveTempDir(p); | |||
askForHardStop(tmpDir); | |||
} | |||
// assuming jar file is in directory SQ_HOME/lib/ | |||
static File detectHomeDir() { | |||
try { | |||
File appJar = new File(Shutdowner.class.getProtectionDomain().getCodeSource().getLocation().toURI()); | |||
return appJar.getParentFile().getParentFile(); | |||
} catch (URISyntaxException e) { | |||
throw new IllegalStateException("Cannot detect path of shutdowner jar file", e); | |||
} | |||
} | |||
/** | |||
* Loads the configuration file ${homeDir}/conf/sonar.properties. | |||
* An empty {@link Properties} is returned if the file does not exist. | |||
*/ | |||
static Properties loadPropertiesFile(File homeDir) { | |||
Properties p = new Properties(); | |||
File propsFile = new File(new File(homeDir, "conf"), "sonar.properties"); | |||
if (propsFile.exists()) { | |||
try (Reader reader = new InputStreamReader(new FileInputStream(propsFile), UTF_8)) { | |||
p.load(reader); | |||
return p; | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Cannot open file " + propsFile, e); | |||
} | |||
} else { | |||
throw new IllegalStateException("Configuration file not found: " + propsFile); | |||
} | |||
} | |||
static File resolveTempDir(Properties p) { | |||
return new File(Optional.ofNullable(p.getProperty("sonar.path.temp")).orElse("temp")); | |||
} | |||
static void askForHardStop(File tmpDir) throws IOException { | |||
writeToShareMemory(tmpDir, 1, (byte) 0xFF); | |||
} | |||
private static void writeToShareMemory(File tmpDir, int offset, byte value) throws IOException { | |||
try (RandomAccessFile sharedMemory = new RandomAccessFile(new File(tmpDir, "sharedmemory"), "rw")) { | |||
// Using values from org.sonar.process.ProcessCommands | |||
MappedByteBuffer mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 50L * 10); | |||
// Now we are stopping all processes as quick as possible | |||
// by asking for stop of "app" process | |||
mappedByteBuffer.put(offset, value); | |||
} | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.application; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -0,0 +1,122 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.application; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.RandomAccessFile; | |||
import java.nio.MappedByteBuffer; | |||
import java.nio.channels.FileChannel; | |||
import java.nio.file.Files; | |||
import java.util.Arrays; | |||
import java.util.Properties; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class ShutdownerTest { | |||
@Rule | |||
public TemporaryFolder temporaryFolder = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private File srcFile; | |||
@Before | |||
public void setUp() throws Exception { | |||
srcFile = new File(Shutdowner.class.getProtectionDomain().getCodeSource().getLocation().toURI()); | |||
} | |||
@Test | |||
public void detectHomeDir_assumes_Shutdowner_jar_is_in_lib_dir_root() { | |||
assertThat(Shutdowner.detectHomeDir()) | |||
.isEqualTo(srcFile.getParentFile().getParentFile()); | |||
} | |||
@Test | |||
public void loadPropertiesFile_fails_with_ISE_if_sonar_properties_not_in_conf_dir() throws IOException { | |||
File homeDir = temporaryFolder.newFolder(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Configuration file not found: " + new File(new File(homeDir, "conf"), "sonar.properties").getAbsolutePath()); | |||
Shutdowner.loadPropertiesFile(homeDir); | |||
} | |||
@Test | |||
public void loadPropertiesFile_reads_sonar_properties_content() throws IOException { | |||
File homeDir = temporaryFolder.newFolder(); | |||
File confDir = new File(homeDir, "conf"); | |||
confDir.mkdirs(); | |||
File sonarProperties = new File(confDir, "sonar.properties"); | |||
sonarProperties.createNewFile(); | |||
Files.write(sonarProperties.toPath(), Arrays.asList("foo=bar")); | |||
Properties properties = Shutdowner.loadPropertiesFile(homeDir); | |||
assertThat(properties.getProperty("foo")).isEqualTo("bar"); | |||
} | |||
@Test | |||
public void resolveTempDir_defaults_to_temp() { | |||
File file = Shutdowner.resolveTempDir(new Properties()); | |||
assertThat(file).isEqualTo(new File("temp")); | |||
} | |||
@Test | |||
public void resolveTempDir_reads_relative_temp_dir_location_from_sonar_path_temp() { | |||
String tempDirPath = "blablabl"; | |||
Properties properties = new Properties(); | |||
properties.put("sonar.path.temp", tempDirPath); | |||
File file = Shutdowner.resolveTempDir(properties); | |||
assertThat(file).isEqualTo(new File(tempDirPath)); | |||
} | |||
@Test | |||
public void resolveTempDir_reads_absolute_temp_dir_location_from_sonar_path_temp() throws IOException { | |||
File tempDirLocation = temporaryFolder.newFolder(); | |||
Properties properties = new Properties(); | |||
properties.put("sonar.path.temp", tempDirLocation.getAbsolutePath()); | |||
File file = Shutdowner.resolveTempDir(properties); | |||
assertThat(file).isEqualTo(tempDirLocation); | |||
} | |||
@Test | |||
public void askForHardStop_write_right_bit_with_right_value_in_right_file() throws Exception { | |||
File tempFolder = temporaryFolder.newFolder(); | |||
Shutdowner.askForHardStop(tempFolder); | |||
try (RandomAccessFile sharedMemory = new RandomAccessFile(new File(tempFolder, "sharedmemory"), "r")) { | |||
// Using values from org.sonar.process.ProcessCommands | |||
MappedByteBuffer mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 50L * 10); | |||
assertThat(mappedByteBuffer.get(1)).isEqualTo((byte) 0xFF); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} |