diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c index 7765308fc0..1f7ae3f944 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c @@ -47,6 +47,7 @@ #include #include #include +#include #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; -} \ No newline at end of file +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h index cd09e1edde..ce05c573c9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h @@ -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); \ No newline at end of file +int remove_docker_container(char **argv, int argc); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/util.h b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/util.h index 6aac1fe1af..372c17a4f8 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/util.h +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/util.h @@ -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. */ diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.c b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.c index 69f27ba661..c0aa80bf81 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.c +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.c @@ -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); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.h b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.h index 7b7322dc5d..fc7b4cca4e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.h +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/docker-util.h @@ -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 diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/utils/test_docker_util.cc b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/utils/test_docker_util.cc index b289857ed2..2817e0ca5c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/utils/test_docker_util.cc +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/utils/test_docker_util.cc @@ -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 > file_cmd_vec; + file_cmd_vec.push_back(std::make_pair( + "[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 > bad_file_cmd_vec; + + bad_file_cmd_vec.push_back(std::make_pair( + "[docker-command-execution]\n docker-command=exec\n image=hadoop/docker-image\n user=nobody", + static_cast(INVALID_DOCKER_CONTAINER_NAME))); + run_docker_command_test(file_cmd_vec, bad_file_cmd_vec, get_docker_exec_command); + free_configuration(&container_executor_cfg); + } }