"""
This case is used for testing security option of the virt-sandbox command.
"""

import re
import logging

from virttest.lvsb import make_sandboxes

# The default base context, please check virt-sandbox man page.
# system_u:system_r:svirt_t:s0 for KVM,
# system_u:system_r:svirt_tcg_t:s0 for QEMU.
# system_u:system_r:svirt_lxc_net_t:s0 for LXC
_selinux_label = {'LXC': 'system_u:system_r:svirt_lxc_net_t:s0',
                  'KVM': 'system_u:system_r:svirt_t:s0',
                  'QEMU': 'system_u:system_r:svirt_tcg_t:s0'}


def verify_selinux_label(params):
    """
    Verify selinux context
    """
    sec_opt = None
    sec_cxt = None
    sec_cxt_sorted = None
    # The 'dynamic' is default security option and the selinux
    # category set is different each time
    options = params.get("lvsb_security_options1", "dynamic")
    cmd_outputs = params['lvsb_result']
    logging.debug("The selinux context '%s' from command output", cmd_outputs)

    # e.g static,label=system_u:system_r:svirt_lxc_net_t:s0:c412,c355
    if "=" in options:
        # To get real security option, it may be 'dynamic' or 'static'
        sec_opt = options.split("=")[0].split(",")[0]
        # To get selinux context
        sec_cxt = options.split("=")[1]
        logging.debug("The selinux context '%s' from input", sec_cxt)

    # The uri looks like '$hypervisor:///', the 'hypervisor' may be
    # LXC, QEMU, XEN, etc and 'lxc' is a default
    uri = params.get('lvsb_uri', 'lxc').upper().split(':')[0]
    expected_selinux_label = _selinux_label.get(uri)

    if sec_opt == "dynamic" or options == "dynamic":
        # The base context format is USER:ROLE:TYPE:LEVEL, and the 'LEVEL'
        # may include category set, which is randomly generated by sandbox
        patterns = r"%s:c\d*,c\d*" % expected_selinux_label
        match = re.search(patterns, cmd_outputs)

        if expected_selinux_label and cmd_outputs and match:
            logging.info("The selinux label '%s' is expected", cmd_outputs)
            return True
        else:
            return False

    if sec_opt == "static":
        # The security option is 'static'
        if sec_cxt and ":" in sec_cxt:
            sec_cxt_list = sec_cxt.split(":")
            if len(sec_cxt_list) == 5:
                user, role, types, level, category = tuple(sec_cxt_list)
                # The selinux category set may be an unordered or same for
                # users, but 'ps -eo label' always returns an ordered and
                # deduplicate selinux category set.
                category = ",".join(sorted(list(set(category.split(",")))))
                selinux_label_category_sorted = ['%s' % user, '%s' % role,
                                                 '%s' % types, '%s' % level,
                                                 '%s' % category]
                sec_cxt_sorted = ":".join(selinux_label_category_sorted)
                logging.debug("The selinux context with sorted and deduplicated"
                              " category: %s", sec_cxt_sorted)

        if (cmd_outputs == sec_cxt or sec_cxt_sorted and
                cmd_outputs == sec_cxt_sorted):
            logging.info("The selinux label '%s' is expected",
                         cmd_outputs)
            return True
        else:
            return False


def run(test, params, env):
    """
    Test security options of virt-sandbox command

    1) Positive testing
       1.1) dynamically allocate an SELinux label, using the default
            base context
       1.2) dynamically allocate an SELinux label, using the base context
            USER:ROLE:TYPE:LEVEL, instead of the default base context
       1.3) staticly allocate an SELinux label, using the selinux context
            USER:ROLE:TYPE:LEVEL
    2) Negative testing
       2.1) invalid security option
       2.2) invalid dynamic_selinux context
       2.3) invalid static selinux context
    """
    status_error = bool("yes" == params.get("status_error", "no"))
    # list of sandbox agregation managers
    sb_list = make_sandboxes(params, env)
    if not sb_list:
        test.fail("Failed to return list of instantiated "
                  "lvsb_testsandboxes classes")

    # Run a sandbox until timeout or finished w/ output
    # store list of stdout's for the sandbox in aggregate type
    cmd_output_list = sb_list[0].results()
    # Remove all duplicate items from result list
    cmd_outputs = list(set(cmd_output_list[0].splitlines()))

    # The selinux context should be unique
    if len(cmd_outputs) > 1:
        logging.error("There are different selinux contexts: %s", cmd_outputs)

    # To get exit codes of the command
    status = sb_list[0].are_failed()

    _params = dict(params)
    # To get output of the command
    _params['lvsb_result'] = cmd_outputs[0]

    # positive and negative testing #########
    if status_error:
        if status == 0:
            test.fail("%d not an expected command "
                      "return value" % status)
        elif verify_selinux_label(_params):
            test.fail("To expect selinux context is different!!")
        else:
            logging.info("It's an expected error: %s", _params['lvsb_result'])
    else:
        if status != 0:
            test.fail("%d not an expected command "
                      "return value" % status)
        if not verify_selinux_label(_params):
            test.fail("The selinux context doesn't match!!")
