[RFC,v1,08/23] dts: merge DTS framework/virt_base.py to DPDK

Message ID 20220406151903.2916254-9-juraj.linkes@pantheon.tech (mailing list archive)
State RFC, archived
Delegated to: Thomas Monjalon
Headers
Series merge DTS test resource files to DPDK |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Juraj Linkeš April 6, 2022, 3:18 p.m. UTC
  ---
 dts/framework/virt_base.py | 553 +++++++++++++++++++++++++++++++++++++
 1 file changed, 553 insertions(+)
 create mode 100644 dts/framework/virt_base.py
  

Patch

diff --git a/dts/framework/virt_base.py b/dts/framework/virt_base.py
new file mode 100644
index 0000000000..d4af8b985f
--- /dev/null
+++ b/dts/framework/virt_base.py
@@ -0,0 +1,553 @@ 
+# BSD LICENSE
+#
+# Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   * Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#   * Neither the name of Intel Corporation nor the names of its
+#     contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import os
+import sys
+import threading
+import traceback
+from random import randint
+
+import framework.exception as exception
+import framework.utils as utils
+
+from .config import VIRTCONF, VirtConf
+from .dut import Dut
+from .logger import getLogger
+from .settings import CONFIG_ROOT_PATH
+from .virt_dut import VirtDut
+
+ST_NOTSTART = "NOTSTART"
+ST_PAUSE = "PAUSE"
+ST_RUNNING = "RUNNING"
+ST_UNKNOWN = "UNKNOWN"
+VM_IMG_LIST = []
+mutex_vm_list = threading.Lock()
+
+
+class VirtBase(object):
+    """
+    Basic module for customer special virtual type. This module implement
+    functions configured and composed in the VM boot command. With these
+    function, we can get and set the VM boot command, and instantiate the VM.
+    """
+
+    def __init__(self, dut, vm_name, suite_name):
+        """
+        Initialize the VirtBase.
+        dut: the instance of Dut
+        vm_name: the name of VM which you have configured in the configure
+        suite_name: the name of test suite
+        """
+        self.host_dut = dut
+        self.vm_name = vm_name
+        self.suite = suite_name
+        # indicate whether the current vm is migration vm
+        self.migration_vm = False
+
+        # create self used host session, need close it later
+        self.host_session = self.host_dut.new_session(self.vm_name)
+
+        self.host_logger = self.host_dut.logger
+        # base_dir existed for host dut has prepared it
+        self.host_session.send_expect("cd %s" % self.host_dut.base_dir, "# ")
+
+        # init the host resource pool for VM
+        self.virt_pool = self.host_dut.virt_pool
+
+        if not self.has_virtual_ability():
+            if not self.enable_virtual_ability():
+                raise Exception("Dut [ %s ] cannot have the virtual ability!!!")
+
+        self.virt_type = self.get_virt_type()
+
+        self.params = []
+        self.local_conf = []
+
+        # default call back function is None
+        self.callback = None
+
+        # vm status is running by default, only be changed in internal module
+        self.vm_status = ST_RUNNING
+
+        # by default no special kernel module is required
+        self.def_driver = ""
+        self.driver_mode = ""
+
+    def get_virt_type(self):
+        """
+        Get the virtual type, such as KVM, XEN or LIBVIRT.
+        """
+        raise NotImplementedError
+
+    def has_virtual_ability(self):
+        """
+        Check if the host have the ability of virtualization.
+        """
+        NotImplemented
+
+    def enable_virtual_ability(self):
+        """
+        Enable the virtual ability on the DUT.
+        """
+        NotImplemented
+
+    def get_vm_login(self):
+        """
+        Get VM credentials.
+        """
+        raise NotImplementedError
+
+    def add_vm_login(self):
+        """
+        Add VM credentials.
+        """
+        raise NotImplementedError
+
+    def _attach_vm(self):
+        """
+        Attach VM.
+        """
+        raise NotImplementedError
+
+    def _quick_start_vm(self):
+        """
+        Quick start VM.
+        """
+        raise NotImplementedError
+
+    def load_global_config(self):
+        """
+        Load global configure in the path CONFIG_ROOT_PATH.
+        """
+        conf = VirtConf(VIRTCONF)
+        conf.load_virt_config(self.virt_type)
+        global_conf = conf.get_virt_config()
+        for param in global_conf:
+            for key in list(param.keys()):
+                if self.find_option_index(key) is None:
+                    self.__save_local_config(key, param[key])
+
+    def set_local_config(self, local_conf):
+        """
+        Configure VM configuration from user input
+        """
+        self.local_conf = local_conf
+
+    def load_local_config(self, suite_name):
+        """
+        Load local configure in the path CONFIG_ROOT_PATH ('DTS_ROOT_PATH/$DTS_CFG_FOLDER/' by default).
+        """
+        # load local configuration by suite and vm name
+        try:
+            conf = VirtConf(CONFIG_ROOT_PATH + os.sep + suite_name + ".cfg")
+            conf.load_virt_config(self.vm_name)
+            self.local_conf = conf.get_virt_config()
+        except:
+            # when met exception in load VM config
+            # just leave local conf untouched
+            pass
+
+        # replace global configurations with local configurations
+        for param in self.local_conf:
+            if "virt_type" in list(param.keys()):
+                # param 'virt_type' is for virt_base only
+                continue
+            # save local configurations
+            for key in list(param.keys()):
+                self.__save_local_config(key, param[key])
+
+    def __save_local_config(self, key, value):
+        """
+        Save the local config into the global dict self.param.
+        """
+        for param in self.params:
+            if key in list(param.keys()):
+                param[key] = value
+                return
+
+        self.params.append({key: value})
+
+    def compose_boot_param(self):
+        """
+        Compose all boot param for starting the VM.
+        """
+        for param in self.params:
+            key = list(param.keys())[0]
+            value = param[key]
+            try:
+                param_func = getattr(self, "add_vm_" + key)
+                if callable(param_func):
+                    if type(value) is list:
+                        for option in value:
+                            param_func(**option)
+                else:
+                    print(utils.RED("Virt %s function not callable!!!" % key))
+            except AttributeError:
+                self.host_logger.error(traceback.print_exception(*sys.exc_info()))
+                print(utils.RED("Virt %s function not implemented!!!" % key))
+            except Exception:
+                self.host_logger.error(traceback.print_exception(*sys.exc_info()))
+                raise exception.VirtConfigParamException(key)
+
+    def add_vm_def_driver(self, **options):
+        """
+        Set default driver which may required when setup VM
+        """
+        if "driver_name" in list(options.keys()):
+            self.def_driver = options["driver_name"]
+        if "driver_mode" in list(options.keys()):
+            self.driver_mode = options["driver_mode"]
+
+    def find_option_index(self, option):
+        """
+        Find the boot option in the params which is generated from
+        the global and local configures, and this function will
+        return the index by which option can be indexed in the
+        param list.
+        """
+        index = 0
+        for param in self.params:
+            key = list(param.keys())[0]
+            if key.strip() == option.strip():
+                return index
+            index += 1
+
+        return None
+
+    def generate_unique_mac(self):
+        """
+        Generate a unique MAC based on the DUT.
+        """
+        mac_head = "00:00:00:"
+        mac_tail = ":".join(
+            ["%02x" % x for x in map(lambda x: randint(0, 255), list(range(3)))]
+        )
+        return mac_head + mac_tail
+
+    def get_vm_ip(self):
+        """
+        Get the VM IP.
+        """
+        raise NotImplementedError
+
+    def get_pci_mappings(self):
+        """
+        Get host and VM pass-through device mapping
+        """
+        NotImplemented
+
+    def isalive(self):
+        """
+        Check whether VM existed.
+        """
+        vm_status = self.host_session.send_expect(
+            "ps aux | grep qemu | grep 'name %s '| grep -v grep" % self.vm_name, "# "
+        )
+
+        if self.vm_name in vm_status:
+            return True
+        else:
+            return False
+
+    def load_config(self):
+        """
+        Load configurations for VM
+        """
+        # load global and suite configuration file
+        self.load_global_config()
+        self.load_local_config(self.suite)
+
+    def attach(self):
+        # load configuration
+        self.load_config()
+
+        # change login user/password
+        index = self.find_option_index("login")
+        if index:
+            value = self.params[index]["login"]
+            for option in value:
+                self.add_vm_login(**option)
+
+        # attach real vm
+        self._attach_vm()
+        return None
+
+    def start(self, load_config=True, set_target=True, cpu_topo="", bind_dev=True):
+        """
+        Start VM and instantiate the VM with VirtDut.
+        """
+        try:
+            if load_config is True:
+                self.load_config()
+            # compose boot command for different hypervisors
+            self.compose_boot_param()
+
+            # start virtual machine
+            self._start_vm()
+
+            if self.vm_status is ST_RUNNING:
+                # connect vm dut and init running environment
+                vm_dut = self.instantiate_vm_dut(
+                    set_target, cpu_topo, bind_dev=bind_dev, autodetect_topo=True
+                )
+            else:
+                vm_dut = None
+
+        except Exception as vm_except:
+            if self.handle_exception(vm_except):
+                print(utils.RED("Handled exception " + str(type(vm_except))))
+            else:
+                print(utils.RED("Unhandled exception " + str(type(vm_except))))
+
+            if callable(self.callback):
+                self.callback()
+
+            return None
+        return vm_dut
+
+    def quick_start(self, load_config=True, set_target=True, cpu_topo=""):
+        """
+        Only Start VM and not do anything else, will be helpful in multiple VMs
+        """
+        try:
+            if load_config is True:
+                self.load_config()
+            # compose boot command for different hypervisors
+            self.compose_boot_param()
+
+            # start virtual machine
+            self._quick_start_vm()
+
+        except Exception as vm_except:
+            if self.handle_exception(vm_except):
+                print(utils.RED("Handled exception " + str(type(vm_except))))
+            else:
+                print(utils.RED("Unhandled exception " + str(type(vm_except))))
+
+            if callable(self.callback):
+                self.callback()
+
+    def migrated_start(self, set_target=True, cpu_topo=""):
+        """
+        Instantiate the VM after migration done
+        There's no need to load param and start VM because VM has been started
+        """
+        try:
+            if self.vm_status is ST_PAUSE:
+                # flag current vm is migration vm
+                self.migration_vm = True
+                # connect backup vm dut and it just inherited from host
+                vm_dut = self.instantiate_vm_dut(
+                    set_target, cpu_topo, bind_dev=False, autodetect_topo=False
+                )
+        except Exception as vm_except:
+            if self.handle_exception(vm_except):
+                print(utils.RED("Handled exception " + str(type(vm_except))))
+            else:
+                print(utils.RED("Unhandled exception " + str(type(vm_except))))
+
+            return None
+
+        return vm_dut
+
+    def handle_exception(self, vm_except):
+        # show exception back trace
+        exc_type, exc_value, exc_traceback = sys.exc_info()
+        traceback.print_exception(
+            exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout
+        )
+        if type(vm_except) is exception.ConfigParseException:
+            # nothing to handle just return True
+            return True
+        elif type(vm_except) is exception.VirtConfigParseException:
+            # nothing to handle just return True
+            return True
+        elif type(vm_except) is exception.VirtConfigParamException:
+            # nothing to handle just return True
+            return True
+        elif type(vm_except) is exception.StartVMFailedException:
+            # start vm failure
+            return True
+        elif type(vm_except) is exception.VirtDutConnectException:
+            # need stop vm
+            self._stop_vm()
+            return True
+        elif type(vm_except) is exception.VirtDutInitException:
+            # need close session
+            vm_except.vm_dut.close()
+            # need stop vm
+            self.stop()
+            return True
+        else:
+            return False
+
+    def _start_vm(self):
+        """
+        Start VM.
+        """
+        NotImplemented
+
+    def _stop_vm(self):
+        """
+        Stop VM.
+        """
+        NotImplemented
+
+    def get_vm_img(self):
+        """
+        get current vm img name from params
+        get format like: 10.67.110.11:TestVhostMultiQueueQemu:/home/img/Ub1604.img
+        """
+        param_len = len(self.params)
+        for i in range(param_len):
+            if "disk" in list(self.params[i].keys()):
+                value = self.params[i]["disk"][0]
+                if "file" in list(value.keys()):
+                    host_ip = self.host_dut.get_ip_address()
+                    return (
+                        host_ip
+                        + ":"
+                        + self.host_dut.test_classname
+                        + ":"
+                        + value["file"]
+                    )
+        return None
+
+    def instantiate_vm_dut(
+        self, set_target=True, cpu_topo="", bind_dev=True, autodetect_topo=True
+    ):
+        """
+        Instantiate the Dut class for VM.
+        """
+        crb = self.host_dut.crb.copy()
+        crb["bypass core0"] = False
+        vm_ip = self.get_vm_ip()
+        crb["IP"] = vm_ip
+        crb["My IP"] = vm_ip
+        username, password = self.get_vm_login()
+        crb["user"] = username
+        crb["pass"] = password
+
+        serializer = self.host_dut.serializer
+
+        try:
+            vm_dut = VirtDut(
+                self,
+                crb,
+                serializer,
+                self.virt_type,
+                self.vm_name,
+                self.suite,
+                cpu_topo,
+                dut_id=self.host_dut.dut_id,
+            )
+        except Exception as vm_except:
+            self.handle_exception(vm_except)
+            raise exception.VirtDutConnectException
+            return None
+
+        vm_dut.nic_type = "any"
+        vm_dut.tester = self.host_dut.tester
+        vm_dut.host_dut = self.host_dut
+        vm_dut.host_session = self.host_session
+        vm_dut.init_log()
+        vm_dut.migration_vm = self.migration_vm
+
+        read_cache = False
+        skip_setup = self.host_dut.skip_setup
+        vm_img = self.get_vm_img()
+        # if current vm is migration vm, skip compile dpdk
+        # if VM_IMG_list include the vm_img, it means the vm have complie the dpdk ok, skip it
+        if self.migration_vm or vm_img in VM_IMG_LIST:
+            skip_setup = True
+        base_dir = self.host_dut.base_dir
+        vm_dut.set_speedup_options(read_cache, skip_setup)
+
+        # package and patch should be set before prerequisites
+        vm_dut.set_package(self.host_dut.package, self.host_dut.patches)
+
+        # base_dir should be set before prerequisites
+        vm_dut.set_directory(base_dir)
+
+        try:
+            # setting up dpdk in vm, must call at last
+            vm_dut.target = self.host_dut.target
+            vm_dut.prerequisites(
+                self.host_dut.package, self.host_dut.patches, autodetect_topo
+            )
+            if set_target:
+                target = self.host_dut.target
+                vm_dut.set_target(target, bind_dev, self.def_driver, self.driver_mode)
+        except:
+            raise exception.VirtDutInitException(vm_dut)
+            return None
+
+        # after prerequisites and set_target, the dpdk compile is ok, add this vm img to list
+        if vm_img not in VM_IMG_LIST:
+            mutex_vm_list.acquire()
+            VM_IMG_LIST.append(vm_img)
+            mutex_vm_list.release()
+
+        self.vm_dut = vm_dut
+        return vm_dut
+
+    def stop(self):
+        """
+        Stop the VM.
+        """
+        self._stop_vm()
+        self.quit()
+
+        self.virt_pool.free_all_resource(self.vm_name)
+
+    def quit(self):
+        """
+        Just quit connection to the VM
+        """
+        if getattr(self, "host_session", None):
+            self.host_session.close()
+            self.host_session = None
+
+        # vm_dut may not init in migration case
+        if getattr(self, "vm_dut", None):
+            if self.vm_status is ST_RUNNING:
+                self.vm_dut.close()
+            else:
+                # when vm is not running, not close session forcely
+                self.vm_dut.close(force=True)
+
+            self.vm_dut.logger.logger_exit()
+            self.vm_dut = None
+
+    def register_exit_callback(self, callback):
+        """
+        Call register exit call back function
+        """
+        self.callback = callback