/**
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat 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 io.fabric8.utils;
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.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import static java.lang.String.format;
/**
* Platform, Java and Docker specific process utilities.
*/
public class Processes {
private static final transient Logger LOG = LoggerFactory.getLogger(Processes.class);
private static boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
/**
* Returns true if the given PID is still alive
*/
public static boolean isProcessAlive(long pid) {
List<Long> processIds = getProcessIds();
if (processIds.isEmpty()) {
// we must be on a platform that the PID list doesn't work like windows
return true;
}
return processIds.contains(pid);
}
/**
* Returns the list of current active PIDs
*/
public static List<Long> getProcessIds() {
if (isWindows) {
return getProcessIdsWindows();
} else {
return getProcessIdsUnix();
}
}
private static List<Long> getProcessIdsWindows() {
String commands = "tasklist /NH";
String message = commands;
LOG.debug("Executing commands: " + message);
List<Long> answer = new ArrayList<Long>();
try {
Process process = Runtime.getRuntime().exec(commands);
// on windows the tasklist returns memory usage as numbers, so we need to chop the line so we only include
// the pid numbers
parseProcesses(process.getInputStream(), answer, message, Filters.<String>trueFilter(), Functions.chopLength(50));
processErrors(process.getErrorStream(), message);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
message + ": " + e, e);
}
return answer;
}
private static List<Long> getProcessIdsUnix() {
String commands = "ps -e";
String message = commands;
LOG.debug("Executing commands: " + message);
List<Long> answer = new ArrayList<Long>();
try {
Process process = Runtime.getRuntime().exec(commands);
parseProcesses(process.getInputStream(), answer, message, Filters.<String>trueFilter(), null);
processErrors(process.getErrorStream(), message);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
message + ": " + e, e);
}
return answer;
}
/**
* Returns the list of current active PIDs for any java based process
* that has a main class which contains any of the given bits of text
*/
public static List<Long> getJavaProcessIds(String... classNameFilter) {
String commands = "jps -l";
String message = commands;
LOG.debug("Executing commands: " + message);
List<Long> answer = new ArrayList<Long>();
Filter<String> filter = Filters.containsAnyString(classNameFilter);
try {
Process process = Runtime.getRuntime().exec(commands);
parseProcesses(process.getInputStream(), answer, message, filter, null);
processErrors(process.getErrorStream(), message);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
message + ": " + e, e);
}
return answer;
}
/**
* Kills all commonly created Java based processes created by fabric8 and its unit tests.
*
* This is handy for unit testing to ensure there's no stray karaf, wildfly, tomcat or java containers running.
*/
public static int killJavaProcesses() {
return killJavaProcesses("karaf", "jboss", "catalina", "spring.Main", "FabricSpringApplication");
}
/**
* Kills all java processes found which include the classNameFilter in their main class
*/
public static int killJavaProcesses(String... classNameFilters) {
int count = 0;
List<Long> javaProcessIds = getJavaProcessIds(classNameFilters);
for (Long processId : javaProcessIds) {
// lets log to the console too as this tends to show up in the junit output
System.out.println("WARNING: Killing Java process " + processId);
LOG.warn("Killing Java process " + processId);
killProcess(processId, "-9");
count++;
}
return count;
}
/**
* Returns a list of active docker containers
*/
public static List<String> getDockerContainerIds() {
String commands = "docker ps -q";
String message = "output of command: " + commands;
LOG.debug("Executing commands: " + message);
final List<String> answer = new ArrayList<>();
try {
Process process = Runtime.getRuntime().exec(commands);
Function<String, Void> fn = new Function<String, Void>() {
@Override
public Void apply(String line) {
if (Strings.isNotBlank(line)) {
answer.add(line.trim());
}
return null;
}
};
processOutput(process.getInputStream(), fn, message);
processErrors(process.getErrorStream(), message);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
message + ": " + e, e);
}
return answer;
}
/**
* Returns a list of active docker containers
*/
public static int killDockerContainer(String containerId) {
// lets log to the console too as this tends to show up in the junit output
System.out.println("Killing Docker container " + containerId);
LOG.warn("WARNING: Killing Docker container " + containerId);
String commands = "docker kill " + containerId;
String message =commands;
LOG.debug("Executing commands: " + message);
final List<String> answer = new ArrayList<>();
Process process = null;
try {
process = Runtime.getRuntime().exec(commands);
processInput(process.getInputStream(), commands);
processErrors(process.getErrorStream(), commands);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
message + ": " + e, e);
}
return process != null ? process.exitValue() : -1;
}
/**
* Kills all docker containers on the current host
*/
public static void killDockerContainers() {
// lets run this in a background thread in case the command blocks due to the docker daemon not running
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
List<String> ids = getDockerContainerIds();
for (String id : ids) {
if (killDockerContainer(id) == 0) {
count++;
}
}
}
}).run();
}
/**
* Attempts to kill the given process
*/
public static int killProcess(Long pid, String params) {
if (pid == null || !isProcessAlive(pid)) {
return 0;
}
if (isWindows) {
if ("-9".equals(params)) {
params = "/F";
}
return killProcessWindows(pid, params);
} else {
return killProcessUnix(pid, params);
}
}
protected static int killProcessWindows(Long pid, String params) {
String commands = "taskkill " + (params != null ? params + " " : "") + "/PID " + pid;
Process process = null;
Runtime runtime = Runtime.getRuntime();
LOG.debug("Executing commands: " + commands);
try {
process = runtime.exec(commands);
processInput(process.getInputStream(), commands);
processErrors(process.getErrorStream(), commands);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
commands + ": " + e, e);
}
try {
return process != null ? process.waitFor() : 1;
} catch (InterruptedException e) {
String message = format("Interrupted while waiting for 'taskkill /PID %d ' command to finish", pid);
throw new RuntimeException(message, e);
}
}
protected static int killProcessUnix(Long pid, String params) {
String commands = "kill " + (params != null ? params + " " : "") + pid;
Process process = null;
Runtime runtime = Runtime.getRuntime();
LOG.debug("Executing commands: " + commands);
try {
process = runtime.exec(commands);
processInput(process.getInputStream(), commands);
processErrors(process.getErrorStream(), commands);
} catch (Exception e) {
LOG.error("Failed to execute process " + "stdin" + " for " +
commands + ": " + e, e);
}
try {
return process != null ? process.waitFor() : 1;
} catch (InterruptedException e) {
String message = format("Interrupted while waiting for 'kill %d ' command to finish", pid);
throw new RuntimeException(message, e);
}
}
protected static void parseProcesses(InputStream inputStream, List<Long> answer, String message,
Filter<String> lineFilter, Function<String, String> preFunction) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
if (preFunction != null) {
line = preFunction.apply(line);
}
if (lineFilter.matches(line)) {
StringTokenizer tokenizer = new StringTokenizer(line);
boolean found = false;
// find the first number which is the pid
while (tokenizer.hasMoreTokens() && !found) {
String pidText = tokenizer.nextToken();
try {
long pid = Long.parseLong(pidText);
answer.add(pid);
found = true;
} catch (NumberFormatException e) {
LOG.debug("Could not parse pid " + pidText + " from command: " + message);
}
}
}
}
} catch (Exception e) {
LOG.debug("Failed to process stdin for " + message + ": " + e, e);
throw e;
} finally {
Closeables.closeQuietly(reader);
}
}
protected static void processInput(InputStream inputStream, String message) throws Exception {
readProcessOutput(inputStream, "stdout for ", message);
}
protected static void processErrors(InputStream inputStream, String message) throws Exception {
readProcessOutput(inputStream, "stderr for ", message);
}
protected static void readProcessOutput(InputStream inputStream, final String prefix, final String message) throws Exception {
Function<String, Void> function = new Function<String, Void>() {
@Override
public Void apply(String line) {
LOG.debug("Error " + prefix + message + ": " + line);
return null;
}
};
processOutput(inputStream, function, prefix + message);
}
protected static void processOutput(InputStream inputStream, Function<String, Void> function, String errrorMessage) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
function.apply(line);
}
} catch (Exception e) {
LOG.error("Failed to process " + errrorMessage + ": " + e, e);
throw e;
} finally {
Closeables.closeQuietly(reader);
}
}
}