mirror of https://github.com/apache/nifi.git
NIFI-13665 Refactored Bootstrap and Runtime Process Handling (#9192)
- Added BootstrapProcess main class to nifi-bootstrap - Added HTTP Management Server to nifi-runtime for status - Added HTTP client to nifi-bootstrap for status - Removed TCP socket listeners from nifi-bootstrap and nifi-runtime - Removed dump and env commands - Removed java property from bootstrap.conf - Removed nifi.bootstrap.listen.port from bootstrap.conf
This commit is contained in:
parent
7945234f5a
commit
6b6e8d6f78
|
@ -22,7 +22,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.apache.nifi.util.LimitingInputStream;
|
|
||||||
|
|
||||||
public class BootstrapRequestReader {
|
public class BootstrapRequestReader {
|
||||||
private final String secretKey;
|
private final String secretKey;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.util;
|
package org.apache.nifi.minifi.bootstrap;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
|
@ -24,20 +24,17 @@ language governing permissions and limitations under the License. -->
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.nifi</groupId>
|
|
||||||
<artifactId>nifi-utils</artifactId>
|
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-security-cert-builder</artifactId>
|
<artifactId>nifi-security-cert-builder</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Referenced in org.apache.nifi.NiFi -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-properties-loader</artifactId>
|
<artifactId>nifi-properties-loader</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -1,106 +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;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class BootstrapCodec {
|
|
||||||
|
|
||||||
private final RunNiFi runner;
|
|
||||||
private final BufferedReader reader;
|
|
||||||
private final BufferedWriter writer;
|
|
||||||
|
|
||||||
public BootstrapCodec(final RunNiFi runner, final InputStream in, final OutputStream out) {
|
|
||||||
this.runner = runner;
|
|
||||||
this.reader = new BufferedReader(new InputStreamReader(in));
|
|
||||||
this.writer = new BufferedWriter(new OutputStreamWriter(out));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void communicate() throws IOException {
|
|
||||||
final String line = reader.readLine();
|
|
||||||
final String[] splits = line.split(" ");
|
|
||||||
if (splits.length < 0) {
|
|
||||||
throw new IOException("Received invalid command from NiFi: " + line);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String cmd = splits[0];
|
|
||||||
final String[] args;
|
|
||||||
if (splits.length == 1) {
|
|
||||||
args = new String[0];
|
|
||||||
} else {
|
|
||||||
args = Arrays.copyOfRange(splits, 1, splits.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
processRequest(cmd, args);
|
|
||||||
} catch (final InvalidCommandException ice) {
|
|
||||||
throw new IOException("Received invalid command from NiFi: " + line + (ice.getMessage() == null ? "" : " - Details: " + ice.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processRequest(final String cmd, final String[] args) throws InvalidCommandException, IOException {
|
|
||||||
switch (cmd) {
|
|
||||||
case "PORT": {
|
|
||||||
if (args.length != 2) {
|
|
||||||
throw new InvalidCommandException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final int port;
|
|
||||||
try {
|
|
||||||
port = Integer.parseInt(args[0]);
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
throw new InvalidCommandException("Invalid Port number; should be integer between 1 and 65535");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port < 1 || port > 65535) {
|
|
||||||
throw new InvalidCommandException("Invalid Port number; should be integer between 1 and 65535");
|
|
||||||
}
|
|
||||||
|
|
||||||
final String secretKey = args[1];
|
|
||||||
|
|
||||||
runner.setNiFiCommandControlPort(port, secretKey);
|
|
||||||
writer.write("OK");
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "STARTED": {
|
|
||||||
if (args.length != 1) {
|
|
||||||
throw new InvalidCommandException("STARTED command must contain a status argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!"true".equals(args[0]) && !"false".equals(args[0])) {
|
|
||||||
throw new InvalidCommandException("Invalid status for STARTED command; should be true or false, but was '" + args[0] + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean started = Boolean.parseBoolean(args[0]);
|
|
||||||
runner.setNiFiStarted(started);
|
|
||||||
writer.write("OK");
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.apache.nifi.bootstrap.command.BootstrapCommand;
|
||||||
|
import org.apache.nifi.bootstrap.command.BootstrapCommandProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.CommandStatus;
|
||||||
|
import org.apache.nifi.bootstrap.command.StandardBootstrapCommandProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Process responsible for reading configuration and maintaining application status
|
||||||
|
*/
|
||||||
|
public class BootstrapProcess {
|
||||||
|
/**
|
||||||
|
* Start Application
|
||||||
|
*
|
||||||
|
* @param arguments Array of arguments
|
||||||
|
*/
|
||||||
|
public static void main(final String[] arguments) {
|
||||||
|
final BootstrapCommandProvider bootstrapCommandProvider = new StandardBootstrapCommandProvider();
|
||||||
|
final BootstrapCommand bootstrapCommand = bootstrapCommandProvider.getBootstrapCommand(arguments);
|
||||||
|
run(bootstrapCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void run(final BootstrapCommand bootstrapCommand) {
|
||||||
|
bootstrapCommand.run();
|
||||||
|
final CommandStatus commandStatus = bootstrapCommand.getCommandStatus();
|
||||||
|
if (CommandStatus.RUNNING == commandStatus) {
|
||||||
|
final Logger logger = LoggerFactory.getLogger(ApplicationClassName.BOOTSTRAP_COMMAND.getName());
|
||||||
|
logger.info("Bootstrap Process Running");
|
||||||
|
} else {
|
||||||
|
final int status = commandStatus.getStatus();
|
||||||
|
System.exit(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,141 +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;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.nifi.bootstrap.util.LimitingInputStream;
|
|
||||||
|
|
||||||
public class NiFiListener {
|
|
||||||
|
|
||||||
private ServerSocket serverSocket;
|
|
||||||
private volatile Listener listener;
|
|
||||||
|
|
||||||
int start(final RunNiFi runner, final int listenPort) throws IOException {
|
|
||||||
serverSocket = new ServerSocket();
|
|
||||||
serverSocket.bind(new InetSocketAddress("localhost", listenPort));
|
|
||||||
|
|
||||||
final int localPort = serverSocket.getLocalPort();
|
|
||||||
listener = new Listener(serverSocket, runner);
|
|
||||||
final Thread listenThread = new Thread(listener);
|
|
||||||
listenThread.setName("Listen to NiFi");
|
|
||||||
listenThread.setDaemon(true);
|
|
||||||
listenThread.start();
|
|
||||||
return localPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() throws IOException {
|
|
||||||
final Listener listener = this.listener;
|
|
||||||
if (listener == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Listener implements Runnable {
|
|
||||||
|
|
||||||
private final ServerSocket serverSocket;
|
|
||||||
private final ExecutorService executor;
|
|
||||||
private final RunNiFi runner;
|
|
||||||
private volatile boolean stopped = false;
|
|
||||||
|
|
||||||
public Listener(final ServerSocket serverSocket, final RunNiFi runner) {
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
this.executor = Executors.newFixedThreadPool(2, new ThreadFactory() {
|
|
||||||
@Override
|
|
||||||
public Thread newThread(final Runnable runnable) {
|
|
||||||
final Thread t = Executors.defaultThreadFactory().newThread(runnable);
|
|
||||||
t.setDaemon(true);
|
|
||||||
t.setName("NiFi Bootstrap Command Listener");
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.runner = runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() throws IOException {
|
|
||||||
stopped = true;
|
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
try {
|
|
||||||
executor.awaitTermination(3, TimeUnit.SECONDS);
|
|
||||||
} catch (final InterruptedException ie) {
|
|
||||||
}
|
|
||||||
|
|
||||||
serverSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (!serverSocket.isClosed()) {
|
|
||||||
try {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Socket socket;
|
|
||||||
try {
|
|
||||||
socket = serverSocket.accept();
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ioe;
|
|
||||||
}
|
|
||||||
|
|
||||||
executor.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
// we want to ensure that we don't try to read data from an InputStream directly
|
|
||||||
// by a BufferedReader because any user on the system could open a socket and send
|
|
||||||
// a multi-gigabyte file without any new lines in order to crash the Bootstrap,
|
|
||||||
// which in turn may cause the Shutdown Hook to shutdown NiFi.
|
|
||||||
// So we will limit the amount of data to read to 4 KB
|
|
||||||
final InputStream limitingIn = new LimitingInputStream(socket.getInputStream(), 4096);
|
|
||||||
final BootstrapCodec codec = new BootstrapCodec(runner, limitingIn, socket.getOutputStream());
|
|
||||||
codec.communicate();
|
|
||||||
} catch (final Throwable t) {
|
|
||||||
System.out.println("Failed to communicate with NiFi due to " + t);
|
|
||||||
t.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (final Throwable t) {
|
|
||||||
System.err.println("Failed to receive information from NiFi due to " + t);
|
|
||||||
t.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,98 +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;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class ShutdownHook extends Thread {
|
|
||||||
|
|
||||||
private final Process nifiProcess;
|
|
||||||
private final Long pid;
|
|
||||||
private final RunNiFi runner;
|
|
||||||
private final int gracefulShutdownSeconds;
|
|
||||||
private final ExecutorService executor;
|
|
||||||
|
|
||||||
private volatile String secretKey;
|
|
||||||
|
|
||||||
public ShutdownHook(final Process nifiProcess, final Long pid, final RunNiFi runner, final String secretKey, final int gracefulShutdownSeconds, final ExecutorService executor) {
|
|
||||||
this.nifiProcess = nifiProcess;
|
|
||||||
this.pid = pid;
|
|
||||||
this.runner = runner;
|
|
||||||
this.secretKey = secretKey;
|
|
||||||
this.gracefulShutdownSeconds = gracefulShutdownSeconds;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSecretKey(final String secretKey) {
|
|
||||||
this.secretKey = secretKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
executor.shutdown();
|
|
||||||
runner.setAutoRestartNiFi(false);
|
|
||||||
final int ccPort = runner.getNiFiCommandControlPort();
|
|
||||||
if (ccPort > 0) {
|
|
||||||
System.out.printf("NiFi PID [%d] shutdown started%n", pid);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final Socket socket = new Socket("localhost", ccPort);
|
|
||||||
final OutputStream out = socket.getOutputStream();
|
|
||||||
out.write(("SHUTDOWN " + secretKey + "\n").getBytes(StandardCharsets.UTF_8));
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
socket.close();
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
System.out.println("Failed to Shutdown NiFi due to " + ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.printf("NiFi PID [%d] shutdown in progress...%n", pid);
|
|
||||||
final long startWait = System.nanoTime();
|
|
||||||
while (RunNiFi.isAlive(nifiProcess)) {
|
|
||||||
final long waitNanos = System.nanoTime() - startWait;
|
|
||||||
final long waitSeconds = TimeUnit.NANOSECONDS.toSeconds(waitNanos);
|
|
||||||
if (waitSeconds >= gracefulShutdownSeconds && gracefulShutdownSeconds > 0) {
|
|
||||||
if (RunNiFi.isAlive(nifiProcess)) {
|
|
||||||
System.out.println("NiFi has not finished shutting down after " + gracefulShutdownSeconds + " seconds. Killing process.");
|
|
||||||
nifiProcess.destroy();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000L);
|
|
||||||
} catch (final InterruptedException ie) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final File statusFile = runner.getStatusFile();
|
|
||||||
if (!statusFile.delete()) {
|
|
||||||
System.err.println("Failed to delete status file " + statusFile.getAbsolutePath() + "; this file should be cleaned up manually");
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
System.err.println("Failed to retrieve status file " + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command to return the status of the application process as a child of the bootstrap process
|
||||||
|
*/
|
||||||
|
class ApplicationProcessStatusBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationProcessStatusBootstrapCommand.class);
|
||||||
|
|
||||||
|
private final ProcessHandle processHandle;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
ApplicationProcessStatusBootstrapCommand(final ProcessHandle processHandle) {
|
||||||
|
this.processHandle = Objects.requireNonNull(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Optional<ProcessHandle> childProcessHandleFound = processHandle.children().findFirst();
|
||||||
|
|
||||||
|
if (childProcessHandleFound.isEmpty()) {
|
||||||
|
logger.info("Application Process not found");
|
||||||
|
commandStatus = CommandStatus.STOPPED;
|
||||||
|
} else {
|
||||||
|
final ProcessHandle childProcessHandle = childProcessHandleFound.get();
|
||||||
|
if (childProcessHandle.isAlive()) {
|
||||||
|
logger.info("Application Process [{}] running", childProcessHandle.pid());
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
} else {
|
||||||
|
logger.info("Application Process [{}] stopped", childProcessHandle.pid());
|
||||||
|
commandStatus = CommandStatus.COMMUNICATION_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command extension of Runnable with command status
|
||||||
|
*/
|
||||||
|
public interface BootstrapCommand extends Runnable {
|
||||||
|
/**
|
||||||
|
* Get Command Status on completion
|
||||||
|
*
|
||||||
|
* @return Command Status
|
||||||
|
*/
|
||||||
|
CommandStatus getCommandStatus();
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for parsing arguments and returning runnable Bootstrap Command
|
||||||
|
*/
|
||||||
|
public interface BootstrapCommandProvider {
|
||||||
|
/**
|
||||||
|
* Get Bootstrap Command
|
||||||
|
*
|
||||||
|
* @param arguments Application arguments
|
||||||
|
* @return Bootstrap Command to run
|
||||||
|
*/
|
||||||
|
BootstrapCommand getBootstrapCommand(String[] arguments);
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of Bootstrap Command Statuses with status codes
|
||||||
|
*/
|
||||||
|
public enum CommandStatus {
|
||||||
|
RUNNING(-1),
|
||||||
|
|
||||||
|
SUCCESS(0),
|
||||||
|
|
||||||
|
ERROR(1),
|
||||||
|
|
||||||
|
STOPPED(3),
|
||||||
|
|
||||||
|
COMMUNICATION_FAILED(4),
|
||||||
|
|
||||||
|
FAILED(5);
|
||||||
|
|
||||||
|
private final int status;
|
||||||
|
|
||||||
|
CommandStatus(final int status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Status Code for use with System.exit()
|
||||||
|
*
|
||||||
|
* @return Status Code
|
||||||
|
*/
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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.process.ManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.property.ApplicationPropertyHandler;
|
||||||
|
import org.apache.nifi.bootstrap.property.SecurityApplicationPropertyHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command to get the command arguments for running the application
|
||||||
|
*/
|
||||||
|
class GetRunCommandBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final String SPACE_SEPARATOR = " ";
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GetRunCommandBootstrapCommand.class);
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private final ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
private final ManagementServerAddressProvider managementServerAddressProvider;
|
||||||
|
|
||||||
|
private final PrintStream outputStream;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
public GetRunCommandBootstrapCommand(final ConfigurationProvider configurationProvider, final ProcessHandleProvider processHandleProvider, final PrintStream outputStream) {
|
||||||
|
this.configurationProvider = Objects.requireNonNull(configurationProvider);
|
||||||
|
this.processHandleProvider = Objects.requireNonNull(processHandleProvider);
|
||||||
|
this.outputStream = Objects.requireNonNull(outputStream);
|
||||||
|
this.managementServerAddressProvider = new StandardManagementServerAddressProvider(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final Optional<ProcessHandle> applicationProcessHandle = processHandleProvider.findApplicationProcessHandle();
|
||||||
|
|
||||||
|
if (applicationProcessHandle.isEmpty()) {
|
||||||
|
final ApplicationPropertyHandler securityApplicationPropertyHandler = new SecurityApplicationPropertyHandler(logger);
|
||||||
|
securityApplicationPropertyHandler.handleProperties(configurationProvider.getApplicationProperties());
|
||||||
|
|
||||||
|
final ProcessBuilderProvider processBuilderProvider = new StandardProcessBuilderProvider(configurationProvider, managementServerAddressProvider);
|
||||||
|
final ProcessBuilder processBuilder = processBuilderProvider.getApplicationProcessBuilder();
|
||||||
|
final List<String> command = processBuilder.command();
|
||||||
|
final String processCommand = String.join(SPACE_SEPARATOR, command);
|
||||||
|
outputStream.println(processCommand);
|
||||||
|
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
} else {
|
||||||
|
logger.info("Application Process [{}] running", applicationProcessHandle.get().pid());
|
||||||
|
commandStatus = CommandStatus.ERROR;
|
||||||
|
}
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
logger.warn("Application Process command building failed", e);
|
||||||
|
commandStatus = CommandStatus.FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* 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.HttpRequestMethod;
|
||||||
|
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.configuration.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ManagementServerPath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence of Bootstrap Commands
|
||||||
|
*/
|
||||||
|
class ManagementServerBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final Logger commandLogger = LoggerFactory.getLogger(ApplicationClassName.BOOTSTRAP_COMMAND.getName());
|
||||||
|
|
||||||
|
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5);
|
||||||
|
|
||||||
|
private static final Duration READ_TIMEOUT = Duration.ofSeconds(15);
|
||||||
|
|
||||||
|
private static final String SERVER_URI = "http://%s%s";
|
||||||
|
|
||||||
|
private static final char QUERY_SEPARATOR = '?';
|
||||||
|
|
||||||
|
private final ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
private final HttpRequestMethod httpRequestMethod;
|
||||||
|
|
||||||
|
private final ManagementServerPath managementServerPath;
|
||||||
|
|
||||||
|
private final String managementServerQuery;
|
||||||
|
|
||||||
|
private final int successStatusCode;
|
||||||
|
|
||||||
|
private final ResponseStreamHandler responseStreamHandler;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
ManagementServerBootstrapCommand(
|
||||||
|
final ProcessHandleProvider processHandleProvider,
|
||||||
|
final ManagementServerPath managementServerPath,
|
||||||
|
final ResponseStreamHandler responseStreamHandler
|
||||||
|
) {
|
||||||
|
this(processHandleProvider, HttpRequestMethod.GET, managementServerPath, null, HttpURLConnection.HTTP_OK, responseStreamHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
ManagementServerBootstrapCommand(
|
||||||
|
final ProcessHandleProvider processHandleProvider,
|
||||||
|
final HttpRequestMethod httpRequestMethod,
|
||||||
|
final ManagementServerPath managementServerPath,
|
||||||
|
final String managementServerQuery,
|
||||||
|
final int successStatusCode,
|
||||||
|
final ResponseStreamHandler responseStreamHandler
|
||||||
|
) {
|
||||||
|
this.processHandleProvider = Objects.requireNonNull(processHandleProvider);
|
||||||
|
this.httpRequestMethod = Objects.requireNonNull(httpRequestMethod);
|
||||||
|
this.managementServerPath = Objects.requireNonNull(managementServerPath);
|
||||||
|
this.managementServerQuery = managementServerQuery;
|
||||||
|
this.successStatusCode = successStatusCode;
|
||||||
|
this.responseStreamHandler = Objects.requireNonNull(responseStreamHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Optional<ProcessHandle> applicationProcessHandle = processHandleProvider.findApplicationProcessHandle();
|
||||||
|
|
||||||
|
if (applicationProcessHandle.isEmpty()) {
|
||||||
|
commandStatus = CommandStatus.STOPPED;
|
||||||
|
getCommandLogger().info("Application Process STOPPED");
|
||||||
|
} else {
|
||||||
|
run(applicationProcessHandle.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void run(final ProcessHandle applicationProcessHandle) {
|
||||||
|
final ManagementServerAddressProvider managementServerAddressProvider = new ProcessHandleManagementServerAddressProvider(applicationProcessHandle);
|
||||||
|
final Optional<String> managementServerAddress = managementServerAddressProvider.getAddress();
|
||||||
|
|
||||||
|
final long pid = applicationProcessHandle.pid();
|
||||||
|
if (managementServerAddress.isEmpty()) {
|
||||||
|
getCommandLogger().info("Application Process [{}] Management Server address not found", pid);
|
||||||
|
commandStatus = CommandStatus.ERROR;
|
||||||
|
} else {
|
||||||
|
final URI managementServerUri = getManagementServerUri(managementServerAddress.get());
|
||||||
|
try (HttpClient httpClient = getHttpClient()) {
|
||||||
|
final HttpRequest httpRequest = getHttpRequest(managementServerUri);
|
||||||
|
|
||||||
|
final HttpResponse<InputStream> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofInputStream());
|
||||||
|
final int statusCode = response.statusCode();
|
||||||
|
try (InputStream responseStream = response.body()) {
|
||||||
|
onResponseStatus(applicationProcessHandle, statusCode, responseStream);
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
commandStatus = CommandStatus.COMMUNICATION_FAILED;
|
||||||
|
getCommandLogger().info("Application Process [{}] Management Server [{}] communication failed", pid, managementServerUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onResponseStatus(final ProcessHandle applicationProcessHandle, final int statusCode, final InputStream responseStream) {
|
||||||
|
final long pid = applicationProcessHandle.pid();
|
||||||
|
|
||||||
|
if (successStatusCode == statusCode) {
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
getCommandLogger().info("Application Process [{}] Command Status [{}] HTTP {}", pid, commandStatus, statusCode);
|
||||||
|
responseStreamHandler.onResponseStream(responseStream);
|
||||||
|
} else {
|
||||||
|
commandStatus = CommandStatus.COMMUNICATION_FAILED;
|
||||||
|
getCommandLogger().warn("Application Process [{}] Command Status [{}] HTTP {}", pid, commandStatus, statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Logger getCommandLogger() {
|
||||||
|
return commandLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRequest getHttpRequest(final URI managementServerUri) {
|
||||||
|
return HttpRequest.newBuilder()
|
||||||
|
.method(httpRequestMethod.name(), HttpRequest.BodyPublishers.noBody())
|
||||||
|
.uri(managementServerUri)
|
||||||
|
.timeout(READ_TIMEOUT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URI getManagementServerUri(final String managementServerAddress) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
final String serverUri = SERVER_URI.formatted(managementServerAddress, managementServerPath.getPath());
|
||||||
|
builder.append(serverUri);
|
||||||
|
|
||||||
|
if (managementServerQuery != null) {
|
||||||
|
builder.append(QUERY_SEPARATOR);
|
||||||
|
builder.append(managementServerQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
return URI.create(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpClient getHttpClient() {
|
||||||
|
final HttpClient.Builder builder = HttpClient.newBuilder();
|
||||||
|
builder.connectTimeout(CONNECT_TIMEOUT);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* 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.management.UnixOperatingSystemMXBean;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.process.RuntimeValidatorExecutor;
|
||||||
|
import org.apache.nifi.bootstrap.property.ApplicationPropertyHandler;
|
||||||
|
import org.apache.nifi.bootstrap.property.SecurityApplicationPropertyHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command to run the application
|
||||||
|
*/
|
||||||
|
class RunBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final String SPACE_SEPARATOR = " ";
|
||||||
|
|
||||||
|
private static final Logger commandLogger = LoggerFactory.getLogger(ApplicationClassName.BOOTSTRAP_COMMAND.getName());
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RunBootstrapCommand.class);
|
||||||
|
|
||||||
|
private static final RuntimeValidatorExecutor runtimeValidatorExecutor = new RuntimeValidatorExecutor();
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private final ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
private final ManagementServerAddressProvider managementServerAddressProvider;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
public RunBootstrapCommand(final ConfigurationProvider configurationProvider, final ProcessHandleProvider processHandleProvider) {
|
||||||
|
this.configurationProvider = Objects.requireNonNull(configurationProvider);
|
||||||
|
this.processHandleProvider = Objects.requireNonNull(processHandleProvider);
|
||||||
|
this.managementServerAddressProvider = new StandardManagementServerAddressProvider(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final Optional<ProcessHandle> applicationProcessHandle = processHandleProvider.findApplicationProcessHandle();
|
||||||
|
|
||||||
|
if (applicationProcessHandle.isEmpty()) {
|
||||||
|
writePlatformProperties();
|
||||||
|
|
||||||
|
runtimeValidatorExecutor.execute();
|
||||||
|
|
||||||
|
final ApplicationPropertyHandler securityApplicationPropertyHandler = new SecurityApplicationPropertyHandler(logger);
|
||||||
|
securityApplicationPropertyHandler.handleProperties(configurationProvider.getApplicationProperties());
|
||||||
|
|
||||||
|
final ProcessBuilderProvider processBuilderProvider = new StandardProcessBuilderProvider(configurationProvider, managementServerAddressProvider);
|
||||||
|
|
||||||
|
final ProcessBuilder processBuilder = processBuilderProvider.getApplicationProcessBuilder();
|
||||||
|
processBuilder.inheritIO();
|
||||||
|
|
||||||
|
final String command = String.join(SPACE_SEPARATOR, processBuilder.command());
|
||||||
|
logger.info(command);
|
||||||
|
|
||||||
|
final Process process = processBuilder.start();
|
||||||
|
if (process.isAlive()) {
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
commandLogger.info("Application Process [{}] started", process.pid());
|
||||||
|
} else {
|
||||||
|
commandStatus = CommandStatus.STOPPED;
|
||||||
|
commandLogger.error("Application Process [{}] start failed", process.pid());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commandLogger.info("Application Process [{}] running", applicationProcessHandle.get().pid());
|
||||||
|
commandStatus = CommandStatus.ERROR;
|
||||||
|
}
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
commandLogger.warn("Application Process run failed", e);
|
||||||
|
commandStatus = CommandStatus.FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePlatformProperties() {
|
||||||
|
final Runtime.Version version = Runtime.version();
|
||||||
|
logger.info("Java Version: {}", version);
|
||||||
|
|
||||||
|
final Runtime runtime = Runtime.getRuntime();
|
||||||
|
logger.info("Available Processors: {}", runtime.availableProcessors());
|
||||||
|
|
||||||
|
final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
if (operatingSystem instanceof UnixOperatingSystemMXBean unixOperatingSystem) {
|
||||||
|
logger.info("Total Memory: {}", unixOperatingSystem.getTotalMemorySize());
|
||||||
|
logger.info("Maximum File Descriptors: {}", unixOperatingSystem.getMaxFileDescriptorCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence of Bootstrap Commands
|
||||||
|
*/
|
||||||
|
class SequenceBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private final List<BootstrapCommand> bootstrapCommands;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
SequenceBootstrapCommand(final List<BootstrapCommand> bootstrapCommands) {
|
||||||
|
this.bootstrapCommands = Objects.requireNonNull(bootstrapCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (final BootstrapCommand bootstrapCommand : bootstrapCommands) {
|
||||||
|
bootstrapCommand.run();
|
||||||
|
commandStatus = bootstrapCommand.getCommandStatus();
|
||||||
|
|
||||||
|
if (CommandStatus.SUCCESS != commandStatus) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
* 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.BootstrapArgument;
|
||||||
|
import org.apache.nifi.bootstrap.command.io.BootstrapArgumentParser;
|
||||||
|
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.io.StandardBootstrapArgumentParser;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.StandardConfigurationProvider;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Bootstrap Command Provider with parsing of supported commands
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
private static final int FIRST_ARGUMENT = 1;
|
||||||
|
|
||||||
|
private static final int SECOND_ARGUMENT = 2;
|
||||||
|
|
||||||
|
private static final int PATH_ARGUMENTS = 2;
|
||||||
|
|
||||||
|
private static final int DAYS_PATH_ARGUMENTS = 3;
|
||||||
|
|
||||||
|
private static final int DAYS_REQUESTED_DEFAULT = 1;
|
||||||
|
|
||||||
|
private static final BootstrapArgumentParser bootstrapArgumentParser = new StandardBootstrapArgumentParser();
|
||||||
|
|
||||||
|
private static final Logger commandLogger = LoggerFactory.getLogger(ApplicationClassName.BOOTSTRAP_COMMAND.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bootstrap Command
|
||||||
|
*
|
||||||
|
* @param arguments Application arguments
|
||||||
|
* @return Bootstrap Command to run
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public BootstrapCommand getBootstrapCommand(final String[] arguments) {
|
||||||
|
final BootstrapCommand bootstrapCommand;
|
||||||
|
|
||||||
|
final Optional<BootstrapArgument> bootstrapArgumentFound = bootstrapArgumentParser.getBootstrapArgument(arguments);
|
||||||
|
if (bootstrapArgumentFound.isPresent()) {
|
||||||
|
final BootstrapArgument bootstrapArgument = bootstrapArgumentFound.get();
|
||||||
|
bootstrapCommand = getBootstrapCommand(bootstrapArgument, arguments);
|
||||||
|
} else {
|
||||||
|
bootstrapCommand = new UnknownBootstrapCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrapCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ResponseStreamHandler commandLoggerStreamHandler = new LoggerResponseStreamHandler(commandLogger);
|
||||||
|
final BootstrapCommand stopBootstrapCommand = new StopBootstrapCommand(processHandleProvider, configurationProvider);
|
||||||
|
|
||||||
|
final BootstrapCommand bootstrapCommand;
|
||||||
|
|
||||||
|
if (BootstrapArgument.CLUSTER_STATUS == bootstrapArgument) {
|
||||||
|
bootstrapCommand = new ManagementServerBootstrapCommand(processHandleProvider, HEALTH_CLUSTER, commandLoggerStreamHandler);
|
||||||
|
} else if (BootstrapArgument.DECOMMISSION == bootstrapArgument) {
|
||||||
|
bootstrapCommand = getDecommissionCommand(processHandleProvider, stopBootstrapCommand, arguments);
|
||||||
|
} else if (BootstrapArgument.DIAGNOSTICS == bootstrapArgument) {
|
||||||
|
bootstrapCommand = getDiagnosticsCommand(processHandleProvider, arguments);
|
||||||
|
} else if (BootstrapArgument.GET_RUN_COMMAND == bootstrapArgument) {
|
||||||
|
bootstrapCommand = new GetRunCommandBootstrapCommand(configurationProvider, processHandleProvider, System.out);
|
||||||
|
} else if (BootstrapArgument.START == bootstrapArgument) {
|
||||||
|
final BootstrapCommand runBootstrapCommand = new RunBootstrapCommand(configurationProvider, processHandleProvider);
|
||||||
|
final ProcessHandle currentProcessHandle = ProcessHandle.current();
|
||||||
|
final BootstrapCommand statusBootstrapCommand = new ApplicationProcessStatusBootstrapCommand(currentProcessHandle);
|
||||||
|
bootstrapCommand = new StartBootstrapCommand(runBootstrapCommand, statusBootstrapCommand);
|
||||||
|
} else if (BootstrapArgument.STATUS == bootstrapArgument) {
|
||||||
|
bootstrapCommand = new ManagementServerBootstrapCommand(processHandleProvider, HEALTH, commandLoggerStreamHandler);
|
||||||
|
} else if (BootstrapArgument.STATUS_HISTORY == bootstrapArgument) {
|
||||||
|
bootstrapCommand = getStatusHistoryCommand(processHandleProvider, arguments);
|
||||||
|
} else if (BootstrapArgument.STOP == bootstrapArgument) {
|
||||||
|
bootstrapCommand = stopBootstrapCommand;
|
||||||
|
} else {
|
||||||
|
bootstrapCommand = new UnknownBootstrapCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrapCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BootstrapCommand getDecommissionCommand(final ProcessHandleProvider processHandleProvider, final BootstrapCommand stopBootstrapCommand, final String[] arguments) {
|
||||||
|
final ResponseStreamHandler responseStreamHandler = new LoggerResponseStreamHandler(commandLogger);
|
||||||
|
final List<BootstrapCommand> bootstrapCommands = new ArrayList<>();
|
||||||
|
final BootstrapCommand decommissionCommand = new ManagementServerBootstrapCommand(processHandleProvider, DELETE, HEALTH_CLUSTER, EMPTY_QUERY, HTTP_ACCEPTED, responseStreamHandler);
|
||||||
|
bootstrapCommands.add(decommissionCommand);
|
||||||
|
if (isShutdownRequested(arguments)) {
|
||||||
|
bootstrapCommands.add(stopBootstrapCommand);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
return new ManagementServerBootstrapCommand(processHandleProvider, GET, HEALTH_STATUS_HISTORY, daysQuery, HTTP_OK, responseStreamHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isShutdownRequested(final String[] arguments) {
|
||||||
|
boolean shutdownRequested = false;
|
||||||
|
|
||||||
|
for (final String argument : arguments) {
|
||||||
|
if (SHUTDOWN_REQUESTED.contentEquals(argument)) {
|
||||||
|
shutdownRequested = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (arguments.length == DAYS_PATH_ARGUMENTS) {
|
||||||
|
final String daysRequestArgument = arguments[FIRST_ARGUMENT];
|
||||||
|
daysRequested = getStatusHistoryDaysRequested(daysRequestArgument);
|
||||||
|
} else {
|
||||||
|
daysRequested = DAYS_REQUESTED_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DAYS_QUERY.formatted(daysRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStatusHistoryDaysRequested(final String daysRequestArgument) {
|
||||||
|
int daysRequested;
|
||||||
|
|
||||||
|
try {
|
||||||
|
daysRequested = Integer.parseInt(daysRequestArgument);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Status History Days requested not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseStreamHandler getStatusHistoryResponseStreamHandler(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 if (arguments.length == DAYS_PATH_ARGUMENTS) {
|
||||||
|
final String outputPathArgument = arguments[SECOND_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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Bootstrap Command executes the Run Command and monitors status
|
||||||
|
*/
|
||||||
|
class StartBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final long MONITOR_INTERVAL = 5;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(StartBootstrapCommand.class);
|
||||||
|
|
||||||
|
private final BootstrapCommand runCommand;
|
||||||
|
|
||||||
|
private final BootstrapCommand statusCommand;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduledExecutorService;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
StartBootstrapCommand(final BootstrapCommand runCommand, final BootstrapCommand statusCommand) {
|
||||||
|
this.runCommand = Objects.requireNonNull(runCommand);
|
||||||
|
this.statusCommand = Objects.requireNonNull(statusCommand);
|
||||||
|
|
||||||
|
this.scheduledExecutorService = Executors.newScheduledThreadPool(1, command -> {
|
||||||
|
final Thread thread = new Thread(command);
|
||||||
|
thread.setName(StartBootstrapCommand.class.getSimpleName());
|
||||||
|
return thread;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runCommand.run();
|
||||||
|
commandStatus = runCommand.getCommandStatus();
|
||||||
|
|
||||||
|
if (CommandStatus.SUCCESS == commandStatus) {
|
||||||
|
logger.info("Application watch started");
|
||||||
|
final WatchCommand watchCommand = new WatchCommand();
|
||||||
|
scheduledExecutorService.scheduleAtFixedRate(watchCommand, MONITOR_INTERVAL, MONITOR_INTERVAL, TimeUnit.SECONDS);
|
||||||
|
commandStatus = CommandStatus.RUNNING;
|
||||||
|
} else {
|
||||||
|
scheduledExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WatchCommand implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
statusCommand.run();
|
||||||
|
final CommandStatus status = statusCommand.getCommandStatus();
|
||||||
|
if (CommandStatus.SUCCESS == status) {
|
||||||
|
logger.debug("Application running");
|
||||||
|
} else if (CommandStatus.FAILED == status) {
|
||||||
|
logger.error("Application watch failed");
|
||||||
|
scheduledExecutorService.shutdown();
|
||||||
|
logger.info("Application watch stopped");
|
||||||
|
commandStatus = CommandStatus.FAILED;
|
||||||
|
} else {
|
||||||
|
logger.warn("Application not running [{}]", status);
|
||||||
|
runCommand.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* 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.process.ProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command to run for stopping application
|
||||||
|
*/
|
||||||
|
class StopBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
private static final Duration FORCE_TERMINATION_TIMEOUT = Duration.ofSeconds(5);
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationClassName.BOOTSTRAP_COMMAND.getName());
|
||||||
|
|
||||||
|
private final ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private CommandStatus commandStatus = CommandStatus.ERROR;
|
||||||
|
|
||||||
|
StopBootstrapCommand(final ProcessHandleProvider processHandleProvider, final ConfigurationProvider configurationProvider) {
|
||||||
|
this.processHandleProvider = Objects.requireNonNull(processHandleProvider);
|
||||||
|
this.configurationProvider = Objects.requireNonNull(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return commandStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Optional<ProcessHandle> processHandle = processHandleProvider.findApplicationProcessHandle();
|
||||||
|
|
||||||
|
if (processHandle.isEmpty()) {
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
logger.info("Application Process not running");
|
||||||
|
} else {
|
||||||
|
stopBootstrapProcess();
|
||||||
|
destroy(processHandle.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopBootstrapProcess() {
|
||||||
|
final Optional<ProcessHandle> bootstrapProcessHandleFound = processHandleProvider.findBootstrapProcessHandle();
|
||||||
|
if (bootstrapProcessHandleFound.isPresent()) {
|
||||||
|
final ProcessHandle bootstrapProcessHandle = bootstrapProcessHandleFound.get();
|
||||||
|
|
||||||
|
final boolean destroyRequested = bootstrapProcessHandle.destroy();
|
||||||
|
final long pid = bootstrapProcessHandle.pid();
|
||||||
|
if (destroyRequested) {
|
||||||
|
logger.info("Bootstrap Process [{}] termination requested", pid);
|
||||||
|
onBootstrapDestroyCompleted(bootstrapProcessHandle);
|
||||||
|
} else {
|
||||||
|
logger.warn("Bootstrap Process [{}] termination request failed", pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBootstrapDestroyCompleted(final ProcessHandle bootstrapProcessHandle) {
|
||||||
|
final long pid = bootstrapProcessHandle.pid();
|
||||||
|
final CompletableFuture<ProcessHandle> onExitHandle = bootstrapProcessHandle.onExit();
|
||||||
|
try {
|
||||||
|
final ProcessHandle completedProcessHandle = onExitHandle.get(FORCE_TERMINATION_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
|
||||||
|
logger.info("Bootstrap Process [{}] termination completed", completedProcessHandle.pid());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.warn("Bootstrap Process [{}] termination failed", pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void destroy(final ProcessHandle applicationProcessHandle) {
|
||||||
|
final boolean destroyRequested = applicationProcessHandle.destroy();
|
||||||
|
logger.info("Application Process [{}] termination requested", applicationProcessHandle.pid());
|
||||||
|
if (destroyRequested) {
|
||||||
|
onDestroyCompleted(applicationProcessHandle);
|
||||||
|
} else {
|
||||||
|
logger.warn("Application Process [{}] termination request failed", applicationProcessHandle.pid());
|
||||||
|
destroyForcibly(applicationProcessHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void destroyForcibly(final ProcessHandle applicationProcessHandle) {
|
||||||
|
final boolean destroyForciblyRequested = applicationProcessHandle.destroyForcibly();
|
||||||
|
if (destroyForciblyRequested) {
|
||||||
|
logger.warn("Application Process [{}] force termination failed", applicationProcessHandle.pid());
|
||||||
|
} else {
|
||||||
|
onDestroyForciblyCompleted(applicationProcessHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDestroyCompleted(final ProcessHandle applicationProcessHandle) {
|
||||||
|
final long pid = applicationProcessHandle.pid();
|
||||||
|
final CompletableFuture<ProcessHandle> onExitHandle = applicationProcessHandle.onExit();
|
||||||
|
final Duration gracefulShutdownTimeout = configurationProvider.getGracefulShutdownTimeout();
|
||||||
|
try {
|
||||||
|
final ProcessHandle completedProcessHandle = onExitHandle.get(gracefulShutdownTimeout.toSeconds(), TimeUnit.SECONDS);
|
||||||
|
logger.info("Application Process [{}] termination completed", completedProcessHandle.pid());
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.warn("Application Process [{}] termination failed", pid);
|
||||||
|
destroyForcibly(applicationProcessHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDestroyForciblyCompleted(final ProcessHandle applicationProcessHandle) {
|
||||||
|
final long pid = applicationProcessHandle.pid();
|
||||||
|
final CompletableFuture<ProcessHandle> onExitHandle = applicationProcessHandle.onExit();
|
||||||
|
try {
|
||||||
|
final ProcessHandle completedProcessHandle = onExitHandle.get(FORCE_TERMINATION_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
|
||||||
|
logger.warn("Application Process [{}] force termination completed", completedProcessHandle.pid());
|
||||||
|
commandStatus = CommandStatus.SUCCESS;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.warn("Application Process [{}] force termination request failed", pid);
|
||||||
|
commandStatus = CommandStatus.ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap Command to run for an unknown command requested
|
||||||
|
*/
|
||||||
|
class UnknownBootstrapCommand implements BootstrapCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandStatus getCommandStatus() {
|
||||||
|
return CommandStatus.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of supported arguments for the bootstrap application
|
||||||
|
*/
|
||||||
|
public enum BootstrapArgument {
|
||||||
|
CLUSTER_STATUS,
|
||||||
|
|
||||||
|
DECOMMISSION,
|
||||||
|
|
||||||
|
DIAGNOSTICS,
|
||||||
|
|
||||||
|
GET_RUN_COMMAND,
|
||||||
|
|
||||||
|
STATUS,
|
||||||
|
|
||||||
|
STATUS_HISTORY,
|
||||||
|
|
||||||
|
START,
|
||||||
|
|
||||||
|
STOP
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for parsing application arguments to Bootstrap Arguments
|
||||||
|
*/
|
||||||
|
public interface BootstrapArgumentParser {
|
||||||
|
/**
|
||||||
|
* Get Bootstrap Argument from application arguments
|
||||||
|
*
|
||||||
|
* @param arguments Application array of arguments
|
||||||
|
* @return Bootstrap Argument or empty when not found
|
||||||
|
*/
|
||||||
|
Optional<BootstrapArgument> getBootstrapArgument(String[] arguments);
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File implementation responsible for reading and transferring responses
|
||||||
|
*/
|
||||||
|
public class FileResponseStreamHandler implements ResponseStreamHandler {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FileResponseStreamHandler.class);
|
||||||
|
|
||||||
|
private final Path outputPath;
|
||||||
|
|
||||||
|
public FileResponseStreamHandler(final Path outputPath) {
|
||||||
|
this.outputPath = Objects.requireNonNull(outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponseStream(final InputStream responseStream) {
|
||||||
|
try (OutputStream outputStream = Files.newOutputStream(outputPath)) {
|
||||||
|
responseStream.transferTo(outputStream);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.warn("Write response stream failed for [%s]".formatted(outputPath), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,11 +14,13 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi;
|
package org.apache.nifi.bootstrap.command.io;
|
||||||
|
|
||||||
public interface NiFiEntryPoint {
|
/**
|
||||||
|
* Enumeration of supported HTTP Request Methods for Management Server
|
||||||
|
*/
|
||||||
|
public enum HttpRequestMethod {
|
||||||
|
DELETE,
|
||||||
|
|
||||||
NiFiServer getServer();
|
GET
|
||||||
|
|
||||||
void shutdownHook(boolean isReload);
|
|
||||||
}
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger implementation responsible for reading and logging stream of lines
|
||||||
|
*/
|
||||||
|
public class LoggerResponseStreamHandler implements ResponseStreamHandler {
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
public LoggerResponseStreamHandler(final Logger logger) {
|
||||||
|
this.logger = Objects.requireNonNull(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponseStream(final InputStream responseStream) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseStream))) {
|
||||||
|
reader.lines().forEach(logger::info);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.warn("Read response stream failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response Stream Handler abstraction for reading Management Server responses
|
||||||
|
*/
|
||||||
|
public interface ResponseStreamHandler {
|
||||||
|
/**
|
||||||
|
* Handle response stream from Management Server
|
||||||
|
*
|
||||||
|
* @param responseStream Response Stream
|
||||||
|
*/
|
||||||
|
void onResponseStream(InputStream responseStream);
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Bootstrap Argument Parser supporting enumerated arguments as the first element in an array
|
||||||
|
*/
|
||||||
|
public class StandardBootstrapArgumentParser implements BootstrapArgumentParser {
|
||||||
|
private static final char HYPHEN = '-';
|
||||||
|
|
||||||
|
private static final char UNDERSCORE = '_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bootstrap Argument from first argument provided
|
||||||
|
*
|
||||||
|
* @param arguments Application array of arguments
|
||||||
|
* @return Bootstrap Argument or empty when not found
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<BootstrapArgument> getBootstrapArgument(final String[] arguments) {
|
||||||
|
final Optional<BootstrapArgument> bootstrapArgumentFound;
|
||||||
|
|
||||||
|
if (arguments == null || arguments.length == 0) {
|
||||||
|
bootstrapArgumentFound = Optional.empty();
|
||||||
|
} else {
|
||||||
|
final String firstArgument = arguments[0];
|
||||||
|
final String formattedArgument = getFormattedArgument(firstArgument);
|
||||||
|
bootstrapArgumentFound = Arrays.stream(BootstrapArgument.values())
|
||||||
|
.filter(bootstrapArgument -> bootstrapArgument.name().equals(formattedArgument))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrapArgumentFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFormattedArgument(final String argument) {
|
||||||
|
final String upperCased = argument.toUpperCase();
|
||||||
|
return upperCased.replace(HYPHEN, UNDERSCORE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for producing or locating the Management Server socket address
|
||||||
|
*/
|
||||||
|
public interface ManagementServerAddressProvider {
|
||||||
|
/**
|
||||||
|
* Get Management Server Address with port number
|
||||||
|
*
|
||||||
|
* @return Management Server Address or empty when not found
|
||||||
|
*/
|
||||||
|
Optional<String> getAddress();
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for creating a Process Builder
|
||||||
|
*/
|
||||||
|
public interface ProcessBuilderProvider {
|
||||||
|
/**
|
||||||
|
* Get Application Process Builder
|
||||||
|
*
|
||||||
|
* @return Process Builder for Application with command arguments configured
|
||||||
|
*/
|
||||||
|
ProcessBuilder getApplicationProcessBuilder();
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.SystemProperty;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider implementation resolves the Management Server Address from command arguments of the application Process Handle
|
||||||
|
*/
|
||||||
|
public class ProcessHandleManagementServerAddressProvider implements ManagementServerAddressProvider {
|
||||||
|
private static final Pattern ADDRESS_ARGUMENT_PATTERN = Pattern.compile("^-D%s=(.+?)$".formatted(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty()));
|
||||||
|
|
||||||
|
private static final int ADDRESS_GROUP = 1;
|
||||||
|
|
||||||
|
private final ProcessHandle processHandle;
|
||||||
|
|
||||||
|
public ProcessHandleManagementServerAddressProvider(final ProcessHandle processHandle) {
|
||||||
|
this.processHandle = Objects.requireNonNull(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Management Server Address with port number from command argument in Process Handle
|
||||||
|
*
|
||||||
|
* @return Management Server Address or null when not found
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getAddress() {
|
||||||
|
final ProcessHandle.Info info = processHandle.info();
|
||||||
|
|
||||||
|
final String managementServerAddress;
|
||||||
|
|
||||||
|
final Optional<String[]> argumentsFound = info.arguments();
|
||||||
|
if (argumentsFound.isPresent()) {
|
||||||
|
final String[] arguments = argumentsFound.get();
|
||||||
|
managementServerAddress = findManagementServerAddress(arguments);
|
||||||
|
} else {
|
||||||
|
managementServerAddress = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(managementServerAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findManagementServerAddress(final String[] arguments) {
|
||||||
|
String managementServerAddress = null;
|
||||||
|
|
||||||
|
for (final String argument : arguments) {
|
||||||
|
final Matcher matcher = ADDRESS_ARGUMENT_PATTERN.matcher(argument);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
managementServerAddress = matcher.group(ADDRESS_GROUP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return managementServerAddress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for finding a Process Handle
|
||||||
|
*/
|
||||||
|
public interface ProcessHandleProvider {
|
||||||
|
/**
|
||||||
|
* Find Application Process Handle
|
||||||
|
*
|
||||||
|
* @return Application Process Handle or empty when not found
|
||||||
|
*/
|
||||||
|
Optional<ProcessHandle> findApplicationProcessHandle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Bootstrap Process Handle
|
||||||
|
*
|
||||||
|
* @return Bootstrap Process Handle or empty when not found
|
||||||
|
*/
|
||||||
|
Optional<ProcessHandle> findBootstrapProcessHandle();
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* 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 java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard Provider reads optional Server Address from Configuration Provider or selects from available ports
|
||||||
|
*/
|
||||||
|
public class StandardManagementServerAddressProvider implements ManagementServerAddressProvider {
|
||||||
|
private static final int STANDARD_PORT = 52020;
|
||||||
|
|
||||||
|
private static final int MAXIMUM_PORT = 52050;
|
||||||
|
|
||||||
|
private static final String NOT_AVAILABLE_MESSAGE = "Management Server Port not available in range [%d-%d]".formatted(STANDARD_PORT, MAXIMUM_PORT);
|
||||||
|
|
||||||
|
private static final String LOCALHOST_ADDRESS = "127.0.0.1:%d";
|
||||||
|
|
||||||
|
private static final String HOST_ADDRESS = "%s:%d";
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
public StandardManagementServerAddressProvider(final ConfigurationProvider configurationProvider) {
|
||||||
|
this.configurationProvider = Objects.requireNonNull(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Management Server Address with port number
|
||||||
|
*
|
||||||
|
* @return Management Server Address
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getAddress() {
|
||||||
|
final Optional<String> address;
|
||||||
|
|
||||||
|
final Optional<URI> managementServerAddress = configurationProvider.getManagementServerAddress();
|
||||||
|
if (managementServerAddress.isPresent()) {
|
||||||
|
final URI serverAddress = managementServerAddress.get();
|
||||||
|
final String hostAddress = HOST_ADDRESS.formatted(serverAddress.getHost(), serverAddress.getPort());
|
||||||
|
address = Optional.of(hostAddress);
|
||||||
|
} else {
|
||||||
|
final int serverPort = getServerPort();
|
||||||
|
address = Optional.of(LOCALHOST_ADDRESS.formatted(serverPort));
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getServerPort() {
|
||||||
|
final OptionalInt portFound = IntStream.range(STANDARD_PORT, MAXIMUM_PORT)
|
||||||
|
.filter(StandardManagementServerAddressProvider::isPortFree)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
return portFound.orElseThrow(() -> new IllegalStateException(NOT_AVAILABLE_MESSAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPortFree(final int port) {
|
||||||
|
try (ServerSocket ignored = new ServerSocket(port)) {
|
||||||
|
return true;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.SystemProperty;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Process Builder Provider for constructing application command arguments
|
||||||
|
*/
|
||||||
|
public class StandardProcessBuilderProvider implements ProcessBuilderProvider {
|
||||||
|
private static final String JAR_FILE_EXTENSION = ".jar";
|
||||||
|
|
||||||
|
private static final BiPredicate<Path, BasicFileAttributes> JAR_FILE_MATCHER = (path, attributes) -> path.getFileName().toString().endsWith(JAR_FILE_EXTENSION);
|
||||||
|
|
||||||
|
private static final int LIBRARY_JAR_DEPTH = 1;
|
||||||
|
|
||||||
|
private static final String SYSTEM_PROPERTY = "-D%s=%s";
|
||||||
|
|
||||||
|
private static final String CLASS_PATH_ARGUMENT = "--class-path";
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private final ManagementServerAddressProvider managementServerAddressProvider;
|
||||||
|
|
||||||
|
public StandardProcessBuilderProvider(final ConfigurationProvider configurationProvider, final ManagementServerAddressProvider managementServerAddressProvider) {
|
||||||
|
this.configurationProvider = Objects.requireNonNull(configurationProvider);
|
||||||
|
this.managementServerAddressProvider = Objects.requireNonNull(managementServerAddressProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessBuilder getApplicationProcessBuilder() {
|
||||||
|
final ProcessBuilder processBuilder = new ProcessBuilder();
|
||||||
|
|
||||||
|
final List<String> command = getCommand();
|
||||||
|
processBuilder.command(command);
|
||||||
|
|
||||||
|
return processBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getCommand() {
|
||||||
|
final List<String> command = new ArrayList<>();
|
||||||
|
|
||||||
|
final ProcessHandle.Info currentProcessHandleInfo = ProcessHandle.current().info();
|
||||||
|
final String currentProcessCommand = getCurrentProcessCommand(currentProcessHandleInfo);
|
||||||
|
command.add(currentProcessCommand);
|
||||||
|
|
||||||
|
final String classPath = getClassPath();
|
||||||
|
command.add(CLASS_PATH_ARGUMENT);
|
||||||
|
command.add(classPath);
|
||||||
|
|
||||||
|
final Path logDirectory = configurationProvider.getLogDirectory();
|
||||||
|
final String logDirectoryProperty = SYSTEM_PROPERTY.formatted(SystemProperty.LOG_DIRECTORY.getProperty(), logDirectory);
|
||||||
|
command.add(logDirectoryProperty);
|
||||||
|
|
||||||
|
final Path applicationProperties = configurationProvider.getApplicationProperties();
|
||||||
|
final String applicationPropertiesProperty = SYSTEM_PROPERTY.formatted(SystemProperty.APPLICATION_PROPERTIES.getProperty(), applicationProperties);
|
||||||
|
command.add(applicationPropertiesProperty);
|
||||||
|
|
||||||
|
final String managementServerAddress = managementServerAddressProvider.getAddress().orElseThrow(() -> new IllegalStateException("Management Server Address not configured"));
|
||||||
|
final String managementServerAddressProperty = SYSTEM_PROPERTY.formatted(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty(), managementServerAddress);
|
||||||
|
command.add(managementServerAddressProperty);
|
||||||
|
|
||||||
|
final List<String> additionalArguments = configurationProvider.getAdditionalArguments();
|
||||||
|
command.addAll(additionalArguments);
|
||||||
|
|
||||||
|
command.add(ApplicationClassName.APPLICATION.getName());
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCurrentProcessCommand(final ProcessHandle.Info currentProcessHandleInfo) {
|
||||||
|
final Optional<String> currentProcessHandleCommand = currentProcessHandleInfo.command();
|
||||||
|
return currentProcessHandleCommand.orElseThrow(IllegalStateException::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassPath() {
|
||||||
|
final Path libraryDirectory = configurationProvider.getLibraryDirectory();
|
||||||
|
try (
|
||||||
|
Stream<Path> libraryFiles = Files.find(libraryDirectory, LIBRARY_JAR_DEPTH, JAR_FILE_MATCHER)
|
||||||
|
) {
|
||||||
|
final List<String> libraryPaths = new ArrayList<>(libraryFiles.map(Path::toString).toList());
|
||||||
|
|
||||||
|
final Path configurationDirectory = configurationProvider.getConfigurationDirectory();
|
||||||
|
libraryPaths.add(configurationDirectory.toString());
|
||||||
|
|
||||||
|
return String.join(File.pathSeparator, libraryPaths);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IllegalStateException("Read Library Directory [%s] failed".formatted(libraryDirectory), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.apache.nifi.bootstrap.configuration.SystemProperty;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process Handle Provider searches running Processes and locates the first handles match based on command arguments
|
||||||
|
*/
|
||||||
|
public class StandardProcessHandleProvider implements ProcessHandleProvider {
|
||||||
|
|
||||||
|
private static final String PROPERTIES_ARGUMENT = "-D%s=%s";
|
||||||
|
|
||||||
|
private final ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
public StandardProcessHandleProvider(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 String propertiesArgument = PROPERTIES_ARGUMENT.formatted(systemProperty.getProperty(), configuration);
|
||||||
|
final ProcessHandle currentProcessHandle = ProcessHandle.current();
|
||||||
|
|
||||||
|
return ProcessHandle.allProcesses()
|
||||||
|
.filter(Predicate.not(currentProcessHandle::equals))
|
||||||
|
.filter(processHandle -> {
|
||||||
|
final ProcessHandle.Info processHandleInfo = processHandle.info();
|
||||||
|
final Optional<String[]> processArguments = processHandleInfo.arguments();
|
||||||
|
final boolean matched;
|
||||||
|
if (processArguments.isPresent()) {
|
||||||
|
final String[] arguments = processArguments.get();
|
||||||
|
matched = Arrays.stream(arguments).anyMatch(propertiesArgument::contentEquals);
|
||||||
|
} else {
|
||||||
|
matched = false;
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
})
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of application class names for processes and logging
|
||||||
|
*/
|
||||||
|
public enum ApplicationClassName {
|
||||||
|
APPLICATION("org.apache.nifi.NiFi"),
|
||||||
|
|
||||||
|
BOOTSTRAP_COMMAND("org.apache.nifi.bootstrap.Command");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
ApplicationClassName(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of supported bootstrap properties for the application
|
||||||
|
*/
|
||||||
|
public enum BootstrapProperty {
|
||||||
|
CONFIGURATION_DIRECTORY("conf.dir"),
|
||||||
|
|
||||||
|
GRACEFUL_SHUTDOWN_SECONDS("graceful.shutdown.seconds"),
|
||||||
|
|
||||||
|
JAVA_ARGUMENT("java.arg"),
|
||||||
|
|
||||||
|
LIBRARY_DIRECTORY("lib.dir"),
|
||||||
|
|
||||||
|
MANAGEMENT_SERVER_ADDRESS("management.server.address"),
|
||||||
|
|
||||||
|
WORKING_DIRECTORY("working.dir");
|
||||||
|
|
||||||
|
private final String property;
|
||||||
|
|
||||||
|
BootstrapProperty(final String property) {
|
||||||
|
this.property = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for access to application configuration properties
|
||||||
|
*/
|
||||||
|
public interface ConfigurationProvider {
|
||||||
|
/**
|
||||||
|
* Get additional arguments for application command
|
||||||
|
*
|
||||||
|
* @return Additional arguments
|
||||||
|
*/
|
||||||
|
List<String> getAdditionalArguments();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file containing application properties
|
||||||
|
*
|
||||||
|
* @return Application properties
|
||||||
|
*/
|
||||||
|
Path getApplicationProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file containing bootstrap configuration
|
||||||
|
*
|
||||||
|
* @return Bootstrap configuration
|
||||||
|
*/
|
||||||
|
Path getBootstrapConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get directory containing application configuration
|
||||||
|
*
|
||||||
|
* @return Configuration directory
|
||||||
|
*/
|
||||||
|
Path getConfigurationDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get directory containing application libraries
|
||||||
|
*
|
||||||
|
* @return Library directory
|
||||||
|
*/
|
||||||
|
Path getLibraryDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get directory containing logs
|
||||||
|
*
|
||||||
|
* @return Log directory
|
||||||
|
*/
|
||||||
|
Path getLogDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout configured for graceful shutdown of application process
|
||||||
|
*
|
||||||
|
* @return Graceful Shutdown Timeout duration
|
||||||
|
*/
|
||||||
|
Duration getGracefulShutdownTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Management Server Address from the bootstrap configuration
|
||||||
|
*
|
||||||
|
* @return Management Server Address or empty when not configured
|
||||||
|
*/
|
||||||
|
Optional<URI> getManagementServerAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get directory for current operations and resolving relative paths
|
||||||
|
*
|
||||||
|
* @return Working directory
|
||||||
|
*/
|
||||||
|
Path getWorkingDirectory();
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of supported bootstrap and application environment variables
|
||||||
|
*/
|
||||||
|
public enum EnvironmentVariable {
|
||||||
|
NIFI_HOME
|
||||||
|
}
|
|
@ -14,25 +14,27 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.bootstrap;
|
package org.apache.nifi.bootstrap.configuration;
|
||||||
|
|
||||||
public class InvalidCommandException extends Exception {
|
/**
|
||||||
|
* Enumeration of Management Server HTTP resource paths
|
||||||
|
*/
|
||||||
|
public enum ManagementServerPath {
|
||||||
|
HEALTH("/health"),
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
HEALTH_CLUSTER("/health/cluster"),
|
||||||
|
|
||||||
public InvalidCommandException() {
|
HEALTH_DIAGNOSTICS("/health/diagnostics"),
|
||||||
super();
|
|
||||||
|
HEALTH_STATUS_HISTORY("/health/status-history");
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
ManagementServerPath(final String path) {
|
||||||
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvalidCommandException(final String message) {
|
public String getPath() {
|
||||||
super(message);
|
return path;
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidCommandException(final Throwable t) {
|
|
||||||
super(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidCommandException(final String message, final Throwable t) {
|
|
||||||
super(message, t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Configuration Provider based on NIFI_HOME environment variable base directory
|
||||||
|
*/
|
||||||
|
public class StandardConfigurationProvider implements ConfigurationProvider {
|
||||||
|
|
||||||
|
private static final String CONFIGURATION_DIRECTORY = "conf";
|
||||||
|
|
||||||
|
private static final String LIBRARY_DIRECTORY = "lib";
|
||||||
|
|
||||||
|
private static final String LOG_DIRECTORY = "logs";
|
||||||
|
|
||||||
|
private static final String APPLICATION_PROPERTIES = "nifi.properties";
|
||||||
|
|
||||||
|
private static final String BOOTSTRAP_CONFIGURATION = "bootstrap.conf";
|
||||||
|
|
||||||
|
private static final String CURRENT_DIRECTORY = "";
|
||||||
|
|
||||||
|
private static final Duration GRACEFUL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(20);
|
||||||
|
|
||||||
|
private final Map<String, String> environmentVariables;
|
||||||
|
|
||||||
|
private final Properties systemProperties;
|
||||||
|
|
||||||
|
private final Properties bootstrapProperties = new Properties();
|
||||||
|
|
||||||
|
public StandardConfigurationProvider(final Map<String, String> environmentVariables, final Properties systemProperties) {
|
||||||
|
this.environmentVariables = Objects.requireNonNull(environmentVariables);
|
||||||
|
this.systemProperties = Objects.requireNonNull(systemProperties);
|
||||||
|
setBootstrapProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get additional arguments for application command from Bootstrap Properties starting with java.arg
|
||||||
|
*
|
||||||
|
* @return Additional arguments
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<String> getAdditionalArguments() {
|
||||||
|
final List<String> additionalArguments = new ArrayList<>();
|
||||||
|
|
||||||
|
for (final String propertyName : bootstrapProperties.stringPropertyNames()) {
|
||||||
|
if (propertyName.startsWith(BootstrapProperty.JAVA_ARGUMENT.getProperty())) {
|
||||||
|
final String additionalArgument = bootstrapProperties.getProperty(propertyName);
|
||||||
|
if (!additionalArgument.isBlank()) {
|
||||||
|
additionalArguments.add(additionalArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return additionalArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Application Properties relative to configuration directory
|
||||||
|
*
|
||||||
|
* @return Application Properties
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getApplicationProperties() {
|
||||||
|
final Path configurationDirectory = getConfigurationDirectory();
|
||||||
|
final Path applicationProperties = configurationDirectory.resolve(APPLICATION_PROPERTIES);
|
||||||
|
|
||||||
|
if (Files.notExists(applicationProperties)) {
|
||||||
|
throw new IllegalStateException("Application Properties [%s] not found".formatted(applicationProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bootstrap Configuration from either System Property or relative to configuration directory
|
||||||
|
*
|
||||||
|
* @return Bootstrap Configuration
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getBootstrapConfiguration() {
|
||||||
|
final Path bootstrapConfiguration;
|
||||||
|
|
||||||
|
final String bootstrapConfigurationProperty = System.getProperty(SystemProperty.BOOTSTRAP_CONFIGURATION.getProperty());
|
||||||
|
if (isEmpty(bootstrapConfigurationProperty)) {
|
||||||
|
final Path configurationDirectory = getConfigurationDirectory();
|
||||||
|
bootstrapConfiguration = configurationDirectory.resolve(BOOTSTRAP_CONFIGURATION);
|
||||||
|
} else {
|
||||||
|
bootstrapConfiguration = Paths.get(bootstrapConfigurationProperty).toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.notExists(bootstrapConfiguration)) {
|
||||||
|
throw new IllegalStateException("Bootstrap Configuration [%s] not found".formatted(bootstrapConfiguration));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrapConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Library Directory from Bootstrap Configuration or relative to configuration directory
|
||||||
|
*
|
||||||
|
* @return Library Directory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getLibraryDirectory() {
|
||||||
|
final Path libraryDirectory = getResolvedDirectory(BootstrapProperty.LIBRARY_DIRECTORY, LIBRARY_DIRECTORY);
|
||||||
|
|
||||||
|
if (Files.notExists(libraryDirectory)) {
|
||||||
|
throw new IllegalStateException("Library Directory [%s] not found".formatted(libraryDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Log Directory from System Property or relative to application home directory
|
||||||
|
*
|
||||||
|
* @return Log Directory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getLogDirectory() {
|
||||||
|
final Path logDirectory;
|
||||||
|
|
||||||
|
final String logDirectoryProperty = systemProperties.getProperty(SystemProperty.LOG_DIRECTORY.getProperty());
|
||||||
|
|
||||||
|
if (isEmpty(logDirectoryProperty)) {
|
||||||
|
final Path applicationHome = getApplicationHome();
|
||||||
|
logDirectory = applicationHome.resolve(LOG_DIRECTORY);
|
||||||
|
} else {
|
||||||
|
logDirectory = Paths.get(logDirectoryProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout configured for graceful shutdown of application process
|
||||||
|
*
|
||||||
|
* @return Graceful Shutdown Timeout duration
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Duration getGracefulShutdownTimeout() {
|
||||||
|
final Duration gracefulShutdownTimeout;
|
||||||
|
|
||||||
|
final String gracefulShutdownSecondsProperty = bootstrapProperties.getProperty(BootstrapProperty.GRACEFUL_SHUTDOWN_SECONDS.getProperty());
|
||||||
|
if (gracefulShutdownSecondsProperty == null || gracefulShutdownSecondsProperty.isEmpty()) {
|
||||||
|
gracefulShutdownTimeout = GRACEFUL_SHUTDOWN_TIMEOUT;
|
||||||
|
} else {
|
||||||
|
final int gracefulShutdownSeconds = Integer.parseInt(gracefulShutdownSecondsProperty);
|
||||||
|
gracefulShutdownTimeout = Duration.ofSeconds(gracefulShutdownSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gracefulShutdownTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Management Server Address from the bootstrap configuration
|
||||||
|
*
|
||||||
|
* @return Management Server Address or empty when not configured
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<URI> getManagementServerAddress() {
|
||||||
|
final Optional<URI> managementServerAddress;
|
||||||
|
|
||||||
|
final String managementServerAddressProperty = bootstrapProperties.getProperty(BootstrapProperty.MANAGEMENT_SERVER_ADDRESS.getProperty());
|
||||||
|
if (managementServerAddressProperty == null || managementServerAddressProperty.isEmpty()) {
|
||||||
|
managementServerAddress = Optional.empty();
|
||||||
|
} else {
|
||||||
|
final URI serverAddress = URI.create(managementServerAddressProperty);
|
||||||
|
managementServerAddress = Optional.of(serverAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return managementServerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Configuration Directory from Bootstrap Configuration or relative to application home directory
|
||||||
|
*
|
||||||
|
* @return Configuration Directory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getConfigurationDirectory() {
|
||||||
|
final Path configurationDirectory = getResolvedDirectory(BootstrapProperty.CONFIGURATION_DIRECTORY, CONFIGURATION_DIRECTORY);
|
||||||
|
|
||||||
|
if (Files.notExists(configurationDirectory)) {
|
||||||
|
throw new IllegalStateException("Configuration Directory [%s] not found".formatted(configurationDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurationDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Working Directory from Bootstrap Configuration or current working directory
|
||||||
|
*
|
||||||
|
* @return Working Directory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Path getWorkingDirectory() {
|
||||||
|
final Path workingDirectory;
|
||||||
|
|
||||||
|
final String workingDirectoryProperty = bootstrapProperties.getProperty(BootstrapProperty.WORKING_DIRECTORY.getProperty());
|
||||||
|
if (isEmpty(workingDirectoryProperty)) {
|
||||||
|
workingDirectory = Paths.get(CURRENT_DIRECTORY).toAbsolutePath();
|
||||||
|
} else {
|
||||||
|
workingDirectory = Paths.get(workingDirectoryProperty).toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getResolvedDirectory(final BootstrapProperty bootstrapProperty, final String relativeDirectory) {
|
||||||
|
final Path resolvedDirectory;
|
||||||
|
|
||||||
|
final String directoryProperty = bootstrapProperties.getProperty(bootstrapProperty.getProperty());
|
||||||
|
if (isEmpty(directoryProperty)) {
|
||||||
|
final Path applicationHome = getApplicationHome();
|
||||||
|
resolvedDirectory = applicationHome.resolve(relativeDirectory);
|
||||||
|
} else {
|
||||||
|
final Path directoryPropertyResolved = Paths.get(directoryProperty);
|
||||||
|
if (directoryPropertyResolved.isAbsolute()) {
|
||||||
|
resolvedDirectory = directoryPropertyResolved;
|
||||||
|
} else {
|
||||||
|
final Path workingDirectory = getWorkingDirectory();
|
||||||
|
resolvedDirectory = workingDirectory.resolve(directoryPropertyResolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize Path removing relative directory elements
|
||||||
|
return resolvedDirectory.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getApplicationHome() {
|
||||||
|
final Path applicationHome;
|
||||||
|
|
||||||
|
final String applicationHomeVariable = environmentVariables.get(EnvironmentVariable.NIFI_HOME.name());
|
||||||
|
if (isEmpty(applicationHomeVariable)) {
|
||||||
|
throw new IllegalStateException("Application Home Environment Variable [NIFI_HOME] not configured");
|
||||||
|
} else {
|
||||||
|
applicationHome = Paths.get(applicationHomeVariable).toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.notExists(applicationHome)) {
|
||||||
|
throw new IllegalStateException("Application Home [%s] not found".formatted(applicationHome));
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationHome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmpty(final String property) {
|
||||||
|
return property == null || property.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBootstrapProperties() {
|
||||||
|
final Path bootstrapConfiguration = getBootstrapConfiguration();
|
||||||
|
|
||||||
|
try (InputStream inputStream = Files.newInputStream(bootstrapConfiguration)) {
|
||||||
|
bootstrapProperties.load(inputStream);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new UncheckedIOException("Bootstrap Properties [%s] loading failed".formatted(bootstrapConfiguration), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of supported system properties for the application
|
||||||
|
*/
|
||||||
|
public enum SystemProperty {
|
||||||
|
/** Path to application properties file */
|
||||||
|
APPLICATION_PROPERTIES("nifi.properties.file.path"),
|
||||||
|
|
||||||
|
/** Path to bootstrap configuration file */
|
||||||
|
BOOTSTRAP_CONFIGURATION("org.apache.nifi.bootstrap.config.file"),
|
||||||
|
|
||||||
|
/** Path to log directory */
|
||||||
|
LOG_DIRECTORY("org.apache.nifi.bootstrap.config.log.dir"),
|
||||||
|
|
||||||
|
/** Socket address and port number for management server */
|
||||||
|
MANAGEMENT_SERVER_ADDRESS("org.apache.nifi.management.server.address");
|
||||||
|
|
||||||
|
private final String property;
|
||||||
|
|
||||||
|
SystemProperty(final String property) {
|
||||||
|
this.property = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,59 +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.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.InvalidPathException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
public final class DumpFileValidator {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DumpFileValidator.class);
|
|
||||||
|
|
||||||
private DumpFileValidator() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean validate(final String filePath) {
|
|
||||||
try {
|
|
||||||
final Path path = Paths.get(filePath);
|
|
||||||
return checkFileCanBeCreated(path);
|
|
||||||
} catch (InvalidPathException e) {
|
|
||||||
System.err.println("Invalid filename. The command parameters are: status-history <number of days> <dumpFile>");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean checkFileCanBeCreated(final Path path) {
|
|
||||||
try (final FileOutputStream outputStream = new FileOutputStream(path.toString());
|
|
||||||
final Closeable onClose = () -> Files.delete(path)) {
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
System.err.println("Invalid filename or there's no write permission to the currently selected file path.");
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not delete file while validating file path.");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +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 java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class LimitingInputStream extends InputStream {
|
|
||||||
|
|
||||||
private final InputStream in;
|
|
||||||
private final long limit;
|
|
||||||
private long bytesRead = 0;
|
|
||||||
|
|
||||||
public LimitingInputStream(final InputStream in, final long limit) {
|
|
||||||
this.in = in;
|
|
||||||
this.limit = limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (bytesRead >= limit) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int val = in.read();
|
|
||||||
if (val > -1) {
|
|
||||||
bytesRead++;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(final byte[] b) throws IOException {
|
|
||||||
if (bytesRead >= limit) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int maxToRead = (int) Math.min(b.length, limit - bytesRead);
|
|
||||||
|
|
||||||
final int val = in.read(b, 0, maxToRead);
|
|
||||||
if (val > 0) {
|
|
||||||
bytesRead += val;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (bytesRead >= limit) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int maxToRead = (int) Math.min(len, limit - bytesRead);
|
|
||||||
|
|
||||||
final int val = in.read(b, off, maxToRead);
|
|
||||||
if (val > 0) {
|
|
||||||
bytesRead += val;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long skip(final long n) throws IOException {
|
|
||||||
final long skipped = in.skip(Math.min(n, limit - bytesRead));
|
|
||||||
bytesRead += skipped;
|
|
||||||
return skipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
return in.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mark(int readlimit) {
|
|
||||||
in.mark(readlimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean markSupported() {
|
|
||||||
return in.markSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() throws IOException {
|
|
||||||
in.reset();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* 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.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ApplicationProcessStatusBootstrapCommandTest {
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle processHandle;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle applicationProcessHandle;
|
||||||
|
|
||||||
|
private ApplicationProcessStatusBootstrapCommand command;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setCommand() {
|
||||||
|
command = new ApplicationProcessStatusBootstrapCommand(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunStopped() {
|
||||||
|
when(processHandle.children()).thenReturn(Stream.empty());
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.STOPPED, commandStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunSuccess() {
|
||||||
|
when(processHandle.children()).thenReturn(Stream.of(applicationProcessHandle));
|
||||||
|
when(applicationProcessHandle.isAlive()).thenReturn(true);
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.SUCCESS, commandStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunCommunicationFailed() {
|
||||||
|
when(processHandle.children()).thenReturn(Stream.of(applicationProcessHandle));
|
||||||
|
when(applicationProcessHandle.isAlive()).thenReturn(false);
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.COMMUNICATION_FAILED, commandStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.process.ProcessHandleProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
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.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
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 GetRunCommandBootstrapCommandTest {
|
||||||
|
private static final String CONFIGURATION_DIRECTORY = "conf";
|
||||||
|
|
||||||
|
private static final String SPACE_SEPARATOR = " ";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRun(@TempDir final Path workingDirectory) throws IOException {
|
||||||
|
final Path configurationDirectory = workingDirectory.resolve(CONFIGURATION_DIRECTORY);
|
||||||
|
assertTrue(configurationDirectory.toFile().mkdir());
|
||||||
|
|
||||||
|
final Path applicationProperties = configurationDirectory.resolve(Properties.class.getSimpleName());
|
||||||
|
Files.writeString(applicationProperties, SPACE_SEPARATOR);
|
||||||
|
|
||||||
|
when(configurationProvider.getApplicationProperties()).thenReturn(applicationProperties);
|
||||||
|
when(configurationProvider.getLibraryDirectory()).thenReturn(workingDirectory);
|
||||||
|
when(configurationProvider.getConfigurationDirectory()).thenReturn(configurationDirectory);
|
||||||
|
|
||||||
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
final PrintStream printStream = new PrintStream(outputStream);
|
||||||
|
|
||||||
|
final GetRunCommandBootstrapCommand command = new GetRunCommandBootstrapCommand(configurationProvider, processHandleProvider, printStream);
|
||||||
|
command.run();
|
||||||
|
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertNotNull(commandStatus);
|
||||||
|
assertEquals(CommandStatus.SUCCESS, commandStatus);
|
||||||
|
|
||||||
|
final String runCommand = outputStream.toString().trim();
|
||||||
|
final List<String> runCommands = List.of(runCommand.split(SPACE_SEPARATOR));
|
||||||
|
|
||||||
|
final String lastCommand = runCommands.getLast();
|
||||||
|
assertEquals(ApplicationClassName.APPLICATION.getName(), lastCommand);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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.process.ProcessHandleProvider;
|
||||||
|
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.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class RunBootstrapCommandTest {
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
private RunBootstrapCommand command;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setCommand() {
|
||||||
|
command = new RunBootstrapCommand(configurationProvider, processHandleProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRun() {
|
||||||
|
command.run();
|
||||||
|
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertNotNull(commandStatus);
|
||||||
|
assertEquals(CommandStatus.FAILED, commandStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class StandardBootstrapCommandProviderTest {
|
||||||
|
private StandardBootstrapCommandProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
provider = new StandardBootstrapCommandProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBootstrapCommandNull() {
|
||||||
|
final BootstrapCommand bootstrapCommand = provider.getBootstrapCommand(null);
|
||||||
|
|
||||||
|
assertNotNull(bootstrapCommand);
|
||||||
|
assertInstanceOf(UnknownBootstrapCommand.class, bootstrapCommand);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StartBootstrapCommandTest {
|
||||||
|
@Mock
|
||||||
|
private BootstrapCommand runBootstrapCommand;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BootstrapCommand statusBootstrapCommand;
|
||||||
|
|
||||||
|
private StartBootstrapCommand command;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setCommand() {
|
||||||
|
command = new StartBootstrapCommand(runBootstrapCommand, statusBootstrapCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunError() {
|
||||||
|
final CommandStatus runCommandStatus = CommandStatus.ERROR;
|
||||||
|
when(runBootstrapCommand.getCommandStatus()).thenReturn(runCommandStatus);
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
assertEquals(runCommandStatus, commandStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunSuccessFailed() {
|
||||||
|
final CommandStatus runCommandStatus = CommandStatus.SUCCESS;
|
||||||
|
when(runBootstrapCommand.getCommandStatus()).thenReturn(runCommandStatus);
|
||||||
|
|
||||||
|
final CommandStatus statusCommandStatus = CommandStatus.FAILED;
|
||||||
|
lenient().when(statusBootstrapCommand.getCommandStatus()).thenReturn(statusCommandStatus);
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
assertEquals(CommandStatus.RUNNING, commandStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* 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.process.ProcessHandleProvider;
|
||||||
|
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.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StopBootstrapCommandTest {
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandleProvider processHandleProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle applicationProcessHandle;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle.Info applicationProcessHandleInfo;
|
||||||
|
|
||||||
|
private StopBootstrapCommand command;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setCommand() {
|
||||||
|
command = new StopBootstrapCommand(processHandleProvider, configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunProcessHandleNotFound() {
|
||||||
|
when(processHandleProvider.findApplicationProcessHandle()).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.SUCCESS, commandStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunDestroyCompleted() {
|
||||||
|
when(processHandleProvider.findApplicationProcessHandle()).thenReturn(Optional.of(applicationProcessHandle));
|
||||||
|
when(applicationProcessHandle.destroy()).thenReturn(true);
|
||||||
|
when(applicationProcessHandle.onExit()).thenReturn(CompletableFuture.completedFuture(applicationProcessHandle));
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.SUCCESS, commandStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRunDestroyFailed() {
|
||||||
|
when(processHandleProvider.findApplicationProcessHandle()).thenReturn(Optional.of(applicationProcessHandle));
|
||||||
|
when(applicationProcessHandle.destroy()).thenReturn(true);
|
||||||
|
when(applicationProcessHandle.onExit()).thenReturn(CompletableFuture.failedFuture(new RuntimeException()));
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
final CommandStatus commandStatus = command.getCommandStatus();
|
||||||
|
|
||||||
|
assertEquals(CommandStatus.ERROR, commandStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
|
||||||
|
class FileResponseStreamHandlerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOnResponseStream(@TempDir final Path outputDirectory) throws IOException {
|
||||||
|
final Path outputPath = outputDirectory.resolve(FileResponseStreamHandlerTest.class.getSimpleName());
|
||||||
|
|
||||||
|
final FileResponseStreamHandler handler = new FileResponseStreamHandler(outputPath);
|
||||||
|
|
||||||
|
final byte[] bytes = String.class.getName().getBytes(StandardCharsets.UTF_8);
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(bytes);
|
||||||
|
|
||||||
|
handler.onResponseStream(inputStream);
|
||||||
|
|
||||||
|
final byte[] read = Files.readAllBytes(outputPath);
|
||||||
|
assertArrayEquals(bytes, read);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class StandardBootstrapArgumentParserTest {
|
||||||
|
private static final String CLUSTER_STATUS_ARGUMENT = "cluster-status";
|
||||||
|
|
||||||
|
private StandardBootstrapArgumentParser parser;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setParser() {
|
||||||
|
parser = new StandardBootstrapArgumentParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBootstrapArgumentNull() {
|
||||||
|
final Optional<BootstrapArgument> bootstrapArgumentFound = parser.getBootstrapArgument(null);
|
||||||
|
|
||||||
|
assertTrue(bootstrapArgumentFound.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBootstrapArgumentClusterStatus() {
|
||||||
|
final Optional<BootstrapArgument> bootstrapArgumentFound = parser.getBootstrapArgument(new String[]{CLUSTER_STATUS_ARGUMENT});
|
||||||
|
|
||||||
|
assertTrue(bootstrapArgumentFound.isPresent());
|
||||||
|
assertEquals(BootstrapArgument.CLUSTER_STATUS, bootstrapArgumentFound.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* 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.SystemProperty;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ProcessHandleManagementServerAddressProviderTest {
|
||||||
|
private static final String SYSTEM_PROPERTY_ARGUMENT = "-D%s=%s";
|
||||||
|
|
||||||
|
private static final String ADDRESS = "127.0.0.1:52020";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle processHandle;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProcessHandle.Info processHandleInfo;
|
||||||
|
|
||||||
|
private ProcessHandleManagementServerAddressProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
provider = new ProcessHandleManagementServerAddressProvider(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAddressNotFound() {
|
||||||
|
when(processHandle.info()).thenReturn(processHandleInfo);
|
||||||
|
|
||||||
|
final Optional<String> managementServerAddress = provider.getAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAddressArgumentNotFound() {
|
||||||
|
when(processHandle.info()).thenReturn(processHandleInfo);
|
||||||
|
when(processHandleInfo.arguments()).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
final Optional<String> managementServerAddress = provider.getAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAddress() {
|
||||||
|
when(processHandle.info()).thenReturn(processHandleInfo);
|
||||||
|
|
||||||
|
final String systemPropertyArgument = SYSTEM_PROPERTY_ARGUMENT.formatted(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty(), ADDRESS);
|
||||||
|
final String[] arguments = new String[]{systemPropertyArgument};
|
||||||
|
|
||||||
|
when(processHandleInfo.arguments()).thenReturn(Optional.of(arguments));
|
||||||
|
|
||||||
|
final Optional<String> managementServerAddress = provider.getAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isPresent());
|
||||||
|
|
||||||
|
final String address = managementServerAddress.get();
|
||||||
|
assertEquals(ADDRESS, address);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StandardManagementServerAddressProviderTest {
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private StandardManagementServerAddressProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
provider = new StandardManagementServerAddressProvider(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAddressFound() {
|
||||||
|
final Optional<String> managementServerAddress = provider.getAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isPresent());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.ApplicationClassName;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.SystemProperty;
|
||||||
|
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.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
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 StandardProcessBuilderProviderTest {
|
||||||
|
private static final String SERVER_ADDRESS = "127.0.0.1:52020";
|
||||||
|
|
||||||
|
private static final String SERVER_ADDRESS_PROPERTY = "-D%s=%s".formatted(SystemProperty.MANAGEMENT_SERVER_ADDRESS.getProperty(), SERVER_ADDRESS);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ManagementServerAddressProvider managementServerAddressProvider;
|
||||||
|
|
||||||
|
private StandardProcessBuilderProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
provider = new StandardProcessBuilderProvider(configurationProvider, managementServerAddressProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetApplicationProcessBuilder(@TempDir final Path workingDirectory) {
|
||||||
|
when(configurationProvider.getLibraryDirectory()).thenReturn(workingDirectory);
|
||||||
|
when(configurationProvider.getConfigurationDirectory()).thenReturn(workingDirectory);
|
||||||
|
when(managementServerAddressProvider.getAddress()).thenReturn(Optional.of(SERVER_ADDRESS));
|
||||||
|
|
||||||
|
final ProcessBuilder processBuilder = provider.getApplicationProcessBuilder();
|
||||||
|
|
||||||
|
assertNotNull(processBuilder);
|
||||||
|
|
||||||
|
final List<String> command = processBuilder.command();
|
||||||
|
|
||||||
|
final String currentCommand = ProcessHandle.current().info().command().orElse(null);
|
||||||
|
final String firstCommand = command.getFirst();
|
||||||
|
assertEquals(currentCommand, firstCommand);
|
||||||
|
|
||||||
|
final String lastCommand = command.getLast();
|
||||||
|
assertEquals(ApplicationClassName.APPLICATION.getName(), lastCommand);
|
||||||
|
|
||||||
|
assertTrue(command.contains(SERVER_ADDRESS_PROPERTY));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StandardProcessHandleProviderTest {
|
||||||
|
@Mock
|
||||||
|
private ConfigurationProvider configurationProvider;
|
||||||
|
|
||||||
|
private StandardProcessHandleProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
provider = new StandardProcessHandleProvider(configurationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFindApplicationProcessHandleEmpty() {
|
||||||
|
final Optional<ProcessHandle> applicationProcessHandle = provider.findApplicationProcessHandle();
|
||||||
|
|
||||||
|
assertTrue(applicationProcessHandle.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFindBootstrapProcessHandleEmpty() {
|
||||||
|
final Optional<ProcessHandle> bootstrapProcessHandle = provider.findBootstrapProcessHandle();
|
||||||
|
|
||||||
|
assertTrue(bootstrapProcessHandle.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* 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.configuration;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class StandardConfigurationProviderTest {
|
||||||
|
|
||||||
|
private static final String CONFIGURATION_DIRECTORY = "conf";
|
||||||
|
|
||||||
|
private static final String BOOTSTRAP_CONFIGURATION = "bootstrap.conf";
|
||||||
|
|
||||||
|
private static final String CURRENT_DIRECTORY = "";
|
||||||
|
|
||||||
|
private static final String MANAGEMENT_SERVER_ADDRESS = "http://127.0.0.1:52020";
|
||||||
|
|
||||||
|
private final Map<String, String> environmentVariables = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final Properties systemProperties = new Properties();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setProvider() {
|
||||||
|
environmentVariables.clear();
|
||||||
|
systemProperties.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testApplicationHomeNotConfigured() {
|
||||||
|
assertThrows(IllegalStateException.class, () -> new StandardConfigurationProvider(environmentVariables, systemProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBootstrapConfiguration(@TempDir final Path applicationHomeDirectory) throws IOException {
|
||||||
|
environmentVariables.put(EnvironmentVariable.NIFI_HOME.name(), applicationHomeDirectory.toString());
|
||||||
|
final Path configurationDirectory = createConfigurationDirectory(applicationHomeDirectory);
|
||||||
|
final Path bootstrapConfiguration = setRequiredConfiguration(applicationHomeDirectory);
|
||||||
|
|
||||||
|
final StandardConfigurationProvider provider = new StandardConfigurationProvider(environmentVariables, systemProperties);
|
||||||
|
|
||||||
|
final Path configurationDirectoryProvided = provider.getConfigurationDirectory();
|
||||||
|
assertEquals(configurationDirectory, configurationDirectoryProvided);
|
||||||
|
|
||||||
|
final Path bootstrapConfigurationProvided = provider.getBootstrapConfiguration();
|
||||||
|
assertEquals(bootstrapConfiguration, bootstrapConfigurationProvided);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetWorkingDirectory(@TempDir final Path applicationHomeDirectory) throws IOException {
|
||||||
|
setRequiredConfiguration(applicationHomeDirectory);
|
||||||
|
|
||||||
|
final StandardConfigurationProvider provider = new StandardConfigurationProvider(environmentVariables, systemProperties);
|
||||||
|
|
||||||
|
final Path workingDirectory = provider.getWorkingDirectory();
|
||||||
|
final Path workingDirectoryExpected = Paths.get(CURRENT_DIRECTORY).toAbsolutePath();
|
||||||
|
|
||||||
|
assertEquals(workingDirectoryExpected, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetManagementServerAddressNotConfigured(@TempDir final Path applicationHomeDirectory) throws IOException {
|
||||||
|
setRequiredConfiguration(applicationHomeDirectory);
|
||||||
|
|
||||||
|
final StandardConfigurationProvider provider = new StandardConfigurationProvider(environmentVariables, systemProperties);
|
||||||
|
|
||||||
|
final Optional<URI> managementServerAddress = provider.getManagementServerAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetManagementServerAddress(@TempDir final Path applicationHomeDirectory) throws IOException {
|
||||||
|
final Path bootstrapConfiguration = setRequiredConfiguration(applicationHomeDirectory);
|
||||||
|
|
||||||
|
final Properties bootstrapProperties = new Properties();
|
||||||
|
bootstrapProperties.put(BootstrapProperty.MANAGEMENT_SERVER_ADDRESS.getProperty(), MANAGEMENT_SERVER_ADDRESS);
|
||||||
|
try (OutputStream outputStream = Files.newOutputStream(bootstrapConfiguration)) {
|
||||||
|
bootstrapProperties.store(outputStream, Properties.class.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
final StandardConfigurationProvider provider = new StandardConfigurationProvider(environmentVariables, systemProperties);
|
||||||
|
|
||||||
|
final Optional<URI> managementServerAddress = provider.getManagementServerAddress();
|
||||||
|
|
||||||
|
assertTrue(managementServerAddress.isPresent());
|
||||||
|
final URI address = managementServerAddress.get();
|
||||||
|
assertEquals(MANAGEMENT_SERVER_ADDRESS, address.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path setRequiredConfiguration(final Path applicationHomeDirectory) throws IOException {
|
||||||
|
environmentVariables.put(EnvironmentVariable.NIFI_HOME.name(), applicationHomeDirectory.toString());
|
||||||
|
final Path configurationDirectory = createConfigurationDirectory(applicationHomeDirectory);
|
||||||
|
return createBootstrapConfiguration(configurationDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path createConfigurationDirectory(final Path applicationHomeDirectory) {
|
||||||
|
final Path configurationDirectory = applicationHomeDirectory.resolve(CONFIGURATION_DIRECTORY);
|
||||||
|
if (configurationDirectory.toFile().mkdir()) {
|
||||||
|
assertTrue(Files.isReadable(configurationDirectory));
|
||||||
|
}
|
||||||
|
return configurationDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path createBootstrapConfiguration(final Path configurationDirectory) throws IOException {
|
||||||
|
final Path bootstrapConfiguration = configurationDirectory.resolve(BOOTSTRAP_CONFIGURATION);
|
||||||
|
assertTrue(bootstrapConfiguration.toFile().createNewFile());
|
||||||
|
return bootstrapConfiguration;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2842,7 +2842,6 @@ properties for minimum and maximum Java Heap size, the garbage collector to use,
|
||||||
|`nifi.diagnostics.on.shutdown.directory`|This property specifies the location of the NiFi diagnostics directory. The default value is `./diagnostics`.
|
|`nifi.diagnostics.on.shutdown.directory`|This property specifies the location of the NiFi diagnostics directory. The default value is `./diagnostics`.
|
||||||
|`nifi.diagnostics.on.shutdown.max.filecount`|This property specifies the maximum permitted number of diagnostic files. If the limit is exceeded, the oldest files are deleted. The default value is `10`.
|
|`nifi.diagnostics.on.shutdown.max.filecount`|This property specifies the maximum permitted number of diagnostic files. If the limit is exceeded, the oldest files are deleted. The default value is `10`.
|
||||||
|`nifi.diagnostics.on.shutdown.max.directory.size`|This property specifies the maximum permitted size of the diagnostics directory. If the limit is exceeded, the oldest files are deleted. The default value is `10 MB`.
|
|`nifi.diagnostics.on.shutdown.max.directory.size`|This property specifies the maximum permitted size of the diagnostics directory. If the limit is exceeded, the oldest files are deleted. The default value is `10 MB`.
|
||||||
|`nifi.bootstrap.listen.port`|This property defines the port used to listen for communications from NiFi. If this property is missing, empty, or `0`, a random ephemeral port is used.
|
|
||||||
|====
|
|====
|
||||||
|
|
||||||
[[proxy_configuration]]
|
[[proxy_configuration]]
|
||||||
|
|
|
@ -29,17 +29,16 @@ set BOOTSTRAP_LIB_DIR=%NIFI_HOME%\lib\bootstrap
|
||||||
set CONF_DIR=%NIFI_HOME%\conf
|
set CONF_DIR=%NIFI_HOME%\conf
|
||||||
|
|
||||||
set LOG_DIR_PROPERTY=-Dorg.apache.nifi.bootstrap.config.log.dir=%NIFI_LOG_DIR%
|
set LOG_DIR_PROPERTY=-Dorg.apache.nifi.bootstrap.config.log.dir=%NIFI_LOG_DIR%
|
||||||
set PID_DIR_PROPERTY=-Dorg.apache.nifi.bootstrap.config.pid.dir=%NIFI_PID_DIR%
|
|
||||||
set CONFIG_FILE_PROPERTY=-Dorg.apache.nifi.bootstrap.config.file=%CONF_DIR%\bootstrap.conf
|
set CONFIG_FILE_PROPERTY=-Dorg.apache.nifi.bootstrap.config.file=%CONF_DIR%\bootstrap.conf
|
||||||
set PROPERTIES_FILE_PROPERTY=-Dnifi.properties.file.path=%CONF_DIR%\nifi.properties
|
set PROPERTIES_FILE_PROPERTY=-Dnifi.properties.file.path=%CONF_DIR%\nifi.properties
|
||||||
set BOOTSTRAP_HEAP_SIZE=48m
|
set BOOTSTRAP_HEAP_SIZE=48m
|
||||||
|
|
||||||
set JAVA_ARGS=%LOG_DIR_PROPERTY% %PID_DIR_PROPERTY% %CONFIG_FILE_PROPERTY% %PROPERTIES_FILE_PROPERTY%
|
set JAVA_ARGS=%LOG_DIR_PROPERTY% %CONFIG_FILE_PROPERTY% %PROPERTIES_FILE_PROPERTY%
|
||||||
set JAVA_PARAMS=-cp %BOOTSTRAP_LIB_DIR%\*;%CONF_DIR% %JAVA_ARGS%
|
set JAVA_PARAMS=-cp %BOOTSTRAP_LIB_DIR%\*;%CONF_DIR% %JAVA_ARGS%
|
||||||
set JAVA_MEMORY=-Xms%BOOTSTRAP_HEAP_SIZE% -Xmx%BOOTSTRAP_HEAP_SIZE%
|
set JAVA_MEMORY=-Xms%BOOTSTRAP_HEAP_SIZE% -Xmx%BOOTSTRAP_HEAP_SIZE%
|
||||||
|
|
||||||
echo JAVA_HOME: %JAVA_HOME%
|
echo JAVA_HOME=%JAVA_HOME%
|
||||||
echo NIFI_HOME: %NIFI_HOME%
|
echo NIFI_HOME=%NIFI_HOME%
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
pushd %NIFI_HOME%
|
pushd %NIFI_HOME%
|
||||||
|
@ -54,7 +53,7 @@ if %RUN_COMMAND% == "set-single-user-credentials" (
|
||||||
) else if %RUN_COMMAND% == "set-sensitive-properties-algorithm" (
|
) 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% org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm %~2
|
||||||
) else (
|
) else (
|
||||||
call "%JAVA_EXE%" %JAVA_MEMORY% %JAVA_PARAMS% org.apache.nifi.bootstrap.RunNiFi %RUN_COMMAND%
|
call "%JAVA_EXE%" %JAVA_MEMORY% %JAVA_PARAMS% org.apache.nifi.bootstrap.BootstrapProcess %RUN_COMMAND%
|
||||||
)
|
)
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -143,15 +143,6 @@ locateJava() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# if command is env, attempt to add more to the classpath
|
|
||||||
if [ "$1" = "env" ]; then
|
|
||||||
[ "x${TOOLS_JAR}" = "x" ] && [ -n "${JAVA_HOME}" ] && TOOLS_JAR=$(find -H "${JAVA_HOME}" -name "tools.jar")
|
|
||||||
[ "x${TOOLS_JAR}" = "x" ] && [ -n "${JAVA_HOME}" ] && TOOLS_JAR=$(find -H "${JAVA_HOME}" -name "classes.jar")
|
|
||||||
if [ "x${TOOLS_JAR}" = "x" ]; then
|
|
||||||
warn "Could not locate tools.jar or classes.jar. Please set manually to avail all command features."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -162,7 +153,7 @@ init() {
|
||||||
unlimitFD
|
unlimitFD
|
||||||
|
|
||||||
# Locate the Java VM to execute
|
# Locate the Java VM to execute
|
||||||
locateJava "$1"
|
locateJava
|
||||||
}
|
}
|
||||||
|
|
||||||
is_nonzero_integer() {
|
is_nonzero_integer() {
|
||||||
|
@ -197,7 +188,6 @@ run() {
|
||||||
|
|
||||||
NIFI_HOME=$(cygpath --path --windows "${NIFI_HOME}")
|
NIFI_HOME=$(cygpath --path --windows "${NIFI_HOME}")
|
||||||
NIFI_LOG_DIR=$(cygpath --path --windows "${NIFI_LOG_DIR}")
|
NIFI_LOG_DIR=$(cygpath --path --windows "${NIFI_LOG_DIR}")
|
||||||
NIFI_PID_DIR=$(cygpath --path --windows "${NIFI_PID_DIR}")
|
|
||||||
BOOTSTRAP_CONF=$(cygpath --path --windows "${BOOTSTRAP_CONF}")
|
BOOTSTRAP_CONF=$(cygpath --path --windows "${BOOTSTRAP_CONF}")
|
||||||
BOOTSTRAP_CONF_DIR=$(cygpath --path --windows "${BOOTSTRAP_CONF_DIR}")
|
BOOTSTRAP_CONF_DIR=$(cygpath --path --windows "${BOOTSTRAP_CONF_DIR}")
|
||||||
BOOTSTRAP_LIBS=$(cygpath --path --windows "${BOOTSTRAP_LIBS}")
|
BOOTSTRAP_LIBS=$(cygpath --path --windows "${BOOTSTRAP_LIBS}")
|
||||||
|
@ -220,26 +210,21 @@ run() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Java home: ${JAVA_HOME}"
|
echo "JAVA_HOME=${JAVA_HOME}"
|
||||||
echo "NiFi home: ${NIFI_HOME}"
|
echo "NIFI_HOME=${NIFI_HOME}"
|
||||||
echo
|
echo
|
||||||
echo "Bootstrap Config File: ${BOOTSTRAP_CONF}"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# run 'start' in the background because the process will continue to run, monitoring NiFi.
|
|
||||||
# all other commands will terminate quickly so want to just wait for them
|
|
||||||
|
|
||||||
#setup directory parameters
|
#setup directory parameters
|
||||||
BOOTSTRAP_LOG_PARAMS="-Dorg.apache.nifi.bootstrap.config.log.dir='${NIFI_LOG_DIR}'"
|
BOOTSTRAP_LOG_PARAMS="-Dorg.apache.nifi.bootstrap.config.log.dir='${NIFI_LOG_DIR}'"
|
||||||
BOOTSTRAP_PID_PARAMS="-Dorg.apache.nifi.bootstrap.config.pid.dir='${NIFI_PID_DIR}'"
|
|
||||||
BOOTSTRAP_CONF_PARAMS="-Dorg.apache.nifi.bootstrap.config.file='${BOOTSTRAP_CONF}'"
|
BOOTSTRAP_CONF_PARAMS="-Dorg.apache.nifi.bootstrap.config.file='${BOOTSTRAP_CONF}'"
|
||||||
|
|
||||||
# uncomment to allow debugging of the bootstrap process
|
# uncomment to allow debugging of the bootstrap process
|
||||||
#BOOTSTRAP_DEBUG_PARAMS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000"
|
#BOOTSTRAP_DEBUG_PARAMS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000"
|
||||||
|
|
||||||
BOOTSTRAP_DIR_PARAMS="${BOOTSTRAP_LOG_PARAMS} ${BOOTSTRAP_PID_PARAMS} ${BOOTSTRAP_CONF_PARAMS}"
|
BOOTSTRAP_DIR_PARAMS="${BOOTSTRAP_LOG_PARAMS} ${BOOTSTRAP_CONF_PARAMS}"
|
||||||
|
|
||||||
run_bootstrap_cmd="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' -Xms48m -Xmx48m ${BOOTSTRAP_DIR_PARAMS} ${BOOTSTRAP_DEBUG_PARAMS} ${BOOTSTRAP_JAVA_OPTS} org.apache.nifi.bootstrap.RunNiFi"
|
MAXIMUM_HEAP_SIZE="-Xmx48m"
|
||||||
|
run_bootstrap_cmd="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' ${MAXIMUM_HEAP_SIZE} ${BOOTSTRAP_DIR_PARAMS} ${BOOTSTRAP_DEBUG_PARAMS} ${BOOTSTRAP_JAVA_OPTS} org.apache.nifi.bootstrap.BootstrapProcess"
|
||||||
run_nifi_cmd="${run_bootstrap_cmd} $@"
|
run_nifi_cmd="${run_bootstrap_cmd} $@"
|
||||||
|
|
||||||
if [ -n "${run_as_user}" ]; then
|
if [ -n "${run_as_user}" ]; then
|
||||||
|
@ -252,11 +237,6 @@ run() {
|
||||||
run_nifi_cmd="${SUDO} -u ${run_as_user} sh -c \"SCRIPT_DIR='${SCRIPT_DIR}' && . '${SCRIPT_DIR}/nifi-env.sh' && ${run_nifi_cmd}\""
|
run_nifi_cmd="${SUDO} -u ${run_as_user} sh -c \"SCRIPT_DIR='${SCRIPT_DIR}' && . '${SCRIPT_DIR}/nifi-env.sh' && ${run_nifi_cmd}\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" = "run" ]; then
|
|
||||||
# Use exec to handover PID to RunNiFi java process, instead of foking it as a child process
|
|
||||||
run_nifi_cmd="exec ${run_nifi_cmd}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$1" = "set-sensitive-properties-algorithm" ]; then
|
if [ "$1" = "set-sensitive-properties-algorithm" ]; then
|
||||||
run_command="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' '-Dnifi.properties.file.path=${NIFI_HOME}/conf/nifi.properties' 'org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm'"
|
run_command="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' '-Dnifi.properties.file.path=${NIFI_HOME}/conf/nifi.properties' 'org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm'"
|
||||||
eval "cd ${NIFI_HOME}"
|
eval "cd ${NIFI_HOME}"
|
||||||
|
@ -287,8 +267,20 @@ run() {
|
||||||
return;
|
return;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" = "start" ]; then
|
eval "cd ${NIFI_HOME}"
|
||||||
( eval "cd ${NIFI_HOME} && ${run_nifi_cmd}" & )> /dev/null 1>&-
|
|
||||||
|
if [ "$1" = "run" ]; then
|
||||||
|
RUN_COMMAND=$(eval "${run_bootstrap_cmd} get-run-command")
|
||||||
|
RUN_COMMAND_STATUS=$?
|
||||||
|
if [ $RUN_COMMAND_STATUS = 0 ]; then
|
||||||
|
exec $RUN_COMMAND
|
||||||
|
else
|
||||||
|
echo "Failed to get run command"
|
||||||
|
echo "${RUN_COMMAND}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ "$1" = "start" ]; then
|
||||||
|
eval "${run_nifi_cmd}" > /dev/null 1>&- &
|
||||||
|
|
||||||
if [ "$2" = "--wait-for-init" ]; then
|
if [ "$2" = "--wait-for-init" ]; then
|
||||||
|
|
||||||
|
@ -304,63 +296,57 @@ run() {
|
||||||
time_since_feedback=0
|
time_since_feedback=0
|
||||||
not_running_counter=0
|
not_running_counter=0
|
||||||
|
|
||||||
is_nifi_loaded="false" # 3 possible values: "true", "false", "not_running". "not_running" means NiFi has not been started.
|
PROCESS_STATUS=1
|
||||||
while [ "$is_nifi_loaded" != "true" ]; do
|
while [ $PROCESS_STATUS != 0 ]; do
|
||||||
time_at_previous_loop=$current_time
|
time_at_previous_loop=$current_time
|
||||||
|
|
||||||
current_time=$(date +%s)
|
current_time=$(date +%s)
|
||||||
if [ "$current_time" -ge "$endtime" ]; then
|
if [ "$current_time" -ge "$endtime" ]; then
|
||||||
echo "Exited the script due to --wait-for-init timeout"
|
echo "Initialization failed after $wait_timeout seconds"
|
||||||
break;
|
break;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
time_since_feedback=$(($time_since_feedback+($current_time-$time_at_previous_loop)))
|
time_since_feedback=$(($time_since_feedback+($current_time-$time_at_previous_loop)))
|
||||||
if [ "$time_since_feedback" -ge "$WAIT_FOR_INIT_FEEDBACK_INTERVAL" ]; then
|
if [ "$time_since_feedback" -ge "$WAIT_FOR_INIT_FEEDBACK_INTERVAL" ]; then
|
||||||
time_since_feedback=0
|
time_since_feedback=0
|
||||||
echo "NiFi has not fully initialized yet..."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
is_nifi_loaded=$( eval "cd ${NIFI_HOME} && ${run_bootstrap_cmd} is_loaded" )
|
eval "cd ${NIFI_HOME} && ${run_bootstrap_cmd} status"
|
||||||
|
PROCESS_STATUS=$?
|
||||||
|
|
||||||
if [ "$is_nifi_loaded" = "not_running" ]; then
|
if [ $PROCESS_STATUS = 3 ]; then
|
||||||
not_running_counter=$(($not_running_counter+1))
|
not_running_counter=$(($not_running_counter+1))
|
||||||
if [ "$not_running_counter" -ge 3 ]; then
|
if [ "$not_running_counter" -ge 3 ]; then
|
||||||
echo "NiFi is not running. Stopped waiting for it to initialize."
|
echo "Initialization failed"
|
||||||
break;
|
break;
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep $WAIT_FOR_INIT_SLEEP_TIME
|
sleep $WAIT_FOR_INIT_SLEEP_TIME
|
||||||
done
|
done
|
||||||
if [ "$is_nifi_loaded" = "true" ]; then
|
if [ $PROCESS_STATUS = 0 ]; then
|
||||||
echo "NiFi initialized."
|
echo "Initialization completed"
|
||||||
echo "Exiting startup script..."
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Wait for logging initialization before returning to shell after starting
|
||||||
|
sleep 1
|
||||||
else
|
else
|
||||||
eval "cd ${NIFI_HOME} && ${run_nifi_cmd}"
|
eval "${run_nifi_cmd}"
|
||||||
fi
|
fi
|
||||||
EXIT_STATUS=$?
|
EXIT_STATUS=$?
|
||||||
|
|
||||||
# Wait just a bit (3 secs) to wait for the logging to finish and then echo a new-line.
|
|
||||||
# We do this to avoid having logs spewed on the console after running the command and then not giving
|
|
||||||
# control back to the user
|
|
||||||
sleep 1
|
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
init "$1"
|
init
|
||||||
run "$@"
|
run "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
install)
|
start|stop|decommission|run|status|cluster-status|diagnostics|status-history|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials)
|
||||||
install "$@"
|
|
||||||
;;
|
|
||||||
|
|
||||||
start|stop|decommission|run|status|is_loaded|dump|diagnostics|status-history|env|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials|cluster-status)
|
|
||||||
main "$@"
|
main "$@"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
@ -370,6 +356,6 @@ case "$1" in
|
||||||
run "start"
|
run "start"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage nifi {start|stop|decommission|run|restart|status|dump|diagnostics|status-history|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials|cluster-status}"
|
echo "Usage nifi.sh {start|stop|decommission|run|restart|status|cluster-status|diagnostics|status-history|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
# Java command to use when running NiFi
|
|
||||||
java=java
|
|
||||||
|
|
||||||
# Username to use when running NiFi. This value will be ignored on Windows.
|
# Username to use when running NiFi. This value will be ignored on Windows.
|
||||||
run.as=${nifi.run.as}
|
run.as=${nifi.run.as}
|
||||||
|
|
||||||
|
@ -61,5 +58,3 @@ java.arg.securityAuthUseSubjectCredsOnly=-Djavax.security.auth.useSubjectCredsOn
|
||||||
# org.apache.jasper.servlet.JasperLoader,org.jvnet.hk2.internal.DelegatingClassLoader,org.apache.nifi.nar.NarClassLoader
|
# org.apache.jasper.servlet.JasperLoader,org.jvnet.hk2.internal.DelegatingClassLoader,org.apache.nifi.nar.NarClassLoader
|
||||||
# End of Java Agent config for native library loading.
|
# End of Java Agent config for native library loading.
|
||||||
|
|
||||||
# Port used to listen for communications from NiFi. If this property is missing, empty, or 0, a random ephemeral port is used.
|
|
||||||
nifi.bootstrap.listen.port=0
|
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<configuration scan="true" scanPeriod="30 seconds">
|
<configuration scan="true" scanPeriod="30 seconds">
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||||
<resetJUL>true</resetJUL>
|
<resetJUL>true</resetJUL>
|
||||||
</contextListener>
|
</contextListener>
|
||||||
|
|
|
@ -1,406 +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;
|
|
||||||
|
|
||||||
import org.apache.nifi.cluster.ClusterDetailsFactory;
|
|
||||||
import org.apache.nifi.cluster.ConnectionState;
|
|
||||||
import org.apache.nifi.controller.DecommissionTask;
|
|
||||||
import org.apache.nifi.controller.status.history.StatusHistoryDump;
|
|
||||||
import org.apache.nifi.diagnostics.DiagnosticsDump;
|
|
||||||
import org.apache.nifi.util.LimitingInputStream;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class BootstrapListener {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BootstrapListener.class);
|
|
||||||
|
|
||||||
private final NiFiEntryPoint nifi;
|
|
||||||
private final int bootstrapPort;
|
|
||||||
private final String secretKey;
|
|
||||||
|
|
||||||
private volatile Listener listener;
|
|
||||||
private volatile ServerSocket serverSocket;
|
|
||||||
private volatile boolean nifiLoaded = false;
|
|
||||||
|
|
||||||
public BootstrapListener(final NiFiEntryPoint nifi, final int bootstrapPort) {
|
|
||||||
this.nifi = nifi;
|
|
||||||
this.bootstrapPort = bootstrapPort;
|
|
||||||
secretKey = UUID.randomUUID().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(final int listenPort) throws IOException {
|
|
||||||
logger.debug("Starting Bootstrap Listener to communicate with Bootstrap Port {}", bootstrapPort);
|
|
||||||
|
|
||||||
serverSocket = new ServerSocket();
|
|
||||||
serverSocket.bind(new InetSocketAddress("localhost", listenPort));
|
|
||||||
serverSocket.setSoTimeout(2000);
|
|
||||||
|
|
||||||
final int localPort = serverSocket.getLocalPort();
|
|
||||||
logger.info("Started Bootstrap Listener, Listening for incoming requests on port {}", localPort);
|
|
||||||
|
|
||||||
listener = new Listener(serverSocket);
|
|
||||||
final Thread listenThread = new Thread(listener);
|
|
||||||
listenThread.setDaemon(true);
|
|
||||||
listenThread.setName("Listen to Bootstrap");
|
|
||||||
listenThread.start();
|
|
||||||
|
|
||||||
logger.debug("Notifying Bootstrap that local port is {}", localPort);
|
|
||||||
sendCommand("PORT", new String[]{String.valueOf(localPort), secretKey});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reload() throws IOException {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.stop();
|
|
||||||
}
|
|
||||||
sendCommand("RELOAD", new String[]{});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNiFiLoaded(boolean nifiLoaded) {
|
|
||||||
this.nifiLoaded = nifiLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendStartedStatus(boolean status) throws IOException {
|
|
||||||
logger.debug("Notifying Bootstrap that the status of starting NiFi is {}", status);
|
|
||||||
sendCommand("STARTED", new String[]{String.valueOf(status)});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendCommand(final String command, final String[] args) throws IOException {
|
|
||||||
try (final Socket socket = new Socket()) {
|
|
||||||
socket.setSoTimeout(60000);
|
|
||||||
socket.connect(new InetSocketAddress("localhost", bootstrapPort));
|
|
||||||
socket.setSoTimeout(60000);
|
|
||||||
|
|
||||||
final StringBuilder commandBuilder = new StringBuilder(command);
|
|
||||||
for (final String arg : args) {
|
|
||||||
commandBuilder.append(" ").append(arg);
|
|
||||||
}
|
|
||||||
commandBuilder.append("\n");
|
|
||||||
|
|
||||||
final String commandWithArgs = commandBuilder.toString();
|
|
||||||
logger.debug("Sending command to Bootstrap: {}", commandWithArgs);
|
|
||||||
|
|
||||||
final OutputStream out = socket.getOutputStream();
|
|
||||||
out.write((commandWithArgs).getBytes(StandardCharsets.UTF_8));
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
logger.debug("Awaiting response from Bootstrap...");
|
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
|
||||||
final String response = reader.readLine();
|
|
||||||
if ("OK".equals(response)) {
|
|
||||||
logger.info("Successfully initiated communication with Bootstrap");
|
|
||||||
} else {
|
|
||||||
logger.error("Failed to communicate with Bootstrap. Bootstrap may be unable to issue or receive commands from NiFi");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Listener implements Runnable {
|
|
||||||
|
|
||||||
private final ServerSocket serverSocket;
|
|
||||||
private final ExecutorService executor;
|
|
||||||
private volatile boolean stopped = false;
|
|
||||||
|
|
||||||
public Listener(final ServerSocket serverSocket) {
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
this.executor = Executors.newFixedThreadPool(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
stopped = true;
|
|
||||||
|
|
||||||
executor.shutdownNow();
|
|
||||||
|
|
||||||
try {
|
|
||||||
serverSocket.close();
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
// nothing to really do here. we could log this, but it would just become
|
|
||||||
// confusing in the logs, as we're shutting down and there's no real benefit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (!stopped) {
|
|
||||||
try {
|
|
||||||
final Socket socket;
|
|
||||||
try {
|
|
||||||
logger.debug("Listening for Bootstrap Requests");
|
|
||||||
socket = serverSocket.accept();
|
|
||||||
} catch (final SocketTimeoutException ste) {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ioe;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Received connection from Bootstrap");
|
|
||||||
socket.setSoTimeout(5000);
|
|
||||||
|
|
||||||
executor.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
final BootstrapRequest request = readRequest(socket.getInputStream());
|
|
||||||
final BootstrapRequest.RequestType requestType = request.getRequestType();
|
|
||||||
|
|
||||||
switch (requestType) {
|
|
||||||
case PING:
|
|
||||||
logger.debug("Received PING request from Bootstrap; responding");
|
|
||||||
sendAnswer(socket.getOutputStream(), "PING");
|
|
||||||
logger.debug("Responded to PING request from Bootstrap");
|
|
||||||
break;
|
|
||||||
case RELOAD:
|
|
||||||
logger.info("Received RELOAD request from Bootstrap");
|
|
||||||
sendAnswer(socket.getOutputStream(), "RELOAD");
|
|
||||||
nifi.shutdownHook(true);
|
|
||||||
return;
|
|
||||||
case SHUTDOWN:
|
|
||||||
logger.info("Received SHUTDOWN request from Bootstrap");
|
|
||||||
sendAnswer(socket.getOutputStream(), "SHUTDOWN");
|
|
||||||
socket.close();
|
|
||||||
nifi.shutdownHook(false);
|
|
||||||
return;
|
|
||||||
case DUMP:
|
|
||||||
logger.info("Received DUMP request from Bootstrap");
|
|
||||||
writeDump(socket.getOutputStream());
|
|
||||||
break;
|
|
||||||
case CLUSTER_STATUS:
|
|
||||||
logger.info("Received CLUSTER_STATUS request from Bootstrap");
|
|
||||||
final String clusterStatus = getClusterStatus();
|
|
||||||
logger.debug("Responding to CLUSTER_STATUS request from Bootstrap with {}", clusterStatus);
|
|
||||||
sendAnswer(socket.getOutputStream(), clusterStatus);
|
|
||||||
break;
|
|
||||||
case DECOMMISSION:
|
|
||||||
logger.info("Received DECOMMISSION request from Bootstrap");
|
|
||||||
|
|
||||||
boolean shutdown = true;
|
|
||||||
final String[] decommissionArgs = request.getArgs();
|
|
||||||
if (decommissionArgs != null) {
|
|
||||||
for (final String arg : decommissionArgs) {
|
|
||||||
if ("--shutdown=false".equalsIgnoreCase(arg)) {
|
|
||||||
shutdown = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Command indicates that after decommission, shutdown={}", shutdown);
|
|
||||||
|
|
||||||
try {
|
|
||||||
decommission();
|
|
||||||
sendAnswer(socket.getOutputStream(), "DECOMMISSION");
|
|
||||||
|
|
||||||
if (shutdown) {
|
|
||||||
nifi.shutdownHook(false);
|
|
||||||
}
|
|
||||||
} catch (final Exception e) {
|
|
||||||
final OutputStream out = socket.getOutputStream();
|
|
||||||
|
|
||||||
out.write(("Failed to decommission node: " + e + "; see app-log for additional details").getBytes(StandardCharsets.UTF_8));
|
|
||||||
out.flush();
|
|
||||||
} finally {
|
|
||||||
if (shutdown) {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case DIAGNOSTICS:
|
|
||||||
logger.info("Received DIAGNOSTICS request from Bootstrap");
|
|
||||||
final String[] args = request.getArgs();
|
|
||||||
boolean verbose = false;
|
|
||||||
if (args == null) {
|
|
||||||
verbose = false;
|
|
||||||
} else {
|
|
||||||
for (final String arg : args) {
|
|
||||||
if ("--verbose=true".equalsIgnoreCase(arg)) {
|
|
||||||
verbose = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeDiagnostics(socket.getOutputStream(), verbose);
|
|
||||||
break;
|
|
||||||
case STATUS_HISTORY:
|
|
||||||
logger.info("Received STATUS_HISTORY request from Bootstrap");
|
|
||||||
final String[] statusHistoryArgs = request.getArgs();
|
|
||||||
final int days = Integer.parseInt(statusHistoryArgs[0]);
|
|
||||||
writeNodeStatusHistory(socket.getOutputStream(), days);
|
|
||||||
break;
|
|
||||||
case IS_LOADED:
|
|
||||||
logger.debug("Received IS_LOADED request from Bootstrap");
|
|
||||||
String answer = String.valueOf(nifiLoaded);
|
|
||||||
sendAnswer(socket.getOutputStream(), answer);
|
|
||||||
logger.debug("Responded to IS_LOADED request from Bootstrap with value: {}", answer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (final Throwable t) {
|
|
||||||
logger.error("Failed to process request from Bootstrap", t);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (final IOException ioe) {
|
|
||||||
logger.warn("Failed to close socket to Bootstrap", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (final Throwable t) {
|
|
||||||
logger.error("Failed to process request from Bootstrap", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeDump(final OutputStream out) throws IOException {
|
|
||||||
final DiagnosticsDump diagnosticsDump = nifi.getServer().getThreadDumpFactory().create(true);
|
|
||||||
diagnosticsDump.writeTo(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getClusterStatus() {
|
|
||||||
final ClusterDetailsFactory clusterDetailsFactory = nifi.getServer().getClusterDetailsFactory();
|
|
||||||
if (clusterDetailsFactory == null) {
|
|
||||||
return ConnectionState.UNKNOWN.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
final ConnectionState connectionState = clusterDetailsFactory.getConnectionState();
|
|
||||||
return connectionState == null ? ConnectionState.UNKNOWN.name() : connectionState.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decommission() throws InterruptedException {
|
|
||||||
final DecommissionTask decommissionTask = nifi.getServer().getDecommissionTask();
|
|
||||||
if (decommissionTask == null) {
|
|
||||||
throw new IllegalArgumentException("This NiFi instance does not support decommissioning");
|
|
||||||
}
|
|
||||||
|
|
||||||
decommissionTask.decommission();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeDiagnostics(final OutputStream out, final boolean verbose) throws IOException {
|
|
||||||
final DiagnosticsDump diagnosticsDump = nifi.getServer().getDiagnosticsFactory().create(verbose);
|
|
||||||
diagnosticsDump.writeTo(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeNodeStatusHistory(final OutputStream out, final int days) throws IOException {
|
|
||||||
final StatusHistoryDump statusHistoryDump = nifi.getServer().getStatusHistoryDumpFactory().create(days);
|
|
||||||
statusHistoryDump.writeTo(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendAnswer(final OutputStream out, final String answer) throws IOException {
|
|
||||||
out.write((answer + "\n").getBytes(StandardCharsets.UTF_8));
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource") // we don't want to close the stream, as the caller will do that
|
|
||||||
private BootstrapRequest readRequest(final InputStream in) throws IOException {
|
|
||||||
// We want to ensure that we don't try to read data from an InputStream directly
|
|
||||||
// by a BufferedReader because any user on the system could open a socket and send
|
|
||||||
// a multi-gigabyte file without any new lines in order to crash the NiFi instance
|
|
||||||
// (or at least cause OutOfMemoryErrors, which can wreak havoc on the running instance).
|
|
||||||
// So we will limit the Input Stream to only 4 KB, which should be plenty for any request.
|
|
||||||
final LimitingInputStream limitingIn = new LimitingInputStream(in, 4096);
|
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(limitingIn));
|
|
||||||
|
|
||||||
final String line = reader.readLine();
|
|
||||||
final String[] splits = line.split(" ");
|
|
||||||
if (splits.length < 1) {
|
|
||||||
throw new IOException("Received invalid request from Bootstrap: " + line);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String requestType = splits[0];
|
|
||||||
final String[] args;
|
|
||||||
if (splits.length == 1) {
|
|
||||||
throw new IOException("Received invalid request from Bootstrap; request did not have a secret key; request type = " + requestType);
|
|
||||||
} else if (splits.length == 2) {
|
|
||||||
args = new String[0];
|
|
||||||
} else {
|
|
||||||
args = Arrays.copyOfRange(splits, 2, splits.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String requestKey = splits[1];
|
|
||||||
if (!secretKey.equals(requestKey)) {
|
|
||||||
throw new IOException("Received invalid Secret Key for request type " + requestType);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new BootstrapRequest(requestType, args);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new IOException("Received invalid request from Bootstrap; request type = " + requestType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BootstrapRequest {
|
|
||||||
public enum RequestType {
|
|
||||||
RELOAD,
|
|
||||||
SHUTDOWN,
|
|
||||||
DUMP,
|
|
||||||
DIAGNOSTICS,
|
|
||||||
DECOMMISSION,
|
|
||||||
PING,
|
|
||||||
IS_LOADED,
|
|
||||||
STATUS_HISTORY,
|
|
||||||
CLUSTER_STATUS
|
|
||||||
}
|
|
||||||
|
|
||||||
private final RequestType requestType;
|
|
||||||
private final String[] args;
|
|
||||||
|
|
||||||
public BootstrapRequest(final String request, final String[] args) {
|
|
||||||
this.requestType = RequestType.valueOf(request);
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestType getRequestType() {
|
|
||||||
return requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public String[] getArgs() {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,6 +25,8 @@ import org.apache.nifi.nar.NarUnpackMode;
|
||||||
import org.apache.nifi.nar.NarUnpacker;
|
import org.apache.nifi.nar.NarUnpacker;
|
||||||
import org.apache.nifi.nar.SystemBundle;
|
import org.apache.nifi.nar.SystemBundle;
|
||||||
import org.apache.nifi.processor.DataUnit;
|
import org.apache.nifi.processor.DataUnit;
|
||||||
|
import org.apache.nifi.runtime.ManagementServer;
|
||||||
|
import org.apache.nifi.runtime.StandardManagementServer;
|
||||||
import org.apache.nifi.util.DiagnosticUtils;
|
import org.apache.nifi.util.DiagnosticUtils;
|
||||||
import org.apache.nifi.util.FileUtils;
|
import org.apache.nifi.util.FileUtils;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
|
@ -38,6 +40,7 @@ import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
@ -50,19 +53,32 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class NiFi implements NiFiEntryPoint {
|
public class NiFi {
|
||||||
|
|
||||||
public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.bootstrap.listen.port";
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
||||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
|
||||||
|
private static final String MANAGEMENT_SERVER_ADDRESS = "org.apache.nifi.management.server.address";
|
||||||
|
|
||||||
|
private static final Pattern MANAGEMENT_SERVER_ADDRESS_PATTERN = Pattern.compile("^(.+?):([1-9][0-9]{3,4})$");
|
||||||
|
|
||||||
|
private static final String MANAGEMENT_SERVER_DEFAULT_ADDRESS = "127.0.0.1:52020";
|
||||||
|
|
||||||
|
private static final int ADDRESS_GROUP = 1;
|
||||||
|
|
||||||
|
private static final int PORT_GROUP = 2;
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class);
|
||||||
|
|
||||||
private final NiFiServer nifiServer;
|
private final NiFiServer nifiServer;
|
||||||
private final BootstrapListener bootstrapListener;
|
|
||||||
private final NiFiProperties properties;
|
private final NiFiProperties properties;
|
||||||
|
|
||||||
|
private final ManagementServer managementServer;
|
||||||
|
|
||||||
private volatile boolean shutdown = false;
|
private volatile boolean shutdown = false;
|
||||||
|
|
||||||
public NiFi(final NiFiProperties properties)
|
public NiFi(final NiFiProperties properties)
|
||||||
|
@ -89,25 +105,6 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
// register the shutdown hook
|
// register the shutdown hook
|
||||||
addShutdownHook();
|
addShutdownHook();
|
||||||
|
|
||||||
final String bootstrapPort = System.getProperty(BOOTSTRAP_PORT_PROPERTY);
|
|
||||||
if (bootstrapPort != null) {
|
|
||||||
try {
|
|
||||||
final int port = Integer.parseInt(bootstrapPort);
|
|
||||||
|
|
||||||
if (port < 1 || port > 65535) {
|
|
||||||
throw new RuntimeException("Failed to start NiFi because system property '" + BOOTSTRAP_PORT_PROPERTY + "' is not a valid integer in the range 1 - 65535");
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrapListener = new BootstrapListener(this, port);
|
|
||||||
bootstrapListener.start(properties.getDefaultListenerBootstrapPort());
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
throw new RuntimeException("Failed to start NiFi because system property '" + BOOTSTRAP_PORT_PROPERTY + "' is not a valid integer in the range 1 - 65535");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGGER.info("NiFi started without Bootstrap Port information provided; will not listen for requests from Bootstrap");
|
|
||||||
bootstrapListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the web working dir - if the application does not start successfully
|
// delete the web working dir - if the application does not start successfully
|
||||||
// the web app directories might be in an invalid state. when this happens
|
// the web app directories might be in an invalid state. when this happens
|
||||||
// jetty will not attempt to re-extract the war into the directory. by removing
|
// jetty will not attempt to re-extract the war into the directory. by removing
|
||||||
|
@ -151,15 +148,12 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
narBundles,
|
narBundles,
|
||||||
extensionMapping);
|
extensionMapping);
|
||||||
|
|
||||||
|
managementServer = getManagementServer();
|
||||||
if (shutdown) {
|
if (shutdown) {
|
||||||
LOGGER.info("NiFi has been shutdown via NiFi Bootstrap. Will not start Controller");
|
LOGGER.info("NiFi has been shutdown via NiFi Bootstrap. Will not start Controller");
|
||||||
} else {
|
} else {
|
||||||
nifiServer.start();
|
nifiServer.start();
|
||||||
|
managementServer.start();
|
||||||
if (bootstrapListener != null) {
|
|
||||||
bootstrapListener.setNiFiLoaded(true);
|
|
||||||
bootstrapListener.sendStartedStatus(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
final long duration = System.nanoTime() - startTime;
|
final long duration = System.nanoTime() - startTime;
|
||||||
final double durationSeconds = TimeUnit.NANOSECONDS.toMillis(duration) / 1000.0;
|
final double durationSeconds = TimeUnit.NANOSECONDS.toMillis(duration) / 1000.0;
|
||||||
|
@ -172,14 +166,16 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setDefaultUncaughtExceptionHandler() {
|
protected void setDefaultUncaughtExceptionHandler() {
|
||||||
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> LOGGER.error("An Unknown Error Occurred in Thread {}", thread, exception));
|
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addShutdownHook() {
|
protected void addShutdownHook() {
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() ->
|
final Thread shutdownHook = Thread.ofPlatform()
|
||||||
// shutdown the jetty server
|
.name(NiFi.class.getSimpleName())
|
||||||
shutdownHook(false)
|
.uncaughtExceptionHandler(new ExceptionHandler())
|
||||||
));
|
.unstarted(this::stop);
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initLogging() {
|
protected void initLogging() {
|
||||||
|
@ -205,12 +201,15 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
|
return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdownHook(final boolean isReload) {
|
/**
|
||||||
|
* Stop Application and shutdown server
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
try {
|
try {
|
||||||
runDiagnosticsOnShutdown();
|
runDiagnosticsOnShutdown();
|
||||||
shutdown();
|
shutdown();
|
||||||
} catch (final Throwable t) {
|
} catch (final Throwable t) {
|
||||||
LOGGER.warn("Problem occurred ensuring Jetty web server was properly terminated", t);
|
LOGGER.warn("Application Controller shutdown failed", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,18 +237,39 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
this.shutdown = true;
|
this.shutdown = true;
|
||||||
|
|
||||||
LOGGER.info("Application Server shutdown started");
|
LOGGER.info("Application Controller shutdown started");
|
||||||
if (nifiServer != null) {
|
|
||||||
|
managementServer.stop();
|
||||||
|
|
||||||
|
if (nifiServer == null) {
|
||||||
|
LOGGER.info("Application Server not running");
|
||||||
|
} else {
|
||||||
nifiServer.stop();
|
nifiServer.stop();
|
||||||
}
|
}
|
||||||
if (bootstrapListener != null) {
|
|
||||||
bootstrapListener.stop();
|
LOGGER.info("Application Controller shutdown completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ManagementServer getManagementServer() {
|
||||||
|
final String managementServerAddressProperty = System.getProperty(MANAGEMENT_SERVER_ADDRESS, MANAGEMENT_SERVER_DEFAULT_ADDRESS);
|
||||||
|
if (managementServerAddressProperty.isBlank()) {
|
||||||
|
throw new IllegalStateException("Management Server Address System Property [%s] not configured".formatted(MANAGEMENT_SERVER_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Matcher matcher = MANAGEMENT_SERVER_ADDRESS_PATTERN.matcher(managementServerAddressProperty);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final String addressGroup = matcher.group(ADDRESS_GROUP);
|
||||||
|
final String portGroup = matcher.group(PORT_GROUP);
|
||||||
|
final int port = Integer.parseInt(portGroup);
|
||||||
|
|
||||||
|
final InetSocketAddress bindAddress = new InetSocketAddress(addressGroup, port);
|
||||||
|
return new StandardManagementServer(bindAddress, nifiServer);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Management Server Address System Property [%s] not valid [%s]".formatted(MANAGEMENT_SERVER_ADDRESS, managementServerAddressProperty));
|
||||||
}
|
}
|
||||||
LOGGER.info("Application Server shutdown completed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,4 +321,12 @@ public class NiFi implements NiFiEntryPoint {
|
||||||
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(final Thread thread, Throwable exception) {
|
||||||
|
LOGGER.error("An Unknown Error Occurred in Thread {}", thread, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import org.apache.nifi.NiFiServer;
|
||||||
|
import org.apache.nifi.cluster.ClusterDetailsFactory;
|
||||||
|
import org.apache.nifi.cluster.ConnectionState;
|
||||||
|
import org.apache.nifi.controller.DecommissionTask;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Handler for Cluster Health status operations
|
||||||
|
*/
|
||||||
|
class HealthClusterHttpHandler implements HttpHandler {
|
||||||
|
private static final String CONTENT_TYPE_HEADER = "Content-Type";
|
||||||
|
|
||||||
|
private static final String TEXT_PLAIN = "text/plain";
|
||||||
|
|
||||||
|
private static final int NO_RESPONSE_BODY = -1;
|
||||||
|
|
||||||
|
private static final String GET_METHOD = "GET";
|
||||||
|
|
||||||
|
private static final String DELETE_METHOD = "DELETE";
|
||||||
|
|
||||||
|
private static final String STATUS = "Cluster Status: %s\n";
|
||||||
|
|
||||||
|
private final NiFiServer server;
|
||||||
|
|
||||||
|
HealthClusterHttpHandler(final NiFiServer server) {
|
||||||
|
this.server = Objects.requireNonNull(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(final HttpExchange exchange) throws IOException {
|
||||||
|
final String requestMethod = exchange.getRequestMethod();
|
||||||
|
|
||||||
|
final OutputStream responseBody = exchange.getResponseBody();
|
||||||
|
|
||||||
|
if (GET_METHOD.contentEquals(requestMethod)) {
|
||||||
|
exchange.getResponseHeaders().set(CONTENT_TYPE_HEADER, TEXT_PLAIN);
|
||||||
|
final ConnectionState connectionState = getConnectionState();
|
||||||
|
final String status = STATUS.formatted(connectionState);
|
||||||
|
final byte[] response = status.getBytes(StandardCharsets.UTF_8);
|
||||||
|
exchange.sendResponseHeaders(HTTP_OK, response.length);
|
||||||
|
responseBody.write(response);
|
||||||
|
} else if (DELETE_METHOD.contentEquals(requestMethod)) {
|
||||||
|
startDecommission();
|
||||||
|
|
||||||
|
exchange.getResponseHeaders().set(CONTENT_TYPE_HEADER, TEXT_PLAIN);
|
||||||
|
final String status = STATUS.formatted(ConnectionState.OFFLOADING);
|
||||||
|
final byte[] response = status.getBytes(StandardCharsets.UTF_8);
|
||||||
|
exchange.sendResponseHeaders(HTTP_ACCEPTED, response.length);
|
||||||
|
responseBody.write(response);
|
||||||
|
} else {
|
||||||
|
exchange.sendResponseHeaders(HTTP_BAD_METHOD, NO_RESPONSE_BODY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startDecommission() {
|
||||||
|
final DecommissionTask decommissionTask = server.getDecommissionTask();
|
||||||
|
Thread.ofVirtual().name(DecommissionTask.class.getSimpleName()).start(() -> {
|
||||||
|
try {
|
||||||
|
decommissionTask.decommission();
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionState getConnectionState() {
|
||||||
|
final ConnectionState connectionState;
|
||||||
|
|
||||||
|
final ClusterDetailsFactory clusterDetailsFactory = server.getClusterDetailsFactory();
|
||||||
|
if (clusterDetailsFactory == null) {
|
||||||
|
connectionState = ConnectionState.UNKNOWN;
|
||||||
|
} else {
|
||||||
|
connectionState = clusterDetailsFactory.getConnectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionState;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import org.apache.nifi.NiFiServer;
|
||||||
|
import org.apache.nifi.diagnostics.DiagnosticsDump;
|
||||||
|
import org.apache.nifi.diagnostics.DiagnosticsFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Handler for Health Diagnostics operations
|
||||||
|
*/
|
||||||
|
class HealthDiagnosticsHttpHandler implements HttpHandler {
|
||||||
|
private static final String CONTENT_TYPE_HEADER = "Content-Type";
|
||||||
|
|
||||||
|
private static final String TEXT_PLAIN = "text/plain";
|
||||||
|
|
||||||
|
private static final int STREAM_RESPONSE_BODY = 0;
|
||||||
|
|
||||||
|
private static final int NO_RESPONSE_BODY = -1;
|
||||||
|
|
||||||
|
private static final String GET_METHOD = "GET";
|
||||||
|
|
||||||
|
private static final String VERBOSE_QUERY_ENABLED = "verbose=true";
|
||||||
|
|
||||||
|
private final NiFiServer server;
|
||||||
|
|
||||||
|
HealthDiagnosticsHttpHandler(final NiFiServer server) {
|
||||||
|
this.server = Objects.requireNonNull(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(final HttpExchange exchange) throws IOException {
|
||||||
|
final String requestMethod = exchange.getRequestMethod();
|
||||||
|
|
||||||
|
if (GET_METHOD.contentEquals(requestMethod)) {
|
||||||
|
exchange.getResponseHeaders().set(CONTENT_TYPE_HEADER, TEXT_PLAIN);
|
||||||
|
exchange.sendResponseHeaders(HTTP_OK, STREAM_RESPONSE_BODY);
|
||||||
|
|
||||||
|
final URI requestUri = exchange.getRequestURI();
|
||||||
|
final boolean verboseRequested = getVerboseRequested(requestUri);
|
||||||
|
|
||||||
|
final DiagnosticsFactory diagnosticsFactory = server.getDiagnosticsFactory();
|
||||||
|
final DiagnosticsDump diagnosticsDump = diagnosticsFactory.create(verboseRequested);
|
||||||
|
try (OutputStream responseBody = exchange.getResponseBody()) {
|
||||||
|
diagnosticsDump.writeTo(responseBody);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exchange.sendResponseHeaders(HTTP_BAD_METHOD, NO_RESPONSE_BODY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getVerboseRequested(final URI requestUri) {
|
||||||
|
final boolean verboseRequested;
|
||||||
|
|
||||||
|
final String query = requestUri.getQuery();
|
||||||
|
if (query == null) {
|
||||||
|
verboseRequested = false;
|
||||||
|
} else {
|
||||||
|
verboseRequested = query.contains(VERBOSE_QUERY_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return verboseRequested;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Handler for Health status operations
|
||||||
|
*/
|
||||||
|
class HealthHttpHandler implements HttpHandler {
|
||||||
|
private static final String CONTENT_TYPE_HEADER = "Content-Type";
|
||||||
|
|
||||||
|
private static final String TEXT_PLAIN = "text/plain";
|
||||||
|
|
||||||
|
private static final int NO_RESPONSE_BODY = -1;
|
||||||
|
|
||||||
|
private static final String GET_METHOD = "GET";
|
||||||
|
|
||||||
|
private static final String STATUS_UP = "Status: UP\n";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(final HttpExchange exchange) throws IOException {
|
||||||
|
final String requestMethod = exchange.getRequestMethod();
|
||||||
|
|
||||||
|
final OutputStream responseBody = exchange.getResponseBody();
|
||||||
|
|
||||||
|
if (GET_METHOD.contentEquals(requestMethod)) {
|
||||||
|
exchange.getResponseHeaders().set(CONTENT_TYPE_HEADER, TEXT_PLAIN);
|
||||||
|
final byte[] response = STATUS_UP.getBytes(StandardCharsets.UTF_8);
|
||||||
|
exchange.sendResponseHeaders(HTTP_OK, response.length);
|
||||||
|
responseBody.write(response);
|
||||||
|
} else {
|
||||||
|
exchange.sendResponseHeaders(HTTP_BAD_METHOD, NO_RESPONSE_BODY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import org.apache.nifi.NiFiServer;
|
||||||
|
import org.apache.nifi.controller.status.history.StatusHistoryDump;
|
||||||
|
import org.apache.nifi.controller.status.history.StatusHistoryDumpFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Handler for Health Status History operations
|
||||||
|
*/
|
||||||
|
class HealthStatusHistoryHttpHandler implements HttpHandler {
|
||||||
|
private static final String CONTENT_TYPE_HEADER = "Content-Type";
|
||||||
|
|
||||||
|
private static final String APPLICATION_JSON = "application/json";
|
||||||
|
|
||||||
|
private static final int STREAM_RESPONSE_BODY = 0;
|
||||||
|
|
||||||
|
private static final int NO_RESPONSE_BODY = -1;
|
||||||
|
|
||||||
|
private static final String GET_METHOD = "GET";
|
||||||
|
|
||||||
|
private static final Pattern DAYS_QUERY_PATTERN = Pattern.compile("^days=(\\d+)$");
|
||||||
|
|
||||||
|
private static final int DAYS_GROUP = 1;
|
||||||
|
|
||||||
|
private static final int DAYS_DEFAULT = 1;
|
||||||
|
|
||||||
|
private final NiFiServer server;
|
||||||
|
|
||||||
|
HealthStatusHistoryHttpHandler(final NiFiServer server) {
|
||||||
|
this.server = Objects.requireNonNull(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(final HttpExchange exchange) throws IOException {
|
||||||
|
final String requestMethod = exchange.getRequestMethod();
|
||||||
|
|
||||||
|
if (GET_METHOD.contentEquals(requestMethod)) {
|
||||||
|
exchange.getResponseHeaders().set(CONTENT_TYPE_HEADER, APPLICATION_JSON);
|
||||||
|
exchange.sendResponseHeaders(HTTP_OK, STREAM_RESPONSE_BODY);
|
||||||
|
|
||||||
|
final URI requestUri = exchange.getRequestURI();
|
||||||
|
final int daysRequested = getDaysRequested(requestUri);
|
||||||
|
|
||||||
|
final StatusHistoryDumpFactory statusHistoryDumpFactory = server.getStatusHistoryDumpFactory();
|
||||||
|
final StatusHistoryDump statusHistoryDump = statusHistoryDumpFactory.create(daysRequested);
|
||||||
|
|
||||||
|
try (OutputStream responseBody = exchange.getResponseBody()) {
|
||||||
|
statusHistoryDump.writeTo(responseBody);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exchange.sendResponseHeaders(HTTP_BAD_METHOD, NO_RESPONSE_BODY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDaysRequested(final URI requestUri) {
|
||||||
|
final int daysRequested;
|
||||||
|
|
||||||
|
final String query = requestUri.getQuery();
|
||||||
|
if (query == null) {
|
||||||
|
daysRequested = DAYS_DEFAULT;
|
||||||
|
} else {
|
||||||
|
final Matcher matcher = DAYS_QUERY_PATTERN.matcher(query);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final String daysGroup = matcher.group(DAYS_GROUP);
|
||||||
|
daysRequested = Integer.parseInt(daysGroup);
|
||||||
|
} else {
|
||||||
|
daysRequested = DAYS_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysRequested;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for controlling Management Server operation
|
||||||
|
*/
|
||||||
|
public interface ManagementServer {
|
||||||
|
/**
|
||||||
|
* Start Server and bind to configured address
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop Server
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import org.apache.nifi.NiFiServer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard Management Server based on Java HttpServer
|
||||||
|
*/
|
||||||
|
public class StandardManagementServer implements ManagementServer {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(StandardManagementServer.class);
|
||||||
|
|
||||||
|
private static final String HEALTH_PATH = "/health";
|
||||||
|
|
||||||
|
private static final String HEALTH_CLUSTER_PATH = "/health/cluster";
|
||||||
|
|
||||||
|
private static final String HEALTH_DIAGNOSTICS_PATH = "/health/diagnostics";
|
||||||
|
|
||||||
|
private static final String HEALTH_STATUS_HISTORY_PATH = "/health/status-history";
|
||||||
|
|
||||||
|
private static final int STOP_DELAY = 0;
|
||||||
|
|
||||||
|
private static final int CONNECTION_BACKLOG = 10;
|
||||||
|
|
||||||
|
private final InetSocketAddress bindAddress;
|
||||||
|
|
||||||
|
private final NiFiServer server;
|
||||||
|
|
||||||
|
private HttpServer httpServer;
|
||||||
|
|
||||||
|
public StandardManagementServer(final InetSocketAddress bindAddress, final NiFiServer server) {
|
||||||
|
this.bindAddress = Objects.requireNonNull(bindAddress, "Bind Address required");
|
||||||
|
this.server = Objects.requireNonNull(server, "Server required");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (httpServer == null) {
|
||||||
|
try {
|
||||||
|
httpServer = HttpServer.create();
|
||||||
|
|
||||||
|
httpServer.createContext(HEALTH_PATH, new HealthHttpHandler());
|
||||||
|
httpServer.createContext(HEALTH_CLUSTER_PATH, new HealthClusterHttpHandler(server));
|
||||||
|
httpServer.createContext(HEALTH_DIAGNOSTICS_PATH, new HealthDiagnosticsHttpHandler(server));
|
||||||
|
httpServer.createContext(HEALTH_STATUS_HISTORY_PATH, new HealthStatusHistoryHttpHandler(server));
|
||||||
|
|
||||||
|
httpServer.bind(bindAddress, CONNECTION_BACKLOG);
|
||||||
|
httpServer.start();
|
||||||
|
|
||||||
|
final InetSocketAddress serverAddress = getServerAddress();
|
||||||
|
|
||||||
|
logger.info("Started Management Server on http://{}:{}", serverAddress.getHostString(), serverAddress.getPort());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new UncheckedIOException("Management Server start failed", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Management Server running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (httpServer == null) {
|
||||||
|
logger.info("Management Server not running");
|
||||||
|
} else {
|
||||||
|
httpServer.stop(STOP_DELAY);
|
||||||
|
logger.info("Management Server stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InetSocketAddress getServerAddress() {
|
||||||
|
return httpServer == null ? bindAddress : httpServer.getAddress();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import org.apache.nifi.NiFiServer;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
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.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StandardManagementServerTest {
|
||||||
|
private static final String LOCALHOST = "127.0.0.1";
|
||||||
|
|
||||||
|
private static final String HEALTH_URI = "http://%s:%d/health";
|
||||||
|
|
||||||
|
private static final String GET_METHOD = "GET";
|
||||||
|
|
||||||
|
private static final String DELETE_METHOD = "DELETE";
|
||||||
|
|
||||||
|
private static final Duration TIMEOUT = Duration.ofSeconds(15);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NiFiServer server;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStartStop() {
|
||||||
|
final InetSocketAddress initialBindAddress = new InetSocketAddress(LOCALHOST, 0);
|
||||||
|
final StandardManagementServer standardManagementServer = new StandardManagementServer(initialBindAddress, server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
standardManagementServer.start();
|
||||||
|
|
||||||
|
final InetSocketAddress bindAddress = standardManagementServer.getServerAddress();
|
||||||
|
assertNotSame(initialBindAddress.getPort(), bindAddress.getPort());
|
||||||
|
} finally {
|
||||||
|
standardManagementServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHealth() throws Exception {
|
||||||
|
final InetSocketAddress initialBindAddress = new InetSocketAddress(LOCALHOST, 0);
|
||||||
|
final StandardManagementServer standardManagementServer = new StandardManagementServer(initialBindAddress, server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
standardManagementServer.start();
|
||||||
|
|
||||||
|
final InetSocketAddress serverAddress = standardManagementServer.getServerAddress();
|
||||||
|
assertNotSame(initialBindAddress.getPort(), serverAddress.getPort());
|
||||||
|
|
||||||
|
assertResponseStatusCode(serverAddress, GET_METHOD, HttpURLConnection.HTTP_OK);
|
||||||
|
} finally {
|
||||||
|
standardManagementServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteHealth() throws Exception {
|
||||||
|
final InetSocketAddress initialBindAddress = new InetSocketAddress(LOCALHOST, 0);
|
||||||
|
|
||||||
|
final StandardManagementServer standardManagementServer = new StandardManagementServer(initialBindAddress, server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
standardManagementServer.start();
|
||||||
|
|
||||||
|
final InetSocketAddress serverAddress = standardManagementServer.getServerAddress();
|
||||||
|
assertNotSame(initialBindAddress.getPort(), serverAddress.getPort());
|
||||||
|
|
||||||
|
assertResponseStatusCode(serverAddress, DELETE_METHOD, HttpURLConnection.HTTP_BAD_METHOD);
|
||||||
|
} finally {
|
||||||
|
standardManagementServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertResponseStatusCode(final InetSocketAddress serverAddress, final String method, final int responseStatusCode) throws IOException, InterruptedException {
|
||||||
|
final URI healthUri = URI.create(HEALTH_URI.formatted(serverAddress.getHostString(), serverAddress.getPort()));
|
||||||
|
|
||||||
|
try (HttpClient httpClient = HttpClient.newBuilder().connectTimeout(TIMEOUT).build()) {
|
||||||
|
final HttpRequest request = HttpRequest.newBuilder(healthUri)
|
||||||
|
.method(method, HttpRequest.BodyPublishers.noBody())
|
||||||
|
.timeout(TIMEOUT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
final int statusCode = response.statusCode();
|
||||||
|
|
||||||
|
assertEquals(responseStatusCode, statusCode);
|
||||||
|
|
||||||
|
final String responseBody = response.body();
|
||||||
|
assertNotNull(responseBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.tests.system;
|
package org.apache.nifi.tests.system;
|
||||||
|
|
||||||
import org.apache.nifi.bootstrap.RunNiFi;
|
import org.apache.nifi.bootstrap.command.process.ManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.ProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardManagementServerAddressProvider;
|
||||||
|
import org.apache.nifi.bootstrap.command.process.StandardProcessBuilderProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.ConfigurationProvider;
|
||||||
|
import org.apache.nifi.bootstrap.configuration.StandardConfigurationProvider;
|
||||||
import org.apache.nifi.registry.security.util.KeystoreType;
|
import org.apache.nifi.registry.security.util.KeystoreType;
|
||||||
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
|
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
|
||||||
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientConfig;
|
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientConfig;
|
||||||
|
@ -51,7 +56,7 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NiFiInstance createInstance() {
|
public NiFiInstance createInstance() {
|
||||||
return new RunNiFiInstance(instanceConfig);
|
return new ProcessNiFiInstance(instanceConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -78,14 +83,14 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
return Objects.hash(instanceConfig);
|
return Objects.hash(instanceConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RunNiFiInstance implements NiFiInstance {
|
private static class ProcessNiFiInstance implements NiFiInstance {
|
||||||
private final File instanceDirectory;
|
private final File instanceDirectory;
|
||||||
private final File configDir;
|
private final File configDir;
|
||||||
private final InstanceConfiguration instanceConfiguration;
|
private final InstanceConfiguration instanceConfiguration;
|
||||||
private File bootstrapConfigFile;
|
private File bootstrapConfigFile;
|
||||||
private RunNiFi runNiFi;
|
private Process process;
|
||||||
|
|
||||||
public RunNiFiInstance(final InstanceConfiguration instanceConfiguration) {
|
public ProcessNiFiInstance(final InstanceConfiguration instanceConfiguration) {
|
||||||
this.instanceDirectory = instanceConfiguration.getInstanceDirectory();
|
this.instanceDirectory = instanceConfiguration.getInstanceDirectory();
|
||||||
this.bootstrapConfigFile = instanceConfiguration.getBootstrapConfigFile();
|
this.bootstrapConfigFile = instanceConfiguration.getBootstrapConfigFile();
|
||||||
this.instanceConfiguration = instanceConfiguration;
|
this.instanceConfiguration = instanceConfiguration;
|
||||||
|
@ -108,30 +113,33 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RunNiFiInstance[dir=" + instanceDirectory + "]";
|
return "ProcessNiFiInstance[home=%s,process=%s]".formatted(instanceDirectory, process);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(final boolean waitForCompletion) {
|
public void start(final boolean waitForCompletion) {
|
||||||
if (runNiFi != null) {
|
if (process != null) {
|
||||||
throw new IllegalStateException("NiFi has already been started");
|
throw new IllegalStateException("NiFi has already been started");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Starting NiFi [{}]", instanceDirectory.getName());
|
logger.info("Starting NiFi [{}]", instanceDirectory.getName());
|
||||||
|
|
||||||
try {
|
final Map<String, String> environmentVariables = Map.of("NIFI_HOME", instanceDirectory.getAbsolutePath());
|
||||||
this.runNiFi = new RunNiFi(bootstrapConfigFile);
|
final ConfigurationProvider configurationProvider = new StandardConfigurationProvider(environmentVariables, new Properties());
|
||||||
} catch (IOException e) {
|
final ManagementServerAddressProvider managementServerAddressProvider = new StandardManagementServerAddressProvider(configurationProvider);
|
||||||
throw new RuntimeException("Failed to start NiFi", e);
|
final ProcessBuilderProvider processBuilderProvider = new StandardProcessBuilderProvider(configurationProvider, managementServerAddressProvider);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runNiFi.start(false);
|
final ProcessBuilder processBuilder = processBuilderProvider.getApplicationProcessBuilder();
|
||||||
|
processBuilder.directory(instanceDirectory);
|
||||||
|
process = processBuilder.start();
|
||||||
|
|
||||||
|
logger.info("Started NiFi [{}] PID [{}]", instanceDirectory.getName(), process.pid());
|
||||||
|
|
||||||
if (waitForCompletion) {
|
if (waitForCompletion) {
|
||||||
waitForStartup();
|
waitForStartup();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new RuntimeException("Failed to start NiFi", e);
|
throw new RuntimeException("Failed to start NiFi", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +233,7 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAccessible() {
|
public boolean isAccessible() {
|
||||||
if (runNiFi == null) {
|
if (process == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,20 +271,27 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (runNiFi == null) {
|
if (process == null) {
|
||||||
logger.info("NiFi Shutdown Ignored (runNiFi==null) [{}]", instanceDirectory.getName());
|
logger.info("NiFi Shutdown Ignored (runNiFi==null) [{}]", instanceDirectory.getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("NiFi Shutdown Started [{}]", instanceDirectory.getName());
|
logger.info("NiFi Process [{}] Shutdown Started [{}]", process.pid(), instanceDirectory.getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runNiFi.stop();
|
process.destroy();
|
||||||
logger.info("NiFi Shutdown Completed [{}]", instanceDirectory.getName());
|
logger.info("NiFi Process [{}] Shutdown Requested [{}]", process.pid(), instanceDirectory.getName());
|
||||||
} catch (IOException e) {
|
process.waitFor(15, TimeUnit.SECONDS);
|
||||||
|
logger.info("NiFi Process [{}] Shutdown Completed [{}]", process.pid(), instanceDirectory.getName());
|
||||||
|
} catch (final Exception e) {
|
||||||
throw new RuntimeException("Failed to stop NiFi", e);
|
throw new RuntimeException("Failed to stop NiFi", e);
|
||||||
} finally {
|
} finally {
|
||||||
runNiFi = null;
|
try {
|
||||||
|
process.destroyForcibly();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.warn("NiFi Process [{}] force termination failed", process.pid(), e);
|
||||||
|
}
|
||||||
|
process = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,11 +367,8 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
||||||
copyContents(new File(getInstanceDirectory(), dirToCopy), new File(destinationDir, dirToCopy));
|
copyContents(new File(getInstanceDirectory(), dirToCopy), new File(destinationDir, dirToCopy));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runNiFi == null) {
|
if (process == null) {
|
||||||
logger.warn("NiFi instance is not running so will not capture diagnostics for {}", getInstanceDirectory());
|
logger.warn("NiFi instance is not running so will not capture diagnostics for {}", getInstanceDirectory());
|
||||||
} else {
|
|
||||||
final File diagnosticsFile = new File(destinationDir, "diagnostics.txt");
|
|
||||||
runNiFi.diagnostics(diagnosticsFile, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final File causeFile = new File(destinationDir, "test-failure-stack-trace.txt");
|
final File causeFile = new File(destinationDir, "test-failure-stack-trace.txt");
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<configuration scan="true" scanPeriod="30 seconds">
|
<configuration scan="true" scanPeriod="30 seconds">
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||||
<resetJUL>true</resetJUL>
|
<resetJUL>true</resetJUL>
|
||||||
</contextListener>
|
</contextListener>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<configuration scan="true" scanPeriod="30 seconds">
|
<configuration scan="true" scanPeriod="30 seconds">
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||||
<resetJUL>true</resetJUL>
|
<resetJUL>true</resetJUL>
|
||||||
</contextListener>
|
</contextListener>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<configuration scan="true" scanPeriod="30 seconds">
|
<configuration scan="true" scanPeriod="30 seconds">
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||||
<resetJUL>true</resetJUL>
|
<resetJUL>true</resetJUL>
|
||||||
</contextListener>
|
</contextListener>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<configuration scan="true" scanPeriod="30 seconds">
|
<configuration scan="true" scanPeriod="30 seconds">
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||||
<resetJUL>true</resetJUL>
|
<resetJUL>true</resetJUL>
|
||||||
</contextListener>
|
</contextListener>
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -129,7 +129,7 @@
|
||||||
<org.apache.httpcomponents.httpcore.version>4.4.16</org.apache.httpcomponents.httpcore.version>
|
<org.apache.httpcomponents.httpcore.version>4.4.16</org.apache.httpcomponents.httpcore.version>
|
||||||
<org.bouncycastle.version>1.78.1</org.bouncycastle.version>
|
<org.bouncycastle.version>1.78.1</org.bouncycastle.version>
|
||||||
<testcontainers.version>1.20.1</testcontainers.version>
|
<testcontainers.version>1.20.1</testcontainers.version>
|
||||||
<org.slf4j.version>2.0.15</org.slf4j.version>
|
<org.slf4j.version>2.0.16</org.slf4j.version>
|
||||||
<com.jayway.jsonpath.version>2.9.0</com.jayway.jsonpath.version>
|
<com.jayway.jsonpath.version>2.9.0</com.jayway.jsonpath.version>
|
||||||
<derby.version>10.17.1.0</derby.version>
|
<derby.version>10.17.1.0</derby.version>
|
||||||
<jetty.version>12.0.12</jetty.version>
|
<jetty.version>12.0.12</jetty.version>
|
||||||
|
|
Loading…
Reference in New Issue