#!/usr/bin/python3
# group: auto quick
#
# Test default kvm features for all machine types
#
# Copyright (c) 2022 Virtuozzo International GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import sys
import os
import re
import iotests

from qemu.machine import QEMUMachine


def list_machine_types():
    "Collect all machine types except 'none'"
    vm = iotests.VM()
    vm.launch()
    machines = [mt['name'] for mt in vm.qmp('query-machines')['return']]
    vm.shutdown()
    return machines


def qmp_find_cpu(vm):
    cpus = vm.qmp('query-cpus-fast')['return']
    return cpus[0]['qom-path']


def get_bool_cpu_property(vm, prop):
    cpu_path = qmp_find_cpu(vm)

    hmp_com_line = 'qom-get ' + cpu_path + ' ' + prop
    qmp_ret = vm.qmp('human-monitor-command', command_line=hmp_com_line)
    feature_state = qmp_ret['return'].strip('\r\n')

    if feature_state in ('true', 'false'):
        return (0, feature_state)

    return (-1, f'Check bool property ({prop}) of the ({cpu_path}) ' +
            f'returned ({feature_state})')


def get_qtree_s_props(vm):
    res = {}

    qtree_out = vm.qmp('human-monitor-command',
                       command_line='info qtree')['return']

    for line in qtree_out.splitlines():
        match = re.match(r'(disable_s\d) = (0|1).*$', line.strip())

        if match is None:
            continue

        prop, val = match.groups()
        val = str(bool(int(val))).lower()  # 'true' or 'false'
        res[prop] = val

    return res


def check_mt(mt, cpu_props, qtree_props=None):
    vm_params = ['-machine', mt, '-accel', 'kvm']

    vm = QEMUMachine(iotests.qemu_prog, vm_params)
    vm.launch()

    errors = 0
    for prop, exp_val in cpu_props.items():
        ret = get_bool_cpu_property(vm, prop)

        if ret[0] == -1:
            print(f'Error ({mt}): {ret[1]}')
            vm.shutdown()
            return 1

        if ret[1] != exp_val:
            print(f'Error ({mt}): {prop} = {ret[1]} (expected {exp_val})')
            errors += 1

    if qtree_props:
        qtree_actual_props = get_qtree_s_props(vm)
        for prop, exp_val in qtree_props.items():
            val = qtree_actual_props.get(prop, None)

            if val is None:
                print(f'Error ({mt}): {prop} not found in qtree')
                errors += 1
            elif val != exp_val:
                print(f'Error ({mt}): {prop} = {val} (expected {exp_val})')
                errors += 1

    vm.shutdown()
    return errors


def main():
    mtregex = re.compile(r'pc-(q35|i440fx)-(vz|rhel)(\d+)\.(\d+)\.(\d+)')

    failed = 0
    # set expecting values for appropriate machine types
    for mt in list_machine_types():
        if mt.startswith('x-') or mt in ('none', 'microvm', 'isapc'):
            continue

        groups = mtregex.search(mt).groups()
        chipset = groups[0]
        platform = groups[1]
        major, minor, _ = map(int, groups[2:])

        cpu_props = {
            'kvm-pv-tlb-flush':   'false',
            'kvm-pv-ipi':         'false',
            'kvm-pv-sched-yield': 'false',
            'kvm-asyncpf-int':    'false'
        }

        if platform == 'vz':
            if major >= 9:
                cpu_props = dict.fromkeys(cpu_props, 'true')
        elif platform == 'rhel':
            if major >= 9 or (major == 8 and minor >= 5):
                cpu_props.update({'kvm-asyncpf-int': 'true'})
        else:
            print(f'Unknown MT: {mt}')

        # Test disable_s{3,4} for RHEL and VZ 'pc' machines
        qtree_props = None
        if platform in ('rhel', 'vz'):
            if chipset == 'i440fx':
                qtree_props = {
                    'disable_s3': 'true',
                    'disable_s4': 'true'
                }
        else:
            print(f'Unknown MT: {mt}')

        failed += check_mt(mt, cpu_props, qtree_props)

    if failed > 0:
        print('Failed')
    else:
        print('Success')


if __name__ == '__main__':
    main()
