YARN-9561. Add C changes for the new RuncContainerRuntime. Contributed by Eric Badger

This commit is contained in:
Eric Badger 2019-12-06 18:33:58 +00:00
parent 705b172b95
commit 76bb297db9
11 changed files with 844 additions and 141 deletions

View File

@ -256,3 +256,26 @@ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/st
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/static/jquery
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/static/jt/jquery.jstree.js
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/resources/TERMINAL
=======
For hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/cJSON.[ch]:
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -16,12 +16,15 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/../../../../../hadoop-common-project/hadoop-common)
set(HADOOP_COMMON_PATH ${CMAKE_SOURCE_DIR}/../../../../../hadoop-common-project/hadoop-common)
list(APPEND CMAKE_MODULE_PATH ${HADOOP_COMMON_PATH})
include(HadoopCommon)
# Set gtest path
set(GTEST_SRC_DIR ${CMAKE_SOURCE_DIR}/../../../../../hadoop-common-project/hadoop-common/src/main/native/gtest)
set(HADOOP_COMMON_SEC_PATH ${HADOOP_COMMON_PATH}/src/main/native/src/org/apache/hadoop/security)
# determine if container-executor.conf.dir is an absolute
# path in case the OS we're compiling on doesn't have
# a hook in get_executable. We'll use this define
@ -115,6 +118,7 @@ include_directories(
main/native/container-executor
main/native/container-executor/impl
main/native/oom-listener/impl
${HADOOP_COMMON_SEC_PATH}
)
# add gtest as system library to suppress gcc warnings
include_directories(SYSTEM ${GTEST_SRC_DIR}/include)
@ -129,6 +133,7 @@ add_library(container
main/native/container-executor/impl/configuration.c
main/native/container-executor/impl/container-executor.c
main/native/container-executor/impl/get_executable.c
main/native/container-executor/impl/utils/file-utils.c
main/native/container-executor/impl/utils/string-utils.c
main/native/container-executor/impl/utils/path-utils.c
main/native/container-executor/impl/modules/cgroups/cgroups-operations.c
@ -138,6 +143,14 @@ add_library(container
main/native/container-executor/impl/modules/devices/devices-module.c
main/native/container-executor/impl/utils/docker-util.c
main/native/container-executor/impl/utils/mount-utils.c
main/native/container-executor/impl/utils/cJSON/cJSON.c
main/native/container-executor/impl/runc/runc.c
main/native/container-executor/impl/runc/runc_base_ctx.c
main/native/container-executor/impl/runc/runc_launch_cmd.c
main/native/container-executor/impl/runc/runc_reap.c
main/native/container-executor/impl/runc/runc_write_config.c
${HADOOP_COMMON_SEC_PATH}/hadoop_user_info.c
${HADOOP_COMMON_SEC_PATH}/hadoop_group_info.c
)
add_executable(container-executor
@ -146,6 +159,7 @@ add_executable(container-executor
target_link_libraries(container-executor
container
crypto
)
output_directory(container-executor target/usr/local/bin)
@ -155,7 +169,9 @@ add_executable(test-container-executor
main/native/container-executor/test/test-container-executor.c
)
target_link_libraries(test-container-executor
container ${EXTRA_LIBS}
container
${EXTRA_LIBS}
crypto
)
output_directory(test-container-executor target/usr/local/bin)
@ -173,8 +189,15 @@ add_executable(cetest
main/native/container-executor/test/modules/fpga/test-fpga-module.cc
main/native/container-executor/test/modules/devices/test-devices-module.cc
main/native/container-executor/test/test_util.cc
main/native/container-executor/test/utils/test_docker_util.cc)
target_link_libraries(cetest gtest container)
main/native/container-executor/test/utils/test_docker_util.cc
main/native/container-executor/test/utils/test_runc_util.cc
)
target_link_libraries(cetest
gtest
container
crypto
)
output_directory(cetest test)
# CGroup OOM listener

View File

@ -21,6 +21,7 @@
#include "utils/docker-util.h"
#include "utils/path-utils.h"
#include "utils/string-utils.h"
#include "runc/runc.h"
#include "util.h"
#include "config.h"
@ -78,6 +79,7 @@ static const int DEFAULT_DOCKER_SUPPORT_ENABLED = 0;
static const int DEFAULT_TC_SUPPORT_ENABLED = 0;
static const int DEFAULT_MOUNT_CGROUP_SUPPORT_ENABLED = 0;
static const int DEFAULT_YARN_SYSFS_SUPPORT_ENABLED = 0;
static const int DEFAULT_RUNC_SUPPORT_ENABLED = 0;
static const char* PROC_PATH = "/proc";
@ -191,7 +193,7 @@ int check_executor_permissions(char *executable_file) {
/**
* Change the effective user id to limit damage.
*/
static int change_effective_user(uid_t user, gid_t group) {
int change_effective_user(uid_t user, gid_t group) {
if (geteuid() == user) {
return 0;
}
@ -211,6 +213,10 @@ static int change_effective_user(uid_t user, gid_t group) {
return 0;
}
int change_effective_user_to_nm() {
return change_effective_user(nm_uid, nm_gid);
}
#ifdef __linux
/**
* Write the pid of the current process to the cgroup file.
@ -408,7 +414,7 @@ static int wait_and_get_exit_code(pid_t pid) {
* the exit code file.
* Returns the exit code of the container process.
*/
static int wait_and_write_exit_code(pid_t pid, const char* exit_code_file) {
int wait_and_write_exit_code(pid_t pid, const char* exit_code_file) {
int exit_code = -1;
exit_code = wait_and_get_exit_code(pid);
@ -510,6 +516,12 @@ int is_yarn_sysfs_support_enabled() {
DEFAULT_YARN_SYSFS_SUPPORT_ENABLED, &executor_cfg);
}
int is_runc_support_enabled() {
return is_feature_enabled(RUNC_SUPPORT_ENABLED_KEY,
DEFAULT_RUNC_SUPPORT_ENABLED, &executor_cfg)
|| runc_module_enabled(&CFG);
}
/**
* Utility function to concatenate argB to argA using the concat_pattern.
*/
@ -642,6 +654,20 @@ char *get_tmp_directory(const char *work_dir) {
return concatenate("%s/%s", "tmp dir", 2, work_dir, TMP_DIR);
}
/**
* Get the private /tmp directory under the working directory
*/
char *get_privatetmp_directory(const char *work_dir) {
return concatenate("%s/%s", "private /tmp dir", 2, work_dir, ROOT_TMP_DIR);
}
/**
* Get the private /tmp directory under the working directory
*/
char *get_private_var_tmp_directory(const char *work_dir) {
return concatenate("%s/%s", "private /var/tmp dir", 2, work_dir, ROOT_VAR_TMP_DIR);
}
/**
* Ensure that the given path and all of the parent directories are created
* with the desired permissions.
@ -810,17 +836,51 @@ static int create_container_directories(const char* user, const char *app_id,
return result;
}
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
// also make the tmp directory
char *tmp_dir = get_tmp_directory(work_dir);
char *private_tmp_dir = get_privatetmp_directory(work_dir);
char *private_var_tmp_dir = get_private_var_tmp_directory(work_dir);
if (tmp_dir == NULL) {
if (tmp_dir == NULL || private_tmp_dir == NULL || private_var_tmp_dir == NULL) {
return OUT_OF_MEMORY;
}
if (mkdirs(tmp_dir, perms) == 0) {
result = 0;
if (mkdirs(tmp_dir, perms) != 0) {
fprintf(ERRORFILE, "Could not create tmp_dir: %s\n", tmp_dir);
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
goto cleanup;
}
if (mkdirs(private_tmp_dir, perms) != 0) {
fprintf(ERRORFILE, "Could not create private_tmp_dir: %s\n", private_tmp_dir);
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
goto cleanup;
}
// clear group sticky bit on private_tmp_dir
if (chmod(private_tmp_dir, perms) != 0) {
fprintf(ERRORFILE, "Could not chmod private_tmp_dir: %s\n", private_tmp_dir);
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
goto cleanup;
}
if (mkdirs(private_var_tmp_dir, perms) != 0) {
fprintf(ERRORFILE, "Could not create private_var_tmp_dir: %s\n", private_var_tmp_dir);
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
goto cleanup;
}
// clear group sticky bit on private_tmp_dir
if (chmod(private_var_tmp_dir, perms) != 0) {
fprintf(ERRORFILE, "Could not chmod private_var_tmp_dir: %s\n", private_var_tmp_dir);
result = COULD_NOT_CREATE_TMP_DIRECTORIES;
goto cleanup;
}
cleanup:
free(tmp_dir);
free(private_tmp_dir);
free(private_var_tmp_dir);
return result;
}
@ -1051,6 +1111,36 @@ static int open_file_as_nm(const char* filename) {
return result;
}
/**
* Check the pidfile as the node manager. File should not exist.
* Returns 0 on file doesn't exist and -1 on file does exist.
*/
int check_pidfile_as_nm(const char* pidfile) {
int result = 0;
uid_t user = geteuid();
gid_t group = getegid();
if (change_effective_user(nm_uid, nm_gid) != 0) {
return -1;
}
struct stat statbuf;
if (stat(pidfile, &statbuf) == 0) {
fprintf(ERRORFILE, "pid file already exists: %s\n", pidfile);
result = -1;
}
if (errno != ENOENT) {
fprintf(ERRORFILE, "Error accessing %s : %s\n", pidfile,
strerror(errno));
result = -1;
}
if (change_effective_user(user, group)) {
result = -1;
}
return result;
}
/**
* Copy a file from a fd to a given filename.
* The new file must not exist and it is created with permissions perm.
@ -1863,6 +1953,61 @@ int create_yarn_sysfs(const char* user, const char *app_id,
return result;
}
int setup_container_paths(const char* user, const char* app_id,
const char *container_id, const char *work_dir, const char *script_name,
const char *cred_file, int https, const char *keystore_file, const char *truststore_file,
char* const* local_dirs, char* const* log_dirs) {
char *script_file_dest = NULL;
char *cred_file_dest = NULL;
char *keystore_file_dest = NULL;
char *truststore_file_dest = NULL;
int container_file_source = -1;
int cred_file_source = -1;
int keystore_file_source = -1;
int truststore_file_source = -1;
int result = initialize_user(user, local_dirs);
if (result != 0) {
return result;
}
int rc = create_script_paths(
work_dir, script_name, cred_file, https, keystore_file, truststore_file, &script_file_dest, &cred_file_dest,
&keystore_file_dest, &truststore_file_dest, &container_file_source, &cred_file_source, &keystore_file_source, &truststore_file_source);
if (rc != 0) {
fputs("Could not create script path\n", ERRORFILE);
goto cleanup;
}
rc = create_log_dirs(app_id, log_dirs);
if (rc != 0) {
fputs("Could not create log files and directories\n", ERRORFILE);
goto cleanup;
}
rc = create_local_dirs(user, app_id, container_id,
work_dir, script_name, cred_file, https, keystore_file, truststore_file, local_dirs, log_dirs,
1, script_file_dest, cred_file_dest, keystore_file_dest, truststore_file_dest,
container_file_source, cred_file_source, keystore_file_source, truststore_file_source);
if (rc != 0) {
fputs("Could not create local files and directories\n", ERRORFILE);
goto cleanup;
}
rc = create_yarn_sysfs(user, app_id, container_id, work_dir, local_dirs);
if (rc != 0) {
fputs("Could not create user yarn sysfs directory\n", ERRORFILE);
goto cleanup;
}
cleanup:
free(script_file_dest);
free(cred_file_dest);
return rc;
}
int launch_docker_container_as_user(const char * user, const char *app_id,
const char *container_id, const char *work_dir,
const char *script_name, const char *cred_file,

View File

@ -52,7 +52,9 @@ enum operations {
REMOVE_DOCKER_CONTAINER = 13,
INSPECT_DOCKER_CONTAINER = 14,
RUN_AS_USER_SYNC_YARN_SYSFS = 15,
EXEC_CONTAINER = 16
EXEC_CONTAINER = 16,
RUN_RUNC_CONTAINER = 17,
REAP_RUNC_LAYER_MOUNTS = 18
};
#define NM_GROUP_KEY "yarn.nodemanager.linux-container-executor.group"
@ -72,10 +74,14 @@ enum operations {
#define TC_SUPPORT_ENABLED_KEY "feature.tc.enabled"
#define MOUNT_CGROUP_SUPPORT_ENABLED_KEY "feature.mount-cgroup.enabled"
#define YARN_SYSFS_SUPPORT_ENABLED_KEY "feature.yarn.sysfs.enabled"
#define RUNC_SUPPORT_ENABLED_KEY "feature.runc.enabled"
#define TMP_DIR "tmp"
#define ROOT_TMP_DIR "private_slash_tmp"
#define ROOT_VAR_TMP_DIR "private_var_slash_tmp"
#define COMMAND_FILE_SECTION "command-execution"
extern struct passwd *user_detail;
extern struct section executor_cfg;
//function used to load the configurations present in the secure config
void read_executor_config(const char* file_name);
@ -175,6 +181,9 @@ int delete_as_user(const char *user,
// assumed to be an absolute path.
int list_as_user(const char *target_dir);
// Check the pidfile as the node manager. File should not exist.
int check_pidfile_as_nm(const char* filename);
// set the uid and gid of the node manager. This is used when doing some
// priviledged operations for setting the effective uid and gid.
void set_nm_uid(uid_t user, gid_t group);
@ -244,6 +253,10 @@ int create_directory_for_user(const char* path);
int change_user(uid_t user, gid_t group);
int change_effective_user(uid_t user, gid_t group);
int change_effective_user_to_nm();
int mount_cgroup(const char *pair, const char *hierarchy);
int check_dir(const char* npath, mode_t st_mode, mode_t desired,
@ -255,6 +268,14 @@ int create_validate_dir(const char* npath, mode_t perm, const char* path,
/** Check if a feature is enabled in the specified configuration. */
int is_feature_enabled(const char* feature_key, int default_value,
struct section *cfg);
char* get_exit_code_file(const char* pid_file);
int wait_and_write_exit_code(pid_t pid, const char* exit_code_file);
int setup_container_paths(const char* user, const char* app_id,
const char *container_id, const char* work_dir, const char* script_path,
const char *cred_path, int https, const char *keystore_file, const char *truststore_file,
char * const* local_dirs, char* const* log_dirs);
/** Check if tc (traffic control) support is enabled in configuration. */
int is_tc_support_enabled();
@ -341,3 +362,9 @@ int remove_docker_container(char **argv, int argc);
* Check if terminal feature is enabled
*/
int is_terminal_support_enabled();
/**
* Check if runC feature is enabled
*/
int is_runc_support_enabled();

View File

@ -26,6 +26,8 @@
#include "modules/cgroups/cgroups-operations.h"
#include "modules/devices/devices-module.h"
#include "utils/string-utils.h"
#include "runc/runc.h"
#include "runc/runc_reap.h"
#include <errno.h>
#include <grp.h>
@ -35,45 +37,33 @@
#include <signal.h>
static void display_usage(FILE *stream) {
const char* disabled = "[DISABLED]";
const char* enabled = " ";
fputs("Usage: container-executor --checksetup\n"
" container-executor --mount-cgroups <hierarchy> "
"<controller=path>...\n", stream);
const char* de = is_tc_support_enabled() ? enabled : disabled;
fprintf(stream,
"Usage: container-executor --checksetup\n"
" container-executor --mount-cgroups <hierarchy> "
"<controller=path>\n" );
"%s container-executor --tc-modify-state <command-file>\n"
"%s container-executor --tc-read-state <command-file>\n"
"%s container-executor --tc-read-stats <command-file>\n",
de, de, de);
if(is_tc_support_enabled()) {
fprintf(stream,
" container-executor --tc-modify-state <command-file>\n"
" container-executor --tc-read-state <command-file>\n"
" container-executor --tc-read-stats <command-file>\n" );
} else {
fprintf(stream,
"[DISABLED] container-executor --tc-modify-state <command-file>\n"
"[DISABLED] container-executor --tc-read-state <command-file>\n"
"[DISABLED] container-executor --tc-read-stats <command-file>\n");
}
de = is_terminal_support_enabled() ? enabled : disabled;
fprintf(stream, "%s container-executor --exec-container <command-file>\n", de);
if(is_docker_support_enabled()) {
fprintf(stream,
" container-executor --run-docker <command-file>\n"
" container-executor --remove-docker-container [hierarchy] "
"<container_id>\n"
" container-executor --inspect-docker-container <container_id>\n");
} else {
fprintf(stream,
"[DISABLED] container-executor --run-docker <command-file>\n"
"[DISABLED] container-executor --remove-docker-container [hierarchy] "
"<container_id>\n"
"[DISABLED] container-executor --inspect-docker-container "
"<format> ... <container_id>\n");
}
de = is_docker_support_enabled() ? enabled : disabled;
fprintf(stream, "%s container-executor --run-docker <command-file>\n", de);
fprintf(stream, "%s container-executor --remove-docker-container [hierarchy] <container_id>\n", de);
fprintf(stream, "%s container-executor --inspect-docker-container <container_id>\n", de);
if (is_terminal_support_enabled()) {
fprintf(stream,
" container-executor --exec-container <command-file>\n");
} else {
fprintf(stream,
"[DISABLED] container-executor --exec-container <command-file>\n");
}
de = is_runc_support_enabled() ? enabled : disabled;
fprintf(stream,
"%s container-executor --run-runc-container <command-file>\n", de);
fprintf(stream,
"%s container-executor --reap-runc-layer-mounts <retain-count>\n", de);
fprintf(stream,
" container-executor <user> <yarn-user> <command> <command-args>\n"
@ -85,27 +75,21 @@ static void display_usage(FILE *stream) {
INITIALIZE_CONTAINER, LAUNCH_CONTAINER);
if(is_tc_support_enabled()) {
fprintf(stream, "optional-tc-command-file\n");
fputs("optional-tc-command-file\n", stream);
} else {
fprintf(stream, "\n");
fputs("\n", stream);
}
if(is_docker_support_enabled()) {
fprintf(stream,
" launch docker container: %2d appid containerid workdir "
de = is_docker_support_enabled() ? enabled : disabled;
fprintf(stream,
"%11s launch docker container: %2d appid containerid workdir "
"container-script tokens pidfile nm-local-dirs nm-log-dirs "
"docker-command-file resources ", LAUNCH_DOCKER_CONTAINER);
} else {
fprintf(stream,
"[DISABLED] launch docker container: %2d appid containerid workdir "
"container-script tokens pidfile nm-local-dirs nm-log-dirs "
"docker-command-file resources ", LAUNCH_DOCKER_CONTAINER);
}
"docker-command-file resources ", de, LAUNCH_DOCKER_CONTAINER);
if(is_tc_support_enabled()) {
fprintf(stream, "optional-tc-command-file\n");
fputs("optional-tc-command-file\n", stream);
} else {
fprintf(stream, "\n");
fputs("\n", stream);
}
fprintf(stream,
@ -244,7 +228,7 @@ static void display_feature_disabled_message(const char* name) {
fprintf(ERRORFILE, "Feature disabled: %s\n", name);
}
/* Use to store parsed input parmeters for various operations */
/* Use to store parsed input parameters for various operations */
static struct {
char *cgroups_hierarchy;
char *traffic_control_command_file;
@ -267,6 +251,7 @@ static struct {
const char *target_dir;
int container_pid;
int signal;
int runc_layer_count;
const char *command_file;
} cmd_input;
@ -435,6 +420,44 @@ static int validate_arguments(int argc, char **argv , int *operation) {
}
}
if (strcmp("--run-runc-container", argv[1]) == 0) {
if (is_runc_support_enabled()) {
if (argc != 3) {
display_usage(stdout);
return INVALID_ARGUMENT_NUMBER;
}
optind++;
cmd_input.command_file = argv[optind++];
*operation = RUN_RUNC_CONTAINER;
return 0;
} else {
display_feature_disabled_message("runc");
return FEATURE_DISABLED;
}
}
if (strcmp("--reap-runc-layer-mounts", argv[1]) == 0) {
if (is_runc_support_enabled()) {
if (argc != 3) {
display_usage(stdout);
return INVALID_ARGUMENT_NUMBER;
}
optind++;
const char* valstr = argv[optind++];
if (sscanf(valstr, "%d", &cmd_input.runc_layer_count) != 1
|| cmd_input.runc_layer_count < 0) {
fprintf(ERRORFILE, "Bad runc layer count: %s\n", valstr);
return INVALID_COMMAND_PROVIDED;
}
*operation = REAP_RUNC_LAYER_MOUNTS;
return 0;
} else {
display_feature_disabled_message("runc");
return FEATURE_DISABLED;
}
}
/* Now we have to validate 'run as user' operations that don't use
a 'long option' - we should fix this at some point. The validation/argument
parsing here is extensive enough that it done in a separate function */
@ -786,6 +809,16 @@ int main(int argc, char **argv) {
exit_code = FEATURE_DISABLED;
}
break;
case RUN_RUNC_CONTAINER:
exit_code = run_runc_container(cmd_input.command_file);
break;
case REAP_RUNC_LAYER_MOUNTS:
exit_code = reap_runc_layer_mounts(cmd_input.runc_layer_count);
break;
default:
fprintf(ERRORFILE, "Unexpected operation code: %d\n", operation);
exit_code = INVALID_COMMAND_PROVIDED;
break;
}
if (exit_code) {

View File

@ -323,6 +323,12 @@ const char *get_error_message(const int error_code) {
return "Invalid docker runtime";
case DOCKER_SERVICE_MODE_DISABLED:
return "Docker service mode disabled";
case ERROR_RUNC_SETUP_FAILED:
return "runC setup failed";
case ERROR_RUNC_RUN_FAILED:
return "runC run failed";
case ERROR_RUNC_REAP_LAYER_MOUNTS_FAILED:
return "runC reap layer mounts failed";
default:
return "Unknown error code";
}

View File

@ -100,7 +100,10 @@ enum errorcodes {
INVALID_DOCKER_IMAGE_TRUST = 72,
INVALID_DOCKER_TMPFS_MOUNT = 73,
INVALID_DOCKER_RUNTIME = 74,
DOCKER_SERVICE_MODE_DISABLED = 75
DOCKER_SERVICE_MODE_DISABLED = 75,
ERROR_RUNC_SETUP_FAILED = 76,
ERROR_RUNC_RUN_FAILED = 77,
ERROR_RUNC_REAP_LAYER_MOUNTS_FAILED = 78
};
/* Macros for min/max. */

View File

@ -22,11 +22,14 @@
#include <dirent.h>
#include <limits.h>
#include <errno.h>
#include <stdarg.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "string-utils.h"
/*
* if all chars in the input str are numbers
* return true/false
@ -189,3 +192,172 @@ int str_ends_with(const char *s, const char *suffix) {
size_t suffix_len = strlen(suffix);
return suffix_len <= slen && !strcmp(s + slen - suffix_len, suffix);
}
/* Returns the corresponding hexadecimal character for a nibble. */
static char nibble_to_hex(unsigned char nib) {
return nib < 10 ? '0' + nib : 'a' + nib - 10;
}
/**
* Converts a sequence of bytes into a hexadecimal string.
*
* Returns a pointer to the allocated string on success or NULL on error.
*/
char* to_hexstring(unsigned char* bytes, unsigned int len) {
char* hexstr = malloc(len * 2 + 1);
if (hexstr == NULL) {
return NULL;
}
unsigned char* src = bytes;
char* dest = hexstr;
for (unsigned int i = 0; i < len; ++i) {
unsigned char val = *src++;
*dest++ = nibble_to_hex((val >> 4) & 0xF);
*dest++ = nibble_to_hex(val & 0xF);
}
*dest = '\0';
return hexstr;
}
/**
* Initialize an uninitialized strbuf with the specified initial capacity.
*
* Returns true on success or false if memory could not be allocated.
*/
bool strbuf_init(strbuf* sb, size_t initial_capacity) {
memset(sb, 0, sizeof(*sb));
char* new_buffer = malloc(initial_capacity);
if (new_buffer == NULL) {
return false;
}
sb->buffer = new_buffer;
sb->capacity = initial_capacity;
sb->length = 0;
return true;
}
/**
* Allocate and initialize a strbuf with the specified initial capacity.
*
* Returns a pointer to the allocated and initialized strbuf or NULL on error.
*/
strbuf* strbuf_alloc(size_t initial_capacity) {
strbuf* sb = malloc(sizeof(*sb));
if (sb != NULL) {
if (!strbuf_init(sb, initial_capacity)) {
free(sb);
sb = NULL;
}
}
return sb;
}
/**
* Detach the underlying character buffer from a string buffer.
*
* Returns the heap-allocated, NULL-terminated character buffer.
* NOTE: The caller is responsible for freeing the result.
*/
char* strbuf_detach_buffer(strbuf* sb) {
char* result = NULL;
if (sb != NULL) {
result = sb->buffer;
sb->buffer = NULL;
sb->length = 0;
sb->capacity = 0;
}
return result;
}
/**
* Release memory associated with a strbuf but not the strbuf structure itself.
* Useful for stack-allocated strbuf objects or structures that embed a strbuf.
* Use strbuf_free for heap-allocated string buffers.
*/
void strbuf_destroy(strbuf* sb) {
if (sb != NULL) {
free(sb->buffer);
sb->buffer = NULL;
sb->capacity = 0;
sb->length = 0;
}
}
/**
* Free a strbuf and all memory associated with it.
*/
void strbuf_free(strbuf* sb) {
if (sb != NULL) {
strbuf_destroy(sb);
free(sb);
}
}
/**
* Resize a strbuf to the specified new capacity.
*
* Returns true on success or false if there was an error.
*/
bool strbuf_realloc(strbuf* sb, size_t new_capacity) {
if (new_capacity < sb->length + 1) {
// New capacity would result in a truncation of the existing string.
return false;
}
char* new_buffer = realloc(sb->buffer, new_capacity);
if (!new_buffer) {
return false;
}
sb->buffer = new_buffer;
sb->capacity = new_capacity;
return true;
}
/**
* Append a formatted string to the current contents of a strbuf.
*
* Returns true on success or false if there was an error.
*/
bool strbuf_append_fmt(strbuf* sb, size_t realloc_extra,
const char* fmt, ...) {
if (sb->length > sb->capacity) {
return false;
}
if (sb->length == sb->capacity) {
size_t incr = (realloc_extra == 0) ? 1024 : realloc_extra;
if (!strbuf_realloc(sb, sb->capacity + incr)) {
return false;
}
}
size_t remain = sb->capacity - sb->length;
va_list vargs;
va_start(vargs, fmt);
int needed = vsnprintf(sb->buffer + sb->length, remain, fmt, vargs);
va_end(vargs);
if (needed == -1) {
return false;
}
needed += 1; // vsnprintf result does NOT include terminating NUL
if (needed > remain) {
// result was truncated so need to realloc and reprint
size_t new_size = sb->length + needed + realloc_extra;
if (!strbuf_realloc(sb, new_size)) {
return false;
}
remain = sb->capacity - sb->length;
va_start(vargs, fmt);
needed = vsnprintf(sb->buffer + sb->length, remain, fmt, vargs);
va_end(vargs);
if (needed == -1) {
return false;
}
needed += 1; // vsnprintf result does NOT include terminating NUL
}
sb->length += needed - 1; // length does not include terminating NUL
return true;
}

View File

@ -23,6 +23,16 @@
#ifndef _UTILS_STRING_UTILS_H_
#define _UTILS_STRING_UTILS_H_
#include <stdbool.h>
#include <stddef.h>
typedef struct strbuf_struct {
char* buffer; // points to beginning of the string
size_t length; // strlen of buffer (sans trailing NUL)
size_t capacity; // capacity of the buffer
} strbuf;
/*
* Get numbers split by comma from a input string
* return false/true
@ -44,4 +54,61 @@ char *make_string(const char *fmt, ...);
* return 1 if succeeded
*/
int str_ends_with(const char *s, const char *suffix);
/**
* Converts a sequence of bytes into a hexadecimal string.
*
* Returns a pointer to the allocated string on success or NULL on error.
*/
char* to_hexstring(unsigned char* bytes, unsigned int len);
/**
* Allocate and initialize a strbuf with the specified initial capacity.
*
* Returns a pointer to the allocated and initialized strbuf or NULL on error.
*/
strbuf* strbuf_alloc(size_t initial_capacity);
/**
* Initialize an uninitialized strbuf with the specified initial capacity.
*
* Returns true on success or false if memory could not be allocated.
*/
bool strbuf_init(strbuf* sb, size_t initial_capacity);
/**
* Resize a strbuf to the specified new capacity.
*
* Returns true on success or false if there was an error.
*/
bool strbuf_realloc(strbuf* sb, size_t new_capacity);
/**
* Detach the underlying character buffer from a string buffer.
*
* Returns the heap-allocated, NULL-terminated character buffer.
* NOTE: The caller is responsible for freeing the result.
*/
char* strbuf_detach_buffer(strbuf* sb);
/**
* Releases the memory underneath a string buffer but does NOT free the
* strbuf structure itself. This is particularly useful for stack-allocated
* strbuf objects or structures that embed a strbuf structure.
* strbuf_free should be used for heap-allocated string buffers.
*/
void strbuf_destroy(strbuf* sb);
/**
* Free a strbuf and all memory associated with it.
*/
void strbuf_free(strbuf* sb);
/**
* Append a formatted string to the current contents of a strbuf.
*
* Returns true on success or false if there was an error.
*/
bool strbuf_append_fmt(strbuf* sb, size_t realloc_extra, const char* fmt, ...);
#endif

View File

@ -17,16 +17,86 @@
*/
#include <gtest/gtest.h>
#include <main/native/container-executor/impl/util.h>
#include <cstdio>
#include <pwd.h>
extern "C" {
#include "util.h"
#include "container-executor.h"
}
#define TMPDIR "/tmp"
#define TEST_ROOT TMPDIR "/test-container-executor"
int write_config_file(const char *file_name, int banned) {
FILE *file;
file = fopen(file_name, "w");
if (file == NULL) {
printf("Failed to open %s.\n", file_name);
return EXIT_FAILURE;
}
if (banned != 0) {
fprintf(file, "banned.users=bannedUser\n");
fprintf(file, "min.user.id=500\n");
} else {
fprintf(file, "min.user.id=0\n");
}
fprintf(file, "allowed.system.users=allowedUser,daemon\n");
fprintf(file, "feature.yarn.sysfs.enabled=1\n");
fclose(file);
return 0;
}
int main(int argc, char **argv) {
ERRORFILE = stderr;
LOGFILE = stdout;
printf("\nMaking test dir\n");
if (mkdirs(TEST_ROOT, 0755) != 0) {
exit(1);
}
if (chmod(TEST_ROOT, 0755) != 0) { // in case of umask
exit(1);
}
// We need a valid config before the test really starts for the check_user
// and set_user calls
printf("\nCreating test.cfg\n");
if (write_config_file(TEST_ROOT "/test.cfg", 1) != 0) {
exit(1);
}
printf("\nLoading test.cfg\n");
read_executor_config(TEST_ROOT "/test.cfg");
printf("\nDetermining user details\n");
char* username = strdup(getpwuid(getuid())->pw_name);
struct passwd *username_info = check_user(username);
printf("\nSetting NM UID\n");
set_nm_uid(username_info->pw_uid, username_info->pw_gid);
// Make sure that username owns all the files now
printf("\nEnsuring ownership of test dir\n");
if (chown(TEST_ROOT, username_info->pw_uid, username_info->pw_gid) != 0) {
exit(1);
}
if (chown(TEST_ROOT "/test.cfg",
username_info->pw_uid, username_info->pw_gid) != 0) {
exit(1);
}
printf("\nSetting effective user\n");
if (set_user(username)) {
exit(1);
}
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
int rc = RUN_ALL_TESTS();
printf("Attempting to clean up from any previous runs\n");
// clean up any junk from previous run
if (system("chmod -R u=rwx " TEST_ROOT "; rm -fr " TEST_ROOT)) {
exit(1);
return rc;
}
}

View File

@ -29,6 +29,7 @@
#include <gtest/gtest.h>
#include <sstream>
#include <openssl/evp.h>
extern "C" {
#include "utils/string-utils.h"
@ -36,97 +37,230 @@
namespace ContainerExecutor {
class TestStringUtils : public ::testing::Test {
protected:
virtual void SetUp() {
class TestStringUtils : public ::testing::Test {
protected:
virtual void SetUp() {
}
}
virtual void TearDown() {
virtual void TearDown() {
}
};
}
};
TEST_F(TestStringUtils, test_get_numbers_split_by_comma) {
const char* input = ",1,2,3,-1,,1,,0,";
int* numbers;
size_t n_numbers;
int rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
TEST_F(TestStringUtils, test_get_numbers_split_by_comma) {
const char* input = ",1,2,3,-1,,1,,0,";
int* numbers;
size_t n_numbers;
int rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(6, n_numbers);
ASSERT_EQ(1, numbers[0]);
ASSERT_EQ(-1, numbers[3]);
ASSERT_EQ(0, numbers[5]);
free(numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(6, n_numbers);
ASSERT_EQ(1, numbers[0]);
ASSERT_EQ(-1, numbers[3]);
ASSERT_EQ(0, numbers[5]);
free(numbers);
input = "3";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(1, n_numbers);
ASSERT_EQ(3, numbers[0]);
free(numbers);
input = "3";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(1, n_numbers);
ASSERT_EQ(3, numbers[0]);
free(numbers);
input = "";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(0, n_numbers);
free(numbers);
input = "";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(0, n_numbers);
free(numbers);
input = ",,";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(0, n_numbers);
free(numbers);
input = ",,";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_EQ(0, rc) << "Should succeeded\n";
ASSERT_EQ(0, n_numbers);
free(numbers);
input = "1,2,aa,bb";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_TRUE(0 != rc) << "Should failed\n";
free(numbers);
input = "1,2,aa,bb";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_TRUE(0 != rc) << "Should failed\n";
free(numbers);
input = "1,2,3,-12312312312312312312321311231231231";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_TRUE(0 != rc) << "Should failed\n";
free(numbers);
}
input = "1,2,3,-12312312312312312312321311231231231";
rc = get_numbers_split_by_comma(input, &numbers, &n_numbers);
std::cout << "Testing input=" << input << "\n";
ASSERT_TRUE(0 != rc) << "Should failed\n";
free(numbers);
}
TEST_F(TestStringUtils, test_validate_container_id) {
TEST_F(TestStringUtils, test_validate_container_id) {
const char *good_input[] = {
"container_e134_1499953498516_50875_01_000007",
"container_1499953498516_50875_01_000007",
"container_e1_12312_11111_02_000001"
};
const char *good_input[] = {
"container_e134_1499953498516_50875_01_000007",
"container_1499953498516_50875_01_000007",
"container_e1_12312_11111_02_000001"
};
const char *bad_input[] = {
"CONTAINER",
"container_e1_12312_11111_02_000001 | /tmp/file"
"container_e1_12312_11111_02_000001 || # /tmp/file",
"container_e1_12312_11111_02_000001 # /tmp/file",
"container_e1_12312_11111_02_000001' || touch /tmp/file #",
"ubuntu || touch /tmp/file #",
"''''''''"
};
const char *bad_input[] = {
"CONTAINER",
"container_e1_12312_11111_02_000001 | /tmp/file"
"container_e1_12312_11111_02_000001 || # /tmp/file",
"container_e1_12312_11111_02_000001 # /tmp/file",
"container_e1_12312_11111_02_000001' || touch /tmp/file #",
"ubuntu || touch /tmp/file #",
"''''''''"
};
int good_input_size = sizeof(good_input) / sizeof(char *);
int i = 0;
for (i = 0; i < good_input_size; i++) {
int op = validate_container_id(good_input[i]);
ASSERT_EQ(1, op);
}
int good_input_size = sizeof(good_input) / sizeof(char *);
int i = 0;
for (i = 0; i < good_input_size; i++) {
int op = validate_container_id(good_input[i]);
ASSERT_EQ(1, op);
}
int bad_input_size = sizeof(bad_input) / sizeof(char *);
int j = 0;
for (j = 0; j < bad_input_size; j++) {
int op = validate_container_id(bad_input[j]);
ASSERT_EQ(0, op);
}
}
int bad_input_size = sizeof(bad_input) / sizeof(char *);
int j = 0;
for (j = 0; j < bad_input_size; j++) {
int op = validate_container_id(bad_input[j]);
ASSERT_EQ(0, op);
}
}
TEST_F(TestStringUtils, test_to_hexstring) {
const char* input = "hello";
char* digest = NULL;
unsigned char raw_digest[EVP_MAX_MD_SIZE];
unsigned int raw_digest_len = 0;
int rc = 0;
EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
ASSERT_NE(nullptr, mdctx) << "Unable to create EVP MD context\n";
rc = EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
ASSERT_EQ(1, rc) << "Unable to initialize SHA256 digester\n";
rc = EVP_DigestFinal_ex(mdctx, raw_digest, &raw_digest_len);
ASSERT_EQ(1, rc) << "Unable to compute digest\n";
rc = EVP_DigestUpdate(mdctx, input, strlen(input));
ASSERT_EQ(1, rc) << "Unable to compute digest\n";
digest = to_hexstring(raw_digest, raw_digest_len);
ASSERT_STREQ("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
digest) << "Digest is not equal to expected hash\n";
EVP_MD_CTX_destroy(mdctx);
free(digest);
}
TEST_F(TestStringUtils, test_strbuf_on_stack) {
const int sb_incr = 16;
strbuf sb;
bool rc;
rc = strbuf_init(&sb, sb_incr);
ASSERT_EQ(true, rc) << "Unable to init strbuf\n";
rc = strbuf_append_fmt(&sb, sb_incr, "%s%s%s", "hello", "foo", "bar");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("hellofoobar", sb.buffer);
rc = strbuf_append_fmt(&sb, sb_incr, "%s%s%s", "some longer strings",
" that will cause the strbuf", " to have to realloc");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("hellofoobarsome longer strings that will cause the strbuf to have to realloc", sb.buffer);
strbuf_destroy(&sb);
}
TEST_F(TestStringUtils, test_strbuf_in_heap) {
const int sb_incr = 16;
strbuf *sb = NULL;
bool rc;
sb = strbuf_alloc(sb_incr);
ASSERT_NE(nullptr, sb) << "Unable to init strbuf\n";
rc = strbuf_append_fmt(sb, sb_incr, "%s%s%s", "hello", "foo", "bar");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf";
ASSERT_STREQ("hellofoobar", sb->buffer);
rc = strbuf_append_fmt(sb, sb_incr, "%s%s%s", "some longer strings",
" that will cause the strbuf", " to have to realloc");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("hellofoobarsome longer strings that will cause the strbuf to have to realloc", sb->buffer);
strbuf_free(sb);
}
TEST_F(TestStringUtils, test_strbuf_detach) {
const int sb_incr = 16;
strbuf sb;
char *buf;
bool rc;
rc = strbuf_init(&sb, sb_incr);
ASSERT_EQ(true, rc) << "Unable to init strbuf\n";
rc = strbuf_append_fmt(&sb, sb_incr, "%s%s%s", "hello", "foo", "bar");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("hellofoobar", sb.buffer);
rc = strbuf_append_fmt(&sb, sb_incr, "%s%s%s", "some longer strings",
" that will cause the strbuf", " to have to realloc");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("hellofoobarsome longer strings that will cause the strbuf to have to realloc", sb.buffer);
buf = strbuf_detach_buffer(&sb);
ASSERT_NE(nullptr, buf) << "Unable to detach char buf from strbuf\n";
rc = strbuf_append_fmt(&sb, sb_incr, "%s%s%s", "Buffer detached",
" so this should allocate", " a new buffer in strbuf");
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_STREQ("Buffer detached so this should allocate a new buffer in strbuf", sb.buffer);
ASSERT_STREQ("hellofoobarsome longer strings that will cause the strbuf to have to realloc", buf);
free(buf);
strbuf_destroy(&sb);
}
TEST_F(TestStringUtils, test_strbuf_realloc) {
const int sb_incr = 5;
strbuf sb;
char buf[] = "1234567890";
bool rc;
int len = strlen(buf);
rc = strbuf_init(&sb, sb_incr);
ASSERT_EQ(true, rc) << "Unable to init strbuf\n";
ASSERT_NE(nullptr, sb.buffer) << "Unable to init strbuf buffer\n";
ASSERT_EQ(5, sb.capacity) << "Unable to init strbuf capacity\n";
ASSERT_EQ(0, sb.length) << "Unable to init strbuf length\n";
rc = strbuf_append_fmt(&sb, sb_incr, "%s", buf);
ASSERT_EQ(true, rc) << "Unable to append format to strbuf\n";
ASSERT_NE(nullptr, sb.buffer) << "Unable to append strbuf buffer\n";
ASSERT_EQ(len + sb_incr + 1, sb.capacity) << "Unable to update strbuf capacity\n";
ASSERT_EQ(len, sb.length) << "Unable to update strbuf length\n";
rc = strbuf_realloc(&sb, 10);
ASSERT_EQ(false, rc) << "realloc to smaller capacity succeeded and has truncated existing string\n";
strbuf_destroy(&sb);
}
} // namespace ContainerExecutor