#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-only
#
# Test to create a systemd scope with pid using cgcreate
#
# Copyright (c) 2023 Oracle and/or its affiliates.
# Author: Kamalesh Babulal <kamalesh.babulal@oracle.com>
#

from process import Process
from systemd import Systemd
from libcgroup import Mode
from cgroup import Cgroup
from run import RunError
import consts
import ftests
import sys
import os

CONTROLLERS = ['cpu', 'pids']
SLICE = 'libcgroup.slice'
CGNAME1 = os.path.join(SLICE, '084cgcreate1.scope')
CGNAME2 = os.path.join(SLICE, '084cgcreate2.scope')


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

    if Cgroup.get_cgroup_mode(config) != Mode.CGROUP_MODE_UNIFIED:
        result = consts.TEST_SKIPPED
        cause = 'This test requires the unified cgroup hierarchy'

    if not Systemd.is_systemd_enabled():
        result = consts.TEST_SKIPPED
        cause = 'Systemd support not compiled in'

    return result, cause


def setup(config):
    pass


def test(config):
    result = consts.TEST_PASSED
    cause = None

    #
    # Test 1: Pass invalid task pid as scope_pid and systemd scope creation
    #         should fail
    #
    try:
        Cgroup.create_and_validate(config, CONTROLLERS, CGNAME1, create_scope=True,
                                   scope_pid=1000000)
    except RunError as re:
        if 'Failed to set unit properties: No such process' not in str(re):
            raise re

    #
    # Test 2: Pass a valid task pid as scope_pid and systemd scope creation
    #         will succeed. Read the scope cgroup.procs to find if the task
    #         passed by us was used as scope task.
    #
    scope_pid = config.process.create_process(config)
    Cgroup.create_and_validate(config, CONTROLLERS, CGNAME1, create_scope=True, scope_pid=scope_pid)

    try:
        pid = Cgroup.get_pids_in_cgroup(config, CGNAME1, CONTROLLERS[0])[0]
        if scope_pid != pid:
            result = consts.TEST_FAILED
            cause = 'scope created with other pid {}, expected pid {}'.format(pid, scope_pid)
            return result, cause
    except RunError:
        result = consts.TEST_FAILED
        cause = "Failed to read pid in {}'s cgroup.procs".format(CGNAME1)
        return result, cause

    #
    # Test 3: Pass the already created task pid as scope_pid for a new systemd
    #         scope.  This would mean the CGNAME1 should be killed and the pid
    #         should be the default scope task of CGNAME2
    Cgroup.create_and_validate(config, CONTROLLERS, CGNAME2, create_scope=True, scope_pid=scope_pid)

    # CGNAME1 should be deleted by the systemd.
    try:
        pid = Cgroup.get_pids_in_cgroup(config, CGNAME1, CONTROLLERS[0])[0]
    except RunError as re:
        if 'No such file or directory' not in re.stderr:
            raise re
    else:
        result = consts.TEST_FAILED
        cause = 'Erroneously succeeded reading cgroup.procs in {}'.format(CGNAME1)
        return result, cause

    try:
        pid = Cgroup.get_pids_in_cgroup(config, CGNAME2, CONTROLLERS[0])[0]
        if scope_pid != pid:
            result = consts.TEST_FAILED
            cause = 'scope created with other pid {}, expected pid {}'.format(pid, scope_pid)
            return result, cause
    except RunError:
        result = consts.TEST_FAILED
        cause = "Failed to read pid in {}'s cgroup.procs".format(CGNAME2)

    return result, cause


def teardown(config):
    pid = Cgroup.get_pids_in_cgroup(config, CGNAME2, CONTROLLERS[0])[0]
    Process.kill(config, pid)

    # systemd will automatically remove the cgroup once there are no more pids in
    # the cgroup, so we don't need to delete CGNAME2.  But let's try to remove the
    # slice
    try:
        Cgroup.delete(config, CONTROLLERS, SLICE)
    except RunError:
        pass


def main(config):
    [result, cause] = prereqs(config)
    if result != consts.TEST_PASSED:
        return [result, cause]

    setup(config)

    [result, cause] = test(config)
    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:
