| /* |
| * 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; |
| } |
| |
| } |