YARN-8777. Container Executor C binary change to execute interactive docker command. Contributed by Eric Yang

This commit is contained in:
Billie Rinaldi 2018-10-11 09:25:21 -07:00
parent 47eeb3eb7b
commit 96d28b4750
6 changed files with 263 additions and 3 deletions

View File

@ -47,6 +47,7 @@
#include <sys/wait.h>
#include <getopt.h>
#include <sys/param.h>
#include <termios.h>
#ifndef HAVE_FCHMODAT
#include "compat/fchmodat.h"
@ -1357,8 +1358,25 @@ char **construct_docker_command(const char *command_file) {
}
int run_docker(const char *command_file) {
struct configuration command_config = {0, NULL};
int ret = read_config(command_file, &command_config);
if (ret != 0) {
free_configuration(&command_config);
return INVALID_COMMAND_FILE;
}
char *value = get_configuration_value("docker-command", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (value != NULL && strcasecmp(value, "exec") == 0) {
free(value);
free_configuration(&command_config);
return run_docker_with_pty(command_file);
}
free_configuration(&command_config);
free(value);
char **args = construct_docker_command(command_file);
char* docker_binary = get_docker_binary(&CFG);
int exit_code = -1;
if (execvp(docker_binary, args) != 0) {
fprintf(ERRORFILE, "Couldn't execute the container launch with args %s - %s",
@ -1375,6 +1393,150 @@ int run_docker(const char *command_file) {
return exit_code;
}
int run_docker_with_pty(const char *command_file) {
int exit_code = -1;
char **args = construct_docker_command(command_file);
char* docker_binary = get_docker_binary(&CFG);
int fdm, fds, rc;
char input[4000];
fdm = posix_openpt(O_RDWR);
if (fdm < 0) {
fprintf(stderr, "Error %d on posix_openpt()\n", errno);
return DOCKER_EXEC_FAILED;
}
rc = grantpt(fdm);
if (rc != 0) {
fprintf(stderr, "Error %d on grantpt()\n", errno);
return DOCKER_EXEC_FAILED;
}
rc = unlockpt(fdm);
if (rc != 0) {
fprintf(stderr, "Error %d on unlockpt()\n", errno);
return DOCKER_EXEC_FAILED;
}
// Open the slave PTY
fds = open(ptsname(fdm), O_RDWR);
// Creation of a child process
if (fork()) {
fd_set fd_in;
// Parent
// Close the slave side of the PTY
close(fds);
while (1) {
// Wait for data from standard input and master side of PTY
FD_ZERO(&fd_in);
FD_SET(0, &fd_in);
FD_SET(fdm, &fd_in);
rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);
switch(rc) {
case -1 : fprintf(stderr, "Error %d on select()\n", errno);
exit(1);
default :
{
// If data on standard input
if (FD_ISSET(0, &fd_in)) {
rc = read(0, input, sizeof(input));
if (rc > 0) {
// Send data on the master side of PTY
ssize_t written = write(fdm, input, rc);
if (written == -1) {
fprintf(stderr, "Error %d writing to container.\n", errno);
exit(DOCKER_EXEC_FAILED);
}
} else {
if (rc < 0) {
fprintf(stderr, "Error %d on read standard input\n", errno);
exit(DOCKER_EXEC_FAILED);
}
}
}
// If data on master side of PTY
if (FD_ISSET(fdm, &fd_in)) {
rc = read(fdm, input, sizeof(input));
if (rc > 0) {
// Send data on standard output
ssize_t written = write(1, input, rc);
if (written == -1) {
fprintf(stderr, "Error %d writing to terminal.\n", errno);
exit(DOCKER_EXEC_FAILED);
}
} else {
if (rc < 0) {
fprintf(stderr, "Error %d on read master PTY\n", errno);
exit(DOCKER_EXEC_FAILED);
}
}
}
}
} // End switch
} // End while
} else {
struct termios slave_orig_term_settings; // Saved terminal settings
struct termios new_term_settings; // Current terminal settings
// Child
// Close the master side of the PTY
close(fdm);
// Save the default parameters of the slave side of the PTY
rc = tcgetattr(fds, &slave_orig_term_settings);
// Set raw mode on the slave side of the PTY
new_term_settings = slave_orig_term_settings;
cfmakeraw (&new_term_settings);
tcsetattr (fds, TCSANOW, &new_term_settings);
// The slave side of the PTY becomes the standard input and outputs of the child process
close(0); // Close standard input (current terminal)
close(1); // Close standard output (current terminal)
close(2); // Close standard error (current terminal)
if (dup(fds) == -1) {
// PTY becomes standard input (0)
exit(DOCKER_EXEC_FAILED);
}
if (dup(fds) == -1) {
// PTY becomes standard output (1)
exit(DOCKER_EXEC_FAILED);
}
if (dup(fds) == -1) {
// PTY becomes standard error (2)
exit(DOCKER_EXEC_FAILED);
}
// Now the original file descriptor is useless
close(fds);
// Make the current process a new session leader
setsid();
// As the child is a session leader, set the controlling terminal to be the slave side of the PTY
// (Mandatory for programs like the shell to make them manage correctly their outputs)
ioctl(0, TIOCSCTTY, 1);
if (execvp(docker_binary, args) != 0) {
fprintf(ERRORFILE, "Couldn't execute the container launch with args %s - %s",
docker_binary, strerror(errno));
free(docker_binary);
free_values(args);
exit_code = DOCKER_EXEC_FAILED;
} else {
free_values(args);
exit_code = 0;
}
}
return exit_code;
}
int exec_docker_command(char *docker_command, char **argv, int argc) {
int i;
char* docker_binary = get_docker_binary(&CFG);
@ -2708,4 +2870,4 @@ int remove_docker_container(char**argv, int argc) {
exit_code = clean_docker_cgroups(yarn_hierarchy, container_id);
}
return exit_code;
}
}

View File

@ -268,6 +268,11 @@ int is_docker_support_enabled();
*/
int run_docker(const char *command_file);
/**
* Run a docker command passing the command file as an argument with terminal.
*/
int run_docker_with_pty(const char *command_file);
/**
* Run a docker command without a command file
*/
@ -295,4 +300,4 @@ char* flatten(char **args);
/**
* Remove docker container
*/
int remove_docker_container(char **argv, int argc);
int remove_docker_container(char **argv, int argc);

View File

@ -68,7 +68,8 @@ enum errorcodes {
DOCKER_IMAGE_INVALID = 40,
// DOCKER_CONTAINER_NAME_INVALID = 41, (NOT USED)
ERROR_COMPILING_REGEX = 42,
INVALID_CONTAINER_ID = 43
INVALID_CONTAINER_ID = 43,
DOCKER_EXEC_FAILED = 44
};
/* Macros for min/max. */

View File

@ -432,6 +432,8 @@ int get_docker_command(const char *command_file, const struct configuration *con
ret = get_docker_volume_command(command_file, conf, args);
} else if (strcmp(DOCKER_START_COMMAND, command) == 0) {
ret = get_docker_start_command(command_file, conf, args);
} else if (strcmp(DOCKER_EXEC_COMMAND, command) == 0) {
ret = get_docker_exec_command(command_file, conf, args);
} else {
ret = UNKNOWN_DOCKER_COMMAND;
}
@ -820,6 +822,57 @@ free_and_exit:
return ret;
}
int get_docker_exec_command(const char *command_file, const struct configuration *conf, args *args) {
int ret = 0, i = 0;
char *container_name = NULL;
char **launch_command = NULL;
struct configuration command_config = {0, NULL};
ret = read_and_verify_command_file(command_file, DOCKER_EXEC_COMMAND, &command_config);
if (ret != 0) {
goto free_and_exit;
}
container_name = get_configuration_value("name", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (container_name == NULL || validate_container_name(container_name) != 0) {
ret = INVALID_DOCKER_CONTAINER_NAME;
goto free_and_exit;
}
ret = add_to_args(args, DOCKER_EXEC_COMMAND);
if (ret != 0) {
goto free_and_exit;
}
ret = add_to_args(args, "-it");
if (ret != 0) {
goto free_and_exit;
}
ret = add_to_args(args, container_name);
if (ret != 0) {
goto free_and_exit;
}
launch_command = get_configuration_values_delimiter("launch-command", DOCKER_COMMAND_FILE_SECTION, &command_config,
",");
if (launch_command != NULL) {
for (i = 0; launch_command[i] != NULL; ++i) {
ret = add_to_args(args, launch_command[i]);
if (ret != 0) {
ret = BUFFER_TOO_SMALL;
goto free_and_exit;
}
}
} else {
ret = INVALID_COMMAND_FILE;
}
free_and_exit:
free(container_name);
free_configuration(&command_config);
free_values(launch_command);
return ret;
}
static int detach_container(const struct configuration *command_config, args *args) {
return add_param_to_command(command_config, "detach", "-d", 0, args);
}

View File

@ -34,6 +34,7 @@
#define DOCKER_KILL_COMMAND "kill"
#define DOCKER_VOLUME_COMMAND "volume"
#define DOCKER_START_COMMAND "start"
#define DOCKER_EXEC_COMMAND "exec"
#define DOCKER_ARG_MAX 1024
#define ARGS_INITIAL_VALUE { 0 };
@ -175,6 +176,15 @@ int get_docker_volume_command(const char *command_file, const struct configurati
*/
int get_docker_start_command(const char* command_file, const struct configuration* conf, args *args);
/**
* Get the Docker exec command line string. The function will verify that the params file is meant for the exec command.
* @param command_file File containing the params for the Docker start command
* @param conf Configuration struct containing the container-executor.cfg details
* @param args Buffer to construct argv
* @return Return code with 0 indicating success and non-zero codes indicating error
*/
int get_docker_exec_command(const char* command_file, const struct configuration* conf, args *args);
/**
* Give an error message for the supplied error code
* @param error_code the error code

View File

@ -1762,4 +1762,33 @@ namespace ContainerExecutor {
free_configuration(&container_executor_cfg);
}
}
TEST_F(TestDockerUtil, test_docker_exec) {
std::string container_executor_contents = "[docker]\n"
" docker.allowed.devices=/dev/test\n docker.trusted.registries=hadoop\n";
write_file(container_executor_cfg_file, container_executor_contents);
int ret = read_config(container_executor_cfg_file.c_str(), &container_executor_cfg);
if (ret != 0) {
FAIL();
}
ret = create_ce_file();
if (ret != 0) {
std::cerr << "Could not create ce file, skipping test" << std::endl;
return;
}
std::vector<std::pair<std::string, std::string> > file_cmd_vec;
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
"[docker-command-execution]\n"
" docker-command=exec\n name=container_e1_12312_11111_02_000001\n launch-command=bash",
"exec -it container_e1_12312_11111_02_000001 bash"));
std::vector<std::pair<std::string, int> > bad_file_cmd_vec;
bad_file_cmd_vec.push_back(std::make_pair<std::string, int>(
"[docker-command-execution]\n docker-command=exec\n image=hadoop/docker-image\n user=nobody",
static_cast<int>(INVALID_DOCKER_CONTAINER_NAME)));
run_docker_command_test(file_cmd_vec, bad_file_cmd_vec, get_docker_exec_command);
free_configuration(&container_executor_cfg);
}
}