From: Kamalesh Babulal Date: Fri, 10 Feb 2023 06:22:06 +0000 (+0000) Subject: ftests: Add a test to validate systemd configurations X-Git-Tag: v3.1.0~198 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0da586ec36e0f0a9bd918ea9d1afc6db20d80b65;p=thirdparty%2Flibcgroup.git ftests: Add a test to validate systemd configurations Add a test case to parse test different combination of valid and invalid systemd configurations. ----------------------------------------------------------------- Test Results: Run Date: Feb 05 08:33:41 Passed: 1 test(s) Skipped: 0 test(s) Failed: 0 test(s) ----------------------------------------------------------------- Timing Results: Test Time (sec) ------------------------------------------------- setup 0.00 060-sudo-cgconfigparser-systemd.py 4.31 teardown 0.00 ------------------------------------------------- Total Run Time 4.31 Signed-off-by: Kamalesh Babulal Signed-off-by: Tom Hromatka --- diff --git a/tests/ftests/060-sudo-cgconfigparser-systemd.py b/tests/ftests/060-sudo-cgconfigparser-systemd.py new file mode 100755 index 00000000..5d2e5db0 --- /dev/null +++ b/tests/ftests/060-sudo-cgconfigparser-systemd.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-only +# +# Advanced cgconfigparser functionality test - systemd configurations +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# Author: Kamalesh Babulal + +from run import Run, RunError +from systemd import Systemd +from cgroup import Cgroup +import consts +import ftests +import time +import sys +import os + +CONTROLLER = 'cpu' +SYSTEMD_CGNAME = '060_cg_in_scope' +OTHER_CGNAME = '060_cg_not_in_scope' + +SLICE = 'libcgtests.slice' +SCOPE = 'test060.scope' + +CONFIG_FILE_NAME = os.path.join(os.getcwd(), '060cgconfig.conf') + +CONFIGURATIONS = [ + # [ 'systemd configuration file', 'Excepted error substring'] + ['systemd {\n}', 'Error: failed to parse file'], + + ['systemd {\n\tslice = libcgroup;\n}', + 'Error: Invalid systemd configuration slice value libcgroup'], + + ['systemd {\n\tscope = test060;\n}', + 'Error: Invalid systemd configuration scope value test060'], + + ['systemd {\n\tslice = libcgroup.slice;\n}', + 'Error: Invalid systemd setting, missing scope name'], + + ['systemd {\n\tscope = test060.scope;\n}', + 'Error: Invalid systemd setting, missing slice name'], + + ['systemd {\n\tsetdefault = yes;\n}', + 'Error: Invalid systemd setting, missing slice name'], + + ['systemd {\n\tpid = 123;\n}', + 'Error: Invalid systemd setting, missing slice name'], + + ['systemd {\n\tInvalid = Invalid;\n}', + 'Error: Invalid systemd configuration Invalid'], + + ['systemd {\n\tslice = libcgroup.slice;\n\tsetdefault = yes;\n\t}', + 'Error: Invalid systemd setting, missing scope name'], + + ['systemd {\n\tscope = test060.scope;\n\tsetdefault = yes;\n\t}', + 'Error: Invalid systemd setting, missing slice name'], + + ['systemd {\n\tslice = libcgroup.slice;\n\tscope = test060.scope;\n\t' + 'setdefault = invalid;\n\t}', + 'Error: Invalid systemd configuration setdefault'], + + ['systemd {\n\tslice = libcgroup.slice;\n\tscope = test060.scope;\n\t' + 'setdefault = yes;\n\tpid = abc;\n}', + 'Error: Invalid systemd configuration pid'], +] + + +def prereqs(config): + result = consts.TEST_PASSED + cause = None + + if config.args.container: + result = consts.TEST_SKIPPED + cause = 'This test cannot be run within a container' + + return result, cause + + +def setup(config): + return consts.TEST_PASSED, None + + +def write_conf_file(config, configurations): + f = open(CONFIG_FILE_NAME, 'w') + f.write(configurations) + f.close() + + +def test_invalid_configurations(config): + result = consts.TEST_PASSED + cause = None + + # Try parsing invalid systemd configurations from CONFIGURATION table + # and none of them is excepted to pass. + for configuration in CONFIGURATIONS: + write_conf_file(config, configuration[0]) + + try: + Cgroup.configparser(config, load_file=CONFIG_FILE_NAME) + except RunError as re: + if configuration[1] not in re.stdout: + result = consts.TEST_FAILED + tmp_cause = ( + 'Unexpected error {}, while parsing configuration:' + '\n{}'.format(re.stdout, configuration[0]) + ) + cause = '\n'.join(filter(None, [cause, tmp_cause])) + else: + result = consts.TEST_FAILED + tmp_cause = ( + 'Creation of systemd default slice/scope, erroneously succeeded with' + 'configuration:\n{}'.format(configuration[0]) + ) + cause = '\n'.join(filter(None, [cause, tmp_cause])) + + return result, cause + + +def test(config): + result = consts.TEST_PASSED + cause = None + + result, cause = test_invalid_configurations(config) + if result == consts.TEST_FAILED: + return result, cause + + pid = Systemd.write_config_with_pid(config, CONFIG_FILE_NAME, SLICE, SCOPE) + + # Pass a valid configuration to the parser + Cgroup.configparser(config, load_file=CONFIG_FILE_NAME) + + if not Cgroup.exists(config, CONTROLLER, os.path.join(SLICE, SCOPE), ignore_systemd=True): + result = consts.TEST_FAILED + cause = 'Failed to create systemd slice/scope' + return result, cause + + # It's invalid to pass the same configuration file twice. The values + # were already read and slice/scope cgroups were created, unless + # something has gone wrong, this should fail. + try: + Cgroup.configparser(config, load_file=CONFIG_FILE_NAME) + except RunError as re: + if 'already exists' not in re.stdout: + result = consts.TEST_FAILED + cause = 'Unexpected error {}'.format(re.stdout) + else: + result = consts.TEST_FAILED + cause = 'Creation of systemd default slice/scope erroneously succeeded' + + # killing the pid, should remove the scope cgroup too. + Run.run(['sudo', 'kill', '-9', pid]) + + # Let's pause and wait for the systemd to remove the scope. + time.sleep(1) + + if Cgroup.exists(config, CONTROLLER, os.path.join(SLICE, SCOPE), ignore_systemd=True): + result = consts.TEST_FAILED + cause = 'Systemd failed to remove the scope {}'.format(SCOPE) + + return result, cause + + +def teardown(config): + # The scope is already removed, when the task was killed. + try: + Systemd.remove_scope_slice_conf(config, SLICE, SCOPE, CONTROLLER, CONFIG_FILE_NAME) + except RunError as re: + if 'scope not loaded' not in re.stderr: + raise re + + +def main(config): + [result, cause] = prereqs(config) + if result != consts.TEST_PASSED: + return [result, cause] + + try: + result = consts.TEST_FAILED + setup(config) + [result, cause] = test(config) + finally: + teardown(config) + + return [result, cause] + + +if __name__ == '__main__': + config = ftests.parse_args() + # this test was invoked directly. run only it + config.args.num = int(os.path.basename(__file__).split('-')[0]) + sys.exit(ftests.main(config)) + +# vim: set et ts=4 sw=4: diff --git a/tests/ftests/Makefile.am b/tests/ftests/Makefile.am index 282e0ff5..96166ee4 100644 --- a/tests/ftests/Makefile.am +++ b/tests/ftests/Makefile.am @@ -80,6 +80,7 @@ EXTRA_DIST_PYTHON_TESTS = \ 057-sudo-set_permissions_v1.py \ 058-sudo-systemd_create_scope2.py \ 059-sudo-invalid_systemd_create_scope2.py \ + 060-sudo-cgconfigparser-systemd.py \ 098-cgdelete-non-existing-shared-mnt-cgroup-v1.py # Intentionally omit the stress test from the extra dist # 999-stress-cgroup_init.py