/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.test.integration.management.util;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.as.test.shared.TimeoutUtil;
import org.jboss.logging.Logger;
import org.wildfly.core.launcher.CliCommandBuilder;
import org.wildfly.core.launcher.Launcher;
/**
* CLI executor with custom configuration file jboss-cli.xml used for testing
* two-way SSL connection
*
* @author Filip Bogyai
*/
public class CustomCLIExecutor {
public static final int MANAGEMENT_NATIVE_PORT = 9999;
public static final int MANAGEMENT_HTTP_PORT = 9990;
public static final int MANAGEMENT_HTTPS_PORT = 9993;
public static final String NATIVE_CONTROLLER = "remote://" + TestSuiteEnvironment.getServerAddress() + ":"
+ MANAGEMENT_NATIVE_PORT;
public static final String HTTP_CONTROLLER = "remote+http://" + TestSuiteEnvironment.getServerAddress() + ":"
+ MANAGEMENT_HTTP_PORT;
public static final String HTTPS_CONTROLLER = "remote+https://" + TestSuiteEnvironment.getServerAddress() + ":"
+ MANAGEMENT_HTTPS_PORT;
private static Logger LOGGER = Logger.getLogger(CustomCLIExecutor.class);
private static final int CLI_PROC_TIMEOUT = TimeoutUtil.adjust(30000);
private static final int STATUS_CHECK_INTERVAL = TimeoutUtil.adjust(2000);
private static final byte[] NEW_LINE = System.lineSeparator().getBytes(StandardCharsets.UTF_8);
public static String execute(File cliConfigFile, String operation) {
String defaultController = TestSuiteEnvironment.getServerAddress() + ":" + TestSuiteEnvironment.getServerPort();
return execute(cliConfigFile, operation, defaultController, false);
}
public static String execute(File cliConfigFile, String operation, String controller) {
return execute(cliConfigFile, operation, controller, false);
}
/**
* Externally executes CLI operation with cliConfigFile settings via defined
* controller
*
* @return String cliOutput
*/
public static String execute(File cliConfigFile, String operation, String controller, boolean logFailure) {
return execute(cliConfigFile, operation, controller, logFailure, null);
}
/**
* Externally executes CLI operation with cliConfigFile settings via defined
* controller
*
* @param cliConfigFile the the configuration file to use or {@code null} to use the default
* @param operation the CLI operation to execute
* @param controller the controller to use
* @param logFailure {@code true} to log failures otherwise {@code false}
* @param failureResponse a response that can be sent to stdin of the launched processes or {@code null}
*
* @return the stdout response from the process
*/
public static String execute(File cliConfigFile, String operation, String controller, boolean logFailure, String failureResponse) {
String jbossDist = System.getProperty("jboss.dist");
if (jbossDist == null) {
fail("jboss.dist system property is not set");
}
final String modulePath = System.getProperty("module.path");
if (modulePath == null) {
fail("module.path system property is not set");
}
final CliCommandBuilder commandBuilder = CliCommandBuilder.of(jbossDist)
.setModuleDirs(modulePath.split(Pattern.quote(File.pathSeparator)));
final List<String> ipv6Args = new ArrayList<>();
TestSuiteEnvironment.getIpv6Args(ipv6Args);
if (!ipv6Args.isEmpty()) {
commandBuilder.addJavaOptions(ipv6Args);
}
final Path cliConfigPath;
if (cliConfigFile != null) {
cliConfigPath = cliConfigFile.toPath().toAbsolutePath();
commandBuilder.addJavaOption("-Djboss.cli.config=" + cliConfigFile.getAbsolutePath());
} else {
cliConfigPath = Paths.get(jbossDist, "bin", "jboss-cli.xml");
}
commandBuilder.addJavaOption("-Djboss.cli.config=" + cliConfigPath);
commandBuilder.addCliArgument("--timeout="+CLI_PROC_TIMEOUT);
// propagate JVM args to the CLI
String cliJvmArgs = System.getProperty("cli.jvm.args");
if (cliJvmArgs != null) {
commandBuilder.addJavaOptions(cliJvmArgs.split("\\s+"));
}
// Note that this only allows for a single system property
if (System.getProperty("cli.args") != null) {
commandBuilder.addJavaOption(System.getProperty("cli.args"));
}
// Set the connection command
commandBuilder.setConnection(controller);
commandBuilder.addCliArgument(operation);
Process cliProc = null;
try {
cliProc = Launcher.of(commandBuilder).launch();
} catch (IOException e) {
fail("Failed to start CLI process: " + e.getLocalizedMessage());
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ByteArrayOutputStream err = new ByteArrayOutputStream();
ConsoleConsumer.start(cliProc.getInputStream(), out);
ConsoleConsumer.start(cliProc.getErrorStream(), err);
boolean wait = true;
int runningTime = 0;
int exitCode = Integer.MIN_VALUE;
do {
try {
Thread.sleep(STATUS_CHECK_INTERVAL);
} catch (InterruptedException e) {
}
runningTime += STATUS_CHECK_INTERVAL;
try {
exitCode = cliProc.exitValue();
wait = false;
} catch (IllegalThreadStateException e) {
// cli still working
if (failureResponse != null) {
try {
final OutputStream stdin = cliProc.getOutputStream();
stdin.write(failureResponse.getBytes(StandardCharsets.UTF_8));
stdin.write(NEW_LINE);
stdin.flush();
} catch (IOException e1) {
LOGGER.debug(out.toString(), e);
}
}
}
if (runningTime >= CLI_PROC_TIMEOUT) {
cliProc.destroy();
wait = false;
LOGGER.info("A timeout has occurred while invoking CLI command.");
}
} while (wait);
final String cliOutput = out.toString();
if (logFailure && exitCode != 0) {
LOGGER.info("Command's output: '" + cliOutput + "'");
if (err.size() > 0) {
LOGGER.info("Command's error log: '" + err.toString() + "'");
} else {
LOGGER.info("No output data for the command.");
}
}
return exitCode + ": " + cliOutput;
}
private static class ConsoleConsumer implements Runnable {
static void start(final InputStream in, final OutputStream target) {
final Thread t = new Thread(new ConsoleConsumer(in, target));
t.start();
}
private final InputStream in;
private final OutputStream target;
private ConsoleConsumer(final InputStream in, final OutputStream target) {
this.in = in;
this.target = target;
}
@Override
public void run() {
final byte[] b = new byte[32];
int len;
try {
while ((len = in.read(b)) != -1) {
target.write(b, 0, len);
}
} catch (IOException ignore) {
}
}
}
}