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);
+    }
+}

Reply via email to