diff --git a/bundle/lib/source/DobbySpecConfig.cpp b/bundle/lib/source/DobbySpecConfig.cpp index 326d428a3..648e995e6 100644 --- a/bundle/lib/source/DobbySpecConfig.cpp +++ b/bundle/lib/source/DobbySpecConfig.cpp @@ -31,10 +31,12 @@ #include #include #include +#include #include #include #include #include +#include #include // Compile time generated strings that (in theory) speeds up the processing @@ -63,6 +65,8 @@ static const ctemplate::StaticTemplateString USERNS_DISABLED = static const ctemplate::StaticTemplateString MEM_LIMIT = STS_INIT(MEM_LIMIT, "MEM_LIMIT"); +static const ctemplate::StaticTemplateString SWAPPINESS_ENABLED = + STS_INIT(SWAPPINESS_ENABLED, "SWAPPINESS_ENABLED"); static const ctemplate::StaticTemplateString CPU_SHARES_ENABLED = STS_INIT(CPU_SHARES_ENABLED, "CPU_SHARES_ENABLED"); static const ctemplate::StaticTemplateString CPU_SHARES_VALUE = @@ -190,6 +194,53 @@ static const ctemplate::StaticTemplateString SECCOMP_SYSCALLS = int DobbySpecConfig::mNumCores = -1; + +// ----------------------------------------------------------------------------- +/** + * @brief Detects whether the system is using cgroup v2 (unified hierarchy). + * + * This checks if /sys/fs/cgroup is mounted as cgroup2 filesystem. + * On cgroupv2, memory.swappiness is not supported in OCI config. + * + * @return true if running on cgroupv2, false otherwise (cgroupv1 or hybrid) + */ +static bool isCgroupV2() +{ + static bool checked = false; + static bool isV2 = false; + + if (!checked) + { + checked = true; + + // Check if /sys/fs/cgroup is mounted as cgroup2 + FILE* procMounts = setmntent("/proc/mounts", "r"); + if (procMounts != nullptr) + { + struct mntent mntBuf; + struct mntent* mnt; + char buf[PATH_MAX + 256]; + + while ((mnt = getmntent_r(procMounts, &mntBuf, buf, sizeof(buf))) != nullptr) + { + if (mnt->mnt_dir && strcmp(mnt->mnt_dir, "/sys/fs/cgroup") == 0) + { + if (mnt->mnt_type && strcmp(mnt->mnt_type, "cgroup2") == 0) + { + AI_LOG_INFO("detected cgroup v2 (unified hierarchy)"); + isV2 = true; + } + break; + } + } + endmntent(procMounts); + } + } + + return isV2; +} + + // TODO: should we only allowed these if a network namespace is enabled ? const std::map DobbySpecConfig::mAllowedCaps = { @@ -1274,6 +1325,11 @@ bool DobbySpecConfig::processMemLimit(const Json::Value& value, } dictionary->SetIntValue(MEM_LIMIT, memLimit); + // Only enable swappiness on cgroupv1 - cgroupv2 doesn't support this in OCI config + if (!isCgroupV2()) + { + dictionary->ShowSection(SWAPPINESS_ENABLED); + } return true; } diff --git a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template index 6cbdabd43..58a5278b2 100644 --- a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template @@ -328,8 +328,8 @@ static const char* ociJsonTemplate = R"JSON( ], "memory": { "limit": {{MEM_LIMIT}}, - "swap": {{MEM_LIMIT}}, - "swappiness": 60 + "swap": {{MEM_LIMIT}}{{#SWAPPINESS_ENABLED}}, + "swappiness": 60{{/SWAPPINESS_ENABLED}} }, "cpu": { {{#CPU_SHARES_ENABLED}} diff --git a/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template index 21fe91d38..420c98895 100644 --- a/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template @@ -339,8 +339,8 @@ static const char* ociJsonTemplate = R"JSON( ], "memory": { "limit": {{MEM_LIMIT}}, - "swap": {{MEM_LIMIT}}, - "swappiness": 60 + "swap": {{MEM_LIMIT}}{{#SWAPPINESS_ENABLED}}, + "swappiness": 60{{/SWAPPINESS_ENABLED}} }, "cpu": { {{#CPU_SHARES_ENABLED}} diff --git a/tests/L2_testing/test_runner/basic_sanity_tests.py b/tests/L2_testing/test_runner/basic_sanity_tests.py index f98e60fe9..ced5d3bd3 100755 --- a/tests/L2_testing/test_runner/basic_sanity_tests.py +++ b/tests/L2_testing/test_runner/basic_sanity_tests.py @@ -19,7 +19,7 @@ from subprocess import check_output import subprocess from time import sleep -import multiprocessing +import threading from os.path import basename tests = ( @@ -85,49 +85,50 @@ def execute_test(): return test_utils.count_print_results(output_table) -# we need to do this asynchronous as if there is no such string we would end in endless loop -def read_asynchronous(proc, string_to_find, timeout): - """Reads asynchronous from process. Ends when found string or timeout occurred. +# Module-level function for multiprocessing compatibility (must be picklable) +def _wait_for_string(proc, string_to_find): + """Waits indefinitely until string is found in process. Must be run with timeout multiprocess. Parameters: proc (process): process in which we want to read string_to_find (string): what we want to find in process - timeout (float): how long we should wait if string not found (seconds) Returns: - found (bool): True if found string_to_find inside proc. + None: Returns nothing if found, never ends if not found """ - # as this function should not be used outside asynchronous read, it is moved inside it - def wait_for_string(proc, string_to_find): - """Waits indefinitely until string is found in process. Must be run with timeout multiprocess. + while True: + # notice that all data are in stderr not in stdout, this is DobbyDaemon design + output = proc.stderr.readline() + if string_to_find in output: + test_utils.print_log("Found string \"%s\"" % string_to_find, test_utils.Severity.debug) + return + - Parameters: - proc (process): process in which we want to read - string_to_find (string): what we want to find in process +# we need to do this asynchronous as if there is no such string we would end in endless loop +def read_asynchronous(proc, string_to_find, timeout): + """Reads asynchronous from process. Ends when found string or timeout occurred. - Returns: - None: Returns nothing if found, never ends if not found + Parameters: + proc (process): process in which we want to read + string_to_find (string): what we want to find in process + timeout (float): how long we should wait if string not found (seconds) - """ + Returns: + found (bool): True if found string_to_find inside proc. - while True: - # notice that all data are in stderr not in stdout, this is DobbyDaemon design - output = proc.stderr.readline() - if string_to_find in output: - test_utils.print_log("Found string \"%s\"" % string_to_find, test_utils.Severity.debug) - return + """ found = False - reader = multiprocessing.Process(target=wait_for_string, args=(proc, string_to_find), kwargs={}) + reader = threading.Thread(target=_wait_for_string, args=(proc, string_to_find)) test_utils.print_log("Starting multithread read", test_utils.Severity.debug) reader.start() reader.join(timeout) # if thread still running if reader.is_alive(): test_utils.print_log("Reader still exists, closing", test_utils.Severity.debug) - reader.terminate() + # Note: threads cannot be forcefully terminated, but the main process will continue test_utils.print_log("Not found string \"%s\"" % string_to_find, test_utils.Severity.error) else: found = True