NIFI-6714 Corrected OSUtils to use Process.pid interface method

Signed-off-by: Nathan Gough <thenatog@gmail.com>

This closes #4970.
This commit is contained in:
exceptionfactory 2021-04-03 23:48:07 -05:00 committed by Nathan Gough
parent fa3da2aa1c
commit ef60e7e404
3 changed files with 124 additions and 174 deletions

View File

@ -31,62 +31,6 @@ import org.slf4j.Logger;
* OS specific utilities with generic method interfaces
*/
public final class OSUtils {
/**
* @param process NiFi Process Reference
* @param logger Logger Reference for Debug
* @return Returns pid or null in-case pid could not be determined
* This method takes {@link Process} and {@link Logger} and returns
* the platform specific ProcessId for Unix like systems, a.k.a <b>pid</b>
* In-case it fails to determine the pid, it will return Null.
* Purpose for the Logger is to log any interaction for debugging.
*/
private static Long getUnicesPid(final Process process, final Logger logger) {
try {
final Class<?> procClass = process.getClass();
final Field pidField = procClass.getDeclaredField("pid");
pidField.setAccessible(true);
final Object pidObject = pidField.get(process);
logger.debug("PID Object = {}", pidObject);
if (pidObject instanceof Number) {
return ((Number) pidObject).longValue();
}
return null;
} catch (final IllegalAccessException | NoSuchFieldException nsfe) {
logger.debug("Could not find PID for child process due to {}", nsfe);
return null;
}
}
/**
* @param process NiFi Process Reference
* @param logger Logger Reference for Debug
* @return Returns pid or null in-case pid could not be determined
* This method takes {@link Process} and {@link Logger} and returns
* the platform specific Handle for Win32 Systems, a.k.a <b>pid</b>
* In-case it fails to determine the pid, it will return Null.
* Purpose for the Logger is to log any interaction for debugging.
*/
private static Long getWindowsProcessId(final Process process, final Logger logger) {
/* determine the pid on windows plattforms */
try {
Field f = process.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(process);
Kernel32 kernel = Kernel32.INSTANCE;
WinNT.HANDLE handle = new WinNT.HANDLE();
handle.setPointer(Pointer.createConstant(handl));
int ret = kernel.GetProcessId(handle);
logger.debug("Detected pid: {}", ret);
return Long.valueOf(ret);
} catch (final IllegalAccessException | NoSuchFieldException nsfe) {
logger.debug("Could not find PID for child process due to {}", nsfe);
}
return null;
}
/**
* @param process NiFi Process Reference
* @param logger Logger Reference for Debug
@ -109,49 +53,35 @@ public final class OSUtils {
* of the pid method to the Process API.
*/
Long pid = null;
if (!System.getProperty("java.version").startsWith("1.")) {
try {
Method pidMethod = process.getClass().getMethod("pid");
pidMethod.setAccessible(true);
Object pidMethodResult = pidMethod.invoke(process);
if (Long.class.isAssignableFrom(pidMethodResult.getClass())) {
pid = (Long) pidMethodResult;
} else {
logger.debug("Could not determine PID for child process because returned PID was not " +
"assignable to type " + Long.class.getName());
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
logger.debug("Could not find PID for child process due to {}", e);
try {
// Get Process.pid() interface method to avoid illegal reflective access
final Method pidMethod = Process.class.getDeclaredMethod("pid");
final Object pidNumber = pidMethod.invoke(process);
if (pidNumber instanceof Long) {
pid = (Long) pidNumber;
}
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
final String processClassName = process.getClass().getName();
if (processClassName.equals("java.lang.UNIXProcess")) {
pid = getUnixPid(process, logger);
} else if (processClassName.equals("java.lang.Win32Process")
|| processClassName.equals("java.lang.ProcessImpl")) {
pid = getWindowsProcessId(process, logger);
} else {
logger.debug("Failed to determine Process ID from [{}]: {}", processClassName, e.getMessage());
}
} else if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
pid = getUnicesPid(process, logger);
} else if (process.getClass().getName().equals("java.lang.Win32Process")
|| process.getClass().getName().equals("java.lang.ProcessImpl")) {
pid = getWindowsProcessId(process, logger);
}
return pid;
}
// The two Java version methods are copied from CertificateUtils in nifi-commons/nifi-security-utils
/**
* Returns the JVM Java major version based on the System properties (e.g. {@code JVM 1.8.0.231} -> {code 8}).
*
* @return the Java major version
*/
public static int getJavaVersion() {
String version = System.getProperty("java.version");
return parseJavaVersion(version);
}
/**
* Returns the major version parsed from the provided Java version string (e.g. {@code "1.8.0.231"} -> {@code 8}).
*
* @param version the Java version string
* @return the major version as an int
*/
public static int parseJavaVersion(String version) {
public static int parseJavaVersion(final String version) {
String majorVersion;
if (version.startsWith("1.")) {
majorVersion = version.substring(2, 3);
@ -167,4 +97,55 @@ public final class OSUtils {
return Integer.parseInt(majorVersion);
}
/**
* @param process NiFi Process Reference
* @param logger Logger Reference for Debug
* @return Returns pid or null in-case pid could not be determined
* This method takes {@link Process} and {@link Logger} and returns
* the platform specific ProcessId for Unix like systems, a.k.a <b>pid</b>
* In-case it fails to determine the pid, it will return Null.
* Purpose for the Logger is to log any interaction for debugging.
*/
private static Long getUnixPid(final Process process, final Logger logger) {
try {
final Class<?> procClass = process.getClass();
final Field pidField = procClass.getDeclaredField("pid");
pidField.setAccessible(true);
final Object pidObject = pidField.get(process);
if (pidObject instanceof Number) {
return ((Number) pidObject).longValue();
}
return null;
} catch (final IllegalAccessException | NoSuchFieldException e) {
logger.debug("Could not find Unix PID", e);
return null;
}
}
/**
* @param process NiFi Process Reference
* @param logger Logger Reference for Debug
* @return Returns pid or null in-case pid could not be determined
* This method takes {@link Process} and {@link Logger} and returns
* the platform specific Handle for Win32 Systems, a.k.a <b>pid</b>
* In-case it fails to determine the pid, it will return Null.
* Purpose for the Logger is to log any interaction for debugging.
*/
private static Long getWindowsProcessId(final Process process, final Logger logger) {
Long pid = null;
try {
final Field handleField = process.getClass().getDeclaredField("handle");
handleField.setAccessible(true);
long peer = handleField.getLong(process);
final Kernel32 kernel = Kernel32.INSTANCE;
final WinNT.HANDLE handle = new WinNT.HANDLE();
handle.setPointer(Pointer.createConstant(peer));
pid = Long.valueOf(kernel.GetProcessId(handle));
} catch (final IllegalAccessException | NoSuchFieldException e) {
logger.debug("Could not find Windows PID", e);
}
return pid;
}
}

View File

@ -1,87 +0,0 @@
/*
* 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.nifi.bootstrap.util
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@RunWith(JUnit4.class)
class OSUtilsTest extends GroovyTestCase {
private static final Logger logger = LoggerFactory.getLogger(OSUtilsTest.class)
@BeforeClass
static void setUpOnce() throws Exception {
logger.metaClass.methodMissing = { String name, args ->
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
}
}
@AfterClass
static void tearDownOnce() throws Exception {
}
@Before
void setUp() throws Exception {
}
@After
void tearDown() throws Exception {
}
@Test
void testShouldParseJavaMajorVersion8Below() {
// Arrange
def possibleVersionStrings = ["1.8", "1.8.0.0", "1.8.0_262"]
// Act
def results = possibleVersionStrings.collect {
OSUtils.parseJavaVersion(it)
}
logger.info("Parsed Java versions: ${results}")
// Assert
assert results.every { it == 8 }
}
@Test
void testShouldParseJavaMajorVersion9Plus() {
// Arrange
def possibleVersionStrings = [
"11.0.6", "11.0.0", "11.12.13", "11"
]
// Act
def results = possibleVersionStrings.collect {
OSUtils.parseJavaVersion(it)
}
logger.info("Parsed Java versions: ${results}")
// Assert
assert results.every { it == 11 }
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.nifi.bootstrap.http;
import org.apache.nifi.bootstrap.util.OSUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class OSUtilsTest {
@Test
public void testGetPid() throws IOException {
final ProcessBuilder builder = new ProcessBuilder();
final Process process = builder.command("java").start();
final Logger logger = LoggerFactory.getLogger("testing");
final Long pid = OSUtils.getProcessId(process, logger);
process.destroy();
assertNotNull("Process ID not found", pid);
}
@Test
public void testParseJavaVersion8() {
final String[] versions = new String[] { "1.8", "1.8.0", "1.8.0_100" };
for (final String version : versions) {
assertEquals(8, OSUtils.parseJavaVersion(version));
}
}
@Test
public void testParseJavaVersion11() {
final String[] versions = new String[] { "11", "11.0", "11.0.11" };
for (final String version : versions) {
assertEquals(11, OSUtils.parseJavaVersion(version));
}
}
}