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.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import org.apache.nifi.util.LimitingInputStream;
|
||||
|
||||
public class BootstrapRequestReader {
|
||||
private final String secretKey;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.util;
|
||||
package org.apache.nifi.minifi.bootstrap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
|
@ -24,20 +24,17 @@ language governing permissions and limitations under the License. -->
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-cert-builder</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- Referenced in org.apache.nifi.NiFi -->
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties-loader</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</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
|
||||
* 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();
|
||||
|
||||
void shutdownHook(boolean isReload);
|
||||
GET
|
||||
}
|
|
@ -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
|
||||
* 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() {
|
||||
super();
|
||||
HEALTH_DIAGNOSTICS("/health/diagnostics"),
|
||||
|
||||
HEALTH_STATUS_HISTORY("/health/status-history");
|
||||
|
||||
private final String path;
|
||||
|
||||
ManagementServerPath(final String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public InvalidCommandException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidCommandException(final Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
public InvalidCommandException(final String message, final Throwable t) {
|
||||
super(message, t);
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -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.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.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]]
|
||||
|
|
|
@ -29,17 +29,16 @@ set BOOTSTRAP_LIB_DIR=%NIFI_HOME%\lib\bootstrap
|
|||
set CONF_DIR=%NIFI_HOME%\conf
|
||||
|
||||
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 PROPERTIES_FILE_PROPERTY=-Dnifi.properties.file.path=%CONF_DIR%\nifi.properties
|
||||
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_MEMORY=-Xms%BOOTSTRAP_HEAP_SIZE% -Xmx%BOOTSTRAP_HEAP_SIZE%
|
||||
|
||||
echo JAVA_HOME: %JAVA_HOME%
|
||||
echo NIFI_HOME: %NIFI_HOME%
|
||||
echo JAVA_HOME=%JAVA_HOME%
|
||||
echo NIFI_HOME=%NIFI_HOME%
|
||||
echo.
|
||||
|
||||
pushd %NIFI_HOME%
|
||||
|
@ -54,7 +53,7 @@ if %RUN_COMMAND% == "set-single-user-credentials" (
|
|||
) else if %RUN_COMMAND% == "set-sensitive-properties-algorithm" (
|
||||
call "%JAVA_EXE%" %JAVA_PARAMS% org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm %~2
|
||||
) 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
|
||||
|
|
|
@ -143,15 +143,6 @@ locateJava() {
|
|||
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() {
|
||||
|
@ -162,7 +153,7 @@ init() {
|
|||
unlimitFD
|
||||
|
||||
# Locate the Java VM to execute
|
||||
locateJava "$1"
|
||||
locateJava
|
||||
}
|
||||
|
||||
is_nonzero_integer() {
|
||||
|
@ -197,7 +188,6 @@ run() {
|
|||
|
||||
NIFI_HOME=$(cygpath --path --windows "${NIFI_HOME}")
|
||||
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_DIR=$(cygpath --path --windows "${BOOTSTRAP_CONF_DIR}")
|
||||
BOOTSTRAP_LIBS=$(cygpath --path --windows "${BOOTSTRAP_LIBS}")
|
||||
|
@ -220,26 +210,21 @@ run() {
|
|||
fi
|
||||
|
||||
echo
|
||||
echo "Java home: ${JAVA_HOME}"
|
||||
echo "NiFi home: ${NIFI_HOME}"
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo "NIFI_HOME=${NIFI_HOME}"
|
||||
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
|
||||
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}'"
|
||||
|
||||
# uncomment to allow debugging of the bootstrap process
|
||||
#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} $@"
|
||||
|
||||
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}\""
|
||||
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
|
||||
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}"
|
||||
|
@ -287,8 +267,20 @@ run() {
|
|||
return;
|
||||
fi
|
||||
|
||||
if [ "$1" = "start" ]; then
|
||||
( eval "cd ${NIFI_HOME} && ${run_nifi_cmd}" & )> /dev/null 1>&-
|
||||
eval "cd ${NIFI_HOME}"
|
||||
|
||||
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
|
||||
|
||||
|
@ -304,63 +296,57 @@ run() {
|
|||
time_since_feedback=0
|
||||
not_running_counter=0
|
||||
|
||||
is_nifi_loaded="false" # 3 possible values: "true", "false", "not_running". "not_running" means NiFi has not been started.
|
||||
while [ "$is_nifi_loaded" != "true" ]; do
|
||||
PROCESS_STATUS=1
|
||||
while [ $PROCESS_STATUS != 0 ]; do
|
||||
time_at_previous_loop=$current_time
|
||||
|
||||
current_time=$(date +%s)
|
||||
if [ "$current_time" -ge "$endtime" ]; then
|
||||
echo "Exited the script due to --wait-for-init timeout"
|
||||
echo "Initialization failed after $wait_timeout seconds"
|
||||
break;
|
||||
fi
|
||||
|
||||
time_since_feedback=$(($time_since_feedback+($current_time-$time_at_previous_loop)))
|
||||
if [ "$time_since_feedback" -ge "$WAIT_FOR_INIT_FEEDBACK_INTERVAL" ]; then
|
||||
time_since_feedback=0
|
||||
echo "NiFi has not fully initialized yet..."
|
||||
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))
|
||||
if [ "$not_running_counter" -ge 3 ]; then
|
||||
echo "NiFi is not running. Stopped waiting for it to initialize."
|
||||
echo "Initialization failed"
|
||||
break;
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep $WAIT_FOR_INIT_SLEEP_TIME
|
||||
done
|
||||
if [ "$is_nifi_loaded" = "true" ]; then
|
||||
echo "NiFi initialized."
|
||||
echo "Exiting startup script..."
|
||||
if [ $PROCESS_STATUS = 0 ]; then
|
||||
echo "Initialization completed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Wait for logging initialization before returning to shell after starting
|
||||
sleep 1
|
||||
else
|
||||
eval "cd ${NIFI_HOME} && ${run_nifi_cmd}"
|
||||
eval "${run_nifi_cmd}"
|
||||
fi
|
||||
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
|
||||
}
|
||||
|
||||
main() {
|
||||
init "$1"
|
||||
init
|
||||
run "$@"
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
install)
|
||||
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)
|
||||
start|stop|decommission|run|status|cluster-status|diagnostics|status-history|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials)
|
||||
main "$@"
|
||||
;;
|
||||
|
||||
|
@ -370,6 +356,6 @@ case "$1" in
|
|||
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
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
# 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.
|
||||
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
|
||||
# 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">
|
||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
||||
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</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.SystemBundle;
|
||||
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.FileUtils;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
|
@ -38,6 +40,7 @@ import java.io.IOException;
|
|||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
@ -50,19 +53,32 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class NiFi implements NiFiEntryPoint {
|
||||
public class NiFi {
|
||||
|
||||
public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.bootstrap.listen.port";
|
||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
||||
private 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 final NiFiServer nifiServer;
|
||||
private final BootstrapListener bootstrapListener;
|
||||
|
||||
private final NiFiProperties properties;
|
||||
|
||||
private final ManagementServer managementServer;
|
||||
|
||||
private volatile boolean shutdown = false;
|
||||
|
||||
public NiFi(final NiFiProperties properties)
|
||||
|
@ -89,25 +105,6 @@ public class NiFi implements NiFiEntryPoint {
|
|||
// register the shutdown hook
|
||||
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
|
||||
// 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
|
||||
|
@ -151,15 +148,12 @@ public class NiFi implements NiFiEntryPoint {
|
|||
narBundles,
|
||||
extensionMapping);
|
||||
|
||||
managementServer = getManagementServer();
|
||||
if (shutdown) {
|
||||
LOGGER.info("NiFi has been shutdown via NiFi Bootstrap. Will not start Controller");
|
||||
} else {
|
||||
nifiServer.start();
|
||||
|
||||
if (bootstrapListener != null) {
|
||||
bootstrapListener.setNiFiLoaded(true);
|
||||
bootstrapListener.sendStartedStatus(true);
|
||||
}
|
||||
managementServer.start();
|
||||
|
||||
final long duration = System.nanoTime() - startTime;
|
||||
final double durationSeconds = TimeUnit.NANOSECONDS.toMillis(duration) / 1000.0;
|
||||
|
@ -172,14 +166,16 @@ public class NiFi implements NiFiEntryPoint {
|
|||
}
|
||||
|
||||
protected void setDefaultUncaughtExceptionHandler() {
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> LOGGER.error("An Unknown Error Occurred in Thread {}", thread, exception));
|
||||
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
|
||||
}
|
||||
|
||||
protected void addShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() ->
|
||||
// shutdown the jetty server
|
||||
shutdownHook(false)
|
||||
));
|
||||
final Thread shutdownHook = Thread.ofPlatform()
|
||||
.name(NiFi.class.getSimpleName())
|
||||
.uncaughtExceptionHandler(new ExceptionHandler())
|
||||
.unstarted(this::stop);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
}
|
||||
|
||||
protected void initLogging() {
|
||||
|
@ -205,12 +201,15 @@ public class NiFi implements NiFiEntryPoint {
|
|||
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 {
|
||||
runDiagnosticsOnShutdown();
|
||||
shutdown();
|
||||
} 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() {
|
||||
this.shutdown = true;
|
||||
|
||||
LOGGER.info("Application Server shutdown started");
|
||||
if (nifiServer != null) {
|
||||
LOGGER.info("Application Controller shutdown started");
|
||||
|
||||
managementServer.stop();
|
||||
|
||||
if (nifiServer == null) {
|
||||
LOGGER.info("Application Server not running");
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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.toolkit.cli.impl.client.nifi.NiFiClient;
|
||||
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientConfig;
|
||||
|
@ -51,7 +56,7 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
|||
|
||||
@Override
|
||||
public NiFiInstance createInstance() {
|
||||
return new RunNiFiInstance(instanceConfig);
|
||||
return new ProcessNiFiInstance(instanceConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,14 +83,14 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
|||
return Objects.hash(instanceConfig);
|
||||
}
|
||||
|
||||
private static class RunNiFiInstance implements NiFiInstance {
|
||||
private static class ProcessNiFiInstance implements NiFiInstance {
|
||||
private final File instanceDirectory;
|
||||
private final File configDir;
|
||||
private final InstanceConfiguration instanceConfiguration;
|
||||
private File bootstrapConfigFile;
|
||||
private RunNiFi runNiFi;
|
||||
private Process process;
|
||||
|
||||
public RunNiFiInstance(final InstanceConfiguration instanceConfiguration) {
|
||||
public ProcessNiFiInstance(final InstanceConfiguration instanceConfiguration) {
|
||||
this.instanceDirectory = instanceConfiguration.getInstanceDirectory();
|
||||
this.bootstrapConfigFile = instanceConfiguration.getBootstrapConfigFile();
|
||||
this.instanceConfiguration = instanceConfiguration;
|
||||
|
@ -108,30 +113,33 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RunNiFiInstance[dir=" + instanceDirectory + "]";
|
||||
return "ProcessNiFiInstance[home=%s,process=%s]".formatted(instanceDirectory, process);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final boolean waitForCompletion) {
|
||||
if (runNiFi != null) {
|
||||
if (process != null) {
|
||||
throw new IllegalStateException("NiFi has already been started");
|
||||
}
|
||||
|
||||
logger.info("Starting NiFi [{}]", instanceDirectory.getName());
|
||||
|
||||
try {
|
||||
this.runNiFi = new RunNiFi(bootstrapConfigFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to start NiFi", e);
|
||||
}
|
||||
final Map<String, String> environmentVariables = Map.of("NIFI_HOME", instanceDirectory.getAbsolutePath());
|
||||
final ConfigurationProvider configurationProvider = new StandardConfigurationProvider(environmentVariables, new Properties());
|
||||
final ManagementServerAddressProvider managementServerAddressProvider = new StandardManagementServerAddressProvider(configurationProvider);
|
||||
final ProcessBuilderProvider processBuilderProvider = new StandardProcessBuilderProvider(configurationProvider, managementServerAddressProvider);
|
||||
|
||||
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) {
|
||||
waitForStartup();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException("Failed to start NiFi", e);
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +233,7 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
|||
|
||||
@Override
|
||||
public boolean isAccessible() {
|
||||
if (runNiFi == null) {
|
||||
if (process == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -263,20 +271,27 @@ public class SpawnedStandaloneNiFiInstanceFactory implements NiFiInstanceFactory
|
|||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (runNiFi == null) {
|
||||
if (process == null) {
|
||||
logger.info("NiFi Shutdown Ignored (runNiFi==null) [{}]", instanceDirectory.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("NiFi Shutdown Started [{}]", instanceDirectory.getName());
|
||||
logger.info("NiFi Process [{}] Shutdown Started [{}]", process.pid(), instanceDirectory.getName());
|
||||
|
||||
try {
|
||||
runNiFi.stop();
|
||||
logger.info("NiFi Shutdown Completed [{}]", instanceDirectory.getName());
|
||||
} catch (IOException e) {
|
||||
process.destroy();
|
||||
logger.info("NiFi Process [{}] Shutdown Requested [{}]", process.pid(), instanceDirectory.getName());
|
||||
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);
|
||||
} 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));
|
||||
}
|
||||
|
||||
if (runNiFi == null) {
|
||||
if (process == null) {
|
||||
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");
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
-->
|
||||
|
||||
<configuration scan="true" scanPeriod="30 seconds">
|
||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
||||
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
-->
|
||||
|
||||
<configuration scan="true" scanPeriod="30 seconds">
|
||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
||||
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
-->
|
||||
|
||||
<configuration scan="true" scanPeriod="30 seconds">
|
||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
||||
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
-->
|
||||
|
||||
<configuration scan="true" scanPeriod="30 seconds">
|
||||
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook" />
|
||||
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</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.bouncycastle.version>1.78.1</org.bouncycastle.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>
|
||||
<derby.version>10.17.1.0</derby.version>
|
||||
<jetty.version>12.0.12</jetty.version>
|
||||
|
|
Loading…
Reference in New Issue