This is an automated email from the ASF dual-hosted git repository.
mbathori pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 2b1055e5f8 NIFI-14156 Improved Bootstrap Process on Windows with
Attach API
2b1055e5f8 is described below
commit 2b1055e5f85c9bf1ea9f8506cd55d73731b28cc8
Author: exceptionfactory <[email protected]>
AuthorDate: Sat Feb 1 11:45:29 2025 -0600
NIFI-14156 Improved Bootstrap Process on Windows with Attach API
- Added Virtual Machine Attach API implementation of to support Bootstrap
commands on Windows
- Improved nifi.cmd start handling to launch in minimized window
This closes #9683.
Signed-off-by: Mark Bathori <[email protected]>
---
.../command/ManagementServerBootstrapCommand.java | 18 ++-
.../command/StandardBootstrapCommandProvider.java | 20 +++-
...tualMachineManagementServerAddressProvider.java | 66 +++++++++++
.../VirtualMachineProcessHandleProvider.java | 127 +++++++++++++++++++++
.../VirtualMachineProcessHandleProviderTest.java | 63 ++++++++++
.../nifi-resources/src/main/resources/bin/nifi.cmd | 11 +-
6 files changed, 299 insertions(+), 6 deletions(-)
diff --git
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/ManagementServerBootstrapCommand.java
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/ManagementServerBootstrapCommand.java
index 296ee06294..2db77be350 100644
---
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/ManagementServerBootstrapCommand.java
+++
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/ManagementServerBootstrapCommand.java
@@ -21,6 +21,7 @@ import
org.apache.nifi.bootstrap.command.io.ResponseStreamHandler;
import
org.apache.nifi.bootstrap.command.process.ManagementServerAddressProvider;
import
org.apache.nifi.bootstrap.command.process.ProcessHandleManagementServerAddressProvider;
import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
+import
org.apache.nifi.bootstrap.command.process.VirtualMachineManagementServerAddressProvider;
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
import org.apache.nifi.bootstrap.configuration.ManagementServerPath;
import org.slf4j.Logger;
@@ -107,7 +108,7 @@ class ManagementServerBootstrapCommand implements
BootstrapCommand {
}
protected void run(final ProcessHandle applicationProcessHandle) {
- final ManagementServerAddressProvider managementServerAddressProvider
= new ProcessHandleManagementServerAddressProvider(applicationProcessHandle);
+ final ManagementServerAddressProvider managementServerAddressProvider
= getManagementServerAddressProvider(applicationProcessHandle);
final Optional<String> managementServerAddress =
managementServerAddressProvider.getAddress();
final long pid = applicationProcessHandle.pid();
@@ -176,4 +177,19 @@ class ManagementServerBootstrapCommand implements
BootstrapCommand {
builder.connectTimeout(CONNECT_TIMEOUT);
return builder.build();
}
+
+ private ManagementServerAddressProvider
getManagementServerAddressProvider(final ProcessHandle
applicationProcessHandle) {
+ final ManagementServerAddressProvider managementServerAddressProvider;
+
+ final ProcessHandle.Info applicationProcessHandleInfo =
applicationProcessHandle.info();
+ final Optional<String[]> arguments =
applicationProcessHandleInfo.arguments();
+ if (arguments.isPresent()) {
+ managementServerAddressProvider = new
ProcessHandleManagementServerAddressProvider(applicationProcessHandle);
+ } else {
+ // Use Virtual Machine Attach API when ProcessHandle does not
support arguments as described in JDK-8176725
+ managementServerAddressProvider = new
VirtualMachineManagementServerAddressProvider(applicationProcessHandle);
+ }
+
+ return managementServerAddressProvider;
+ }
}
diff --git
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/StandardBootstrapCommandProvider.java
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/StandardBootstrapCommandProvider.java
index 94bd59d852..7ea42402e5 100644
---
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/StandardBootstrapCommandProvider.java
+++
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/StandardBootstrapCommandProvider.java
@@ -24,6 +24,7 @@ import
org.apache.nifi.bootstrap.command.io.ResponseStreamHandler;
import org.apache.nifi.bootstrap.command.io.StandardBootstrapArgumentParser;
import org.apache.nifi.bootstrap.command.process.StandardProcessHandleProvider;
import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
+import
org.apache.nifi.bootstrap.command.process.VirtualMachineProcessHandleProvider;
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
import org.apache.nifi.bootstrap.configuration.StandardConfigurationProvider;
@@ -99,7 +100,7 @@ public class StandardBootstrapCommandProvider implements
BootstrapCommandProvide
private BootstrapCommand getBootstrapCommand(final BootstrapArgument
bootstrapArgument, final String[] arguments) {
final ConfigurationProvider configurationProvider = new
StandardConfigurationProvider(System.getenv(), System.getProperties());
- final ProcessHandleProvider processHandleProvider = new
StandardProcessHandleProvider(configurationProvider);
+ final ProcessHandleProvider processHandleProvider =
getProcessHandleProvider(configurationProvider);
final ResponseStreamHandler commandLoggerStreamHandler = new
LoggerResponseStreamHandler(commandLogger);
final BootstrapCommand stopBootstrapCommand = new
StopBootstrapCommand(processHandleProvider, configurationProvider);
@@ -238,4 +239,21 @@ public class StandardBootstrapCommandProvider implements
BootstrapCommandProvide
return responseStreamHandler;
}
+
+ private ProcessHandleProvider getProcessHandleProvider(final
ConfigurationProvider configurationProvider) {
+ final ProcessHandleProvider processHandleProvider;
+
+ final ProcessHandle currentProcessHandle = ProcessHandle.current();
+ final ProcessHandle.Info currentProcessHandleInfo =
currentProcessHandle.info();
+ final Optional<String[]> currentProcessArguments =
currentProcessHandleInfo.arguments();
+
+ if (currentProcessArguments.isPresent()) {
+ processHandleProvider = new
StandardProcessHandleProvider(configurationProvider);
+ } else {
+ // Use Virtual Machine Attach API when ProcessHandle does not
support arguments as described in JDK-8176725
+ processHandleProvider = new
VirtualMachineProcessHandleProvider(configurationProvider);
+ }
+
+ return processHandleProvider;
+ }
}
diff --git
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineManagementServerAddressProvider.java
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineManagementServerAddressProvider.java
new file mode 100644
index 0000000000..dd142ca811
--- /dev/null
+++
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineManagementServerAddressProvider.java
@@ -0,0 +1,66 @@
+/*
+ * 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.command.process;
+
+import com.sun.tools.attach.VirtualMachine;
+import org.apache.nifi.bootstrap.configuration.SystemProperty;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * Provider implementation resolves the Management Server Address from System
Properties of Application Virtual Machine using the Attach API
+ */
+public class VirtualMachineManagementServerAddressProvider implements
ManagementServerAddressProvider {
+ private final ProcessHandle processHandle;
+
+ public VirtualMachineManagementServerAddressProvider(final ProcessHandle
processHandle) {
+ this.processHandle = Objects.requireNonNull(processHandle);
+ }
+
+ /**
+ * Get Management Server Address from System Properties of Application
Virtual Machine using Attach API
+ *
+ * @return Management Server Address or null when not found
+ */
+ @Override
+ public Optional<String> getAddress() {
+ String managementServerAddress = null;
+
+ final String applicationProcessId = Long.toString(processHandle.pid());
+
+ try {
+ final VirtualMachine virtualMachine =
VirtualMachine.attach(applicationProcessId);
+ try {
+ managementServerAddress = getAddress(virtualMachine);
+ } finally {
+ virtualMachine.detach();
+ }
+ } catch (final Exception ignored) {
+
+ }
+
+ return Optional.ofNullable(managementServerAddress);
+ }
+
+ private String getAddress(final VirtualMachine virtualMachine) throws
IOException {
+ final Properties systemProperties =
virtualMachine.getSystemProperties();
+ return
systemProperties.getProperty(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty());
+ }
+}
diff --git
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProvider.java
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProvider.java
new file mode 100644
index 0000000000..74e8c5ff03
--- /dev/null
+++
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProvider.java
@@ -0,0 +1,127 @@
+/*
+ * 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.command.process;
+
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+import com.sun.tools.attach.spi.AttachProvider;
+import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
+import org.apache.nifi.bootstrap.configuration.SystemProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * Virtual Machine implementation of ProcessHandle Provider using the Attach
API with System Properties
+ */
+public class VirtualMachineProcessHandleProvider implements
ProcessHandleProvider {
+ private static final Logger logger =
LoggerFactory.getLogger(VirtualMachineProcessHandleProvider.class);
+
+ private final ConfigurationProvider configurationProvider;
+
+ public VirtualMachineProcessHandleProvider(final ConfigurationProvider
configurationProvider) {
+ this.configurationProvider =
Objects.requireNonNull(configurationProvider);
+ }
+
+ /**
+ * Find Process Handle for Application based on matching argument for path
to application properties
+ *
+ * @return Application Process Handle or empty when not found
+ */
+ @Override
+ public Optional<ProcessHandle> findApplicationProcessHandle() {
+ final Path applicationProperties =
configurationProvider.getApplicationProperties();
+ return findProcessHandle(SystemProperty.APPLICATION_PROPERTIES,
applicationProperties);
+ }
+
+ /**
+ * Find Process Handle for Bootstrap based on matching argument for path
to bootstrap configuration
+ *
+ * @return Bootstrap Process Handle or empty when not found
+ */
+ @Override
+ public Optional<ProcessHandle> findBootstrapProcessHandle() {
+ final Path bootstrapConfiguration =
configurationProvider.getBootstrapConfiguration();
+ return findProcessHandle(SystemProperty.BOOTSTRAP_CONFIGURATION,
bootstrapConfiguration);
+ }
+
+ private Optional<ProcessHandle> findProcessHandle(final SystemProperty
systemProperty, final Path configuration) {
+ final ProcessHandle currentProcessHandle = ProcessHandle.current();
+ final String currentProcessId =
Long.toString(currentProcessHandle.pid());
+
+ Optional<ProcessHandle> processHandleFound = Optional.empty();
+
+ final List<VirtualMachineDescriptor> virtualMachineDescriptors =
VirtualMachine.list();
+ for (final VirtualMachineDescriptor virtualMachineDescriptor :
virtualMachineDescriptors) {
+ final String virtualMachineId = virtualMachineDescriptor.id();
+ if (currentProcessId.equals(virtualMachineId)) {
+ continue;
+ }
+
+ processHandleFound = findProcessHandle(virtualMachineDescriptor,
systemProperty, configuration);
+ if (processHandleFound.isPresent()) {
+ break;
+ }
+ }
+
+ return processHandleFound;
+ }
+
+ private Optional<ProcessHandle> findProcessHandle(final
VirtualMachineDescriptor descriptor, final SystemProperty systemProperty, final
Path configuration) {
+ final AttachProvider attachProvider = descriptor.provider();
+ final String virtualMachineId = descriptor.id();
+
+ Optional<ProcessHandle> processHandle = Optional.empty();
+ try {
+ final VirtualMachine virtualMachine =
attachProvider.attachVirtualMachine(virtualMachineId);
+ logger.debug("Attached Virtual Machine [{}]", virtualMachine.id());
+ try {
+ processHandle = findProcessHandle(virtualMachine,
systemProperty, configuration);
+ } finally {
+ virtualMachine.detach();
+ }
+ } catch (final Exception e) {
+ logger.debug("Attach Virtual Machine [{}] failed",
virtualMachineId, e);
+ }
+
+ return processHandle;
+ }
+
+ private Optional<ProcessHandle> findProcessHandle(final VirtualMachine
virtualMachine, final SystemProperty systemProperty, final Path configuration)
throws IOException {
+ final Properties systemProperties =
virtualMachine.getSystemProperties();
+ final String configurationProperty =
systemProperties.getProperty(systemProperty.getProperty());
+ final String configurationPath = configuration.toString();
+
+ final Optional<ProcessHandle> processHandle;
+
+ if (configurationPath.equals(configurationProperty)) {
+ final String virtualMachineId = virtualMachine.id();
+ final long processId = Long.parseLong(virtualMachineId);
+ processHandle = ProcessHandle.of(processId);
+ } else {
+ processHandle = Optional.empty();
+ }
+
+ return processHandle;
+ }
+}
diff --git
a/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProviderTest.java
b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProviderTest.java
new file mode 100644
index 0000000000..11d53e2b4c
--- /dev/null
+++
b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/process/VirtualMachineProcessHandleProviderTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.command.process;
+
+import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.nio.file.Path;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class VirtualMachineProcessHandleProviderTest {
+ @Mock
+ private ConfigurationProvider configurationProvider;
+
+ @TempDir
+ private Path tempDir;
+
+ private VirtualMachineProcessHandleProvider provider;
+
+ @BeforeEach
+ void setProvider() {
+ provider = new
VirtualMachineProcessHandleProvider(configurationProvider);
+ }
+
+ @Test
+ void testFindApplicationProcessHandleEmpty() {
+
when(configurationProvider.getApplicationProperties()).thenReturn(tempDir);
+ final Optional<ProcessHandle> applicationProcessHandle =
provider.findApplicationProcessHandle();
+
+ assertTrue(applicationProcessHandle.isEmpty());
+ }
+
+ @Test
+ void testFindBootstrapProcessHandleEmpty() {
+
when(configurationProvider.getBootstrapConfiguration()).thenReturn(tempDir);
+ final Optional<ProcessHandle> bootstrapProcessHandle =
provider.findBootstrapProcessHandle();
+
+ assertTrue(bootstrapProcessHandle.isEmpty());
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.cmd
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.cmd
index 6a57487606..d8cf90df66 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.cmd
+++
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.cmd
@@ -33,7 +33,7 @@ set
CONFIG_FILE_PROPERTY=-Dorg.apache.nifi.bootstrap.config.file=%CONF_DIR%\boot
set
PROPERTIES_FILE_PROPERTY=-Dnifi.properties.file.path=%CONF_DIR%\nifi.properties
set BOOTSTRAP_HEAP_SIZE=48m
-set JAVA_ARGS=%LOG_DIR_PROPERTY% %CONFIG_FILE_PROPERTY%
%PROPERTIES_FILE_PROPERTY%
+set JAVA_ARGS=%LOG_DIR_PROPERTY% %CONFIG_FILE_PROPERTY%
set JAVA_PARAMS=-cp %BOOTSTRAP_LIB_DIR%\*;%CONF_DIR% %JAVA_ARGS%
set JAVA_MEMORY=-Xms%BOOTSTRAP_HEAP_SIZE% -Xmx%BOOTSTRAP_HEAP_SIZE%
@@ -47,11 +47,14 @@ set RUN_COMMAND="%~1"
if %RUN_COMMAND% == "set-single-user-credentials" (
rem Set credentials with quoted arguments passed to Java command
set "CREDENTIALS=^"%~2^" ^"%~3^""
- call "%JAVA_EXE%" %JAVA_PARAMS%
org.apache.nifi.authentication.single.user.command.SetSingleUserCredentials
%CREDENTIALS%
+ call "%JAVA_EXE%" %JAVA_PARAMS% %PROPERTIES_FILE_PROPERTY%
org.apache.nifi.authentication.single.user.command.SetSingleUserCredentials
%CREDENTIALS%
) else if %RUN_COMMAND% == "set-sensitive-properties-key" (
- call "%JAVA_EXE%" %JAVA_PARAMS%
org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesKey %~2
+ call "%JAVA_EXE%" %JAVA_PARAMS% %PROPERTIES_FILE_PROPERTY%
org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesKey %~2
) else if %RUN_COMMAND% == "set-sensitive-properties-algorithm" (
- call "%JAVA_EXE%" %JAVA_PARAMS%
org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm %~2
+ call "%JAVA_EXE%" %JAVA_PARAMS% %PROPERTIES_FILE_PROPERTY%
org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm %~2
+) else if %RUN_COMMAND% == "start" (
+ rem Start bootstrap process in new minimized window
+ call start /MIN "Apache NiFi" "%JAVA_EXE%" %JAVA_MEMORY% %JAVA_PARAMS%
org.apache.nifi.bootstrap.BootstrapProcess %RUN_COMMAND%
) else (
call "%JAVA_EXE%" %JAVA_MEMORY% %JAVA_PARAMS%
org.apache.nifi.bootstrap.BootstrapProcess %RUN_COMMAND%
)