From 9f0342503cdd6cc9d9c715146f9cd5be79eafa22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 3 Jul 2024 02:02:05 +0200 Subject: [PATCH 01/33] SOLR-17450 Status Tool pure java --- solr/bin/solr | 60 +------ solr/bin/solr.cmd | 30 +--- .../org/apache/solr/cli/SolrProcessMgr.java | 158 ++++++++++++++++++ .../java/org/apache/solr/cli/StatusTool.java | 138 ++++++++++++--- .../apache/solr/cli/SolrProcessMgrTest.java | 151 +++++++++++++++++ solr/packaging/test/test_status.bats | 21 ++- 6 files changed, 445 insertions(+), 113 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java create mode 100644 solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java diff --git a/solr/bin/solr b/solr/bin/solr index 036775a766b..ac33f061b97 100755 --- a/solr/bin/solr +++ b/solr/bin/solr @@ -496,55 +496,13 @@ function run_tool() { # shellcheck disable=SC2086 "$JAVA" $SOLR_SSL_OPTS $AUTHC_OPTS ${SOLR_ZK_CREDS_AND_ACLS:-} ${SOLR_TOOL_OPTS:-} -Dsolr.install.dir="$SOLR_TIP" \ - -Dlog4j.configurationFile="$DEFAULT_SERVER_DIR/resources/log4j2-console.xml" \ + -Dlog4j.configurationFile="$DEFAULT_SERVER_DIR/resources/log4j2-console.xml" -Dsolr.pid.dir="$SOLR_PID_DIR" \ -classpath "$DEFAULT_SERVER_DIR/solr-webapp/webapp/WEB-INF/lib/*:$DEFAULT_SERVER_DIR/lib/ext/*:$DEFAULT_SERVER_DIR/lib/*" \ org.apache.solr.cli.SolrCLI "$@" return $? } # end run_tool function -# get status about any Solr nodes running on this host -function get_status() { - # first, see if Solr is running - numSolrs=$(find "$SOLR_PID_DIR" -name "solr-*.pid" -type f | wc -l | tr -d ' ') - if [ "$numSolrs" != "0" ]; then - echo -e "\nFound $numSolrs Solr nodes: " - while read PIDF - do - ID=$(cat "$PIDF") - port=$(jetty_port "$ID") - if [ "$port" != "" ]; then - echo -e "\nSolr process $ID running on port $port" - run_tool status --solr-url "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:$port" "$@" - echo "" - else - echo -e "\nSolr process $ID from $PIDF not found." - fi - done < <(find "$SOLR_PID_DIR" -name "solr-*.pid" -type f) - else - # no pid files but check using ps just to be sure - numSolrs=$(ps auxww | grep start\.jar | grep solr\.solr\.home | grep -v grep | wc -l | sed -e 's/^[ \t]*//') - if [ "$numSolrs" != "0" ]; then - echo -e "\nFound $numSolrs Solr nodes: " - PROCESSES=$(ps auxww | grep start\.jar | grep solr\.solr\.home | grep -v grep | awk '{print $2}' | sort -r) - for ID in $PROCESSES - do - port=$(jetty_port "$ID") - if [ "$port" != "" ]; then - echo "" - echo "Solr process $ID running on port $port" - run_tool status --solr-url "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:$port" "$@" - echo "" - fi - done - else - echo -e "\nNo Solr nodes are running.\n" - run_tool status "$@" - fi - fi - -} # end get_status - # tries to gracefully stop Solr using the Jetty # stop command and if that fails, then uses kill -9 # (will attempt to thread dump before killing) @@ -635,12 +593,6 @@ else exit fi -# status tool -if [ "$SCRIPT_CMD" == "status" ]; then - get_status - exit $? -fi - # configure authentication if [[ "$SCRIPT_CMD" == "auth" ]]; then : "${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}" @@ -648,7 +600,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then echo -e "\nSolr server directory $SOLR_SERVER_DIR not found!\n" exit 1 fi - + if [ -z "${SOLR_HOME:-}" ]; then SOLR_HOME="$SOLR_SERVER_DIR/solr" elif [[ $SOLR_HOME != /* ]]; then @@ -659,7 +611,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then SOLR_PID_DIR="$SOLR_HOME" fi fi - + if [ -z "${AUTH_PORT:-}" ]; then for ID in $(ps auxww | grep java | grep start\.jar | awk '{print $2}' | sort -r) do @@ -669,13 +621,13 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then break fi done - fi - + fi + run_tool auth $@ --solr-url "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:${AUTH_PORT:-8983}" --auth-conf-dir "$SOLR_HOME" "--solr-include-file" "$SOLR_INCLUDE" exit $? fi -# at this point all tools that have a custom run process, like "status" and "auth" have been run and exited. +# at this point all tools that have a custom run process, like "status" and "auth" have been run and exited. # Unless a command is one of the ones in the if clause below, we will just run it with the default run_tool function and then exit. if [ "$SCRIPT_CMD" != "start" ] && [ "$SCRIPT_CMD" != "stop" ] && [ "$SCRIPT_CMD" != "restart" ]; then # hand off the command to the SolrCLI and let it handle the option parsing and validation diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd index 49f08502dae..422230aed46 100755 --- a/solr/bin/solr.cmd +++ b/solr/bin/solr.cmd @@ -253,7 +253,7 @@ IF "%1"=="-h" goto run_solrcli IF "%1"=="--help" goto run_solrcli IF "%1"=="-help" goto run_solrcli IF "%1"=="/?" goto run_solrcli -IF "%1"=="status" goto get_status +IF "%1"=="status" goto run_solrcli IF "%1"=="version" goto run_solrcli IF "%1"=="-v" goto run_solrcli IF "%1"=="-version" goto run_solrcli @@ -1204,34 +1204,6 @@ REM Run the requested example REM End of run_example goto done -:get_status -REM Find all Java processes, correlate with those listening on a port -REM and then try to contact via that port using the status tool -for /f "usebackq" %%i in (`dir /b "%SOLR_TIP%\bin" ^| findstr /i "^solr-.*\.port$"`) do ( - set SOME_SOLR_PORT= - For /F "Delims=" %%J In ('type "%SOLR_TIP%\bin\%%i"') do set SOME_SOLR_PORT=%%~J - if NOT "!SOME_SOLR_PORT!"=="" ( - for /f "tokens=2,5" %%j in ('netstat -aon ^| find "TCP " ^| find ":0 " ^| find ":!SOME_SOLR_PORT! "') do ( - IF NOT "%%k"=="0" ( - if "%%j"=="%SOLR_JETTY_HOST%:!SOME_SOLR_PORT!" ( - @echo. - set has_info=1 - echo Found Solr process %%k running on port !SOME_SOLR_PORT! - REM Passing in %2 (-h or --help) directly is captured by a custom help path for usage output - "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^ - -Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^ - -classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^ - org.apache.solr.cli.SolrCLI status --solr-url !SOLR_URL_SCHEME!://%SOLR_TOOL_HOST%:!SOME_SOLR_PORT! %2 - @echo. - ) - ) - ) - ) -) -if NOT "!has_info!"=="1" echo No running Solr nodes found. -set has_info= -goto done - :run_solrcli "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^ -Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^ diff --git a/solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java b/solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java new file mode 100644 index 00000000000..0a79cca9696 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import static org.apache.solr.servlet.SolrDispatchFilter.SOLR_INSTALL_DIR_ATTRIBUTE; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.solr.common.util.EnvUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Class to interact with Solr OS processes */ +public class SolrProcessMgr { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Map pidProcessMap; + private final Map portProcessMap; + private final Path pidDir; + private static final Pattern pidFilePattern = Pattern.compile("^solr-([0-9]+)\\.(pid|port)$"); + // Set this to true during testing to allow the SolrProcessMgr to find only mock Solr processes + public static boolean enableTestingMode = false; + + public SolrProcessMgr() { + pidProcessMap = + ProcessHandle.allProcesses() + .filter(p -> p.info().command().orElse("").contains("java")) + .filter(p -> p.info().commandLine().orElse("").contains("-Djetty.port=")) + .filter( + p -> + !enableTestingMode + || p.info().commandLine().orElse("").contains("-DmockSolr=true")) + .collect( + Collectors.toUnmodifiableMap( + ProcessHandle::pid, + ph -> + new SolrProcess( + ph.pid(), parsePortFromProcess(ph).orElseThrow(), isProcessSsl(ph)))); + portProcessMap = + pidProcessMap.values().stream().collect(Collectors.toUnmodifiableMap(p -> p.port, p -> p)); + pidDir = + Paths.get( + EnvUtils.getProperty( + "solr.pid.dir", EnvUtils.getProperty(SOLR_INSTALL_DIR_ATTRIBUTE + "/bin", "/tmp"))); + } + + public boolean isRunningWithPort(Integer port) { + return portProcessMap.containsKey(port); + } + + public boolean isRunningWithPid(Long pid) { + return pidProcessMap.containsKey(pid); + } + + public Optional processForPort(Integer port) { + return portProcessMap.containsKey(port) + ? Optional.of(portProcessMap.get(port)) + : Optional.empty(); + } + + /** Return the SolrProcess for a given PID, if it is running */ + public Optional getProcessForPid(Long pid) { + return pidProcessMap.containsKey(pid) ? Optional.of(pidProcessMap.get(pid)) : Optional.empty(); + } + + public Collection scanSolrPidFiles() throws IOException { + List processes = new ArrayList<>(); + try (Stream pidFiles = + Files.list(pidDir) + .filter(p -> pidFilePattern.matcher(p.getFileName().toString()).matches())) { + for (Path p : pidFiles.collect(Collectors.toList())) { + Long pid = Long.valueOf(Files.readAllLines(p).get(0)); + Optional process = getProcessForPid(pid); + if (process.isPresent()) { + processes.add(process.get()); + } else { + log.warn("PID file found for PID {}, but no process found. Deleting PID file", pid); + Files.deleteIfExists(p); + } + } + return processes; + } + } + + public Collection getAllRunning() { + return pidProcessMap.values(); + } + + private Optional parsePortFromProcess(ProcessHandle ph) { + Optional portStr = + Arrays.stream(ph.info().arguments().orElse(new String[] {})) + .filter(a -> a.contains("-Djetty.port=")) + .map(s -> s.split("=")[1]) + .findFirst(); + return portStr.isPresent() ? portStr.map(Integer::parseInt) : Optional.empty(); + } + + private boolean isProcessSsl(ProcessHandle ph) { + return Arrays.stream(ph.info().arguments().orElse(new String[] {})) + .anyMatch( + arg -> List.of("--module=https", "--module=ssl", "--module=ssl-reload").contains(arg)); + } + + /** Represents a running Solr process */ + public static class SolrProcess { + private final long pid; + private final int port; + private final boolean isHttps; + + public SolrProcess(long pid, int port, boolean isHttps) { + this.pid = pid; + this.port = port; + this.isHttps = isHttps; + } + + public long getPid() { + return pid; + } + + public int getPort() { + return port; + } + + public boolean isHttps() { + return isHttps; + } + + public String getLocalUrl() { + return String.format("%s://localhost:%s/solr", isHttps ? "https" : "http", port); + } + } +} diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index dd9a579266e..6342bc314ba 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -17,17 +17,21 @@ package org.apache.solr.cli; +import static org.apache.solr.cli.SolrCLI.OPTION_SOLRURL; + import java.io.PrintStream; import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; +import org.apache.solr.cli.SolrProcessMgr.SolrProcess; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.request.CollectionAdminRequest; @@ -36,6 +40,8 @@ import org.apache.solr.common.util.NamedList; import org.noggit.CharArr; import org.noggit.JSONWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Supports status command in the bin/solr script. @@ -43,12 +49,18 @@ *

Get the status of a Solr server. */ public class StatusTool extends ToolBase { + private static final Logger log = LoggerFactory.getLogger(StatusTool.class); + + private static Map processes; + private final SolrProcessMgr processMgr; + public StatusTool() { this(CLIO.getOutStream()); } public StatusTool(PrintStream stdout) { super(stdout); + processMgr = new SolrProcessMgr(); } @Override @@ -65,37 +77,115 @@ public String getName() { .desc("Wait up to the specified number of seconds to see Solr running.") .build(); + public static final Option OPTION_PORT = + Option.builder("p") + .longOpt("port") + .argName("PORT") + .required(false) + .hasArg() + .desc("Port on localhost to check status for") + .build(); + + public static final Option OPTION_SHORT = + Option.builder() + .longOpt("short") + .argName("SHORT") + .required(false) + .desc("Short format. Prints one URL per line for running instances") + .build(); + @Override public List

Get the status of a Solr server. */ public class StatusTool extends ToolBase { - private static final Logger log = LoggerFactory.getLogger(StatusTool.class); - private static Map processes; private final SolrProcessMgr processMgr; @@ -127,7 +124,7 @@ public void runImpl(CommandLine cli) throws Exception { } Collection procs = processMgr.getAllRunning(); if (!shortFormat) { - CLIO.out(String.format("\nFound %s Solr nodes: ", procs.size())); + CLIO.out(String.format(Locale.ROOT, "\nFound %s Solr nodes: ", procs.size())); } if (!procs.isEmpty()) { for (SolrProcess process : procs) { @@ -149,7 +146,7 @@ public void runImpl(CommandLine cli) throws Exception { CLIO.out(solrUrl); } } else { - CLIO.out(String.format("\nFound %s Solr nodes: ", 1)); + CLIO.out(String.format(Locale.ROOT, "\nFound %s Solr nodes: ", 1)); Optional process = processMgr.processForPort(urlPort); if (process.isPresent()) { printProcessStatus(process.get(), cli); @@ -165,7 +162,11 @@ private void printProcessStatus(SolrProcess process, CommandLine cli) throws Exc boolean shortFormat = cli.hasOption(OPTION_SHORT); String pidUrl = process.getLocalUrl(); CLIO.out( - String.format("\nSolr process %s running on port %s", process.getPid(), process.getPort())); + String.format( + Locale.ROOT, + "\nSolr process %s running on port %s", + process.getPid(), + process.getPort())); if (shortFormat) { CLIO.out(pidUrl); } else { From 8cc2841cdf7b60c3c14cd6ce3b802dd552527a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 01:27:44 +0200 Subject: [PATCH 03/33] Another test, fix https parsing bug --- .../org/apache/solr/cli/SolrProcessMgrTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java index 217a4f4a7a0..536d38877c0 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java @@ -77,7 +77,7 @@ private static Pair createProcess(int port, boolean https) thr "-Djetty.port=" + port, "-DisHttps=" + https, "-DmockSolr=true", - https ? "--module=http" : "--module=https", + https ? "--module=https" : "--module=http", "SolrProcessMgrTest$MockSolrProcess"); // Start the process @@ -136,6 +136,20 @@ public void testGetAllRunning() { assertEquals(2, processes.size()); } + public void testSolrProcessMethods() { + SolrProcess http = solrProcessMgr.processForPort(processHttp.getKey()).orElseThrow(); + assertEquals(processHttp.getValue().pid(), http.getPid()); + assertEquals(processHttp.getKey().intValue(), http.getPort()); + assertFalse(http.isHttps()); + assertEquals("http://localhost:" + processHttp.getKey() + "/solr", http.getLocalUrl()); + + SolrProcess https = solrProcessMgr.processForPort(processHttps.getKey()).orElseThrow(); + assertEquals(processHttps.getValue().pid(), https.getPid()); + assertEquals(processHttps.getKey().intValue(), https.getPort()); + assertTrue(https.isHttps()); + assertEquals("https://localhost:" + processHttps.getKey() + "/solr", https.getLocalUrl()); + } + public static class MockSolrProcess { public static void main(String[] args) { int port = Integer.parseInt(System.getProperty("jetty.port")); From a2c5890e29ac780c0654ea89d0285e7f658a9476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 01:32:37 +0200 Subject: [PATCH 04/33] Add security mgr permission --- gradle/testing/randomization/policies/solr-tests.policy | 2 ++ solr/server/etc/security.policy | 2 ++ 2 files changed, 4 insertions(+) diff --git a/gradle/testing/randomization/policies/solr-tests.policy b/gradle/testing/randomization/policies/solr-tests.policy index dae3f218ec3..6f6f1b7ef6d 100644 --- a/gradle/testing/randomization/policies/solr-tests.policy +++ b/gradle/testing/randomization/policies/solr-tests.policy @@ -107,6 +107,8 @@ grant { permission java.lang.RuntimePermission "writeFileDescriptor"; // needed by hadoop http permission java.lang.RuntimePermission "getProtectionDomain"; + // SolrProcessMgr to list processes + permission java.lang.RuntimePermission "manageProcess"; // These two *have* to be spelled out a separate permission java.lang.management.ManagementPermission "control"; diff --git a/solr/server/etc/security.policy b/solr/server/etc/security.policy index c898ac8dbfe..9c7aa380d3d 100644 --- a/solr/server/etc/security.policy +++ b/solr/server/etc/security.policy @@ -108,6 +108,8 @@ grant { permission java.lang.RuntimePermission "getProtectionDomain"; // needed by aws s3 sdk (Apache HTTP Client) permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.reflect"; + // SolrProcessMgr to list processes + permission java.lang.RuntimePermission "manageProcess"; // These two *have* to be spelled out a separate permission java.lang.management.ManagementPermission "control"; From 7688673323f1582e4806bf5617a458b08f184007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 01:36:44 +0200 Subject: [PATCH 05/33] Fix test_help bats test --- solr/packaging/test/test_help.bats | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/solr/packaging/test/test_help.bats b/solr/packaging/test/test_help.bats index f131cba0bce..111346f26c1 100644 --- a/solr/packaging/test/test_help.bats +++ b/solr/packaging/test/test_help.bats @@ -60,10 +60,8 @@ setup() { @test "status help flag prints help" { run solr status --help - assert_output --partial 'usage: status' + assert_output --partial 'usage: bin/solr status [--max-wait-secs ] [-p ] [--short] [-url ]' refute_output --partial 'ERROR' - # Make sure custom selection of options for status help works. - refute_output --partial '--solr-url' } @test "healthcheck help flag prints help" { From d724052fe7ec5c93cdd703c75d2dc3d256ee7488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 17:53:48 +0200 Subject: [PATCH 06/33] Review feedback about throwing exception instead of doing exit --- solr/core/src/java/org/apache/solr/cli/StatusTool.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index acc17b4d078..26ab3114b17 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -104,8 +104,7 @@ public void runImpl(CommandLine cli) throws Exception { boolean shortFormat = cli.hasOption(OPTION_SHORT); if (port != null && solrUrl != null) { - CLIO.err("Please provide either --port or --solr-url, not both"); - System.exit(1); + throw new IllegalArgumentException("Only one of port or url can be specified"); } if (port != null) { From e487af10eb913f31c1534d4e2e68e66c12d35bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 17:57:57 +0200 Subject: [PATCH 07/33] Rename Manager --- ...rocessMgr.java => SolrProcessManager.java} | 6 +-- .../java/org/apache/solr/cli/StatusTool.java | 6 +-- ...rTest.java => SolrProcessManagerTest.java} | 44 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) rename solr/core/src/java/org/apache/solr/cli/{SolrProcessMgr.java => SolrProcessManager.java} (97%) rename solr/core/src/test/org/apache/solr/cli/{SolrProcessMgrTest.java => SolrProcessManagerTest.java} (76%) diff --git a/solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java similarity index 97% rename from solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java rename to solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java index d89307a859c..258d11a8ffe 100644 --- a/solr/core/src/java/org/apache/solr/cli/SolrProcessMgr.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java @@ -38,17 +38,17 @@ import org.slf4j.LoggerFactory; /** Class to interact with Solr OS processes */ -public class SolrProcessMgr { +public class SolrProcessManager { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final Map pidProcessMap; private final Map portProcessMap; private final Path pidDir; private static final Pattern pidFilePattern = Pattern.compile("^solr-([0-9]+)\\.(pid|port)$"); - // Set this to true during testing to allow the SolrProcessMgr to find only mock Solr processes + // Set this to true during testing to allow the SolrProcessManager to find only mock Solr processes public static boolean enableTestingMode = false; - public SolrProcessMgr() { + public SolrProcessManager() { pidProcessMap = ProcessHandle.allProcesses() .filter(p -> p.info().command().orElse("").contains("java")) diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index 26ab3114b17..329ed6c734b 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -32,7 +32,7 @@ import java.util.concurrent.TimeoutException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; -import org.apache.solr.cli.SolrProcessMgr.SolrProcess; +import org.apache.solr.cli.SolrProcessManager.SolrProcess; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.request.CollectionAdminRequest; @@ -49,7 +49,7 @@ */ public class StatusTool extends ToolBase { private static Map processes; - private final SolrProcessMgr processMgr; + private final SolrProcessManager processMgr; public StatusTool() { this(CLIO.getOutStream()); @@ -57,7 +57,7 @@ public StatusTool() { public StatusTool(PrintStream stdout) { super(stdout); - processMgr = new SolrProcessMgr(); + processMgr = new SolrProcessManager(); } @Override diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java similarity index 76% rename from solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java rename to solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index 536d38877c0..67cdcfddd6d 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessMgrTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -23,12 +23,12 @@ import java.util.Collection; import org.apache.commons.math3.util.Pair; import org.apache.solr.SolrTestCase; -import org.apache.solr.cli.SolrProcessMgr.SolrProcess; +import org.apache.solr.cli.SolrProcessManager.SolrProcess; import org.junit.AfterClass; import org.junit.BeforeClass; -public class SolrProcessMgrTest extends SolrTestCase { - private static SolrProcessMgr solrProcessMgr; +public class SolrProcessManagerTest extends SolrTestCase { + private static SolrProcessManager solrProcessManager; private static Pair processHttp; private static Pair processHttps; private static Path pidDir; @@ -37,7 +37,7 @@ public class SolrProcessMgrTest extends SolrTestCase { public static void beforeClass() throws Exception { processHttp = createProcess(findAvailablePort(), false); processHttps = createProcess(findAvailablePort(), true); - SolrProcessMgr.enableTestingMode = true; + SolrProcessManager.enableTestingMode = true; System.setProperty("jetty.port", Integer.toString(processHttp.getKey())); pidDir = Files.createTempDirectory("solr-pid-dir").toAbsolutePath(); pidDir.toFile().deleteOnExit(); @@ -49,14 +49,14 @@ public static void beforeClass() throws Exception { pidDir.resolve("solr-" + processHttps.getKey() + ".pid"), Long.toString(processHttps.getValue().pid())); Files.writeString(pidDir.resolve("solr-99999.pid"), "99999"); // Invalid - solrProcessMgr = new SolrProcessMgr(); + solrProcessManager = new SolrProcessManager(); } @AfterClass public static void afterClass() throws Exception { processHttp.getValue().destroyForcibly(); processHttps.getValue().destroyForcibly(); - SolrProcessMgr.enableTestingMode = false; + SolrProcessManager.enableTestingMode = false; System.clearProperty("jetty.port"); System.clearProperty("solr.pid.dir"); } @@ -78,7 +78,7 @@ private static Pair createProcess(int port, boolean https) thr "-DisHttps=" + https, "-DmockSolr=true", https ? "--module=https" : "--module=http", - "SolrProcessMgrTest$MockSolrProcess"); + "SolrProcessManagerTest$MockSolrProcess"); // Start the process Process process = processBuilder.start(); @@ -86,7 +86,7 @@ private static Pair createProcess(int port, boolean https) thr } public void testGetLocalUrl() { - solrProcessMgr + solrProcessManager .getAllRunning() .forEach( p -> { @@ -97,53 +97,53 @@ public void testGetLocalUrl() { } public void testIsRunningWithPort() { - assertFalse(solrProcessMgr.isRunningWithPort(0)); - assertTrue(solrProcessMgr.isRunningWithPort(processHttp.getKey())); - assertTrue(solrProcessMgr.isRunningWithPort(processHttps.getKey())); + assertFalse(solrProcessManager.isRunningWithPort(0)); + assertTrue(solrProcessManager.isRunningWithPort(processHttp.getKey())); + assertTrue(solrProcessManager.isRunningWithPort(processHttps.getKey())); } public void testIsRunningWithPid() { - assertFalse(solrProcessMgr.isRunningWithPid(0L)); - assertTrue(solrProcessMgr.isRunningWithPid(processHttp.getValue().pid())); - assertTrue(solrProcessMgr.isRunningWithPid(processHttps.getValue().pid())); + assertFalse(solrProcessManager.isRunningWithPid(0L)); + assertTrue(solrProcessManager.isRunningWithPid(processHttp.getValue().pid())); + assertTrue(solrProcessManager.isRunningWithPid(processHttps.getValue().pid())); } public void testProcessForPort() { assertEquals( processHttp.getKey().intValue(), - (solrProcessMgr.processForPort(processHttp.getKey()).orElseThrow().getPort())); + (solrProcessManager.processForPort(processHttp.getKey()).orElseThrow().getPort())); assertEquals( processHttps.getKey().intValue(), - (solrProcessMgr.processForPort(processHttps.getKey()).orElseThrow().getPort())); + (solrProcessManager.processForPort(processHttps.getKey()).orElseThrow().getPort())); } public void testGetProcessForPid() { assertEquals( processHttp.getValue().pid(), - (solrProcessMgr.getProcessForPid(processHttp.getValue().pid()).orElseThrow().getPid())); + (solrProcessManager.getProcessForPid(processHttp.getValue().pid()).orElseThrow().getPid())); assertEquals( processHttps.getValue().pid(), - (solrProcessMgr.getProcessForPid(processHttps.getValue().pid()).orElseThrow().getPid())); + (solrProcessManager.getProcessForPid(processHttps.getValue().pid()).orElseThrow().getPid())); } public void testScanSolrPidFiles() throws IOException { - Collection processes = solrProcessMgr.scanSolrPidFiles(); + Collection processes = solrProcessManager.scanSolrPidFiles(); assertEquals(2, processes.size()); } public void testGetAllRunning() { - Collection processes = solrProcessMgr.getAllRunning(); + Collection processes = solrProcessManager.getAllRunning(); assertEquals(2, processes.size()); } public void testSolrProcessMethods() { - SolrProcess http = solrProcessMgr.processForPort(processHttp.getKey()).orElseThrow(); + SolrProcess http = solrProcessManager.processForPort(processHttp.getKey()).orElseThrow(); assertEquals(processHttp.getValue().pid(), http.getPid()); assertEquals(processHttp.getKey().intValue(), http.getPort()); assertFalse(http.isHttps()); assertEquals("http://localhost:" + processHttp.getKey() + "/solr", http.getLocalUrl()); - SolrProcess https = solrProcessMgr.processForPort(processHttps.getKey()).orElseThrow(); + SolrProcess https = solrProcessManager.processForPort(processHttps.getKey()).orElseThrow(); assertEquals(processHttps.getValue().pid(), https.getPid()); assertEquals(processHttps.getKey().intValue(), https.getPort()); assertTrue(https.isHttps()); From d0aad43104a5b8a0bb1fa3e1a9328279c80715ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 17:58:49 +0200 Subject: [PATCH 08/33] Tidy --- .../src/java/org/apache/solr/cli/SolrProcessManager.java | 3 ++- .../src/test/org/apache/solr/cli/SolrProcessManagerTest.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java index 258d11a8ffe..54b0bf3e735 100644 --- a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java @@ -45,7 +45,8 @@ public class SolrProcessManager { private final Map portProcessMap; private final Path pidDir; private static final Pattern pidFilePattern = Pattern.compile("^solr-([0-9]+)\\.(pid|port)$"); - // Set this to true during testing to allow the SolrProcessManager to find only mock Solr processes + // Set this to true during testing to allow the SolrProcessManager to find only mock Solr + // processes public static boolean enableTestingMode = false; public SolrProcessManager() { diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index 67cdcfddd6d..18e0b35da95 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -123,7 +123,10 @@ public void testGetProcessForPid() { (solrProcessManager.getProcessForPid(processHttp.getValue().pid()).orElseThrow().getPid())); assertEquals( processHttps.getValue().pid(), - (solrProcessManager.getProcessForPid(processHttps.getValue().pid()).orElseThrow().getPid())); + (solrProcessManager + .getProcessForPid(processHttps.getValue().pid()) + .orElseThrow() + .getPid())); } public void testScanSolrPidFiles() throws IOException { From ebeda4ff97bacc7eba3e91a7ea3e8bb3c1411de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 17:59:34 +0200 Subject: [PATCH 09/33] Try to skip the manageProcess permission in runtime, only keep it for tests --- solr/server/etc/security.policy | 2 -- 1 file changed, 2 deletions(-) diff --git a/solr/server/etc/security.policy b/solr/server/etc/security.policy index 9c7aa380d3d..c898ac8dbfe 100644 --- a/solr/server/etc/security.policy +++ b/solr/server/etc/security.policy @@ -108,8 +108,6 @@ grant { permission java.lang.RuntimePermission "getProtectionDomain"; // needed by aws s3 sdk (Apache HTTP Client) permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.reflect"; - // SolrProcessMgr to list processes - permission java.lang.RuntimePermission "manageProcess"; // These two *have* to be spelled out a separate permission java.lang.management.ManagementPermission "control"; From 045b4b4d32287a3a3ef5ec7749ef041d27b44c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sat, 14 Sep 2024 18:22:05 +0200 Subject: [PATCH 10/33] Add a permission, compact some code --- .../testing/randomization/policies/solr-tests.policy | 2 ++ .../org/apache/solr/cli/SolrProcessManagerTest.java | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/gradle/testing/randomization/policies/solr-tests.policy b/gradle/testing/randomization/policies/solr-tests.policy index 6f6f1b7ef6d..e8e244c73fc 100644 --- a/gradle/testing/randomization/policies/solr-tests.policy +++ b/gradle/testing/randomization/policies/solr-tests.policy @@ -244,6 +244,8 @@ grant { // expanded to a wildcard if set, allows all networking everywhere permission java.net.SocketPermission "${solr.internal.network.permission}", "accept,listen,connect,resolve"; + + permission java.io.FilePermission "${java.home}${/}-", "execute"; }; // Grant all permissions to Gradle test runner classes. diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index 18e0b35da95..ba1f6aaa88b 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -31,7 +31,6 @@ public class SolrProcessManagerTest extends SolrTestCase { private static SolrProcessManager solrProcessManager; private static Pair processHttp; private static Pair processHttps; - private static Path pidDir; @BeforeClass public static void beforeClass() throws Exception { @@ -39,7 +38,7 @@ public static void beforeClass() throws Exception { processHttps = createProcess(findAvailablePort(), true); SolrProcessManager.enableTestingMode = true; System.setProperty("jetty.port", Integer.toString(processHttp.getKey())); - pidDir = Files.createTempDirectory("solr-pid-dir").toAbsolutePath(); + Path pidDir = Files.createTempDirectory("solr-pid-dir").toAbsolutePath(); pidDir.toFile().deleteOnExit(); System.setProperty("solr.pid.dir", pidDir.toString()); Files.writeString( @@ -89,11 +88,9 @@ public void testGetLocalUrl() { solrProcessManager .getAllRunning() .forEach( - p -> { - assertEquals( - (p.isHttps() ? "https" : "http") + "://localhost:" + p.getPort() + "/solr", - p.getLocalUrl()); - }); + p -> assertEquals( + (p.isHttps() ? "https" : "http") + "://localhost:" + p.getPort() + "/solr", + p.getLocalUrl())); } public void testIsRunningWithPort() { From 989a32f3050c09f023c276a8dce215181efc0e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 15 Sep 2024 16:22:29 +0200 Subject: [PATCH 11/33] Fix tests --- .../randomization/policies/solr-tests.policy | 2 +- .../solr/cli/SolrProcessManagerTest.java | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/gradle/testing/randomization/policies/solr-tests.policy b/gradle/testing/randomization/policies/solr-tests.policy index e8e244c73fc..ac0d539f492 100644 --- a/gradle/testing/randomization/policies/solr-tests.policy +++ b/gradle/testing/randomization/policies/solr-tests.policy @@ -245,7 +245,7 @@ grant { // expanded to a wildcard if set, allows all networking everywhere permission java.net.SocketPermission "${solr.internal.network.permission}", "accept,listen,connect,resolve"; - permission java.io.FilePermission "${java.home}${/}-", "execute"; + //permission java.io.FilePermission "${java.home}${/}-", "execute"; }; // Grant all permissions to Gradle test runner classes. diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index ba1f6aaa88b..165210206cd 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -16,8 +16,12 @@ */ package org.apache.solr.cli; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.ServerSocket; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -76,21 +80,28 @@ private static Pair createProcess(int port, boolean https) thr "-Djetty.port=" + port, "-DisHttps=" + https, "-DmockSolr=true", - https ? "--module=https" : "--module=http", - "SolrProcessManagerTest$MockSolrProcess"); + "org.apache.solr.cli.SolrProcessManagerTest$MockSolrProcess", + https ? "--module=https" : "--module=http"); - // Start the process + // Start the process and read first line of output Process process = processBuilder.start(); + try (InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(isr)) { + System.out.println(br.readLine()); + } return new Pair<>(port, process); } public void testGetLocalUrl() { + assertFalse(solrProcessManager.getAllRunning().isEmpty()); solrProcessManager .getAllRunning() .forEach( - p -> assertEquals( - (p.isHttps() ? "https" : "http") + "://localhost:" + p.getPort() + "/solr", - p.getLocalUrl())); + p -> + assertEquals( + (p.isHttps() ? "https" : "http") + "://localhost:" + p.getPort() + "/solr", + p.getLocalUrl())); } public void testIsRunningWithPort() { @@ -150,6 +161,10 @@ public void testSolrProcessMethods() { assertEquals("https://localhost:" + processHttps.getKey() + "/solr", https.getLocalUrl()); } + /** + * This class is started as new java process by {@link SolrProcessManagerTest#createProcess}, and + * it listens to a HTTP(s) port to simulate a real Solr process. + */ public static class MockSolrProcess { public static void main(String[] args) { int port = Integer.parseInt(System.getProperty("jetty.port")); From 114f9e17a5c235d2dc397c8428b5fd231afa36b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 15 Sep 2024 16:52:16 +0200 Subject: [PATCH 12/33] Re-enable execute permission --- gradle/testing/randomization/policies/solr-tests.policy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/testing/randomization/policies/solr-tests.policy b/gradle/testing/randomization/policies/solr-tests.policy index ac0d539f492..e8e244c73fc 100644 --- a/gradle/testing/randomization/policies/solr-tests.policy +++ b/gradle/testing/randomization/policies/solr-tests.policy @@ -245,7 +245,7 @@ grant { // expanded to a wildcard if set, allows all networking everywhere permission java.net.SocketPermission "${solr.internal.network.permission}", "accept,listen,connect,resolve"; - //permission java.io.FilePermission "${java.home}${/}-", "execute"; + permission java.io.FilePermission "${java.home}${/}-", "execute"; }; // Grant all permissions to Gradle test runner classes. From e34b70164f510ee38bbd9587ee29fe636ace849b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Mon, 16 Sep 2024 00:00:22 +0200 Subject: [PATCH 13/33] Use a shorter classpath --- .../org/apache/solr/cli/SolrProcessManagerTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index 165210206cd..aed3d9f9022 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -17,6 +17,7 @@ package org.apache.solr.cli; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -24,7 +25,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Collection; +import java.util.stream.Collectors; import org.apache.commons.math3.util.Pair; import org.apache.solr.SolrTestCase; import org.apache.solr.cli.SolrProcessManager.SolrProcess; @@ -72,14 +75,18 @@ private static int findAvailablePort() throws IOException { private static Pair createProcess(int port, boolean https) throws IOException { // Get the path to the java executable from the current JVM + String classPath = + Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)) + .filter(p -> p.contains("solr/core/build")) + .collect(Collectors.joining(File.pathSeparator)); ProcessBuilder processBuilder = new ProcessBuilder( System.getProperty("java.home") + "/bin/java", - "-cp", - System.getProperty("java.class.path"), "-Djetty.port=" + port, "-DisHttps=" + https, "-DmockSolr=true", + "-cp", + classPath, "org.apache.solr.cli.SolrProcessManagerTest$MockSolrProcess", https ? "--module=https" : "--module=http"); From a38167410caaa11089effb01161fe6c7568fd3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 13 Oct 2024 16:57:22 +0200 Subject: [PATCH 14/33] Windows support --- .../apache/solr/cli/SolrProcessManager.java | 74 +++++++++++++++++-- .../solr/cli/SolrProcessManagerTest.java | 8 +- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java index 54b0bf3e735..a8cbf8cea91 100644 --- a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java @@ -18,7 +18,9 @@ import static org.apache.solr.servlet.SolrDispatchFilter.SOLR_INSTALL_DIR_ATTRIBUTE; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.nio.file.Path; @@ -33,6 +35,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.lucene.util.Constants; +import org.apache.solr.common.SolrException; import org.apache.solr.common.util.EnvUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,11 +57,9 @@ public SolrProcessManager() { pidProcessMap = ProcessHandle.allProcesses() .filter(p -> p.info().command().orElse("").contains("java")) - .filter(p -> p.info().commandLine().orElse("").contains("-Djetty.port=")) + .filter(p -> commandLine(p).orElse("").contains("-Djetty.port=")) .filter( - p -> - !enableTestingMode - || p.info().commandLine().orElse("").contains("-DmockSolr=true")) + p -> !enableTestingMode || commandLine(p).orElse("").contains("-DmockSolr=true")) .collect( Collectors.toUnmodifiableMap( ProcessHandle::pid, @@ -116,7 +118,7 @@ public Collection getAllRunning() { private Optional parsePortFromProcess(ProcessHandle ph) { Optional portStr = - Arrays.stream(ph.info().arguments().orElse(new String[] {})) + arguments(ph).stream() .filter(a -> a.contains("-Djetty.port=")) .map(s -> s.split("=")[1]) .findFirst(); @@ -124,11 +126,71 @@ private Optional parsePortFromProcess(ProcessHandle ph) { } private boolean isProcessSsl(ProcessHandle ph) { - return Arrays.stream(ph.info().arguments().orElse(new String[] {})) + return arguments(ph).stream() .anyMatch( arg -> List.of("--module=https", "--module=ssl", "--module=ssl-reload").contains(arg)); } + /** + * Gets the command line of a process as a string. This is a workaround for the fact that + * ProcessHandle.info().command() is not (yet) implemented on Windows. + * + * @param ph the process handle + * @return the command line of the process + */ + private static Optional commandLine(ProcessHandle ph) { + if (!Constants.WINDOWS) { + return ph.info().commandLine(); + } else { + long desiredProcessid = ph.pid(); + try { + Process process = + new ProcessBuilder( + "wmic", + "process", + "where", + "ProcessID=" + desiredProcessid, + "get", + "commandline", + "/format:list") + .redirectErrorStream(true) + .start(); + try (InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream()); + BufferedReader reader = new BufferedReader(inputStreamReader)) { + while (true) { + String line = reader.readLine(); + if (line == null) { + return Optional.empty(); + } + if (!line.startsWith("CommandLine=")) { + continue; + } + return Optional.of(line.substring("CommandLine=".length())); + } + } + } catch (IOException e) { + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, + "Error getting command line for process " + desiredProcessid, + e); + } + } + } + + /** + * Gets the arguments of a process as a list of strings. With workaround for Windows. + * + * @param ph the process handle + * @return the arguments of the process + */ + private static List arguments(ProcessHandle ph) { + if (!Constants.WINDOWS) { + return Arrays.asList(ph.info().arguments().orElse(new String[] {})); + } else { + return Arrays.asList(commandLine(ph).orElse("").split("\\s+")); + } + } + /** Represents a running Solr process */ public static class SolrProcess { private final long pid; diff --git a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java index aed3d9f9022..0f0af3d6ae1 100644 --- a/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java +++ b/solr/core/src/test/org/apache/solr/cli/SolrProcessManagerTest.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.stream.Collectors; import org.apache.commons.math3.util.Pair; +import org.apache.lucene.util.Constants; import org.apache.solr.SolrTestCase; import org.apache.solr.cli.SolrProcessManager.SolrProcess; import org.junit.AfterClass; @@ -38,6 +39,7 @@ public class SolrProcessManagerTest extends SolrTestCase { private static SolrProcessManager solrProcessManager; private static Pair processHttp; private static Pair processHttps; + private static final String PID_SUFFIX = Constants.WINDOWS ? ".pid" : ".port"; @BeforeClass public static void beforeClass() throws Exception { @@ -49,12 +51,12 @@ public static void beforeClass() throws Exception { pidDir.toFile().deleteOnExit(); System.setProperty("solr.pid.dir", pidDir.toString()); Files.writeString( - pidDir.resolve("solr-" + processHttp.getKey() + ".pid"), + pidDir.resolve("solr-" + processHttp.getKey() + PID_SUFFIX), Long.toString(processHttp.getValue().pid())); Files.writeString( - pidDir.resolve("solr-" + processHttps.getKey() + ".pid"), + pidDir.resolve("solr-" + processHttps.getKey() + PID_SUFFIX), Long.toString(processHttps.getValue().pid())); - Files.writeString(pidDir.resolve("solr-99999.pid"), "99999"); // Invalid + Files.writeString(pidDir.resolve("solr-99999" + PID_SUFFIX), "99999"); // Invalid solrProcessManager = new SolrProcessManager(); } From 95fbeec5d628b3a738e24120ff791acb12dfda8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 13 Oct 2024 17:09:22 +0200 Subject: [PATCH 15/33] ForbiddenAPIs --- .../core/src/java/org/apache/solr/cli/SolrProcessManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java index a8cbf8cea91..6937cd5665c 100644 --- a/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -155,7 +156,8 @@ private static Optional commandLine(ProcessHandle ph) { "/format:list") .redirectErrorStream(true) .start(); - try (InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream()); + try (InputStreamReader inputStreamReader = + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8); BufferedReader reader = new BufferedReader(inputStreamReader)) { while (true) { String line = reader.readLine(); From cc922f879cd257d0698633b9086f43d81d4e7d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 13 Oct 2024 18:37:04 +0200 Subject: [PATCH 16/33] Fix status help test --- solr/packaging/test/test_help.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/packaging/test/test_help.bats b/solr/packaging/test/test_help.bats index 90306553e20..54bab9fe85b 100644 --- a/solr/packaging/test/test_help.bats +++ b/solr/packaging/test/test_help.bats @@ -60,7 +60,7 @@ setup() { @test "status help flag prints help" { run solr status --help - assert_output --partial 'usage: bin/solr status [--max-wait-secs ] [-p ] [--short] [-url ]' + assert_output --partial 'usage: bin/solr status' refute_output --partial 'ERROR' } From a5c7b1436f60f1164d74b6129823329e7366ca25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Mon, 14 Oct 2024 00:18:40 +0200 Subject: [PATCH 17/33] When using solrUrl,print "Found 1 Solr nodes" only if actually found --- solr/core/src/java/org/apache/solr/cli/StatusTool.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index a3c01fbd8d4..3955089c35a 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -139,15 +139,16 @@ public void runImpl(CommandLine cli) throws Exception { } } } else { + // We have a solrUrl int urlPort = portFromUrl(solrUrl); if (shortFormat) { if (processMgr.isRunningWithPort(urlPort)) { CLIO.out(solrUrl); } } else { - CLIO.out(String.format(Locale.ROOT, "\nFound %s Solr nodes: ", 1)); Optional process = processMgr.processForPort(urlPort); if (process.isPresent()) { + CLIO.out(String.format(Locale.ROOT, "\nFound %s Solr nodes: ", 1)); printProcessStatus(process.get(), cli); } else { CLIO.out("\nNo Solr running on port " + urlPort + "."); From 335efbb277f00d37bb3bc01e7030c02865157855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Mon, 14 Oct 2024 00:18:49 +0200 Subject: [PATCH 18/33] Fix BasicAuthIntegrationTest by allowing StatusTool to test provided URL even if no OS process --- .../core/src/java/org/apache/solr/cli/StatusTool.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index 3955089c35a..8210bdd7260 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -17,6 +17,7 @@ package org.apache.solr.cli; +import static org.apache.solr.cli.SolrCLI.OPTION_CREDENTIALS; import static org.apache.solr.cli.SolrCLI.OPTION_SOLRURL; import java.io.PrintStream; @@ -99,6 +100,7 @@ public List

Get the status of a Solr server. */ public class StatusTool extends ToolBase { - private static Map processes; private final SolrProcessManager processMgr; public StatusTool() { @@ -100,7 +98,6 @@ public List