This is an automated email from the ASF dual-hosted git repository.
pvillard 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 b586cd189d NIFI-15139 Fixed bootstrap diagnostics argument handling
b586cd189d is described below
commit b586cd189da472cd607a72529b8314e69b5f9206
Author: exceptionfactory <[email protected]>
AuthorDate: Mon Oct 27 16:10:00 2025 -0500
NIFI-15139 Fixed bootstrap diagnostics argument handling
- Refactored diagnostics command handling to support verbose and output
path arguments together
Signed-off-by: Pierre Villard <[email protected]>
This closes #10469.
---
.../DiagnosticsBootstrapCommandProvider.java | 92 +++++++++
.../command/StandardBootstrapCommandProvider.java | 42 +---
.../DiagnosticsBootstrapCommandProviderTest.java | 223 +++++++++++++++++++++
3 files changed, 317 insertions(+), 40 deletions(-)
diff --git
a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProvider.java
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProvider.java
new file mode 100644
index 0000000000..14727c85b8
--- /dev/null
+++
b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.apache.nifi.bootstrap.command.io.FileResponseStreamHandler;
+import org.apache.nifi.bootstrap.command.io.LoggerResponseStreamHandler;
+import org.apache.nifi.bootstrap.command.io.ResponseStreamHandler;
+import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.apache.nifi.bootstrap.command.io.HttpRequestMethod.GET;
+import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALTH_DIAGNOSTICS;
+
+/**
+ * Diagnostics implementation of Bootstrap Command Provider supporting
optional verbose and optional path arguments
+ */
+class DiagnosticsBootstrapCommandProvider implements BootstrapCommandProvider {
+ private static final String VERBOSE_REQUESTED = "--verbose";
+
+ private static final String VERBOSE_QUERY = "verbose=true";
+
+ private static final int FIRST_ARGUMENT = 1;
+
+ private final ProcessHandleProvider processHandleProvider;
+
+ DiagnosticsBootstrapCommandProvider(final ProcessHandleProvider
processHandleProvider) {
+ this.processHandleProvider =
Objects.requireNonNull(processHandleProvider, "Process Handle Provider
required");
+ }
+
+ @Override
+ public BootstrapCommand getBootstrapCommand(final String[] arguments) {
+ final String verboseQuery = getVerboseQuery(arguments);
+ final ResponseStreamHandler responseStreamHandler =
getDiagnosticsResponseStreamHandler(arguments);
+ return new ManagementServerBootstrapCommand(processHandleProvider,
GET, HEALTH_DIAGNOSTICS, verboseQuery, HTTP_OK, responseStreamHandler);
+ }
+
+ private String getVerboseQuery(final String[] arguments) {
+ String query = null;
+
+ for (final String argument : arguments) {
+ if (VERBOSE_REQUESTED.contentEquals(argument)) {
+ query = VERBOSE_QUERY;
+ break;
+ }
+ }
+
+ return query;
+ }
+
+ private ResponseStreamHandler getDiagnosticsResponseStreamHandler(final
String[] arguments) {
+ final ResponseStreamHandler responseStreamHandler;
+
+ final Optional<Path> outputPathFound = Arrays.stream(arguments)
+ .skip(FIRST_ARGUMENT)
+ .filter(Predicate.not(VERBOSE_REQUESTED::contentEquals))
+ .findFirst()
+ .map(Paths::get);
+
+ if (outputPathFound.isPresent()) {
+ final Path outputPath = outputPathFound.get();
+ responseStreamHandler = new FileResponseStreamHandler(outputPath);
+ } else {
+ final Logger logger =
LoggerFactory.getLogger(StandardBootstrapCommandProvider.class);
+ responseStreamHandler = new LoggerResponseStreamHandler(logger);
+ }
+
+ return responseStreamHandler;
+ }
+}
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 7ea42402e5..c643370eb2 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
@@ -44,7 +44,6 @@ import static
org.apache.nifi.bootstrap.command.io.HttpRequestMethod.DELETE;
import static org.apache.nifi.bootstrap.command.io.HttpRequestMethod.GET;
import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALTH;
import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALTH_CLUSTER;
-import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALTH_DIAGNOSTICS;
import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALTH_STATUS_HISTORY;
/**
@@ -53,10 +52,6 @@ import static
org.apache.nifi.bootstrap.configuration.ManagementServerPath.HEALT
public class StandardBootstrapCommandProvider implements
BootstrapCommandProvider {
private static final String SHUTDOWN_REQUESTED = "--shutdown=true";
- private static final String VERBOSE_REQUESTED = "--verbose";
-
- private static final String VERBOSE_QUERY = "verbose=true";
-
private static final String DAYS_QUERY = "days=%d";
private static final String EMPTY_QUERY = null;
@@ -111,7 +106,8 @@ public class StandardBootstrapCommandProvider implements
BootstrapCommandProvide
} else if (BootstrapArgument.DECOMMISSION == bootstrapArgument) {
bootstrapCommand = getDecommissionCommand(processHandleProvider,
stopBootstrapCommand, arguments);
} else if (BootstrapArgument.DIAGNOSTICS == bootstrapArgument) {
- bootstrapCommand = getDiagnosticsCommand(processHandleProvider,
arguments);
+ final DiagnosticsBootstrapCommandProvider
diagnosticsBootstrapCommandProvider = new
DiagnosticsBootstrapCommandProvider(processHandleProvider);
+ bootstrapCommand =
diagnosticsBootstrapCommandProvider.getBootstrapCommand(arguments);
} else if (BootstrapArgument.GET_RUN_COMMAND == bootstrapArgument) {
bootstrapCommand = new
GetRunCommandBootstrapCommand(configurationProvider, processHandleProvider,
System.out);
} else if (BootstrapArgument.START == bootstrapArgument) {
@@ -143,27 +139,6 @@ public class StandardBootstrapCommandProvider implements
BootstrapCommandProvide
return new SequenceBootstrapCommand(bootstrapCommands);
}
- private BootstrapCommand getDiagnosticsCommand(final ProcessHandleProvider
processHandleProvider, final String[] arguments) {
- final String verboseQuery = getVerboseQuery(arguments);
- final ResponseStreamHandler responseStreamHandler =
getDiagnosticsResponseStreamHandler(arguments);
- return new ManagementServerBootstrapCommand(processHandleProvider,
GET, HEALTH_DIAGNOSTICS, verboseQuery, HTTP_OK, responseStreamHandler);
- }
-
- private ResponseStreamHandler getDiagnosticsResponseStreamHandler(final
String[] arguments) {
- final ResponseStreamHandler responseStreamHandler;
-
- if (arguments.length == PATH_ARGUMENTS) {
- final String outputPathArgument = arguments[FIRST_ARGUMENT];
- final Path outputPath = Paths.get(outputPathArgument);
- responseStreamHandler = new FileResponseStreamHandler(outputPath);
- } else {
- final Logger logger =
LoggerFactory.getLogger(StandardBootstrapCommandProvider.class);
- responseStreamHandler = new LoggerResponseStreamHandler(logger);
- }
-
- return responseStreamHandler;
- }
-
private BootstrapCommand getStatusHistoryCommand(final
ProcessHandleProvider processHandleProvider, final String[] arguments) {
final String daysQuery = getStatusHistoryDaysQuery(arguments);
final ResponseStreamHandler responseStreamHandler =
getStatusHistoryResponseStreamHandler(arguments);
@@ -183,19 +158,6 @@ public class StandardBootstrapCommandProvider implements
BootstrapCommandProvide
return shutdownRequested;
}
- private String getVerboseQuery(final String[] arguments) {
- String query = null;
-
- for (final String argument : arguments) {
- if (VERBOSE_REQUESTED.contentEquals(argument)) {
- query = VERBOSE_QUERY;
- break;
- }
- }
-
- return query;
- }
-
private String getStatusHistoryDaysQuery(final String[] arguments) {
final int daysRequested;
diff --git
a/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProviderTest.java
b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProviderTest.java
new file mode 100644
index 0000000000..b3dc174a47
--- /dev/null
+++
b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/command/DiagnosticsBootstrapCommandProviderTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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;
+
+import com.sun.net.httpserver.HttpServer;
+import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
+import org.apache.nifi.bootstrap.configuration.ManagementServerPath;
+import org.apache.nifi.bootstrap.configuration.SystemProperty;
+import org.junit.jupiter.api.AfterEach;
+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.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class DiagnosticsBootstrapCommandProviderTest {
+ private static final String JAVA_COMMAND = "java";
+
+ private static final String VERBOSE_ARGUMENT = "--verbose";
+
+ private static final String[] EMPTY_ARGUMENTS = new String[0];
+
+ private static final String[] VERBOSE_ARGUMENTS = new
String[]{JAVA_COMMAND, VERBOSE_ARGUMENT};
+
+ private static final URI DIAGNOSTICS_URI =
URI.create(ManagementServerPath.HEALTH_DIAGNOSTICS.getPath());
+
+ private static final URI DIAGNOSTICS_VERBOSE_URI =
URI.create("%s?verbose=true".formatted(ManagementServerPath.HEALTH_DIAGNOSTICS.getPath()));
+
+ private static final String MANAGEMENT_SERVER_ARGUMENT_FORMAT =
"-D%s=127.0.0.1:%d";
+
+ private static final String LOCALHOST_ADDRESS = "127.0.0.1";
+
+ private static final int RANDOM_PORT = 0;
+
+ private static final int BACKLOG = 0;
+
+ private static final int EMPTY_CONTENT_LENGTH = -1;
+
+ private final List<URI> requestUris = new ArrayList<>();
+
+ @Mock
+ private ProcessHandleProvider processHandleProvider;
+
+ @Mock
+ private ProcessHandle processHandle;
+
+ @Mock
+ private ProcessHandle.Info processHandleInfo;
+
+ private HttpServer httpServer;
+
+ private DiagnosticsBootstrapCommandProvider provider;
+
+ @BeforeEach
+ void setProvider() throws IOException {
+ final InetSocketAddress bindAddress = new
InetSocketAddress(LOCALHOST_ADDRESS, RANDOM_PORT);
+ httpServer = HttpServer.create(bindAddress, BACKLOG);
+
httpServer.createContext(ManagementServerPath.HEALTH_DIAGNOSTICS.getPath(),
exchange -> {
+ final URI requestUri = exchange.getRequestURI();
+ requestUris.add(requestUri);
+ exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK,
EMPTY_CONTENT_LENGTH);
+ });
+ httpServer.start();
+
+ provider = new
DiagnosticsBootstrapCommandProvider(processHandleProvider);
+ }
+
+ @AfterEach
+ void stopServer() {
+ httpServer.stop(0);
+ }
+
+ @Test
+ void testGetBootstrapCommandEmptyArgumentsRunStopped() {
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(EMPTY_ARGUMENTS);
+
+ assertNotNull(bootstrapCommand);
+ assertInstanceOf(ManagementServerBootstrapCommand.class,
bootstrapCommand);
+
+ bootstrapCommand.run();
+ final CommandStatus commandStatus =
bootstrapCommand.getCommandStatus();
+ assertEquals(CommandStatus.STOPPED, commandStatus);
+ }
+
+ @Test
+ void testGetBootstrapCommandEmptyArgumentsRunSuccess() {
+ setProcessHandle();
+
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(EMPTY_ARGUMENTS);
+
+ assertCommandStatusSuccess(bootstrapCommand);
+
+ final URI firstUri = requestUris.getFirst();
+ assertEquals(DIAGNOSTICS_URI, firstUri);
+ }
+
+ @Test
+ void testGetBootstrapCommandVerboseArgument() {
+ setProcessHandle();
+
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(VERBOSE_ARGUMENTS);
+
+ assertCommandStatusSuccess(bootstrapCommand);
+
+ final URI firstUri = requestUris.getFirst();
+ assertEquals(DIAGNOSTICS_VERBOSE_URI, firstUri);
+ }
+
+ @Test
+ void testGetBootstrapCommandPathArgument(@TempDir final Path tempDir) {
+ setProcessHandle();
+
+ final Path diagnosticsOutputPath =
tempDir.resolve(UUID.randomUUID().toString());
+ final String diagnosticsOutputPathArgument =
diagnosticsOutputPath.toString();
+ final String[] arguments = new String[]{JAVA_COMMAND,
diagnosticsOutputPathArgument};
+
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(arguments);
+
+ assertCommandStatusSuccess(bootstrapCommand);
+
+ final URI firstUri = requestUris.getFirst();
+ assertEquals(DIAGNOSTICS_URI, firstUri);
+
+ final boolean diagnosticsOutputPathExists =
Files.exists(diagnosticsOutputPath);
+ assertTrue(diagnosticsOutputPathExists);
+ }
+
+ @Test
+ void testGetBootstrapCommandPathVerboseArguments(@TempDir final Path
tempDir) {
+ setProcessHandle();
+
+ final Path diagnosticsOutputPath =
tempDir.resolve(UUID.randomUUID().toString());
+ final String diagnosticsOutputPathArgument =
diagnosticsOutputPath.toString();
+ final String[] arguments = new String[]{JAVA_COMMAND,
diagnosticsOutputPathArgument, VERBOSE_ARGUMENT};
+
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(arguments);
+
+ assertCommandStatusSuccess(bootstrapCommand);
+ assertVerboseAndPathArgumentsProcessed(diagnosticsOutputPath);
+ }
+
+ @Test
+ void testGetBootstrapCommandVerbosePathArguments(@TempDir final Path
tempDir) {
+ setProcessHandle();
+
+ final Path diagnosticsOutputPath =
tempDir.resolve(UUID.randomUUID().toString());
+ final String diagnosticsOutputPathArgument =
diagnosticsOutputPath.toString();
+ final String[] arguments = new String[]{JAVA_COMMAND,
VERBOSE_ARGUMENT, diagnosticsOutputPathArgument};
+
+ final BootstrapCommand bootstrapCommand =
provider.getBootstrapCommand(arguments);
+
+ assertCommandStatusSuccess(bootstrapCommand);
+ assertVerboseAndPathArgumentsProcessed(diagnosticsOutputPath);
+ }
+
+ private void assertVerboseAndPathArgumentsProcessed(final Path
expectedOutputPath) {
+ final URI firstUri = requestUris.getFirst();
+ assertEquals(DIAGNOSTICS_VERBOSE_URI, firstUri);
+
+ final boolean expectedOutputPathFound =
Files.exists(expectedOutputPath);
+ assertTrue(expectedOutputPathFound);
+ }
+
+ private void assertCommandStatusSuccess(final BootstrapCommand
bootstrapCommand) {
+ assertNotNull(bootstrapCommand);
+ assertInstanceOf(ManagementServerBootstrapCommand.class,
bootstrapCommand);
+
+ bootstrapCommand.run();
+ final CommandStatus commandStatus =
bootstrapCommand.getCommandStatus();
+ assertEquals(CommandStatus.SUCCESS, commandStatus);
+
+ assertFalse(requestUris.isEmpty());
+ }
+
+ private void setProcessHandle() {
+
when(processHandleProvider.findApplicationProcessHandle()).thenReturn(Optional.of(processHandle));
+ when(processHandle.info()).thenReturn(processHandleInfo);
+
+ final String managementServerAddressArgument =
getServerAddressArgument();
+ final String[] arguments = new
String[]{managementServerAddressArgument};
+ when(processHandleInfo.arguments()).thenReturn(Optional.of(arguments));
+ }
+
+ private String getServerAddressArgument() {
+ final InetSocketAddress bindAddress = httpServer.getAddress();
+ final int serverPort = bindAddress.getPort();
+ return
MANAGEMENT_SERVER_ARGUMENT_FORMAT.formatted(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty(),
serverPort);
+ }
+}