blob: 5a3ce743f85ff7d07f396195a9c2f92af0ec75ba [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.hadoop.yarn.server.nodemanager.containermanager.linux.privileged;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.Shell.ShellCommandExecutor;
import org.apache.hadoop.util.Shell.ExitCodeException;
import org.apache.hadoop.yarn.api.ApplicationConstants;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* provides mechanisms to execute PrivilegedContainerOperations *
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
public class PrivilegedOperationExecutor {
private static final Logger LOG =
LoggerFactory.getLogger(PrivilegedOperationExecutor
.class);
private volatile static PrivilegedOperationExecutor instance;
private String containerExecutorExe;
public static String getContainerExecutorExecutablePath(Configuration conf) {
String yarnHomeEnvVar =
System.getenv(ApplicationConstants.Environment.HADOOP_YARN_HOME.key());
File hadoopBin = new File(yarnHomeEnvVar, "bin");
String defaultPath =
new File(hadoopBin, "container-executor").getAbsolutePath();
return null == conf
? defaultPath
: conf.get(YarnConfiguration.NM_LINUX_CONTAINER_EXECUTOR_PATH,
defaultPath);
}
private void init(Configuration conf) {
containerExecutorExe = getContainerExecutorExecutablePath(conf);
}
private PrivilegedOperationExecutor(Configuration conf) {
init(conf);
}
public static PrivilegedOperationExecutor getInstance(Configuration conf) {
if (instance == null) {
synchronized (PrivilegedOperationExecutor.class) {
if (instance == null) {
instance = new PrivilegedOperationExecutor(conf);
}
}
}
return instance;
}
/**
* @param prefixCommands in some cases ( e.g priorities using nice ),
* prefix commands are necessary
* @param operation the type and arguments for the operation to be
* executed
* @return execution string array for priviledged operation
*/
public String[] getPrivilegedOperationExecutionCommand(List<String>
prefixCommands,
PrivilegedOperation operation) {
List<String> fullCommand = new ArrayList<String>();
if (prefixCommands != null && !prefixCommands.isEmpty()) {
fullCommand.addAll(prefixCommands);
}
fullCommand.add(containerExecutorExe);
String cliSwitch = operation.getOperationType().getOption();
if (!cliSwitch.isEmpty()) {
fullCommand.add(cliSwitch);
}
fullCommand.addAll(operation.getArguments());
String[] fullCommandArray =
fullCommand.toArray(new String[fullCommand.size()]);
if (LOG.isDebugEnabled()) {
LOG.debug("Privileged Execution Command Array: " +
Arrays.toString(fullCommandArray));
}
return fullCommandArray;
}
/**
* Executes a privileged operation. It is up to the callers to ensure that
* each privileged operation's parameters are constructed correctly. The
* parameters are passed verbatim to the container-executor binary.
*
* @param prefixCommands in some cases ( e.g priorities using nice ),
* prefix commands are necessary
* @param operation the type and arguments for the operation to be executed
* @param workingDir (optional) working directory for execution
* @param env (optional) env of the command will include specified vars
* @param grabOutput return (possibly large) shell command output
* @param inheritParentEnv inherit the env vars from the parent process
* @return stdout contents from shell executor - useful for some privileged
* operations - e.g --tc_read
* @throws org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationException
*/
public String executePrivilegedOperation(List<String> prefixCommands,
PrivilegedOperation operation, File workingDir,
Map<String, String> env, boolean grabOutput, boolean inheritParentEnv)
throws PrivilegedOperationException {
String[] fullCommandArray = getPrivilegedOperationExecutionCommand
(prefixCommands, operation);
ShellCommandExecutor exec = new ShellCommandExecutor(fullCommandArray,
workingDir, env, 0L, inheritParentEnv);
try {
exec.execute();
if (LOG.isDebugEnabled()) {
LOG.debug("command array:");
LOG.debug(Arrays.toString(fullCommandArray));
LOG.debug("Privileged Execution Operation Output:");
LOG.debug(exec.getOutput());
}
} catch (ExitCodeException e) {
if (operation.isFailureLoggingEnabled()) {
StringBuilder logBuilder = new StringBuilder("Shell execution returned "
+ "exit code: ")
.append(exec.getExitCode())
.append(". Privileged Execution Operation Stderr: ")
.append(System.lineSeparator())
.append(e.getMessage())
.append(System.lineSeparator())
.append("Stdout: " + exec.getOutput())
.append(System.lineSeparator());
logBuilder.append("Full command array for failed execution: ")
.append(System.lineSeparator());
logBuilder.append(Arrays.toString(fullCommandArray));
LOG.warn(logBuilder.toString());
}
//stderr from shell executor seems to be stuffed into the exception
//'message' - so, we have to extract it and set it as the error out
throw new PrivilegedOperationException(e, e.getExitCode(),
exec.getOutput(), e.getMessage());
} catch (IOException e) {
LOG.warn("IOException executing command: ", e);
throw new PrivilegedOperationException(e);
}
if (grabOutput) {
return exec.getOutput();
}
return null;
}
/**
* Executes a privileged operation. It is up to the callers to ensure that
* each privileged operation's parameters are constructed correctly. The
* parameters are passed verbatim to the container-executor binary.
*
* @param operation the type and arguments for the operation to be executed
* @param grabOutput return (possibly large) shell command output
* @return stdout contents from shell executor - useful for some privileged
* operations - e.g --tc_read
* @throws org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationException
*/
public String executePrivilegedOperation(PrivilegedOperation operation,
boolean grabOutput) throws PrivilegedOperationException {
return executePrivilegedOperation(null, operation, null, null, grabOutput,
true);
}
//Utility functions for squashing together operations in supported ways
//At some point, we need to create a generalized mechanism that uses a set
//of squashing 'rules' to squash an set of PrivilegedOperations of varying
//types - e.g Launch Container + Add Pid to CGroup(s) + TC rules
/**
* Squash operations for cgroups - e.g mount, add pid to cgroup etc .,
* For now, we only implement squashing for 'add pid to cgroup' since this
* is the only optimization relevant to launching containers
*
* @return single squashed cgroup operation. Null on failure.
*/
public static PrivilegedOperation squashCGroupOperations
(List<PrivilegedOperation> ops) throws PrivilegedOperationException {
if (ops.size() == 0) {
return null;
}
StringBuffer finalOpArg = new StringBuffer(PrivilegedOperation
.CGROUP_ARG_PREFIX);
boolean noTasks = true;
for (PrivilegedOperation op : ops) {
if (!op.getOperationType()
.equals(PrivilegedOperation.OperationType.ADD_PID_TO_CGROUP)) {
LOG.warn("Unsupported operation type: " + op.getOperationType());
throw new PrivilegedOperationException("Unsupported operation type:"
+ op.getOperationType());
}
List<String> args = op.getArguments();
if (args.size() != 1) {
LOG.warn("Invalid number of args: " + args.size());
throw new PrivilegedOperationException("Invalid number of args: "
+ args.size());
}
String arg = args.get(0);
String tasksFile = StringUtils.substringAfter(arg,
PrivilegedOperation.CGROUP_ARG_PREFIX);
if (tasksFile == null || tasksFile.isEmpty()) {
LOG.warn("Invalid argument: " + arg);
throw new PrivilegedOperationException("Invalid argument: " + arg);
}
if (tasksFile.equals(PrivilegedOperation.CGROUP_ARG_NO_TASKS)) {
//Don't append to finalOpArg
continue;
}
if (noTasks == false) {
//We have already appended at least one tasks file.
finalOpArg.append(PrivilegedOperation.LINUX_FILE_PATH_SEPARATOR);
finalOpArg.append(tasksFile);
} else {
finalOpArg.append(tasksFile);
noTasks = false;
}
}
if (noTasks) {
finalOpArg.append(PrivilegedOperation.CGROUP_ARG_NO_TASKS); //there
// were no tasks file to append
}
PrivilegedOperation finalOp = new PrivilegedOperation(
PrivilegedOperation.OperationType.ADD_PID_TO_CGROUP, finalOpArg
.toString());
return finalOp;
}
}