blob: ae40d6ab6b6ef34a195b7dbf40602e3b4c888aae [file] [log] [blame]
/*
* 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.slider.funtest.framework
import org.apache.bigtop.itest.shell.Shell
import org.apache.slider.core.exceptions.SliderException
import org.apache.slider.common.tools.SliderUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class SliderShell extends Shell {
private static final Logger log = LoggerFactory.getLogger(SliderShell.class);
private static final Logger LOG = log;
public static final String BASH = '/bin/bash -s'
public static final String CMD = 'cmd'
/**
* Configuration directory, shared across all instances. Not marked as volatile,
* assumed set up during @BeforeClass
*/
public static File confDir;
public static File scriptFile;
public File shellScript;
public static final List<String> slider_classpath_extra = []
/**
* Environment varaibles
*/
protected static final Map<String, String> environment = [:]
final String command
/**
* Build the command
* @param commands
*/
SliderShell(Collection<String> commands) {
super(org.apache.hadoop.util.Shell.WINDOWS ? CMD : BASH)
assert confDir != null;
assert scriptFile != null;
shellScript = scriptFile;
command = scriptFile.absolutePath + " " + commands.join(" ")
}
/**
* Exec the command
* @return the script exit code
*/
int execute() {
log.info(command)
setEnv(FuntestProperties.ENV_SLIDER_CONF_DIR, confDir)
if (!slider_classpath_extra.empty) {
setEnv(FuntestProperties.ENV_SLIDER_CLASSPATH_EXTRA,
SliderUtils.join(slider_classpath_extra,
pathElementSeparator,
false))
}
List<String> commandLine = buildEnvCommands()
commandLine << command
if (org.apache.hadoop.util.Shell.WINDOWS) {
// Ensure the errorlevel returned by last call is set for the invoking shell
commandLine << "@echo ERRORLEVEL=%ERRORLEVEL%"
commandLine << "@exit %ERRORLEVEL%"
}
String script = commandLine.join("\n")
log.debug(script)
exec(script);
signCorrectReturnCode()
return ret;
}
public String getPathElementSeparator() {
File.pathSeparator
}
public static boolean isWindows() {
return org.apache.hadoop.util.Shell.WINDOWS
}
/**
* Set an environment variable
* @param var variable name
* @param val value
*/
public static void setEnv(String var, Object val) {
environment[var] = val.toString()
}
/**
* Get an environment variable
* @param var variable name
* @return the value or null
*/
public static String getEnv(String var) {
return environment[var]
}
/**
* Build up a list of environment variable setters from the
* env variables
* @return a list of commands to set up the env on the target system.
*/
public static List<String> buildEnvCommands() {
List<String> commands = []
environment.each { String var, String val ->
commands << env(var, val)
}
return commands
}
/**
* Add an environment variable
* @param var variable
* @param val value (which will be stringified)
* @return an env variable command
*/
static String env(String var, Object val) {
if (isWindows()) {
return "set " + var + "=${val.toString()}"
} else {
return "export " + var + "=${val.toString()};"
}
}
/**
* Fix up the return code so that a value of 255 is mapped back to -1
* @return twos complement return code from an unsigned byte
*/
int signCorrectReturnCode() {
ret = signCorrect(ret)
}
/**
* Execute expecting a specific exit code
* @param expectedExitCode the expected exit code
*/
void execute(int expectedExitCode) {
execute()
assertExitCode(expectedExitCode)
}
/**
* Exec any slider command
* @param conf
* @param commands
* @return the shell
*/
public static SliderShell run(int exitCode, Collection<String> commands) {
SliderShell shell = new SliderShell(commands)
shell.execute(exitCode);
return shell
}
/**
* Sign-correct a process exit code
* @param exitCode the incoming exit code
* @return the sign-corrected version
*/
public static int signCorrect(int exitCode) {
return (exitCode << 24) >> 24;
}
@Override
public String toString() {
return ret + " =>" + command
}
/**
* Dump the command, return code and outputs to the log.
* stdout is logged at info; stderr at error.
*/
public void dumpOutput() {
log.error(toString())
log.error("return code = ${signCorrectReturnCode()}")
if (out.size() != 0) {
log.info("\n<stdout>\n${stdoutHistory}\n</stdout>");
}
if (err.size() != 0) {
log.error("\n<stderr>\n${stdErrHistory}\n</stderr>");
}
}
/**
* Get the stderr history
* @return the history
*/
public String getStdErrHistory() {
return err.join('\n')
}
/**
* Get the stdout history
* @return the history
*/
public String getStdoutHistory() {
return out.join('\n')
}
/**
* Assert the shell exited with a given error code
* if not the output is printed and an assertion is raised
* @param errorCode expected error code
*/
public void assertExitCode(int errorCode, String extra="") {
if (this.ret != errorCode) {
dumpOutput()
throw new SliderException(ret,
"Expected exit code of command ${command} : ${errorCode} - actual=${ret} $extra")
}
}
/**
* Execute shell script consisting of as many Strings as we have arguments,
* NOTE: individual strings are concatenated into a single script as though
* they were delimited with new line character. All quoting rules are exactly
* what one would expect in standalone shell script.
*
* After executing the script its return code can be accessed as getRet(),
* stdout as getOut() and stderr as getErr(). The script itself can be accessed
* as getScript()
* WARNING: it isn't thread safe
* @param args shell script split into multiple Strings
* @return Shell object for chaining
*/
Shell exec(Object... args) {
Process proc = "$shell".execute()
script = args.join("\n")
ByteArrayOutputStream baosErr = new ByteArrayOutputStream(4096);
ByteArrayOutputStream baosOut = new ByteArrayOutputStream(4096);
proc.consumeProcessOutput(baosOut, baosErr)
Thread.start {
def writer = new PrintWriter(new BufferedOutputStream(proc.out))
writer.println(script)
writer.flush()
writer.close()
}
proc.waitFor()
ret = proc.exitValue()
out = streamToLines(baosOut)
err = streamToLines(baosErr)
if (LOG.isTraceEnabled()) {
if (ret != 0) {
LOG.trace("return: $ret");
}
if (out.size() != 0) {
LOG.trace("\n<stdout>\n${out.join('\n')}\n</stdout>");
}
if (err.size() != 0) {
LOG.trace("\n<stderr>\n${err.join('\n')}\n</stderr>");
}
}
return this
}
/**
* Convert a stream to lines in an array
* @param out output stream
* @return the list of entries
*/
protected List<String> streamToLines(ByteArrayOutputStream out) {
if (out.size() != 0) {
return out.toString().split('\n');
} else {
return [];
}
}
public String findLineEntry(String[] locaters) {
int index = 0;
def output = out +"\n"+ err
for (String str in output) {
if (str.contains("\"" + locaters[index] + "\"")) {
if (locaters.size() == index + 1) {
return str;
} else {
index++;
}
}
}
return null;
}
public boolean outputContains(
String lookThisUp,
int n = 1) {
int count = 0
def output = out + "\n" + err
for (String str in output) {
int subCount = countString(str, lookThisUp)
count = count + subCount
if (count == n) {
return true;
}
}
return false;
}
public static int countString(String str, String search) {
int count = 0
if (SliderUtils.isUnset(str) || SliderUtils.isUnset(search)) {
return count
}
int index = str.indexOf(search, 0)
while (index >= 0) {
++count
index = str.indexOf(search, index + 1)
}
return count
}
public findLineEntryValue(String[] locaters) {
String line = findLineEntry(locaters);
if (line != null) {
log.info("Parsing {} for value.", line)
int dividerIndex = line.indexOf(":");
if (dividerIndex > 0) {
String value = line.substring(dividerIndex + 1).trim()
if (value.endsWith(",")) {
value = value.subSequence(0, value.length() - 1)
}
return value;
}
}
return null;
}
}