Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-17450 StatusTool with pure Java code #2712

Merged
merged 36 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9f03425
SOLR-17450 Status Tool pure java
janhoy Jul 3, 2024
e1f1e93
Precommit
janhoy Sep 13, 2024
8cc2841
Another test, fix https parsing bug
janhoy Sep 13, 2024
a2c5890
Add security mgr permission
janhoy Sep 13, 2024
7688673
Fix test_help bats test
janhoy Sep 13, 2024
d724052
Review feedback about throwing exception instead of doing exit
janhoy Sep 14, 2024
e487af1
Rename Manager
janhoy Sep 14, 2024
d0aad43
Tidy
janhoy Sep 14, 2024
ebeda4f
Try to skip the manageProcess permission in runtime, only keep it for…
janhoy Sep 14, 2024
045b4b4
Add a permission, compact some code
janhoy Sep 14, 2024
989a32f
Fix tests
janhoy Sep 15, 2024
114f9e1
Re-enable execute permission
janhoy Sep 15, 2024
e34b701
Use a shorter classpath
janhoy Sep 15, 2024
a381674
Windows support
janhoy Oct 13, 2024
95fbeec
ForbiddenAPIs
janhoy Oct 13, 2024
9059295
Merge branch 'main' into SOLR-17450-status-tool-java
janhoy Oct 13, 2024
cc922f8
Fix status help test
janhoy Oct 13, 2024
a5c7b14
When using solrUrl,print "Found 1 Solr nodes" only if actually found
janhoy Oct 13, 2024
335efbb
Fix BasicAuthIntegrationTest by allowing StatusTool to test provided …
janhoy Oct 13, 2024
5ac06b5
Tidy
janhoy Oct 13, 2024
55f6798
Don't consult local processes when passed --solr-url
janhoy Oct 15, 2024
84ba3f6
Merge branch 'main' into SOLR-17450-status-tool-java
janhoy Oct 15, 2024
6f565f1
Refactor to make code more readable
janhoy Oct 15, 2024
bddc2ce
Update solr/packaging/test/test_status.bats
janhoy Oct 15, 2024
52f5af4
Invalid url test
janhoy Oct 15, 2024
ff51469
Handle default ports when resolving from URL
janhoy Oct 15, 2024
cef59a7
Update security policy for wmic.exe
janhoy Oct 15, 2024
09f0bb9
Fix pid dir bug
janhoy Oct 16, 2024
58f7d87
Handle PORT file on windows
janhoy Oct 16, 2024
3082e69
Restore correct printout when starting Solr on Windows
janhoy Oct 16, 2024
7d92970
Reverting unnecessary change to policy
janhoy Oct 16, 2024
47fb94c
Merge branch 'main' into SOLR-17450-status-tool-java
janhoy Oct 17, 2024
0364452
Make --max-wait-secs a stealth option
janhoy Oct 17, 2024
9835f06
Fix precommit
janhoy Oct 17, 2024
0d0ee43
Cleanup code structure.
janhoy Oct 18, 2024
a3002f3
Remove extra two lines of output, adapt test.
janhoy Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gradle/testing/randomization/policies/solr-tests.policy
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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";
janhoy marked this conversation as resolved.
Show resolved Hide resolved

// These two *have* to be spelled out a separate
permission java.lang.management.ManagementPermission "control";
Expand Down Expand Up @@ -250,6 +252,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";
janhoy marked this conversation as resolved.
Show resolved Hide resolved
};

// Grant all permissions to Gradle test runner classes.
Expand Down
50 changes: 1 addition & 49 deletions solr/bin/solr
Original file line number Diff line number Diff line change
Expand Up @@ -493,55 +493,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)
Expand Down Expand Up @@ -632,12 +590,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}"
Expand Down
30 changes: 1 addition & 29 deletions solr/bin/solr.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1208,34 +1208,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" ^
Expand Down
224 changes: 224 additions & 0 deletions solr/core/src/java/org/apache/solr/cli/SolrProcessManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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.BufferedReader;
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
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.lucene.util.Constants;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.EnvUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Class to interact with Solr OS processes */
public class SolrProcessManager {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final Map<Long, SolrProcess> pidProcessMap;
private final Map<Integer, SolrProcess> 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
public static boolean enableTestingMode = false;

public SolrProcessManager() {
pidProcessMap =
ProcessHandle.allProcesses()
.filter(p -> p.info().command().orElse("").contains("java"))
.filter(p -> commandLine(p).orElse("").contains("-Djetty.port="))
.filter(
p -> !enableTestingMode || commandLine(p).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<SolrProcess> 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<SolrProcess> getProcessForPid(Long pid) {
return pidProcessMap.containsKey(pid) ? Optional.of(pidProcessMap.get(pid)) : Optional.empty();
}

public Collection<SolrProcess> scanSolrPidFiles() throws IOException {
List<SolrProcess> processes = new ArrayList<>();
try (Stream<Path> 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<SolrProcess> 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<SolrProcess> getAllRunning() {
return pidProcessMap.values();
}

private Optional<Integer> parsePortFromProcess(ProcessHandle ph) {
Optional<String> portStr =
arguments(ph).stream()
.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 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<String> 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(), StandardCharsets.UTF_8);
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<String> 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;
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(Locale.ROOT, "%s://localhost:%s/solr", isHttps ? "https" : "http", port);
}
}
}
Loading
Loading