From patchwork Wed Apr 6 15:18:48 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Juraj_Linke=C5=A1?= X-Patchwork-Id: 109307 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 3BEB7A0509; Wed, 6 Apr 2022 17:20:11 +0200 (CEST) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 59E2D42917; Wed, 6 Apr 2022 17:19:19 +0200 (CEST) Received: from lb.pantheon.sk (lb.pantheon.sk [46.229.239.20]) by mails.dpdk.org (Postfix) with ESMTP id 1DAD8428B6 for ; Wed, 6 Apr 2022 17:19:17 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by lb.pantheon.sk (Postfix) with ESMTP id 0AA7A19E0DF; Wed, 6 Apr 2022 17:19:15 +0200 (CEST) X-Virus-Scanned: amavisd-new at siecit.sk Received: from lb.pantheon.sk ([127.0.0.1]) by localhost (lb.pantheon.sk [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ld7D3pmXFANC; Wed, 6 Apr 2022 17:19:14 +0200 (CEST) Received: from entguard.lab.pantheon.local (unknown [46.229.239.141]) by lb.pantheon.sk (Postfix) with ESMTP id A5735184FEE; Wed, 6 Apr 2022 17:19:08 +0200 (CEST) From: =?utf-8?q?Juraj_Linke=C5=A1?= To: thomas@monjalon.net, david.marchand@redhat.com, Honnappa.Nagarahalli@arm.com, ohilyard@iol.unh.edu, lijuan.tu@intel.com Cc: dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= Subject: [RFC PATCH v1 08/23] dts: merge DTS framework/virt_base.py to DPDK Date: Wed, 6 Apr 2022 15:18:48 +0000 Message-Id: <20220406151903.2916254-9-juraj.linkes@pantheon.tech> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220406151903.2916254-1-juraj.linkes@pantheon.tech> References: <20220406151903.2916254-1-juraj.linkes@pantheon.tech> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org --- dts/framework/virt_base.py | 553 +++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 dts/framework/virt_base.py 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